-
Notifications
You must be signed in to change notification settings - Fork 0
Add Playwright UI test service for E2E testing with WebView2 support #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
@copilot Add WebView2 support for the WPF/Console hosts as well. You won't be able to run them because they require Windows, but there needs to be an abstraction defined that will make both hosts work. It looks like the opportunity would be to have AdaptiveRemoteHost choose HeadlessUITestService or BlazorWebViewUITestService when calling CreateUITestServiceAsync, depending on the type of host. You should also add the support for passing the command line parameters/environment to the BlazorWebView. Other notes: test/AdaptiveRemote.EndtoEndTests.TestServices/AdaptiveRemote.EndtoEndTests.TestServices.csproj:19 - If we add a shared test interface, then the TestServices project won't have to take a direct dependency on this specific host project. test/AdaptiveRemote.EndtoEndTests.TestServices/Host/AdaptiveRemoteHost.cs:53 - Consider a shared helper method for the Lazy constructors, to reduce duplicated code as we add services. test/AdaptiveRemote.EndtoEndTests.TestServices/HeadlessUITestService.cs:18 - This should hold onto the browserService and access browserService.Page, rather than capturing the IPage right away. The page could change if the browser needs to refresh or navigate elsewhere. test/AdaptiveRemote.EndtoEndTests.TestServices/HeadlessUITestService.cs:86 - All this logic should be unnecessary. Playwright has the ability to throw meaningful errors if there are no matches or ambiguous matches. We should use that rather than building our own. test/AdaptiveRemote.EndtoEndTests.TestServices/IUITestServiceExtensions.cs:36 - This should throw a TimeoutException if WaitForAsyncTask returns false. (Maybe WaitForAsyncTask should always throw TimeoutException, except then the exception message can't tell you what timed out.) test/AdaptiveRemote.EndtoEndTests/HeadlessHostTests.cs:59 - Creating a separate test is ok for now, but I really want to modify RunStandardE2ETestAsync to use the IUITestService instead of InvokeCommand, once all the hosts support IUITestService. This separate test is duplicating a lot of unclear test logic. |
Addressed in commit 684a2ec: Interface abstraction (line 11): Created Shared test interface (line 19): Removed TestServices dependency on Headless project by using Shared helper method (line 53): Added Access Page dynamically (line 18): Updated to hold Simplified locator logic (line 86): Now uses Playwright's TimeoutException (line 36): Fixed Working on WebView2 support and command-line parameters next. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything in this file, except the acquisition of the IPage, will want to be shared between the HeadlessHost implementation and the BlazorWebView implementation. (I.e. all the logic regarding how buttons are found, checked, and clicked)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe there could be a base class with an abstract "IPage" property, so that all these methods work against "IPage" but the subclasses are responsible for how the IPage is acquired.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created UITestServiceBase abstract class with abstract GetPageAsync() method in commit 11e9d21. All shared logic now in base class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created UITestServiceBase abstract class in commit 11e9d21. All button interaction logic (finding, checking visibility/enabled state, clicking) is now shared. Subclasses only implement GetPageAsync() to provide their specific IPage source.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Page and Browser properties can be removed from this class, now that they are no longer being directly referenced by the test service
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed unused Page and Browser properties in commit 11e9d21. Only the IBrowserProvider.TestContext explicit interface implementation remains.
| /// Gets the browser page/document instance for interacting with the UI. | ||
| /// The actual type depends on the browser implementation (e.g., Playwright IPage, WebView2 CoreWebView2). | ||
| /// </summary> | ||
| object? BrowserPage { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is also used to pass the debugger port in BlazorWebView cases, it shouldn't be called "BrowserPage"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to TestContext in commit 11e9d21. Now clearly indicates it can be either an IPage or a port number depending on implementation.
| // UI-related host services | ||
| services.AddSingleton(mainWindow); | ||
| services.AddSingleton<IBrowserProvider, WebView2BrowserProvider>(); | ||
| services.AddHostedService<WebView2TestConfigurator>(); // Must run before BlazorWindowServicesSetter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Combine WebView2TestConfigurator and BlazorWindowServicesSetter into one class. They're both basically doing the same thing, setting up the BlazorWebView.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Combined in commit 11e9d21. BlazorWindowServicesSetter now handles both WebView2 configuration (if test mode enabled) and service provider setup. Removed separate WebView2TestConfigurator class.
| { | ||
| private readonly IConfiguration _configuration; | ||
|
|
||
| public WebView2BrowserProvider(IConfiguration configuration) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import the TestingSettings type and use it, instead of accessing IConfiguration like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated in commit 11e9d21. Now uses IOptions<TestingSettings> and accesses _testingSettings.WebViewRemoteDebuggingPort.
| private readonly BlazorWebView _browser; | ||
| private readonly IConfiguration _configuration; | ||
|
|
||
| public WebView2TestConfigurator(MainWindow mainWindow, IConfiguration configuration) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also here, use the TestingSettings type instead of raw IConfiguration access.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated in commit 11e9d21. BlazorWindowServicesSetter now uses IOptions<TestingSettings> instead of IConfiguration.
| await _browser.Dispatcher.InvokeAsync(async () => | ||
| { | ||
| // Create per-instance user data folder to avoid collisions | ||
| string userDataFolder = System.IO.Path.Combine( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's necessary to create a unique user data folder for this. We don't when running normally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed in commit 11e9d21. Now passes null for user data folder, using default location like normal operation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment in HeadlessUITestService. Logic in these two services should be shared, not duplicated. Everything except for how the IPage is acquired.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored in commit 11e9d21. Both services now extend UITestServiceBase which contains all shared logic. Only GetPageAsync() differs between implementations.
| _testEndpoint.CreateTestServiceAsync<ApplicationTestService>); | ||
|
|
||
| // Choose UI test service based on host type | ||
| if (_settings.ExePath.Contains("Headless", StringComparison.OrdinalIgnoreCase)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of basing this on the ExePath, let's add a setting to AdaptiveRemoteHostSettings, so that the test can control which kind of UI service it expects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added UIServiceType enum and UIService property to AdaptiveRemoteHostSettings in commit 11e9d21. Test controls which service type is expected. Defaults to Headless for "Headless" in path, otherwise BlazorWebView.
Test Results306 tests 304 ✅ 47s ⏱️ Results for commit d806beb. ♻️ This comment has been updated with latest results. |
Co-authored-by: jodavis <6740581+jodavis@users.noreply.github.com>
Co-authored-by: jodavis <6740581+jodavis@users.noreply.github.com>
Co-authored-by: jodavis <6740581+jodavis@users.noreply.github.com>
…c number Co-authored-by: jodavis <6740581+jodavis@users.noreply.github.com>
- Added WebViewRemoteDebuggingPort setting to TestingSettings - Created WebView2TestConfigurator to configure WebView2 environment with remote debugging - Created WebView2BrowserProvider to provide remote debugging port to test services - Created BlazorWebViewUITestService that connects to WebView2 via Playwright - Updated AdaptiveRemoteHost to choose HeadlessUITestService or BlazorWebViewUITestService based on host type - Tests can now drive UI on both Headless and WebView2-based hosts Co-authored-by: jodavis <6740581+jodavis@users.noreply.github.com>
…configuration - Created UITestServiceBase abstract class to share logic between Headless and BlazorWebView implementations - Removed unused Page and Browser properties from PlaywrightBrowserLifetimeService - Renamed IBrowserProvider.BrowserPage to TestContext for better clarity - Combined WebView2TestConfigurator into BlazorWindowServicesSetter - Updated WebView2BrowserProvider and BlazorWindowServicesSetter to use TestingSettings instead of IConfiguration - Removed unique user data folder creation (not necessary) - Added UIServiceType enum and property to AdaptiveRemoteHostSettings - Updated AdaptiveRemoteHost to use UIService property instead of ExePath check - Made TestingSettings public for cross-assembly access Co-authored-by: jodavis <6740581+jodavis@users.noreply.github.com>
11e9d21 to
d806beb
Compare
Playwright UI Test Integration - Complete with Code Review Feedback Addressed
Core Architecture
BrowserPagetoTestContextfor clarityWebViewRemoteDebuggingPortconfigurationHeadless Host Integration
PlaywrightBrowserLifetimeServiceimplementsIBrowserProvider(remains internal)HeadlessUITestServiceextends UITestServiceBase, provides synchronous IPage accessWebView2 Support (WPF/Console)
Test Infrastructure
CreateLazyTestServicehelper reduces duplicationTests Passing ✅
Original prompt
This section details on the original issue you should resolve
<issue_title>Use Playwright to drive UI in E2E tests</issue_title>
<issue_description>Is your feature request related to a problem? Please describe.
As we add tests for more functionality, it will be useful to drive the tests directly through the HTML-based UI rather than internal services, to test more of the functionality end-to-end.
Describe the solution you'd like
The headless host already uses Playwright to launch its headless browser. I would also like the BlazorWebView to allow Playwright to attach, so the tests can drive the application via the same interface regardless of which host is used.
Describe alternatives you've considered
We're currently driving the test by invoking commands directly, but this is not testing the Blazor view's ability to invoke commands.
Additional context
Playwright driver design — AdaptiveRemote End-to-End Tests
Exit criteria: each host can configure its browser so that Playwright can connect to it and inspect/interact with UI elements. Tests are updated to validate proper display of some UI elements, test execution of a command, and invoke shutdown via the Exit button.
Overview
This document describes a small UI test service and host configuration guidance to allow the existing end-to-end test suite to drive the application's UI using Playwright rather than invoking internal services directly. The aim is to make tests exercise the real HTML/CSS UI produced by the Blazor app so tests better reflect user experience.
Goals
IsButtonVisible,IsButtonEnabled, andClickButtonthat locate buttons by their exact visible label (trimmed), case-sensitive, and fail if multiple matches exist.Test service: IUITestService (intent-based)
Location and loading
AdaptiveRemote.AppalongsideITestEndpointandIApplicationTestService.AdaptiveRemote.EndToEndTests.TestServicesand loaded into host processes viaITestEndpointso implementations have direct access to host Playwright/WebView2 objects.IApplicationTestService).API surface (async)
Task<bool> IsButtonVisibleAsync(string label, CancellationToken cancellationToken = default)Task<bool> IsButtonEnabledAsync(string label, CancellationToken cancellationToken = default)Task ClickButtonAsync(string label, CancellationToken cancellationToken = default)Concrete interface (C#)
Matching rules
Behavior details
IsButtonVisibleAsyncreturnstrueonly if a matching element exists in the rendered DOM and is visible (i.e., would be perceivable to a user). Use Playwright'sIsVisiblesemantics for implementation (hidden elements,display:none,aria-hidden,opacity:0treated as not visible).IsButtonEnabledAsyncreturnstrueonly if the element has nodisabledattribute and is not inert viaaria-disabled="true"(treataria-disabledas disabled for our tests).ClickButtonAsyncmust ensure the element is visible and enabled before performing a click; otherwise it should throw a clear exception. After issuing the click it should wait for Playwright's default navigation/async stability mechanisms (e.g., wait for network/DOM settles) for a short timeout (default 2s) to let UI updates happen, but callers must still perform explicit assertions where needed.Synchronous wrappers (test-side)
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.