--- date: 2026-03-05T10:14:52+00:00 git_commit: ffea279b54ad84be09bd0e82b3ed9c89a95fc606 branch: master topic: "End-to-End Testing for Vue 3 with Playwright" tags: [research, e2e, playwright, testing, frontend] status: 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](https://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 (`/api` → `localhost: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 ```bash 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`. ### 6. Recommended playwright.config.ts ```typescript 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 ```json "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:** ```typescript await page.goto('/events/abc-123') await page.waitForURL('/events/abc-123') // confirms SPA router resolved ``` **Waiting for reactive content (auto-retry):** ```typescript await expect(page.getByRole('heading', { name: 'My Event' })).toBeVisible() // Playwright auto-retries assertions for up to the configured timeout ``` **URL assertions:** ```typescript await expect(page).toHaveURL(/\/events\/.+/) ``` **API mocking (for isolated E2E tests):** ```typescript 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:** ```typescript 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:** ```yaml - 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-9` — `dev` 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 - [Testing | Vue.js](https://vuejs.org/guide/scaling-up/testing) — official E2E recommendation - [Installation | Playwright](https://playwright.dev/docs/intro) - [webServer | Playwright](https://playwright.dev/docs/test-webserver) — Vite integration - [CI Intro | Playwright](https://playwright.dev/docs/ci-intro) - [Docker | Playwright](https://playwright.dev/docs/docker) - [Cypress vs Playwright 2026 | BugBug](https://bugbug.io/blog/test-automation-tools/cypress-vs-playwright/) - [Playwright vs Cypress | Katalon](https://katalon.com/resources-center/blog/playwright-vs-cypress) ## 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).