Files
fete/docs/agents/research/2026-03-05-e2e-testing-playwright-vue3.md
nitrix 9cf199dd9f Add Playwright E2E tests with MSW API mocking
- Playwright + @msw/playwright + @msw/source for OpenAPI-driven mocks
- Chromium-only configuration with Vite dev server integration
- Smoke tests: home page, CTA, navigation
- US-1 tests: validation, event creation flow, localStorage, error handling
- Suppress Node 25 --localstorage-file warning from MSW cookieStore
2026-03-06 18:16:07 +01:00

11 KiB

date, git_commit, branch, topic, tags, status
date git_commit branch topic tags status
2026-03-05T10:14:52+00:00 ffea279b54 master End-to-End Testing for Vue 3 with Playwright
research
e2e
playwright
testing
frontend
complete

Research: End-to-End Testing for Vue 3 with Playwright

Research Question

How to set up and structure end-to-end tests for the fete Vue 3 + Vite frontend using Playwright?

Summary

Playwright is Vue 3's officially recommended E2E testing framework. It integrates with Vite projects through a webServer config block (no Vite plugin needed), supports Chromium/Firefox/WebKit under a single API, and is fully free including parallelism. The fete project's existing vitest.config.ts already excludes e2e/**, making the integration path clean.

Detailed Findings

1. Current Frontend Test Infrastructure

The project uses Vitest 4.0.18 with jsdom for unit/component tests:

  • Config: frontend/vitest.config.ts — merges with vite.config, uses jsdom environment, bail on first failure
  • Exclusion: Already excludes e2e/** from Vitest's test discovery (vitest.config.ts:10)
  • Existing tests: 3 test files with ~25 tests total:
    • src/composables/__tests__/useEventStorage.spec.ts (6 tests)
    • src/views/__tests__/EventCreateView.spec.ts (11 tests)
    • src/views/__tests__/EventStubView.spec.ts (8 tests)
  • No E2E framework is currently configured

2. Why Playwright

Vue's official testing guide (vuejs.org/guide/scaling-up/testing) positions Playwright as the primary E2E recommendation. Key advantages over Cypress:

Dimension Playwright Cypress
Browser support Chromium, Firefox, WebKit Chrome-family, Firefox (WebKit experimental)
Parallelism Free, native Requires paid Cypress Cloud
Architecture Out-of-process (CDP/BiDi) In-browser (same process)
Speed 35-45% faster in parallel Slower at scale
Pricing 100% free, Apache 2.0 Cloud features cost money
Privacy No account, no cloud dependency Cloud service integration

Playwright aligns with fete's privacy constraints (no cloud dependency, no account required).

3. Playwright + Vite Integration

Playwright does not use a Vite plugin. Integration is purely through process management:

  1. Playwright reads webServer.command and spawns the Vite dev server
  2. Polls webServer.url until ready
  3. Runs tests against use.baseURL
  4. Kills the server after all tests finish

The existing Vite dev proxy (/apilocalhost:8080) works transparently — E2E tests can hit the real backend or intercept via page.route() mocks.

Note: @playwright/experimental-ct-vue exists for component-level testing (mounting individual Vue components without a server), but is still experimental and is a different category from E2E.

4. Installation

cd frontend
npm install --save-dev @playwright/test
npx playwright install --with-deps chromium

Using npm init playwright@latest generates scaffolding automatically, but for an existing project manual setup is cleaner.

5. Project Structure

frontend/
  playwright.config.ts       # Playwright config
  e2e/                       # E2E test directory
    home.spec.ts
    event-create.spec.ts
    event-view.spec.ts
    fixtures/                # shared test fixtures (optional)
    helpers/                 # page object models (optional)
  playwright-report/         # generated HTML report (gitignored)
  test-results/              # generated artifacts (gitignored)

The e2e/ directory is already excluded from Vitest via vitest.config.ts:10.

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

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: process.env.CI ? 'github' : 'html',

  use: {
    baseURL: 'http://localhost:5173',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },

  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    // Uncomment for cross-browser coverage:
    // { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    // { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  ],

  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:5173',
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
    stdout: 'pipe',
  },
})

Key decisions:

  • testDir: './e2e' — separates E2E from Vitest unit tests
  • forbidOnly: !!process.env.CI — prevents test.only from shipping to CI
  • workers: process.env.CI ? 1 : undefined — single worker in CI avoids shared-state flakiness; locally uses all cores
  • reporter: 'github' — GitHub Actions annotations in CI
  • command: 'npm run dev' — runs generate:api first (via the existing npm script), then starts Vite
  • reuseExistingServer: !process.env.CI — reuses running dev server locally for fast iteration

7. package.json Scripts

"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:debug": "playwright test --debug"

8. .gitignore Additions

playwright-report/
test-results/

9. TypeScript Configuration

The existing tsconfig.app.json excludes src/**/__tests__/*. Since E2E tests live in e2e/ (outside src/), they are already excluded from the app build.

A separate tsconfig for E2E tests is not strictly required — Playwright's own TypeScript support handles it. If needed, a minimal e2e/tsconfig.json can extend tsconfig.node.json.

10. Vue-Specific Testing Patterns

Router navigation:

await page.goto('/events/abc-123')
await page.waitForURL('/events/abc-123')  // confirms SPA router resolved

Waiting for reactive content (auto-retry):

await expect(page.getByRole('heading', { name: 'My Event' })).toBeVisible()
// Playwright auto-retries assertions for up to the configured timeout

URL assertions:

await expect(page).toHaveURL(/\/events\/.+/)

API mocking (for isolated E2E tests):

await page.route('/api/events/**', async (route) => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify({ title: 'Test Event', date: '2026-04-01' }),
  })
})

Locator strategy — prefer accessible locators:

page.getByRole('button', { name: 'RSVP' })    // best
page.getByLabel('Event Title')                 // form fields
page.getByTestId('event-card')                 // data-testid fallback
page.locator('.some-class')                    // last resort

11. CI Integration

GitHub Actions workflow:

- name: Install Playwright browsers
  run: npx playwright install --with-deps chromium
  # --with-deps installs OS-level libraries (libglib, libnss, etc.)
  # Specify 'chromium' to save ~2min vs installing all browsers

- name: Run E2E tests
  run: npx playwright test

- uses: actions/upload-artifact@v4
  if: ${{ !cancelled() }}
  with:
    name: playwright-report
    path: frontend/playwright-report/
    retention-days: 30

Docker: Use official images mcr.microsoft.com/playwright:v1.x.x-noble (Ubuntu 24.04). Alpine is unsupported (browsers need glibc). Key flag: --ipc=host prevents Chromium memory exhaustion. The Playwright Docker image version must match the @playwright/test package version exactly.

For the fete project, E2E tests run as a separate CI step, not inside the app's Dockerfile.

12. Integration with Existing Backend

Two approaches for E2E tests:

  1. Mocked backend (via page.route()): Fast, isolated, no backend dependency. Good for frontend-only testing.
  2. Real backend: Start Spring Boot alongside Vite. Tests hit /api through the Vite proxy. More realistic but requires Java in CI. Could use Docker Compose.

The Vite proxy config (vite.config.ts:19-23) already forwards /api to localhost:8080, so both approaches work without changes.

Code References

  • frontend/vitest.config.ts:10 — E2E exclusion pattern already in place
  • frontend/vite.config.ts:19-23 — API proxy configuration for backend integration
  • frontend/package.json:8-9dev script runs generate:api before Vite
  • frontend/src/router/index.ts — Route definitions (Home, Create, Event views)
  • frontend/src/api/client.ts — openapi-fetch client using /api base URL
  • frontend/tsconfig.app.json — App TypeScript config (excludes test files)

Architecture Documentation

Test Pyramid in fete

Layer Framework Directory Purpose
Unit Vitest + jsdom src/**/__tests__/ Composables, isolated logic
Component Vitest + @vue/test-utils src/**/__tests__/ Vue component behavior
E2E Playwright (proposed) e2e/ Full browser, user flows
Visual browser-interactive-testing skill .agent-tests/ Agent-driven screenshots

Decision Points for Implementation

  1. Start with Chromium only — add Firefox/WebKit later if needed
  2. Use npm run dev as webServer command (includes API type generation)
  3. API mocking by default — use page.route() for E2E isolation; full-stack tests as a separate concern
  4. data-testid attributes on key interactive elements for stable selectors
  5. Page Object Model recommended once the test suite grows beyond 5-10 tests

Sources

Decisions (2026-03-05)

  • Mocked backend only — E2E tests use page.route() to mock API responses. No real Spring Boot backend in E2E.
  • Mocking stack: @msw/playwright + @msw/source — reads OpenAPI spec at runtime, generates MSW handlers, per-test overrides via network.use().
  • US-1 flows first — Event creation is the only implemented user story; E2E tests cover that flow.
  • No CI caching yet — Playwright browser binaries are not cached; CI runner needs reconfiguration first.
  • E2E tests are part of frontend tasks — every frontend user story includes E2E test coverage going forward.
  • OpenAPI examples mandatory — all response schemas in the OpenAPI spec must include example: fields (required for @msw/source mock generation).