diff --git a/tests/E2E Tests/WebAppUiTests/B2CWebAppCallsWebApiLocally.cs b/tests/E2E Tests/WebAppUiTests/B2CWebAppCallsWebApiLocally.cs index f2ee2c21a..a8f58f4e6 100644 --- a/tests/E2E Tests/WebAppUiTests/B2CWebAppCallsWebApiLocally.cs +++ b/tests/E2E Tests/WebAppUiTests/B2CWebAppCallsWebApiLocally.cs @@ -6,7 +6,6 @@ using System.Diagnostics; using System.IO; using System.Runtime.Versioning; -using System.Threading; using System.Threading.Tasks; using Azure.Identity; using Microsoft.Identity.Lab.Api; @@ -42,7 +41,7 @@ public B2CWebAppCallsWebApiLocally(ITestOutputHelper output) [Fact] [SupportedOSPlatform("windows")] - public async Task Susi_B2C_LocalAccount_TodoAppFucntionsCorrectlyAsync() + public async Task Susi_B2C_LocalAccount_TodoAppFunctionsCorrectlyAsync() { // Web app and api environmental variable setup. DefaultAzureCredential azureCred = new(); @@ -77,11 +76,11 @@ public async Task Susi_B2C_LocalAccount_TodoAppFucntionsCorrectlyAsync() { // Start the web app and api processes. // The delay before starting client prevents transient devbox issue where the client fails to load the first time after rebuilding. - serviceProcess = UiTestHelpers.StartProcessLocally(_testAssemblyPath, _devAppPath + TC.s_todoListServicePath, TC.s_todoListServiceExe, serviceEnvVars); + serviceProcess = UiTestHelpers.StartProcessLocally(_testAssemblyPath, _devAppPath + TC.s_todoListServicePath, TC.s_todoListServiceExe, _output, serviceEnvVars); await Task.Delay(3000); - clientProcess = UiTestHelpers.StartProcessLocally(_testAssemblyPath, _devAppPath + TC.s_todoListClientPath, TC.s_todoListClientExe, clientEnvVars); + clientProcess = UiTestHelpers.StartProcessLocally(_testAssemblyPath, _devAppPath + TC.s_todoListClientPath, TC.s_todoListClientExe, _output, clientEnvVars, 5); - if (!UiTestHelpers.ProcessesAreAlive(new List() { clientProcess, serviceProcess })) + if (!UiTestHelpers.ProcessesAreAlive([clientProcess, serviceProcess])) { Assert.Fail(TC.WebAppCrashedString); } diff --git a/tests/E2E Tests/WebAppUiTests/TestingWebAppLocally.cs b/tests/E2E Tests/WebAppUiTests/TestingWebAppLocally.cs index 91ba12984..4959fa83f 100644 --- a/tests/E2E Tests/WebAppUiTests/TestingWebAppLocally.cs +++ b/tests/E2E Tests/WebAppUiTests/TestingWebAppLocally.cs @@ -76,7 +76,7 @@ private async Task ExecuteWebAppCallsGraphFlowAsync(string upn, string credentia try { - process = UiTestHelpers.StartProcessLocally(_uiTestAssemblyLocation, _devAppPath, _devAppExecutable, clientEnvVars); + process = UiTestHelpers.StartProcessLocally(_uiTestAssemblyLocation, _devAppPath, _devAppExecutable, _output, clientEnvVars); if (!UiTestHelpers.ProcessIsAlive(process)) { Assert.Fail(TC.WebAppCrashedString); } diff --git a/tests/E2E Tests/WebAppUiTests/UiTestHelpers.cs b/tests/E2E Tests/WebAppUiTests/UiTestHelpers.cs index d80a39d16..403e09e5b 100644 --- a/tests/E2E Tests/WebAppUiTests/UiTestHelpers.cs +++ b/tests/E2E Tests/WebAppUiTests/UiTestHelpers.cs @@ -13,7 +13,6 @@ using Azure.Core; using Azure.Security.KeyVault.Secrets; using Microsoft.Identity.Web.Test.Common; -using Microsoft.IdentityModel.Tokens; using Microsoft.Playwright; using Xunit.Abstractions; @@ -142,8 +141,15 @@ await page.Context.Tracing.StartAsync(new() /// The name of the executable that launches the process. /// The port for the process to listen on. /// If the launch URL is http or https. Default is https. + /// Optionally, maximum number of retries if the process exited prematurely. /// The started process. - public static Process StartProcessLocally(string testAssemblyLocation, string appLocation, string executableName, Dictionary? environmentVariables = null) + public static Process StartProcessLocally( + string testAssemblyLocation, + string appLocation, + string executableName, + ITestOutputHelper output, + Dictionary? environmentVariables = null, + int maxRetries = 0) { string applicationWorkingDirectory = GetApplicationWorkingDirectory(testAssemblyLocation, appLocation); ProcessStartInfo processStartInfo = new ProcessStartInfo(applicationWorkingDirectory + executableName) @@ -161,7 +167,13 @@ public static Process StartProcessLocally(string testAssemblyLocation, string ap } } - Process? process = Process.Start(processStartInfo); + var currentAttempt = 1; + Process? process; + do + { + Thread.Sleep(1000 * currentAttempt++); // linear backoff + process = Process.Start(processStartInfo); + } while (currentAttempt++ <= maxRetries && ProcessIsAlive(process)); if (process == null) { @@ -169,6 +181,13 @@ public static Process StartProcessLocally(string testAssemblyLocation, string ap } else { + // Log the output and error streams + process.OutputDataReceived += (sender, e) => output.WriteLine(e.Data); + process.ErrorDataReceived += (sender, e) => output.WriteLine(e.Data); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + return process; } } @@ -275,9 +294,9 @@ public static bool ProcessesAreAlive(List processes) /// /// Process to check /// True if alive false if not - public static bool ProcessIsAlive(Process process) + public static bool ProcessIsAlive(Process? process) { - return !process.HasExited; + return process != null && !process.HasExited; } /// @@ -310,7 +329,7 @@ internal static async Task GetValueFromKeyvaultWitDefaultCredsAsync(Uri return (await client.GetSecretAsync(keyvaultSecretName)).Value.Value; } - internal static bool StartAndVerifyProcessesAreRunning(List processDataEntries, out Dictionary processes) + internal static bool StartAndVerifyProcessesAreRunning(List processDataEntries, ITestOutputHelper output, out Dictionary processes) { processes = new Dictionary(); @@ -321,6 +340,7 @@ internal static bool StartAndVerifyProcessesAreRunning(List processDataEntry.TestAssemblyLocation, processDataEntry.AppLocation, processDataEntry.ExecutableName, + output, processDataEntry.EnvironmentVariables); processes.Add(processDataEntry.ExecutableName, process); @@ -332,7 +352,7 @@ internal static bool StartAndVerifyProcessesAreRunning(List { if (!UiTestHelpers.ProcessesAreAlive(processes.Values.ToList())) { - RestartProcesses(processes, processDataEntries); + RestartProcesses(processes, processDataEntries , output); } } @@ -344,7 +364,7 @@ internal static bool StartAndVerifyProcessesAreRunning(List return true; } - static void RestartProcesses(Dictionary processes, List processDataEntries) + static void RestartProcesses(Dictionary processes, List processDataEntries, ITestOutputHelper output) { //attempt to restart failed processes foreach (KeyValuePair processEntry in processes) @@ -356,6 +376,7 @@ static void RestartProcesses(Dictionary processes, List { /*grpcProcessOptions,*/ serviceProcessOptions, clientProcessOptions }, out processes); + bool areProcessesRunning = UiTestHelpers.StartAndVerifyProcessesAreRunning(new List { /*grpcProcessOptions,*/ serviceProcessOptions, clientProcessOptions }, _output, out processes); if (!areProcessesRunning) { @@ -219,7 +217,7 @@ public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds // The delay before starting client prevents transient devbox issue where the client fails to load the first time after rebuilding. var serviceProcessOptions = new ProcessStartOptions(_testAssemblyLocation, _devAppPathCiam + TC.s_myWebApiPath, TC.s_myWebApiExe, serviceEnvVars); var clientProcessOptions = new ProcessStartOptions(_testAssemblyLocation, _devAppPathCiam + TC.s_myWebAppPath, TC.s_myWebAppExe, clientEnvVars); - bool areProcessesRunning = UiTestHelpers.StartAndVerifyProcessesAreRunning(new List { serviceProcessOptions, clientProcessOptions }, out processes); + bool areProcessesRunning = UiTestHelpers.StartAndVerifyProcessesAreRunning(new List { serviceProcessOptions, clientProcessOptions }, _output, out processes); if (!areProcessesRunning) {