86 lines
2.7 KiB
Markdown
86 lines
2.7 KiB
Markdown
|
|
# 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`:
|
||
|
|
|
||
|
|
```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:
|
||
|
|
|
||
|
|
```ts
|
||
|
|
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:**
|
||
|
|
|
||
|
|
```ts
|
||
|
|
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()` |
|