Some checks failed
Deploy Web Apps / deploy (push) Has been cancelled
Queue Release Build / prepare (push) Successful in 21s
Queue Release Build / build-linux (push) Successful in 27m44s
Queue Release Build / build-windows (push) Successful in 32m16s
Queue Release Build / finalize (push) Successful in 1m54s
12 KiB
12 KiB
name, description
| name | description |
|---|---|
| playwright-e2e | Write and run Playwright E2E tests for MetoYou (Angular chat + WebRTC voice/video app). Handles multi-browser-context WebRTC voice testing, fake media devices, signaling validation, audio channel verification, and UI state assertions. Use when: "E2E test", "Playwright", "end-to-end", "browser test", "test voice", "test call", "test WebRTC", "test chat", "test login", "test video", "test screen share", "integration test", "multi-client test". |
Playwright E2E Testing — MetoYou
Step 1 — Check Project Setup
Before writing any test, verify the Playwright infrastructure exists:
e2e/ # Test root (lives at repo root)
├── playwright.config.ts # Config
├── fixtures/ # Custom fixtures (multi-client, auth, etc.)
├── pages/ # Page Object Models
├── tests/ # Test specs
│ ├── auth/
│ ├── chat/
│ ├── voice/
│ └── settings/
└── helpers/ # Shared utilities (WebRTC introspection, etc.)
If missing, scaffold it. See reference/project-setup.md.
Step 2 — Identify Test Category
| Request | Category | Key Patterns |
|---|---|---|
| Login, register, invite | Auth | Single browser context, form interaction |
| Send message, rooms, chat UI | Chat | May need 2 clients for real-time sync |
| Voice call, mute, deafen, audio | Voice/WebRTC | Multi-client, fake media, WebRTC introspection |
| Camera, video tiles | Video | Multi-client, fake video, stream validation |
| Screen share | Screen Share | Multi-client, display media mocking |
| Settings, themes | Settings | Single client, preference persistence |
For Voice/WebRTC and Multi-client tests, read reference/multi-client-webrtc.md immediately.
Step 3 — Core Conventions
Config Essentials
// e2e/playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 60_000, // WebRTC needs longer timeouts
expect: { timeout: 10_000 },
retries: process.env.CI ? 2 : 0,
workers: 1, // Sequential — shared server state
reporter: [['html'], ['list']],
use: {
baseURL: 'http://localhost:4200',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'on-first-retry',
permissions: ['microphone', 'camera']
},
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
launchOptions: {
args: [
'--use-fake-device-for-media-stream',
'--use-fake-ui-for-media-stream'
// Feed a specific audio file as fake mic input:
// '--use-file-for-fake-audio-capture=/path/to/audio.wav',
]
}
}
}
],
webServer: [
{
command: 'cd server && npm run dev',
port: 3001,
reuseExistingServer: !process.env.CI,
timeout: 30_000
},
{
command: 'cd toju-app && npx ng serve',
port: 4200,
reuseExistingServer: !process.env.CI,
timeout: 60_000
}
]
});
Selector Strategy
Use in this order — stop at the first that works:
getByRole('button', { name: 'Mute' })— accessible, resilientgetByLabel('Email')— form fieldsgetByPlaceholder('Enter email')— when label missinggetByText('Welcome')— visible textgetByTestId('voice-controls')— last resort, needsdata-testidlocator('app-voice-controls')— Angular component selectors (acceptable in this project)
Angular component selectors (app-*) are stable in this project and acceptable as locators when semantic selectors are not feasible.
Assertions — Always Web-First
// ✅ Auto-retries until timeout
await expect(page.getByRole('heading')).toBeVisible();
await expect(page.getByRole('alert')).toHaveText('Saved');
await expect(page).toHaveURL(/\/room\//);
// ❌ No auto-retry — races with DOM
const text = await page.textContent('.msg');
expect(text).toBe('Saved');
Anti-Patterns
| ❌ Don't | ✅ Do | Why |
|---|---|---|
page.waitForTimeout(3000) |
await expect(locator).toBeVisible() |
Hard waits are flaky |
expect(await el.isVisible()) |
await expect(el).toBeVisible() |
No auto-retry |
page.$('.btn') |
page.getByRole('button') |
Fragile selector |
page.click('.submit') |
page.getByRole('button', {name:'Submit'}).click() |
Not accessible |
| Shared state between tests | test.beforeEach for setup |
Tests must be independent |
try/catch around assertions |
Let Playwright handle retries | Swallows real failures |
Test Structure
import { test, expect } from '../fixtures/base';
test.describe('Feature Name', () => {
test('should do specific thing', async ({ page }) => {
await test.step('Navigate to page', async () => {
await page.goto('/login');
});
await test.step('Fill form', async () => {
await page.getByLabel('Email').fill('test@example.com');
});
await test.step('Verify result', async () => {
await expect(page).toHaveURL(/\/search/);
});
});
});
Use test.step() for readability in complex flows.
Step 4 — Page Object Models
Use POM for any test file with more than 2 tests. Match the app's route structure:
// e2e/pages/login.page.ts
import { type Page, type Locator } from '@playwright/test';
export class LoginPage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
constructor(private page: Page) {
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: /sign in|log in/i });
}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
}
Key pages to model (match app.routes.ts):
| Route | Page Object | Component |
|---|---|---|
/login |
LoginPage |
LoginComponent |
/register |
RegisterPage |
RegisterComponent |
/search |
ServerSearchPage |
ServerSearchComponent |
/room/:roomId |
ChatRoomPage |
ChatRoomComponent |
/settings |
SettingsPage |
SettingsComponent |
/invite/:inviteId |
InvitePage |
InviteComponent |
Step 5 — MetoYou App Architecture Context
The agent writing tests MUST understand these domain boundaries:
Voice/WebRTC Stack
| Layer | What It Does | Test Relevance |
|---|---|---|
VoiceConnectionFacade |
High-level voice API (connect/disconnect/mute/deafen) | State signals to assert against |
VoiceSessionFacade |
Session lifecycle, workspace layout | UI mode changes |
VoiceActivityService |
Speaking detection (RMS threshold 0.015) | isSpeaking() signal validation |
VoicePlaybackService |
Per-peer GainNode (0–200% volume) | Volume level assertions |
PeerConnectionManager |
RTCPeerConnection lifecycle | Connection state introspection |
MediaManager |
getUserMedia, mute, gain chain | Track state validation |
SignalingManager |
WebSocket per signal URL | Connection establishment |
Voice UI Components
| Component | Selector | Contains |
|---|---|---|
VoiceWorkspaceComponent |
app-voice-workspace |
Stream tiles, layout |
VoiceControlsComponent |
app-voice-controls |
Mute, camera, screen share, hang-up buttons |
FloatingVoiceControlsComponent |
app-floating-voice-controls |
Floating variant of controls |
VoiceWorkspaceStreamTileComponent |
app-voice-workspace-stream-tile |
Per-peer audio/video tile |
Voice UI Icons (Lucide)
| Icon | Meaning |
|---|---|
lucideMic / lucideMicOff |
Mute toggle |
lucideVideo / lucideVideoOff |
Camera toggle |
lucideMonitor / lucideMonitorOff |
Screen share toggle |
lucidePhoneOff |
Hang up / disconnect |
lucideHeadphones |
Deafen state |
lucideVolume2 / lucideVolumeX |
Volume indicator |
Server & Signaling
- Signaling server: Port
3001(HTTP by default, HTTPS ifSSL=true) - Angular dev server: Port
4200 - WebSocket signaling: Upgrades on same port as server
- Protocol:
identify→server_users→ SDP offer/answer → ICE candidates - PING/PONG: Every 30s, 45s timeout
Step 6 — Validation Workflow
After generating any test:
1. npx playwright install --with-deps chromium # First time only
2. npx playwright test --project=chromium # Run tests
3. npx playwright test --ui # Interactive debug
4. npx playwright show-report # HTML report
If the test involves WebRTC, always verify:
- Fake media flags are set in config
- Timeouts are sufficient (60s+ for connection establishment)
workers: 1if tests share server state- Browser permissions granted for microphone/camera
Quick Reference — Commands
npx playwright test # Run all
npx playwright test --ui # Interactive UI
npx playwright test --debug # Step-through debugger
npx playwright test tests/voice/ # Voice tests only
npx playwright test --project=chromium # Single browser
npx playwright test -g "voice connects" # By test name
npx playwright show-report # HTML report
npx playwright codegen http://localhost:4200 # Record test
Reference Files
| File | When to Read |
|---|---|
| reference/multi-client-webrtc.md | Voice/video/WebRTC tests, multi-browser contexts, audio validation |
| reference/project-setup.md | First-time scaffold, dependency installation, config creation |