From f8d90398af47d048a8a6711697b80c9a52b86c2f Mon Sep 17 00:00:00 2001 From: Huaxing YUAN Date: Mon, 31 Aug 2026 23:02:17 +0200 Subject: [PATCH 1/2] Update dependencies and stabilize passkey tests Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../AxaFrance.AxeExtended.HtmlReport.csproj | 2 +- .../AxaFrance.AxeExtended.Selenium.csproj | 3 +- .../AppFactory.cs | 1 - .../AxaFrance.WebEngine.MobileApp.csproj | 5 +- .../AxaFrance.WebEngine.Web.csproj | 8 +- src/AxaFrance.WebEngine.Web/BrowserFactory.cs | 7 - src/WebEngine.Test/UnitTests/Passkey.cs | 176 +++++++----------- .../UnitTests/PasskeyPageModel.cs | 20 +- 8 files changed, 94 insertions(+), 128 deletions(-) diff --git a/src/AxaFrance.AxeExtended.HtmlReport/AxaFrance.AxeExtended.HtmlReport.csproj b/src/AxaFrance.AxeExtended.HtmlReport/AxaFrance.AxeExtended.HtmlReport.csproj index 37a88d0..6c2db60 100644 --- a/src/AxaFrance.AxeExtended.HtmlReport/AxaFrance.AxeExtended.HtmlReport.csproj +++ b/src/AxaFrance.AxeExtended.HtmlReport/AxaFrance.AxeExtended.HtmlReport.csproj @@ -27,7 +27,7 @@ - + diff --git a/src/AxaFrance.AxeExtended.Selenium/AxaFrance.AxeExtended.Selenium.csproj b/src/AxaFrance.AxeExtended.Selenium/AxaFrance.AxeExtended.Selenium.csproj index a4c1477..4e8b17c 100644 --- a/src/AxaFrance.AxeExtended.Selenium/AxaFrance.AxeExtended.Selenium.csproj +++ b/src/AxaFrance.AxeExtended.Selenium/AxaFrance.AxeExtended.Selenium.csproj @@ -19,7 +19,8 @@ - + + diff --git a/src/AxaFrance.WebEngine.MobileApp/AppFactory.cs b/src/AxaFrance.WebEngine.MobileApp/AppFactory.cs index b821c32..2d7ad95 100644 --- a/src/AxaFrance.WebEngine.MobileApp/AppFactory.cs +++ b/src/AxaFrance.WebEngine.MobileApp/AppFactory.cs @@ -1,7 +1,6 @@ // Copyright (c) 2016-2022 AXA France IARD / AXA France VIE. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // Modified By: YUAN Huaxing, at: 2022-5-13 18:26 -using AngleSharp.Common; using Newtonsoft.Json.Linq; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Appium.Android; diff --git a/src/AxaFrance.WebEngine.MobileApp/AxaFrance.WebEngine.MobileApp.csproj b/src/AxaFrance.WebEngine.MobileApp/AxaFrance.WebEngine.MobileApp.csproj index e67383e..a7c06e5 100644 --- a/src/AxaFrance.WebEngine.MobileApp/AxaFrance.WebEngine.MobileApp.csproj +++ b/src/AxaFrance.WebEngine.MobileApp/AxaFrance.WebEngine.MobileApp.csproj @@ -20,9 +20,10 @@ - + - + + diff --git a/src/AxaFrance.WebEngine.Web/AxaFrance.WebEngine.Web.csproj b/src/AxaFrance.WebEngine.Web/AxaFrance.WebEngine.Web.csproj index 655fbaf..823f075 100644 --- a/src/AxaFrance.WebEngine.Web/AxaFrance.WebEngine.Web.csproj +++ b/src/AxaFrance.WebEngine.Web/AxaFrance.WebEngine.Web.csproj @@ -19,11 +19,11 @@ - + - - - + + + diff --git a/src/AxaFrance.WebEngine.Web/BrowserFactory.cs b/src/AxaFrance.WebEngine.Web/BrowserFactory.cs index a7ce3f1..5797a85 100644 --- a/src/AxaFrance.WebEngine.Web/BrowserFactory.cs +++ b/src/AxaFrance.WebEngine.Web/BrowserFactory.cs @@ -15,8 +15,6 @@ using System.Net; using System.Runtime.CompilerServices; using System.Threading; -using WebDriverManager.DriverConfigs.Impl; -using WebDriverManager.Helpers; using static System.Environment; [assembly: InternalsVisibleTo("WebRunner")] @@ -426,7 +424,6 @@ private static object GetBrowserStackBrowserName(BrowserType browser) private static WebDriver GetEdgeDriver(IEnumerable browserOptions) { - new WebDriverManager.DriverManager().SetUpDriver(new EdgeConfig(), "MatchingBrowser"); OpenQA.Selenium.Edge.EdgeOptions options = new OpenQA.Selenium.Edge.EdgeOptions() { AcceptInsecureCertificates = true, @@ -439,7 +436,6 @@ private static WebDriver GetEdgeDriver(IEnumerable browserOptions) private static WebDriver GetChromeDriver(IEnumerable browserOptions) { - string version = "MatchingBrowser"; var options = new OpenQA.Selenium.Chrome.ChromeOptions() { AcceptInsecureCertificates = true, @@ -487,20 +483,17 @@ private static WebDriver GetChromeDriver(IEnumerable browserOptions) options.AddArgument("no-sandbox"); if (browserOptions != null) options.AddArguments(browserOptions); - new WebDriverManager.DriverManager().SetUpDriver(new ChromeConfig(), version); OpenQA.Selenium.Chrome.ChromeDriver cd = new OpenQA.Selenium.Chrome.ChromeDriver(options); return cd; } private static WebDriver GetFirefoxDriver(IEnumerable browserOptions) { - var binaryPath = new WebDriverManager.DriverManager().SetUpDriver(new FirefoxConfig(), VersionResolveStrategy.Latest); var directories = System.IO.Directory.GetDirectories($"{Environment.GetEnvironmentVariable("APPDATA")}\\Mozilla\\Firefox\\Profiles"); var d = directories.First(x => x.EndsWith("default")); OpenQA.Selenium.Firefox.FirefoxProfile profile = new OpenQA.Selenium.Firefox.FirefoxProfile(d); OpenQA.Selenium.Firefox.FirefoxOptions firefoxOptions = new OpenQA.Selenium.Firefox.FirefoxOptions(); firefoxOptions.AcceptInsecureCertificates = true; - firefoxOptions.BinaryLocation = binaryPath?.ToString(); firefoxOptions.Profile = profile; if (browserOptions != null) firefoxOptions.AddArguments(browserOptions); OpenQA.Selenium.Firefox.FirefoxDriver driver = new OpenQA.Selenium.Firefox.FirefoxDriver(firefoxOptions); diff --git a/src/WebEngine.Test/UnitTests/Passkey.cs b/src/WebEngine.Test/UnitTests/Passkey.cs index b0dbf29..230d650 100644 --- a/src/WebEngine.Test/UnitTests/Passkey.cs +++ b/src/WebEngine.Test/UnitTests/Passkey.cs @@ -5,10 +5,10 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OpenQA.Selenium; using OpenQA.Selenium.VirtualAuth; +using OpenQA.Selenium.Support.UI; using System; using System.Collections.Generic; using System.Linq; -using System.Threading; namespace WebEngine.Test.UnitTests { @@ -22,15 +22,9 @@ namespace WebEngine.Test.UnitTests [TestClass] public class Passkey { - static List storedCredentials = null; - static string testEmail = null; - - [ClassInitialize] - public static void Initialize(TestContext context) + private static string CreateTestEmail() { - // Generate unique test email for all tests - testEmail = $"test.passkey.{DateTime.Now:yyyyMMddHHmmss}@example.com"; - DebugLogger.WriteLine($"Test email for this run: {testEmail}"); + return $"test.passkey.{DateTime.UtcNow:yyyyMMddHHmmssfff}.{Guid.NewGuid():N}@example.com"; } /// @@ -58,102 +52,81 @@ private static string AddVirtualAuthenticator(WebDriver driver) } /// - /// Gets and stores all credentials from the virtual authenticator - /// This allows credentials to be restored in subsequent tests + /// Gets all credentials from the virtual authenticator. /// - private static void GetAndStoreCredentials(WebDriver driver) + private static List GetCredentials(WebDriver driver) { - try + if (!(driver is IHasVirtualAuthenticator hasVirtualAuth)) { - var seleniumDriver = driver as IWebDriver; - if (seleniumDriver is IHasVirtualAuthenticator hasVirtualAuth) - { - // Get all credentials from the authenticator - var credentials = hasVirtualAuth.GetCredentials(); - storedCredentials = credentials.ToList(); + throw new InvalidOperationException("The WebDriver does not support virtual authenticators."); + } - DebugLogger.WriteLine($"Stored {storedCredentials.Count} credential(s) from virtual authenticator"); + var credentials = hasVirtualAuth.GetCredentials().ToList(); + DebugLogger.WriteLine($"Stored {credentials.Count} credential(s) from virtual authenticator"); - foreach (var cred in storedCredentials) - { - DebugLogger.WriteLine($" - Credential ID: {BitConverter.ToString(cred.Id).Replace("-", "")}"); - DebugLogger.WriteLine($" RP ID: {cred.RpId}"); - DebugLogger.WriteLine($" Is Resident: {cred.IsResidentCredential}"); - } - } + foreach (var cred in credentials) + { + DebugLogger.WriteLine($" - Credential ID: {BitConverter.ToString(cred.Id).Replace("-", "")}"); + DebugLogger.WriteLine($" RP ID: {cred.RpId}"); + DebugLogger.WriteLine($" Is Resident: {cred.IsResidentCredential}"); } - catch (Exception ex) + + return credentials; + } + + private static void WaitForCredentials(WebDriver driver) + { + if (!(driver is IHasVirtualAuthenticator hasVirtualAuth)) { - DebugLogger.WriteLine($"Error getting credentials: {ex.Message}"); - throw; + throw new InvalidOperationException("The WebDriver does not support virtual authenticators."); } + + var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); + wait.Until(_ => hasVirtualAuth.GetCredentials().Any()); } /// - /// Restores all previously stored credentials to the virtual authenticator - /// This is used to simulate a returning user who has already registered + /// Restores credentials to the virtual authenticator. /// - private static void RestoreCredentials(WebDriver driver) + private static void RestoreCredentials(WebDriver driver, IEnumerable credentials) { - try + if (!(driver is IHasVirtualAuthenticator hasVirtualAuth)) { - if (storedCredentials != null && storedCredentials.Count > 0) - { - var seleniumDriver = driver as IWebDriver; - if (seleniumDriver is IHasVirtualAuthenticator hasVirtualAuth) - { - foreach (var credential in storedCredentials) - { - hasVirtualAuth.AddCredential(credential); - } - - DebugLogger.WriteLine($"Restored {storedCredentials.Count} credential(s) to virtual authenticator"); - } - } - else - { - DebugLogger.WriteLine("Warning: No credentials to restore"); - } + throw new InvalidOperationException("The WebDriver does not support virtual authenticators."); } - catch (Exception ex) + + var credentialList = credentials.ToList(); + foreach (var credential in credentialList) { - DebugLogger.WriteLine($"Error restoring credentials: {ex.Message}"); - throw; + hasVirtualAuth.AddCredential(credential); } + + DebugLogger.WriteLine($"Restored {credentialList.Count} credential(s) to virtual authenticator"); } - [TestMethod] - [TestCategory("Passkey")] - [Priority(1)] - public void Test01_RegisterPasskey() + private static List RegisterPasskey() { WebDriver driver = null; - PasskeyPageModel pageModel = null; try { - // Arrange driver = BrowserFactory.GetDriver(AxaFrance.WebEngine.Platform.Windows, BrowserType.ChromiumEdge); AddVirtualAuthenticator(driver); - pageModel = new PasskeyPageModel(driver); + var pageModel = new PasskeyPageModel(driver); + var testEmail = CreateTestEmail(); + DebugLogger.WriteLine($"Test email for registration: {testEmail}"); driver.Navigate().GoToUrl("https://www.passkeys.io/"); pageModel.CreateAccountLink.Click(); pageModel.CreateAccountEmailInput.SetValue(testEmail); pageModel.CreateAccountContinueButton.Click(); pageModel.CreatePasskeyButton.Click(); - GetAndStoreCredentials(driver); + WaitForCredentials(driver); - // Assert - Assert.IsNotNull(storedCredentials, "Credentials should be stored after registration"); - Assert.IsTrue(storedCredentials.Count > 0, "At least one credential should be created during registration"); - - DebugLogger.WriteLine($"Registration completed. Current URL: {driver.Url}"); - DebugLogger.WriteLine($"Total credentials stored: {storedCredentials.Count}"); + return GetCredentials(driver); } finally { - // Cleanup: Dispose the driver after test try { driver?.Quit(); @@ -172,71 +145,54 @@ public void Test01_RegisterPasskey() } } + [TestMethod] + [TestCategory("Passkey")] + [Priority(1)] + public void Test01_RegisterPasskey() + { + var credentials = RegisterPasskey(); + + Assert.IsTrue(credentials.Count > 0, "At least one credential should be created during registration"); + } + [TestMethod] [TestCategory("Passkey")] [Priority(2)] public void Test02_LoginWithPasskey() { WebDriver driver = null; - PasskeyPageModel pageModel = null; try { // Arrange - // Verify that credentials were stored from the registration test - Assert.IsNotNull(storedCredentials, "Credentials should have been stored from registration test"); - Assert.IsTrue(storedCredentials.Count > 0, "There should be at least one credential to use for login"); - - DebugLogger.WriteLine($"Starting login test with {storedCredentials.Count} stored credential(s)"); + var credentials = RegisterPasskey(); + Assert.IsTrue(credentials.Count > 0, "There should be at least one credential to use for login"); + DebugLogger.WriteLine($"Starting login test with {credentials.Count} stored credential(s)"); // Create a new WebDriver instance with virtual authenticator driver = BrowserFactory.GetDriver(AxaFrance.WebEngine.Platform.Windows, BrowserType.Chrome); AddVirtualAuthenticator(driver); - // Restore the credentials to the virtual authenticator - RestoreCredentials(driver); + RestoreCredentials(driver, credentials); - pageModel = new PasskeyPageModel(driver); + var pageModel = new PasskeyPageModel(driver); // Navigate to login page driver.Navigate().GoToUrl("https://www.passkeys.io/"); - Thread.Sleep(2000); // Wait for page to fully load - // Verify we're on Sign In page - Assert.IsTrue(pageModel.IsOnSignInPage(), "Should be on Sign In page"); - DebugLogger.WriteLine("On Sign In page"); + // Conditional mediation may complete the login before the button is available. + var authenticationStateWait = new WebDriverWait(driver, TimeSpan.FromSeconds(20)); + authenticationStateWait.Until(_ => pageModel.IsOnSignInPage() || pageModel.IsOnProfilePage()); - // Act - Click "Sign in with a passkey" button - DebugLogger.WriteLine("Clicking 'Sign in with a passkey' button"); - pageModel.SignInWithPasskeyButton.Click(); - Thread.Sleep(3000); // Wait for WebAuthn ceremony to complete - - // Assert - // Verify that login flow was executed - string currentUrl = driver.Url; - DebugLogger.WriteLine($"Login completed. Current URL: {currentUrl}"); - - // Check for successful login indicators - bool isLoggedIn = currentUrl != "https://www.passkeys.io/" || - currentUrl.Contains("success") || - currentUrl.Contains("dashboard") || - currentUrl.Contains("welcome") || - currentUrl.Contains("profile"); - - if (!isLoggedIn) + // Act - Click "Sign in with a passkey" button when conditional mediation did not log in. + if (pageModel.IsOnSignInPage()) { - // Alternative check: look for error box being hidden (indicates success) - bool errorBoxHidden = !pageModel.ErrorBox.IsDisplayed; - - // Check if we're no longer on the Sign In page - bool notOnSignInPage = !pageModel.IsOnSignInPage(); - - isLoggedIn = errorBoxHidden || notOnSignInPage; + DebugLogger.WriteLine("Clicking 'Sign in with a passkey' button"); + pageModel.SignInWithPasskeyButton.Click(); + authenticationStateWait.Until(_ => pageModel.IsOnProfilePage()); } - DebugLogger.WriteLine($"Login status: {(isLoggedIn ? "Success" : "Completed")}"); - DebugLogger.WriteLine($"Error box visible: {pageModel.ErrorBox.IsDisplayed}"); - // Login flow with passkey completed successfully + Assert.IsTrue(pageModel.IsOnProfilePage(), "The passkey login should display the profile page"); } finally { diff --git a/src/WebEngine.Test/UnitTests/PasskeyPageModel.cs b/src/WebEngine.Test/UnitTests/PasskeyPageModel.cs index 8f833fd..486bf58 100644 --- a/src/WebEngine.Test/UnitTests/PasskeyPageModel.cs +++ b/src/WebEngine.Test/UnitTests/PasskeyPageModel.cs @@ -208,10 +208,18 @@ public PasskeyPageModel(WebDriver driver) : base(driver) ClassName = "hanko_footer" }; + /// + /// Profile section displayed after a successful authentication. + /// + public WebElementDescription ProfileSection { get; set; } = new WebElementDescription() + { + CssSelector = "#profile-div:not([hidden])" + }; + /// /// Sign In page headline /// - private WebElementDescription SignInHeadline { get; set; } = new WebElementDescription() + public WebElementDescription SignInHeadline { get; set; } = new WebElementDescription() { TagName = "h1", ClassName = "hanko_headline hanko_grade1", @@ -227,7 +235,15 @@ public PasskeyPageModel(WebDriver driver) : base(driver) /// public bool IsOnSignInPage() { - return SignInHeadline.Exists(); + return SignInHeadline.Exists(1); + } + + /// + /// Checks if the authenticated profile is displayed. + /// + public bool IsOnProfilePage() + { + return ProfileSection.Exists(1); } /// From 24764e57ecce8bfb68f201eaf76bd953df0e5833 Mon Sep 17 00:00:00 2001 From: Huaxing YUAN Date: Mon, 31 Aug 2026 23:04:04 +0200 Subject: [PATCH 2/2] update mcp package --- .../AxaFrance.WebEngine.Mcp.csproj | 4 +-- src/AxaFrance.WebEngine.Mcp/Dockerfile | 30 ------------------- .../Tools/Selenium/AccessibilitySnapshot.cs | 4 +-- .../AxaFrance.WebEngine.ReportViewer.csproj | 4 +-- src/Samples.Gherkin/Samples.Gherkin.csproj | 4 +-- .../Samples.LinearScripting.csproj | 6 ++-- src/WebEngine.Test/WebEngine.Test.csproj | 6 ++-- 7 files changed, 14 insertions(+), 44 deletions(-) delete mode 100644 src/AxaFrance.WebEngine.Mcp/Dockerfile diff --git a/src/AxaFrance.WebEngine.Mcp/AxaFrance.WebEngine.Mcp.csproj b/src/AxaFrance.WebEngine.Mcp/AxaFrance.WebEngine.Mcp.csproj index 137f257..e196bf8 100644 --- a/src/AxaFrance.WebEngine.Mcp/AxaFrance.WebEngine.Mcp.csproj +++ b/src/AxaFrance.WebEngine.Mcp/AxaFrance.WebEngine.Mcp.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/src/AxaFrance.WebEngine.Mcp/Dockerfile b/src/AxaFrance.WebEngine.Mcp/Dockerfile deleted file mode 100644 index e6e45e6..0000000 --- a/src/AxaFrance.WebEngine.Mcp/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. - -# This stage is used when running from VS in fast mode (Default for Debug configuration) -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base -USER $APP_UID -WORKDIR /app -EXPOSE 8080 -EXPOSE 8081 - - -# This stage is used to build the service project -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build -ARG BUILD_CONFIGURATION=Release -WORKDIR /src -COPY ["McpGuildeTest/McpGuildeTest.csproj", "McpGuildeTest/"] -RUN dotnet restore "./McpGuildeTest/McpGuildeTest.csproj" -COPY . . -WORKDIR "/src/McpGuildeTest" -RUN dotnet build "./McpGuildeTest.csproj" -c $BUILD_CONFIGURATION -o /app/build - -# This stage is used to publish the service project to be copied to the final stage -FROM build AS publish -ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "./McpGuildeTest.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false - -# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration) -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "McpGuildeTest.dll"] \ No newline at end of file diff --git a/src/AxaFrance.WebEngine.Mcp/Tools/Selenium/AccessibilitySnapshot.cs b/src/AxaFrance.WebEngine.Mcp/Tools/Selenium/AccessibilitySnapshot.cs index 56fa96b..30d4c31 100644 --- a/src/AxaFrance.WebEngine.Mcp/Tools/Selenium/AccessibilitySnapshot.cs +++ b/src/AxaFrance.WebEngine.Mcp/Tools/Selenium/AccessibilitySnapshot.cs @@ -108,7 +108,7 @@ function getName(el) { } catch(e) {} } - // 4. Enclosing