Playwright Integration Testing¶
Demonstrates browser-based integration testing using Playwright with Aspire's DistributedApplicationTestingBuilder to spin up a complete distributed application for end-to-end testing.
๐ Overview¶
This example shows how to:
- Set up Playwright for browser automation in .NET integration tests
- Use
DistributedApplicationTestingBuilderto orchestrate a full-stack app during tests - Test a React + Vite frontend communicating with a .NET API
- Generate TypeScript API clients using Kiota from TypeSpec definitions
๐๏ธ Architecture¶
flowchart LR
subgraph TestBuilder["DistributedApplicationTestingBuilder"]
Playwright["Playwright<br/>(Browser)"] --> Frontend["React + Vite<br/>Frontend<br/>(Kiota TS)"]
Frontend --> API["Weather API<br/>(.NET 10)<br/>(OpenAPI)"]
end
๐งฉ Components¶
| Project | Description |
|---|---|
aspire-playwright.AppHost |
Aspire orchestration for API and frontend |
aspire-playwright.Api |
.NET 10 Weather API with built-in OpenAPI |
aspire-playwright.Frontend |
React + Vite frontend with Kiota TypeScript client |
aspire-playwright.ServiceDefaults |
OpenTelemetry and health check configuration |
aspire-playwright.Tests |
Playwright integration tests |
aspire-playwright.TypeSpec |
TypeSpec API definition generating OpenAPI |
๐ Key Features¶
- Full-Stack Integration Tests: Spin up complete distributed apps for browser testing
- Playwright Browser Automation: Real browser testing with Chromium
- Aspire 13
AddJavaScriptApp: Modern JavaScript/TypeScript app orchestration - .NET 10 OpenAPI: Built-in
AddOpenApi()andMapOpenApi()(no Swashbuckle) - Kiota TypeScript Client: Type-safe API calls from React frontend
- TypeSpec API Definition: Contract-first API design
๐ Project Structure¶
foundry/dotnet/aspire-playwright/
โโโ aspire-playwright.sln
โโโ Directory.Build.props # Shared build properties
โโโ Directory.Packages.props # Central package management
โโโ aspire-playwright.AppHost/
โ โโโ Program.cs # Aspire orchestration
โ โโโ Properties/launchSettings.json
โโโ aspire-playwright.Api/
โ โโโ Program.cs # Weather API with OpenAPI
โโโ aspire-playwright.Frontend/
โ โโโ src/
โ โ โโโ App.tsx # Main React component
โ โ โโโ client/ # Kiota-generated TS client
โ โโโ package.json
โ โโโ vite.config.ts
โโโ aspire-playwright.ServiceDefaults/
โ โโโ Extensions.cs # OpenTelemetry configuration
โโโ aspire-playwright.Tests/
โ โโโ WeatherAppTests.cs # Playwright integration tests
โโโ aspire-playwright.TypeSpec/
โโโ main.tsp # TypeSpec API definition
๐ Getting Started¶
Prerequisites¶
- .NET 10.0 SDK
- Node.js 22.x or 24.x
- Playwright browsers (auto-installed on first test run)
Running the Application¶
Running the Tests¶
On first run, Playwright will download the required browser (Chromium).
๐งช Test Examples¶
The test suite includes 10 integration tests:
| Test | Description |
|---|---|
WeatherTable_Should_Display_5_Records |
Verifies API returns exactly 5 weather records |
RefreshButton_Should_Update_Weather_Data |
Verifies refresh button fetches new random data |
WeatherTable_Should_Have_Correct_Columns |
Validates table headers (Date, ยฐC, ยฐF, Summary) |
MultipleRefreshes_Should_Track_Refresh_Count |
Tests refresh counter increments correctly |
Temperature_Conversion_Should_Be_Correct |
Validates ยฐF = 32 + (ยฐC / 0.5556) formula |
WeatherData_Should_Have_Valid_Summaries |
Validates summaries from known set |
WeatherData_Should_Have_Valid_Date_Format |
Validates dates are parseable and in range |
Page_Should_Have_Correct_Title_And_Heading |
Verifies page title and h1 heading |
LoadingState_Should_Display_While_Fetching |
Tests loading indicator visibility |
Temperature_Range_Should_Be_Valid |
Validates temperatures within API range |
Test Pattern¶
public class WeatherAppTests : IAsyncLifetime
{
private DistributedApplication? _app;
private IPlaywright? _playwright;
private IBrowser? _browser;
public async Task InitializeAsync()
{
// Start the distributed application
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.aspire_playwright_AppHost>();
_app = await appHost.BuildAsync();
await _app.StartAsync();
// Initialize Playwright
_playwright = await Playwright.CreateAsync();
_browser = await _playwright.Chromium.LaunchAsync();
}
[Fact]
public async Task WeatherTable_Should_Display_5_Records()
{
var page = await _browser!.NewPageAsync();
await page.GotoAsync(_frontendUrl!);
var rows = await page.Locator("[data-testid='weather-table'] tbody tr")
.CountAsync();
Assert.Equal(5, rows);
}
}
๐ง Key Implementation Details¶
AppHost Configuration (Aspire 13)¶
var builder = DistributedApplication.CreateBuilder(args);
var weatherApi = builder.AddProject<Projects.aspire_playwright_Api>("weather-api");
builder.AddJavaScriptApp("frontend", "../aspire-playwright.Frontend", "dev")
.WithEnvironment("VITE_API_URL", weatherApi.GetEndpoint("http"))
.WithHttpEndpoint(env: "PORT")
.WaitFor(weatherApi);
builder.Build().Run();
.NET 10 OpenAPI (No Swashbuckle)¶
Kiota TypeScript Client¶
import { WeatherApiClient } from './generated/weatherApiClient';
import { FetchRequestAdapter } from '@microsoft/kiota-http-fetchlibrary';
const adapter = new FetchRequestAdapter(new AnonymousAuthenticationProvider());
adapter.baseUrl = import.meta.env.VITE_API_URL;
const client = new WeatherApiClient(adapter);
const forecasts = await client.weatherforecast.get();
๐ก API Endpoints¶
| Endpoint | Method | Description |
|---|---|---|
/weatherforecast |
GET | Returns 5 random weather items |
/openapi/v1.json |
GET | OpenAPI specification |
๐ Related Documentation¶
๐ Source Code¶
Location: foundry/dotnet/aspire-playwright/