API Proposal: Microsoft.AspNetCore.Components.Testing E2E infrastructure
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 290
Description
## Background and Motivation
Blazor E2E testing currently requires significant boilerplate for process management, readiness detection, proxy routing, interactivity awareness, and tracing. Each test project reimplements this infrastructure. This package provides a Playwright + xUnit v3 + YARP-based E2E testing library that handles all of these concerns, enabling developers to write concise E2E tests for Blazor applications.
PR #65958. No linked issue. Adds `Microsoft.AspNetCore.Components.Testing` E2E test infrastructure.
## Proposed API
```diff
namespace Microsoft.AspNetCore.Components.Testing.Infrastructure;
+ public class ServerFixture : IAsyncLifetime, IAsyncDisposable
+ {
+ public string ProxyUrl { get; }
+ public ValueTask InitializeAsync();
+ public ValueTask DisposeAsync();
+ public Task StartServerAsync(string appName, Action? configure = null);
+ public Task StartServerAsync(Action? configure = null);
+ }
+ public class ServerInstance : IAsyncDisposable
+ {
+ public string AppName { get; }
+ public string AppUrl { get; }
+ public string Id { get; }
+ public string? PublicUrl { get; }
+ public string TestUrl { get; }
+ public ValueTask DisposeAsync();
+ }
+ public class ServerStartOptions
+ {
+ public Dictionary EnvironmentVariables { get; }
+ public int ReadinessTimeoutMs { get; set; }
+ public void ConfigureServices(Type type, string methodName);
+ public void ConfigureServices(string methodName);
+ }
+ public static class PlaywrightExtensions
+ {
+ public static Task NewTracedContextAsync(this BrowserTest test, ServerInstance server, BrowserNewContextOptions? options = null);
+ public static string SanitizeFileName(string name);
+ public static Task SetTestSession(this IBrowserContext context, ServerInstance server, string sessionId);
+ public static Task TraceAsync(this IBrowserContext context, string artifactDir);
+ public static Task WaitForBlazorAsync(this IPage page);
+ public static Task WaitForEnhancedNavigationAsync(this IPage page);
+ public static Task WaitForEnhancedNavigationAsync(this IPage page, Func navigationAction);
+ public static Task WaitForInteractiveAsync(this IPage page, string selector);
+ public static BrowserNewContextOptions WithArtifacts(this BrowserNewContextOptions options, string? artifactDir = null);
+ public static BrowserNewContextOptions WithServerRouting(this BrowserNewContextOptions options, ServerInstance server);
+ }
+ public class TracedContext : IAsyncDisposable
+ {
+ public IBrowserContext Context { get; }
+ public void Deconstruct(out IBrowserContext context);
+ public ValueTask DisposeAsync();
+ public Task NewPageAsync();
+ }
+ public class TracingSession : IAsyncDisposable
+ {
+ public ValueTask DisposeAsync();
+ public static Task StartAsync(IBrowserContext context, string artifactDir, bool recordVideo);
+ }
+ public class RemoteLock : IAsyncDisposable
+ {
+ public ValueTask DisposeAsync();
+ public Task ReleaseAsync();
+ }
+ public class ResourceLock : IAsyncDisposable
+ {
+ public ValueTask DisposeAsync();
+ public Task ReleaseAsync();
+ public Task WaitForRequestAsync();
+ public static Task CreateAsync(IPage page, Regex urlPattern);
+ }
+ public class TestLockClient
+ {
+ public RemoteLock Lock(string name);
+ public static Task CreateAsync(ServerInstance server, IBrowserContext context);
+ }
+ public class TestLockProvider
+ {
+ public bool Release(string key);
+ public Task WaitOn(string key);
+ }
+ public class TestSessionContext
+ {
+ public string? Id { get; set; }
+ }
+ public class TestReadinessHostingStartup : IHostingStartup
+ {
+ public void Configure(IWebHostBuilder builder);
+ }
+ public interface IE2EServiceOverrideResolver
+ {
+ Action? TryResolve(string assemblyQualifiedTypeName, string methodName);
+ }
```
## Usage Examples
```csharp
[Fact]
public async Task Counter_IncrementsOnClick()
{
await using var ctx = await this.NewTracedContextAsync(_server);
var page = await ctx.NewPageAsync();
await page.GotoAsync($"{_server.TestUrl}/counter");
await page.WaitForInteractiveAsync("button.btn-primary");
await page.GetByRole(AriaRole.Button, new() { Name = "Click me" }).ClickAsync();
await Expect(page.Locator("p[role='status']")).ToHaveTextAsync("Current count: 1");
}
```
## Alternative Designs
Each test project building its own infrastructure. The unified library reduces duplication and provides consistent patterns.
## Risks
Large public API surface. The library depends on Playwright and xUnit v3, which constrains the testing framework choice. This is primarily intended for testing Blazor applications within the ASP.NET Core repo and by library authors.
Contributor guide
Assessment
This issue has not been assessed yet.