Skip to content

Working with E2E Testing

davidkallesen edited this page Apr 17, 2026 · 1 revision

Working with E2E Testing

The Showcase sample includes a Playwright end-to-end test suite that tests both the Blazor WASM and React frontends using the same test specs.

Architecture

test/Showcase.E2ETests/
β”œβ”€β”€ fixtures/test.ts           # Custom base test exposing appType fixture
β”œβ”€β”€ page-objects/*.page.ts     # Thin page wrappers with role-based selectors
β”œβ”€β”€ specs/*.spec.ts            # Test specs (each runs once per app)
β”œβ”€β”€ playwright.config.ts       # Two projects: blazor + react
β”œβ”€β”€ package.json
└── tsconfig.json

Key design: Playwright's multi-project feature runs every spec file against both apps automatically. page.goto('/accounts') resolves to http://localhost:5048/accounts for Blazor and http://localhost:5173/accounts for React.

Selector Strategy

Both MudBlazor and MUI render semantic HTML, so role-based selectors work across both apps:

page.getByRole('heading', { name: 'Accounts' })   // both apps
page.getByRole('button', { name: 'Create' })       // both apps
page.getByLabel(/name/i)                            // both apps
page.getByRole('table')                             // both apps

Only the navigation sidebar needs app-specific selectors (Blazor renders <a> links, React renders <div role="button">). The navigation.page.ts page object handles this via the appType fixture.

Prerequisites

cd test/Showcase.E2ETests
npm install
npx playwright install chromium

Running Tests

The webServer config in playwright.config.ts auto-starts the API and both frontends. If they're already running (e.g., via Aspire), Playwright reuses the existing servers.

npm test                  # run all tests (both apps)
npm run test:blazor       # Blazor only
npm run test:react        # React only
npm run test:ui           # interactive UI mode
npm run report            # view HTML report from last run

Useful Flags

npx playwright test accounts.spec.ts          # single spec file
npx playwright test --headed                  # visible browser
npx playwright test --headed --project=react  # visible browser, React only
npx playwright test --debug                   # step-by-step debugger

Ports

App Port Source
Showcase.Api 15046 Properties/launchSettings.json
Showcase.BlazorApp 5048 Properties/launchSettings.json
Showcase.ReactApp 5173 vite.config.ts

Page Object Pattern

Each page has a thin page object wrapping its key elements. Page objects use Locator properties and action methods β€” no abstract base classes or inheritance hierarchies.

// page-objects/accounts.page.ts
export class AccountsPage {
  readonly heading: Locator;
  readonly nameInput: Locator;
  readonly createButton: Locator;

  constructor(page: Page) {
    this.heading = page.getByRole('heading', { name: 'Accounts', exact: true });
    this.nameInput = page.getByLabel(/^name$/i);
    this.createButton = page.getByRole('button', { name: /^create$/i });
  }

  async goto() { await this.page.goto('/accounts'); }
  async createAccount(name: string) { /* ... */ }
}

Handling App Differences

When Blazor and React render different text for the same element, use regex:

// Blazor: "Paginated Accounts", React: "Accounts (Paginated)"
this.heading = page.getByRole('heading', { name: /paginated|accounts.*paginated/i });

When only navigation differs, use the appType fixture:

import type { AppType } from '../fixtures/test';

export class NavigationPage {
  constructor(private page: Page, private appType: AppType) {}

  async clickNavItem(label: string) {
    if (this.appType === 'blazor') {
      await this.page.getByRole('link', { name: label }).click();
    } else {
      await this.page.getByRole('button', { name: label }).click();
    }
  }
}

Test Coverage

15 spec files covering all Showcase pages:

Spec Route What It Tests
home / Dashboard heading, feature cards
accounts /accounts CRUD: create form, load, table
accounts-paginated /accounts/paginated Page size, load, pagination
accounts-streaming /accounts/async-enumerable Start/cancel/clear streaming
tasks /tasks CRUD: create form, load, table
users /users Search, filters, user list
user-form /users/create Form fields, submit, cancel
user-details /users/:id Profile display, buttons
files /files File cards, preview
files-upload /files/upload Upload tabs
notifications /notifications Connect/disconnect controls
notification-subscriptions /notifications/subscriptions Create form
webhook-demo /webhooks/demo Page content
exception-testing /testing/exceptions Run all tests, results
navigation sidebar Nav items, click navigation

Writing New Tests

  1. Create a page object in page-objects/ with role-based selectors
  2. Create a spec in specs/ importing { test, expect } from '../fixtures/test'
  3. Use getByRole, getByLabel, and getByText β€” avoid CSS selectors
  4. Use regex when Blazor and React render different text
  5. Only use appType when the DOM structure differs (not just text)

Never import from @playwright/test directly in spec files β€” always use ../fixtures/test so the appType fixture is available.

🏠 Home

πŸ’Ό Why This Tool?

πŸ“– Getting Started

βš™οΈ Features

🌐 Frontend

πŸ“‹ Reference


πŸ”— Resources

Clone this wiki locally