Skip to main content
End-to-end tests use Playwright. There are two modes: full browser E2E across 5 device configurations, and fast integration tests that run schema/content validation without launching a browser.

Running E2E tests

# Full suite — all 5 browser projects (builds Astro first)
npm run test:e2e

# Chromium only — fastest feedback
npm run test:chromium

# Headed mode — watch the browser
npm run test:headed

# Interactive Playwright UI explorer
npm run test:ui

# Specific test file
npx playwright test tests/e2e/smoke.spec.ts

# Specific browser project
npx playwright test --project=mobile-safari

Integration tests (no browser)

Integration tests use the same Playwright/Vitest runner but skip browser launch entirely. They validate schema structure, module imports, Storybook configuration, and story file conventions:
npx playwright test tests/integration
Integration test directories in tests/integration/:
  • blocks-2-1/ — Block registry and component conventions
  • schema-1-3/ — Sanity schema validation
  • storybook-1-4.test.ts — Storybook config, story files, and build verification
  • site-settings-2-3/ — Site settings schema
  • sponsor-3-1/ — Sponsor document schema
  • template-2-0/, variant-2-4/ — Template block wiring

Browser matrix

Five browser projects cover the primary desktop and mobile combinations:
ProjectDeviceUse case
chromiumDesktop ChromePrimary desktop browser
firefoxDesktop FirefoxCross-browser coverage
webkitDesktop SafarimacOS/iOS engine
mobile-chromePixel 7Android responsive
mobile-safariiPhone 14iOS responsive

playwright.config.ts

import { defineConfig, devices } from '@playwright/test'

const BASE_URL = process.env.BASE_URL || 'http://localhost:4321'

export default defineConfig({
  testDir: './tests',
  outputDir: './test-results',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,

  reporter: [
    ['html', { outputFolder: 'playwright-report', open: 'never' }],
    ['junit', { outputFile: 'test-results/results.xml' }],
    ['list'],
  ],

  timeout: 60_000,
  expect: { timeout: 10_000 },

  use: {
    baseURL: BASE_URL,
    actionTimeout: 15_000,
    navigationTimeout: 30_000,
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },

  webServer: {
    command: 'sh -c "npm run build --workspace=astro-app && npm run preview --workspace=astro-app"',
    url: BASE_URL,
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
  },

  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
    { name: 'mobile-chrome', use: { ...devices['Pixel 7'] } },
    { name: 'mobile-safari', use: { ...devices['iPhone 14'] } },
  ],
})

CI behavior

  • forbidOnly: true.only() in test files blocks the pipeline.
  • retries: 2 — Each test gets two retries before failing.
  • workers: 1 — Serial execution for stability (prevents port conflicts).
  • Traces captured on first retry, screenshots and videos on failure.

Test file locations

tests/
├── e2e/
│   ├── smoke.spec.ts              # Homepage smoke + a11y + perf baseline
│   ├── navigation.spec.ts         # Header/footer links and routing
│   ├── homepage-2-2.spec.ts       # Homepage block rendering
│   ├── pages-1-2.spec.ts          # Dynamic page routes
│   ├── dynamic-pages.spec.ts      # Sanity-driven page rendering
│   ├── sponsors.spec.ts           # Sponsor directory pages
│   ├── projects.spec.ts           # Project cards and listings
│   ├── events-calendar.spec.ts    # Event calendar block
│   ├── seo-structured-data.spec.ts # JSON-LD and meta tags
│   ├── site-settings-2-3.spec.ts  # Global site settings
│   ├── gtm-datalayer.spec.ts      # GA4 / GTM data layer
│   ├── portal-auth.spec.ts        # Sponsor portal auth flows
│   ├── portal-events.spec.ts      # Portal event management
│   ├── portal-progress.spec.ts    # Portal progress tracking
│   └── error-pages.spec.ts        # 404 and error handling
├── integration/                   # Schema/config validation (no browser)
└── support/
    ├── fixtures/
    │   └── index.ts               # Merged test fixtures
    └── helpers/
        └── a11y.ts                # axe-core WCAG 2.1 AA helper

Fixtures

All test files import from the shared fixtures index instead of importing directly from @playwright/test:
import { test, expect } from '../support/fixtures'
The fixture layer provides two automatic behaviors:
  • network-error-monitor — Fails the test if any HTTP 4xx/5xx responses are detected during page navigation. Opt out per-test with { annotation: [{ type: 'skipNetworkMonitoring' }] }.
  • log — Structured logging attached to Playwright HTML reports.

Writing a new E2E test

1

Create the spec file

Add a .spec.ts file in tests/e2e/. Name it after the feature (e.g., contact-form.spec.ts).
2

Import from fixtures

import { test, expect } from '../support/fixtures'
import { expectAccessible } from '../support/helpers/a11y'
3

Write tests with semantic locators

Prefer getByRole, getByText, and locator with semantic selectors. Use data-testid for elements with no semantic role:
test('page loads', async ({ page }) => {
  await page.goto('/sponsors')
  await expect(page.getByRole('heading', { name: /sponsors/i })).toBeVisible()
})
4

Include an accessibility assertion

Every new page or block test must call expectAccessible:
test('no a11y violations', async ({ page }) => {
  await page.goto('/sponsors')
  await expectAccessible(page)
})

Setting BASE_URL

By default, tests run against http://localhost:4321. Override with the BASE_URL environment variable to test a deployed preview URL:
BASE_URL=https://preview.ywcccapstone1.com npm run test:e2e
Install browser binaries once after cloning: npx playwright install --with-deps

Dependencies

PackagePurpose
@playwright/testTest runner and browser automation
@axe-core/playwrightWCAG accessibility auditing
@seontechnologies/playwright-utilsNetwork monitor and logging fixtures

Build docs developers (and LLMs) love