From be486a375d26a068cf0b4f9ea69243da1c9cf32a Mon Sep 17 00:00:00 2001 From: Josh Lozensky Date: Wed, 25 Sep 2024 17:25:43 -0700 Subject: [PATCH] Refactored tests to be more uniform in implementation --- .../B2CWebAppCallsWebApiLocally.cs | 44 +++--- .../WebAppUiTests/TestingWebAppLocally.cs | 27 ++-- .../E2E Tests/WebAppUiTests/UiTestHelpers.cs | 102 +++++++++++--- .../WebAppCallsApiCallsGraphLocally.cs | 131 +++++------------- .../WebAppUiTests/WebAppIntegrationTests.cs | 2 +- 5 files changed, 155 insertions(+), 151 deletions(-) diff --git a/tests/E2E Tests/WebAppUiTests/B2CWebAppCallsWebApiLocally.cs b/tests/E2E Tests/WebAppUiTests/B2CWebAppCallsWebApiLocally.cs index a9aa53e1c..c66be3bfd 100644 --- a/tests/E2E Tests/WebAppUiTests/B2CWebAppCallsWebApiLocally.cs +++ b/tests/E2E Tests/WebAppUiTests/B2CWebAppCallsWebApiLocally.cs @@ -4,9 +4,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Runtime.Versioning; -using System.Threading; +using System.Text; using System.Threading.Tasks; using Azure.Identity; using Microsoft.Identity.Lab.Api; @@ -14,6 +15,7 @@ using Xunit; using Xunit.Abstractions; using TC = Microsoft.Identity.Web.Test.Common.TestConstants; +using UITH = WebAppUiTests.UiTestHelpers; namespace WebAppUiTests #if !FROM_GITHUB_ACTION @@ -26,6 +28,7 @@ public class B2CWebAppCallsWebApiLocally : IClassFixture { {"ASPNETCORE_ENVIRONMENT", "Development" }, @@ -60,8 +63,8 @@ public async Task Susi_B2C_LocalAccount_TodoAppFucntionsCorrectly() }; // Get email and password from keyvault. - string email = await UiTestHelpers.GetValueFromKeyvaultWitDefaultCreds(_keyvaultUri, KeyvaultEmailName, azureCred); - string password = await UiTestHelpers.GetValueFromKeyvaultWitDefaultCreds(_keyvaultUri, KeyvaultPasswordName, azureCred); + string email = await UITH.GetValueFromKeyvaultWitDefaultCreds(_keyvaultUri, KeyvaultEmailName, azureCred); + string password = await UITH.GetValueFromKeyvaultWitDefaultCreds(_keyvaultUri, KeyvaultPasswordName, azureCred); // Playwright setup. To see browser UI, set 'Headless = false'. const string TraceFileName = TraceClassName + "_TodoAppFunctionsCorrectly"; @@ -70,20 +73,28 @@ public async Task Susi_B2C_LocalAccount_TodoAppFucntionsCorrectly() IBrowserContext context = await browser.NewContextAsync(new BrowserNewContextOptions { IgnoreHTTPSErrors = true }); await context.Tracing.StartAsync(new() { Screenshots = true, Snapshots = true, Sources = true }); - Process? serviceProcess= null; - Process? clientProcess = null; + + Dictionary? processes = null; try { // 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); + var serviceProcess = new ProcessStartOptions(_testAssemblyPath, _devAppPath + TC.s_todoListServicePath, TC.s_todoListServiceExe, serviceEnvVars); await Task.Delay(3000); - clientProcess = UiTestHelpers.StartProcessLocally(_testAssemblyPath, _devAppPath + TC.s_todoListClientPath, TC.s_todoListClientExe, clientEnvVars); + var clientProcess = new ProcessStartOptions(_testAssemblyPath, _devAppPath + TC.s_todoListClientPath, TC.s_todoListClientExe, clientEnvVars); + + bool processesAreRunning = UITH.StartAndVerifyProcessesAreRunning(new List() { serviceProcess, clientProcess }, out processes, NumProcessRetries); - if (!UiTestHelpers.ProcessesAreAlive(new List() { clientProcess, serviceProcess })) + if (!processesAreRunning) { - Assert.Fail(TC.WebAppCrashedString); + _output.WriteLine($"Process not started after {NumProcessRetries} attempts."); + StringBuilder runningProcesses = new StringBuilder(); + foreach (var process in processes) + { + runningProcesses.AppendLine(CultureInfo.InvariantCulture, $"Is {process.Key} running: {UITH.ProcessIsAlive(process.Value)}"); + } + Assert.Fail(TC.WebAppCrashedString + " " + runningProcesses.ToString()); } // Navigate to web app the retry logic ensures the web app has time to start up to establish a connection. @@ -103,7 +114,7 @@ public async Task Susi_B2C_LocalAccount_TodoAppFucntionsCorrectly() if (InitialConnectionRetryCount == 0) { throw ex; } } } - LabResponse labResponse = await LabUserHelper.GetB2CLocalAccountAsync().ConfigureAwait(false); + LabResponse labResponse = await LabUserHelper.GetB2CLocalAccountAsync(); // Initial sign in _output.WriteLine("Starting web app sign-in flow."); @@ -138,7 +149,7 @@ public async Task Susi_B2C_LocalAccount_TodoAppFucntionsCorrectly() _output.WriteLine("Starting web app create new todo flow."); await page.GetByRole(AriaRole.Link, new() { Name = "Create New" }).ClickAsync(); ILocator titleEntryBox = page.GetByLabel("Title"); - await UiTestHelpers.FillEntryBox(titleEntryBox, TC.TodoTitle1); + await UITH.FillEntryBox(titleEntryBox, TC.TodoTitle1); await Assertions.Expect(page.GetByRole(AriaRole.Cell, new() { Name = TC.TodoTitle1 })).ToBeVisibleAsync(_assertVisibleOptions); _output.WriteLine("Web app create new todo flow successful."); @@ -164,14 +175,11 @@ public async Task Susi_B2C_LocalAccount_TodoAppFucntionsCorrectly() } finally { - // Add the following to make sure all processes and their children are stopped. - Queue processes = new Queue(); - if (serviceProcess != null) { processes.Enqueue(serviceProcess); } - if (clientProcess != null) { processes.Enqueue(clientProcess); } - UiTestHelpers.KillProcessTrees(processes); + // Make sure all application processes and their children are stopped. + UITH.EndProcesses(processes); // Stop tracing and export it into a zip archive. - string path = UiTestHelpers.GetTracePath(_testAssemblyPath, TraceFileName); + string path = UITH.GetTracePath(_testAssemblyPath, TraceFileName); await context.Tracing.StopAsync(new() { Path = path }); _output.WriteLine($"Trace data for {TraceFileName} recorded to {path}."); diff --git a/tests/E2E Tests/WebAppUiTests/TestingWebAppLocally.cs b/tests/E2E Tests/WebAppUiTests/TestingWebAppLocally.cs index 216b23651..bd118a73f 100644 --- a/tests/E2E Tests/WebAppUiTests/TestingWebAppLocally.cs +++ b/tests/E2E Tests/WebAppUiTests/TestingWebAppLocally.cs @@ -12,6 +12,7 @@ using Xunit; using Xunit.Abstractions; using TC = Microsoft.Identity.Web.Test.Common.TestConstants; +using UITH = WebAppUiTests.UiTestHelpers; namespace WebAppUiTests; @@ -39,11 +40,11 @@ public TestingWebAppLocally(ITestOutputHelper output) [SupportedOSPlatform("windows")] public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPassword() { - LabResponse labResponse = await LabUserHelper.GetDefaultUserAsync().ConfigureAwait(false); + LabResponse labResponse = await LabUserHelper.GetDefaultUserAsync(); var clientEnvVars = new Dictionary(); - await ExecuteWebAppCallsGraphFlow(labResponse.User.Upn, labResponse.User.GetOrFetchPassword(), clientEnvVars, TraceFileClassName).ConfigureAwait(false); + await ExecuteWebAppCallsGraphFlow(labResponse.User.Upn, labResponse.User.GetOrFetchPassword(), clientEnvVars, TraceFileClassName); } [Theory] @@ -61,7 +62,7 @@ public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailWithCiamPassw {"AzureAd__Instance", "" } }; - await ExecuteWebAppCallsGraphFlow("idlab@msidlabciam6.onmicrosoft.com", LabUserHelper.FetchUserPassword("msidlabciam6"), clientEnvVars, TraceFileClassNameCiam).ConfigureAwait(false); + await ExecuteWebAppCallsGraphFlow("idlab@msidlabciam6.onmicrosoft.com", LabUserHelper.FetchUserPassword("msidlabciam6"), clientEnvVars, TraceFileClassNameCiam); } private async Task ExecuteWebAppCallsGraphFlow(string upn, string credential, Dictionary? clientEnvVars, string traceFileClassName) @@ -76,9 +77,9 @@ private async Task ExecuteWebAppCallsGraphFlow(string upn, string credential, Di try { - process = UiTestHelpers.StartProcessLocally(_uiTestAssemblyLocation, _devAppPath, _devAppExecutable, clientEnvVars); + process = UITH.StartProcessLocally(_uiTestAssemblyLocation, _devAppPath, _devAppExecutable, clientEnvVars); - if (!UiTestHelpers.ProcessIsAlive(process)) + if (!UITH.ProcessIsAlive(process)) { Assert.Fail(TC.WebAppCrashedString); } IPage page = await browser.NewPageAsync(); @@ -104,7 +105,7 @@ private async Task ExecuteWebAppCallsGraphFlow(string upn, string credential, Di // Act Trace.WriteLine("Starting Playwright automation: web app sign-in & call Graph."); string email = upn; - await UiTestHelpers.FirstLogin_MicrosoftIdFlow_ValidEmailPassword(page, email, credential, _output); + await UITH.FirstLogin_MicrosoftIdFlow_ValidEmailPassword(page, email, credential, _output); // Assert await Assertions.Expect(page.GetByText("Welcome")).ToBeVisibleAsync(_assertVisibleOptions); @@ -120,21 +121,11 @@ private async Task ExecuteWebAppCallsGraphFlow(string upn, string credential, Di Queue processes = new(); if (process != null) { processes.Enqueue(process); } - -#if WINDOWS - UiTestHelpers.KillProcessTrees(processes); -#else - while (processes.Count > 0) - { - Process p = processes.Dequeue(); - p.Kill(); - p.WaitForExit(); - } -#endif + UITH.KillProcessTrees(processes); // Cleanup Playwright // Stop tracing and export it into a zip archive. - string path = UiTestHelpers.GetTracePath(_uiTestAssemblyLocation, TraceFileName); + string path = UITH.GetTracePath(_uiTestAssemblyLocation, TraceFileName); await context.Tracing.StopAsync(new() { Path = path }); _output.WriteLine($"Trace data for {TraceFileName} recorded to {path}."); await browser.DisposeAsync(); diff --git a/tests/E2E Tests/WebAppUiTests/UiTestHelpers.cs b/tests/E2E Tests/WebAppUiTests/UiTestHelpers.cs index 6d758340b..f82227336 100644 --- a/tests/E2E Tests/WebAppUiTests/UiTestHelpers.cs +++ b/tests/E2E Tests/WebAppUiTests/UiTestHelpers.cs @@ -8,12 +8,12 @@ using System.Linq; using System.Management; using System.Runtime.Versioning; +using System.Text; using System.Threading; using System.Threading.Tasks; using Azure.Core; using Azure.Security.KeyVault.Secrets; using Microsoft.Identity.Web.Test.Common; -using Microsoft.IdentityModel.Tokens; using Microsoft.Playwright; using Xunit.Abstractions; @@ -21,6 +21,32 @@ namespace WebAppUiTests { public static class UiTestHelpers { + /// + /// Navigates to a web page with retry logic to ensure establish a connection in case a web app needs more startup time. + /// + /// The uri to navigate to + /// A page in a playwright browser + /// + public static async Task NavigateToWebApp(string uri, IPage page) + { + uint InitialConnectionRetryCount = 5; + while (InitialConnectionRetryCount > 0) + { + try + { + await page.GotoAsync(uri); + break; + } + catch (PlaywrightException ex) + { + await Task.Delay(1000); + InitialConnectionRetryCount--; + if (InitialConnectionRetryCount == 0) + { throw ex; } + } + } + } + /// /// Login flow for the first time in a given browsing session. /// @@ -32,10 +58,9 @@ public static class UiTestHelpers public static async Task FirstLogin_MicrosoftIdFlow_ValidEmailPassword(IPage page, string email, string password, ITestOutputHelper? output = null, bool staySignedIn = false) { string staySignedInText = staySignedIn ? "Yes" : "No"; - WriteLine(output, $"Logging in ... Entering and submitting user name: {email}."); - ILocator emailInputLocator = page.GetByPlaceholder(TestConstants.EmailText); - await FillEntryBox(emailInputLocator, email); - await EnterPassword_MicrosoftIdFlow_ValidPassword(page, password, staySignedInText); + await EnterEmailAsync(page, email, output); + await EnterPasswordAsync(page, password, output); + await StaySignedIn_MicrosoftIdFlow(page, staySignedInText, output); } /// @@ -52,7 +77,15 @@ public static async Task SuccessiveLogin_MicrosoftIdFlow_ValidEmailPassword(IPag WriteLine(output, $"Logging in again in this browsing session... selecting user via email: {email}."); await SelectKnownAccountByEmail_MicrosoftIdFlow(page, email); - await EnterPassword_MicrosoftIdFlow_ValidPassword(page, password, staySignedInText); + await EnterPasswordAsync(page, password, output); + await StaySignedIn_MicrosoftIdFlow(page, staySignedInText, output); + } + + public static async Task EnterEmailAsync(IPage page, string email, ITestOutputHelper? output = null) + { + WriteLine(output, $"Logging in ... Entering and submitting user name: {email}."); + ILocator emailInputLocator = page.GetByPlaceholder(TestConstants.EmailText); + await FillEntryBox(emailInputLocator, email); } /// @@ -64,7 +97,7 @@ public static async Task SuccessiveLogin_MicrosoftIdFlow_ValidEmailPassword(IPag public static async Task PerformSignOut_MicrosoftIdFlow(IPage page, string email, string signOutPageUrl, ITestOutputHelper? output = null) { WriteLine(output, "Signing out ..."); - await SelectKnownAccountByEmail_MicrosoftIdFlow(page, email); + await SelectKnownAccountByEmail_MicrosoftIdFlow(page, email.ToLowerInvariant()); await page.WaitForURLAsync(signOutPageUrl); WriteLine(output, "Sign out page successfully reached."); } @@ -87,7 +120,7 @@ private static async Task SelectKnownAccountByEmail_MicrosoftIdFlow(IPage page, /// The password for the account you're logging into. /// "Yes" or "No" to stay signed in for the given browsing session. /// The writer for output to the test's console. - public static async Task EnterPassword_MicrosoftIdFlow_ValidPassword(IPage page, string password, string staySignedInText, ITestOutputHelper? output = null) + public static async Task EnterPasswordAsync(IPage page, string password, ITestOutputHelper? output = null) { // If using an account that has other non-password validation options, the below code should be uncommented /* WriteLine(output, "Selecting \"Password\" as authentication method"); @@ -96,7 +129,10 @@ public static async Task EnterPassword_MicrosoftIdFlow_ValidPassword(IPage page, WriteLine(output, "Logging in ... entering and submitting password."); ILocator passwordInputLocator = page.GetByPlaceholder(TestConstants.PasswordText); await FillEntryBox(passwordInputLocator, password); + } + public static async Task StaySignedIn_MicrosoftIdFlow(IPage page, string staySignedInText, ITestOutputHelper? output = null) + { WriteLine(output, $"Logging in ... Clicking {staySignedInText} on whether the browser should stay signed in."); await page.GetByRole(AriaRole.Button, new() { Name = staySignedInText }).ClickAsync(); } @@ -219,13 +255,26 @@ public static string GetTracePath(string testAssemblyLocation, string traceName) ); } + public static void EndProcesses(Dictionary? processes) + { + Queue processQueue = new(); + if (processes != null) + { + foreach (var process in processes) + { + processQueue.Enqueue(process.Value); + } + } + KillProcessTrees(processQueue); + } + /// /// Kills the processes in the queue and all of their children /// /// queue of parent processes - [SupportedOSPlatform("windows")] public static void KillProcessTrees(Queue processQueue) { +#if WINDOWS Process currentProcess; while (processQueue.Count > 0) { @@ -240,6 +289,14 @@ public static void KillProcessTrees(Queue processQueue) currentProcess.Kill(); currentProcess.Close(); } +#else + while (processQueue.Count > 0) + { + Process p = processQueue.Dequeue(); + p.Kill(); + p.WaitForExit(); + } +#endif } /// @@ -310,7 +367,7 @@ internal static async Task GetValueFromKeyvaultWitDefaultCreds(Uri keyva return (await client.GetSecretAsync(keyvaultSecretName)).Value.Value; } - internal static bool StartAndVerifyProcessesAreRunning(List processDataEntries, out Dictionary processes) + internal static bool StartAndVerifyProcessesAreRunning(List processDataEntries, out Dictionary processes, uint numRetries) { processes = new Dictionary(); @@ -324,16 +381,16 @@ internal static bool StartAndVerifyProcessesAreRunning(List processDataEntry.EnvironmentVariables); processes.Add(processDataEntry.ExecutableName, process); + + // Gives the current process time to start up before the next process is run Thread.Sleep(5000); } //Verify that processes are running - for (int i = 0; i < 2; i++) + for (int i = 0; i < numRetries; i++) { - if (!UiTestHelpers.ProcessesAreAlive(processes.Values.ToList())) - { - RestartProcesses(processes, processDataEntries); - } + if (!UiTestHelpers.ProcessesAreAlive(processes.Values.ToList())) { RestartProcesses(processes, processDataEntries);} + else{ break; } } if (!UiTestHelpers.ProcessesAreAlive(processes.Values.ToList())) @@ -364,6 +421,20 @@ static void RestartProcesses(Dictionary processes, List? processes) + { + StringBuilder runningProcesses = new StringBuilder(); + if (processes != null) + { + foreach (var process in processes) + { +#pragma warning disable CA1305 // Specify IFormatProvider + runningProcesses.AppendLine($"Is {process.Key} running: {UiTestHelpers.ProcessIsAlive(process.Value)}"); +#pragma warning restore CA1305 // Specify IFormatProvider + } + } + return runningProcesses.ToString(); + } } /// @@ -403,4 +474,3 @@ public ProcessStartOptions( } } } - diff --git a/tests/E2E Tests/WebAppUiTests/WebAppCallsApiCallsGraphLocally.cs b/tests/E2E Tests/WebAppUiTests/WebAppCallsApiCallsGraphLocally.cs index ec8aa1fd0..c935050e4 100644 --- a/tests/E2E Tests/WebAppUiTests/WebAppCallsApiCallsGraphLocally.cs +++ b/tests/E2E Tests/WebAppUiTests/WebAppCallsApiCallsGraphLocally.cs @@ -3,18 +3,18 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Runtime.Versioning; -using System.Threading; +using System.Text; using System.Threading.Tasks; using Microsoft.Identity.Lab.Api; -using TC = Microsoft.Identity.Web.Test.Common.TestConstants; using Microsoft.Playwright; using Xunit; using Xunit.Abstractions; using Process = System.Diagnostics.Process; -using System.Linq; -using System.Text; +using TC = Microsoft.Identity.Web.Test.Common.TestConstants; +using UITH = WebAppUiTests.UiTestHelpers; namespace WebAppUiTests #if !FROM_GITHUB_ACTION @@ -30,6 +30,7 @@ public class WebAppCallsApiCallsGraphLocally : IClassFixture { /*grpcProcessOptions,*/ serviceProcessOptions, clientProcessOptions }, out processes); + bool areProcessesRunning = UITH.StartAndVerifyProcessesAreRunning(new List { /*grpcProcessOptions,*/ serviceProcessOptions, clientProcessOptions }, out processes, NumProcessRetries); if (!areProcessesRunning) { - _output.WriteLine("Process not started after 3 attempts."); + _output.WriteLine($"Process not started after {NumProcessRetries} attempts."); StringBuilder runningProcesses = new StringBuilder(); foreach (var process in processes) { -#pragma warning disable CA1305 // Specify IFormatProvider - runningProcesses.AppendLine($"Is {process.Key} running: {UiTestHelpers.ProcessIsAlive(process.Value)}"); -#pragma warning restore CA1305 // Specify IFormatProvider + runningProcesses.AppendLine(CultureInfo.InvariantCulture, $"Is {process.Key} running: {UITH.ProcessIsAlive(process.Value)}"); } Assert.Fail(TC.WebAppCrashedString + " " + runningProcesses.ToString()); } - page = await NavigateToWebApp(context, TodoListClientPort).ConfigureAwait(false); - LabResponse labResponse = await LabUserHelper.GetDefaultUserAsync().ConfigureAwait(false); + await UITH.NavigateToWebApp(TC.LocalhostUrl + TodoListClientPort, page); + LabResponse labResponse = await LabUserHelper.GetDefaultUserAsync(); // Initial sign in _output.WriteLine("Starting web app sign-in flow."); string email = labResponse.User.Upn; - await UiTestHelpers.FirstLogin_MicrosoftIdFlow_ValidEmailPassword(page, email, labResponse.User.GetOrFetchPassword(), _output); + await UITH.FirstLogin_MicrosoftIdFlow_ValidEmailPassword(page, email, labResponse.User.GetOrFetchPassword(), _output); await Assertions.Expect(page.GetByText("TodoList")).ToBeVisibleAsync(_assertVisibleOptions); await Assertions.Expect(page.GetByText(email)).ToBeVisibleAsync(_assertVisibleOptions); _output.WriteLine("Web app sign-in flow successful."); @@ -111,14 +110,14 @@ public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds // Sign out _output.WriteLine("Starting web app sign-out flow."); await page.GetByRole(AriaRole.Link, new() { Name = "Sign out" }).ClickAsync(); - await UiTestHelpers.PerformSignOut_MicrosoftIdFlow(page, email, TC.LocalhostUrl + TodoListClientPort + SignOutPageUriPath, _output); + await UITH.PerformSignOut_MicrosoftIdFlow(page, email, TC.LocalhostUrl + TodoListClientPort + SignOutPageUriPath, _output); _output.WriteLine("Web app sign out successful."); // Sign in again using Todo List button _output.WriteLine("Starting web app sign-in flow using Todo List button after sign out."); await page.GetByRole(AriaRole.Link, new() { Name = "TodoList" }).ClickAsync(); - await UiTestHelpers.SuccessiveLogin_MicrosoftIdFlow_ValidEmailPassword(page, email, labResponse.User.GetOrFetchPassword(), _output); - var todoLink = page.GetByRole(AriaRole.Link, new() { Name = "Create New" }); + await UITH.SuccessiveLogin_MicrosoftIdFlow_ValidEmailPassword(page, email, labResponse.User.GetOrFetchPassword(), _output); + var todoLink = page.GetByRole(AriaRole.Link, new() { Name = "Create New" }); await Assertions.Expect(todoLink).ToBeVisibleAsync(_assertVisibleOptions); _output.WriteLine("Web app sign-in flow successful using Todo List button after sign out."); @@ -126,14 +125,14 @@ public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds _output.WriteLine("Starting web app create new todo flow."); await todoLink.ClickAsync(); var titleEntryBox = page.GetByLabel("Title"); - await UiTestHelpers.FillEntryBox(titleEntryBox, TC.TodoTitle1); + await UITH.FillEntryBox(titleEntryBox, TC.TodoTitle1); await Assertions.Expect(page.GetByRole(AriaRole.Cell, new() { Name = TC.TodoTitle1 })).ToBeVisibleAsync(_assertVisibleOptions); _output.WriteLine("Web app create new todo flow successful."); // Edit todo item _output.WriteLine("Starting web app edit todo flow."); await page.GetByRole(AriaRole.Link, new() { Name = "Edit" }).ClickAsync(); - await UiTestHelpers.FillEntryBox(titleEntryBox, TC.TodoTitle2); + await UITH.FillEntryBox(titleEntryBox, TC.TodoTitle2); await Assertions.Expect(page.GetByRole(AriaRole.Cell, new() { Name = TC.TodoTitle2 })).ToBeVisibleAsync(_assertVisibleOptions); _output.WriteLine("Web app edit todo flow successful."); @@ -160,17 +159,17 @@ public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds _output.WriteLine("No Screenshot."); } - string runningProcesses = GetRunningProcessAsString(processes); + string runningProcesses = UITH.GetRunningProcessAsString(processes); Assert.Fail($"the UI automation failed: {ex} output: {ex.Message}.\n{runningProcesses}\nTest run: {guid}"); } finally { - // Add the following to make sure all processes and their children are stopped. - EndProcesses(processes); + // Make sure all application processes and their children are stopped. + UITH.EndProcesses(processes); // Stop tracing and export it into a zip archive. - string path = UiTestHelpers.GetTracePath(_testAssemblyLocation, TraceFileName); + string path = UITH.GetTracePath(_testAssemblyLocation, TraceFileName); await context.Tracing.StopAsync(new() { Path = path }); _output.WriteLine($"Trace data for {TraceFileName} recorded to {path}."); @@ -211,7 +210,7 @@ public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds IBrowser browser = await playwright.Chromium.LaunchAsync(new() { Headless = true }); IBrowserContext context = await browser.NewContextAsync(new BrowserNewContextOptions { IgnoreHTTPSErrors = true }); await context.Tracing.StartAsync(new() { Screenshots = true, Snapshots = true, Sources = true }); - IPage? page = null; + IPage page = await context.NewPageAsync(); try { @@ -219,27 +218,27 @@ 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 = UITH.StartAndVerifyProcessesAreRunning(new List { serviceProcessOptions, clientProcessOptions }, out processes, NumProcessRetries); if (!areProcessesRunning) { - _output.WriteLine("Process not started after 3 attempts."); + _output.WriteLine($"Process not started after {NumProcessRetries} attempts."); StringBuilder runningProcesses = new StringBuilder(); foreach (var process in processes) { #pragma warning disable CA1305 // Specify IFormatProvider - runningProcesses.AppendLine($"Is {process.Key} running: {UiTestHelpers.ProcessIsAlive(process.Value )}"); + runningProcesses.AppendLine($"Is {process.Key} running: {UITH.ProcessIsAlive(process.Value)}"); #pragma warning restore CA1305 // Specify IFormatProvider } Assert.Fail(TC.WebAppCrashedString + " " + runningProcesses.ToString()); } - page = await NavigateToWebApp(context, WebAppCiamPort); + await UITH.NavigateToWebApp(TC.LocalhostUrl + WebAppCiamPort, page); // Initial sign in _output.WriteLine("Starting web app sign-in flow."); string email = "idlab@msidlabciam6.onmicrosoft.com"; - await UiTestHelpers.FirstLogin_MicrosoftIdFlow_ValidEmailPassword(page, email, LabUserHelper.FetchUserPassword("msidlabciam6"), _output); + await UITH.FirstLogin_MicrosoftIdFlow_ValidEmailPassword(page, email, LabUserHelper.FetchUserPassword("msidlabciam6"), _output); await Assertions.Expect(page.GetByText("Welcome")).ToBeVisibleAsync(_assertVisibleOptions); await Assertions.Expect(page.GetByText(email)).ToBeVisibleAsync(_assertVisibleOptions); _output.WriteLine("Web app sign-in flow successful."); @@ -247,20 +246,20 @@ public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds // Sign out _output.WriteLine("Starting web app sign-out flow."); await page.GetByRole(AriaRole.Link, new() { Name = "Sign out" }).ClickAsync(); - await UiTestHelpers.PerformSignOut_MicrosoftIdFlow(page, email, TC.LocalhostUrl + WebAppCiamPort + SignOutPageUriPath, _output); + await UITH.PerformSignOut_MicrosoftIdFlow(page, email, TC.LocalhostUrl + WebAppCiamPort + SignOutPageUriPath, _output); _output.WriteLine("Web app sign out successful."); // Sign in again using Todo List button _output.WriteLine("Starting web app sign-in flow using sign in button after sign out."); await page.GetByRole(AriaRole.Link, new() { Name = "Sign in" }).ClickAsync(); - await UiTestHelpers.FirstLogin_MicrosoftIdFlow_ValidEmailPassword(page, email, LabUserHelper.FetchUserPassword("msidlabciam6"), _output); + await UITH.FirstLogin_MicrosoftIdFlow_ValidEmailPassword(page, email, LabUserHelper.FetchUserPassword("msidlabciam6"), _output); await Assertions.Expect(page.GetByText("Welcome")).ToBeVisibleAsync(_assertVisibleOptions); await Assertions.Expect(page.GetByText(email)).ToBeVisibleAsync(_assertVisibleOptions); _output.WriteLine("Web app sign-in flow successful using Sign in button after sign out."); } catch (Exception ex) { - //Adding guid incase of multiple test runs. This will allow screenshots to be matched to their appropriet test runs. + //Adding guid in case of multiple test runs. This will allow screenshots to be matched to their appropriet test runs. var guid = Guid.NewGuid().ToString(); try { @@ -274,17 +273,17 @@ public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds _output.WriteLine("No Screenshot."); } - string runningProcesses = GetRunningProcessAsString(processes); + string runningProcesses = UITH.GetRunningProcessAsString(processes); Assert.Fail($"the UI automation failed: {ex} output: {ex.Message}.\n{runningProcesses}\nTest run: {guid}"); } finally { // Add the following to make sure all processes and their children are stopped. - EndProcesses(processes); + UITH.EndProcesses(processes); // Stop tracing and export it into a zip archive. - string path = UiTestHelpers.GetTracePath(_testAssemblyLocation, TraceFileName); + string path = UITH.GetTracePath(_testAssemblyLocation, TraceFileName); await context.Tracing.StopAsync(new() { Path = path }); _output.WriteLine($"Trace data for {TraceFileName} recorded to {path}."); @@ -293,70 +292,6 @@ public async Task ChallengeUser_MicrosoftIdFlow_LocalApp_ValidEmailPasswordCreds playwright.Dispose(); } } - - private string GetRunningProcessAsString(Dictionary? processes) - { - StringBuilder runningProcesses = new StringBuilder(); - if (processes != null) - { - foreach (var process in processes) - { -#pragma warning disable CA1305 // Specify IFormatProvider - runningProcesses.AppendLine($"Is {process.Key} running: {UiTestHelpers.ProcessIsAlive(process.Value)}"); -#pragma warning restore CA1305 // Specify IFormatProvider - } - } - return runningProcesses.ToString(); - } - - private void EndProcesses(Dictionary? processes) - { - Queue processQueue = new(); - if (processes != null) - { - foreach (var process in processes) - { - processQueue.Enqueue(process.Value); - } - } - -#if WINDOWS - UiTestHelpers.KillProcessTrees(processQueue); -#else - while (processQueue.Count > 0) - { - Process p = processQueue.Dequeue(); - p.Kill(); - p.WaitForExit(); - } -#endif - } - - private async Task NavigateToWebApp(IBrowserContext context, uint port) - { - // Navigate to web app - IPage page = await context.NewPageAsync(); - - // The retry logic ensures the web app has time to start up to establish a connection. - uint InitialConnectionRetryCount = 5; - while (InitialConnectionRetryCount > 0) - { - try - { - await page.GotoAsync(TC.LocalhostUrl + port); - break; - } - catch (PlaywrightException ex) - { - await Task.Delay(1000); - InitialConnectionRetryCount--; - if (InitialConnectionRetryCount == 0) - { throw ex; } - } - } - - return page; - } } } #endif // !FROM_GITHUB_ACTION diff --git a/tests/E2E Tests/WebAppUiTests/WebAppIntegrationTests.cs b/tests/E2E Tests/WebAppUiTests/WebAppIntegrationTests.cs index e6d0fa59d..44c73fbd3 100644 --- a/tests/E2E Tests/WebAppUiTests/WebAppIntegrationTests.cs +++ b/tests/E2E Tests/WebAppUiTests/WebAppIntegrationTests.cs @@ -28,7 +28,7 @@ public async Task ChallengeUser_MicrosoftIdentityFlow_RemoteApp_ValidEmailPasswo IBrowser browser = await playwright.Chromium.LaunchAsync(new() { Headless = true }); IPage page = await browser.NewPageAsync(); await page.GotoAsync(UrlString); - LabResponse labResponse = await LabUserHelper.GetDefaultUserAsync().ConfigureAwait(false); + LabResponse labResponse = await LabUserHelper.GetDefaultUserAsync(); try {