- 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
274 lines
11 KiB
Markdown
274 lines
11 KiB
Markdown
---
|
|
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).
|