veza/docs/testing/E2E_STABILITY_GUIDE.md
senke 172581ff02 chore(cleanup): remove orphan code + archive disabled workflows + .playwright-mcp
Triple cleanup, landed together because they share the same cleanup
branch intent and touch non-overlapping trees.

1. 38× tracked .playwright-mcp/*.yml stage-deleted
   MCP session recordings that had been inadvertently committed.
   .gitignore already covers .playwright-mcp/ (post-audit J2 block
   added in d12b901de). Working tree copies removed separately.

2. 19× disabled CI workflows moved to docs/archive/workflows/
   Legacy .yml.disabled files in .github/workflows/ were 1676 LOC of
   dead config (backend-ci, cd, staging-validation, accessibility,
   chromatic, visual-regression, storybook-audit, contract-testing,
   zap-dast, container-scan, semgrep, sast, mutation-testing,
   rust-mutation, load-test-nightly, flaky-report, openapi-lint,
   commitlint, performance). Preserved in docs/archive/workflows/
   for historical reference; `.github/workflows/` now only lists the
   5 actually-running pipelines.

3. Orphan code removed (0 consumers confirmed via grep)
   - veza-backend-api/internal/repository/user_repository.go
     In-memory UserRepository mock, never imported anywhere.
   - proto/chat/chat.proto
     Chat server Rust deleted 2026-02-22 (commit 279a10d31); proto
     file was orphan spec. Chat lives 100% in Go backend now.
   - veza-common/src/types/chat.rs (Conversation, Message, MessageType,
     Attachment, Reaction)
   - veza-common/src/types/websocket.rs (WebSocketMessage,
     PresenceStatus, CallType — depended on chat::MessageType)
   - veza-common/src/types/mod.rs updated: removed `pub mod chat;`,
     `pub mod websocket;`, and their re-exports.
   Only `veza_common::logging` is consumed by veza-stream-server
   (verified with `grep -r "veza_common::"`). `cargo check` on
   veza-common passes post-removal.

Refs: AUDIT_REPORT.md §8.2 "Code mort / orphelin" + §9.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:33:40 +02:00

2.7 KiB

E2E Test Stability Guide

Architecture

tests/e2e/
├── playwright.config.ts          # Main config (sharding, multi-browser)
├── global-setup.ts               # Creates test users via API
├── global-teardown.ts            # Cleanup
├── helpers.ts                    # Core helpers (login, navigate, assert)
├── helpers/
│   └── selectors.ts              # Centralized selectors (SEL object)
├── fixtures/
│   ├── auth.fixture.ts           # API-driven auth fixtures
│   ├── factories.ts              # Test data factories (user, playlist, etc.)
│   └── file-helpers.ts           # Mock MP3 file generators
├── *.spec.ts                     # Test specs
└── audit/                        # Audit-specific specs (a11y, visual, etc.)

Selectors

Always use data-testid for E2E selectors. Import from helpers/selectors.ts:

import { SEL } from './helpers/selectors';

// Good
await page.getByTestId(SEL.toast.success);
await page.getByTestId(SEL.dialog.confirm);

// Bad — fragile, breaks on text changes
await page.getByText('Create');
await page.locator('button.submit');

Component data-testid are defined in apps/web/src/components/ui/testids.ts and mirrored in SEL.

Authentication

Use API login, not UI login for tests that don't test the login flow:

import { test, expect } from '../fixtures/auth.fixture';

test('playlist CRUD', async ({ listenerPage }) => {
  // listenerPage is already authenticated via API
  await listenerPage.goto('/playlists');
});

Data Factories

Create test data via API, not UI clicks:

import { createPlaylist, ensureTracksExist } from '../fixtures/factories';

test('add track to playlist', async ({ creatorPage }) => {
  const playlist = await createPlaylist(creatorPage, { name: 'Test Playlist' });
  // ...
});

CI Configuration

  • e2e-critical: @critical tag only, Chromium, blocks PR (<3min)
  • e2e-full: All tests, 4-way sharded, all browsers, 10-15min

Debugging Flaky Tests

  1. Run locally with trace: PLAYWRIGHT_TRACE=on npm run e2e:serial
  2. Check tests/e2e/test-results/ for trace files
  3. Open trace: npx playwright show-trace <trace.zip>
  4. Check flaky report: node scripts/flaky-detection.mjs

Common Pitfalls

Problem Solution
Toast selector mismatch Use data-testid="toast-success" not text content
Dialog button collision Use data-testid="dialog-confirm" not getByText('Create')
Rate limit in tests Env DISABLE_RATE_LIMIT_FOR_TESTS=true
Stale selector after navigation Wait for main element after navigateTo()
Login flaky Use loginViaAPI() instead of loginViaUI()