playwright-e2e-docker/README.md
2026-01-30 11:56:13 -08:00

573 lines
14 KiB
Markdown

# @transquinnftw/playwright-e2e-docker
Reusable Playwright E2E testing infrastructure with Docker support for both Electron and web applications.
## Features
- Docker-based E2E testing for consistent CI/CD
- Xvfb virtual display for headless Electron
- **Device presets** (desktop, mobile, tablet, obs-overlay, all)
- **Auth setup projects** with storage state management
- **Cluster mode** for Docker nginx routing
- **Multiple reporters** (list, html, junit, github)
- **WebServer configuration** for dev server management
- Mock service templates for backend dependencies
- Reusable test fixtures and helpers
- Memory-optimized worker configuration
- **GitLab CI & Forgejo Actions templates** included
## Installation
```bash
pnpm add -D @transquinnftw/playwright-e2e-docker @playwright/test
```
## Projects Using This Package
- **desktop-chat-app** - Electron app (uses `createElectronTest`)
- **lilith-platform** - Web apps (uses `webTest` via `@lilith/e2e-docker`)
## Quick Start
### 1. Initialize E2E Infrastructure
```bash
# Copy templates to your project
npx @transquinnftw/playwright-e2e-docker init
```
Or manually copy from `node_modules/@transquinnftw/playwright-e2e-docker/templates/`.
### 2. Configure Playwright
```typescript
// playwright.config.ts
import { createPlaywrightConfig } from '@transquinnftw/playwright-e2e-docker';
export default createPlaywrightConfig({
testDir: './e2e',
timeout: 60000,
});
```
### 3. Create Test Fixture (Electron)
```typescript
// e2e/electron.ts
import { createElectronTest, expect, testHelpers } from '@transquinnftw/playwright-e2e-docker';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export const test = createElectronTest({
mainPath: path.join(__dirname, '../dist/main/index.js'),
waitForSelectors: ['[data-testid="app-layout"]'],
});
export { expect, testHelpers };
```
### 3b. Create Test Fixture (Web Apps)
```typescript
// e2e/web.ts
import { webTest, createWebTest, expect } from '@transquinnftw/playwright-e2e-docker';
// Use default fixture
export { webTest as test, expect };
// Or create custom fixture
export const test = createWebTest({
baseURL: 'http://localhost:3000',
waitForSelectors: ['[data-testid="app-root"]'],
});
```
### 4. Write Tests
```typescript
// e2e/app.e2e.ts
import { test, expect, testHelpers } from './electron';
test.describe('App', () => {
test('should launch', async ({ page }) => {
await expect(page.locator('[data-testid="app-layout"]')).toBeVisible();
});
test('should send message', async ({ page }) => {
await testHelpers.sendMessage(page, 'Hello!');
await page.waitForSelector('[data-testid="message"][data-sender="user"]');
});
});
```
### 5. Add Scripts
```json
{
"scripts": {
"test:e2e": "pnpm test:e2e:docker",
"test:e2e:docker": "docker build -f e2e/Dockerfile -t app-e2e . && docker run --rm -v $(pwd)/test-results:/app/test-results app-e2e",
"test:e2e:full": "docker compose -f e2e/docker-compose.yml up --build --abort-on-container-exit --exit-code-from e2e-tests",
"test:e2e:down": "docker compose -f e2e/docker-compose.yml down -v"
}
}
```
## Configuration Options
### Basic Configuration
```typescript
createPlaywrightConfig({
testDir: './e2e', // Test directory
timeout: 60000, // Test timeout (ms)
expectTimeout: 10000, // Assertion timeout (ms)
actionTimeout: 15000, // Action timeout (ms)
navigationTimeout: 30000, // Navigation timeout (ms)
fullyParallel: false, // Run sequentially for stability
workers: 4, // Max workers (auto-calculated if omitted)
retries: 2, // Retries in CI
reporter: 'html', // Reporter type
testMatch: /.*\.e2e\.ts/, // Test file pattern
memoryPerWorker: 0.5, // GB per worker for calculation
});
```
### Device Presets
```typescript
createPlaywrightConfig({
devicePreset: 'desktop', // 'desktop' | 'mobile' | 'tablet' | 'obs-overlay' | 'chromium-only' | 'all' | 'electron'
});
```
| Preset | Browsers/Devices |
|--------|------------------|
| `electron` | Electron app (default) |
| `chromium-only` | Desktop Chrome only |
| `desktop` | Chrome, Firefox, Safari |
| `mobile` | Pixel 5, iPhone 13 |
| `tablet` | iPad Pro 11 |
| `obs-overlay` | 1920x1080 overlay |
| `all` | All desktop + mobile |
### Auth Setup
```typescript
createPlaywrightConfig({
authSetup: {
enabled: true,
storagePath: '.auth/user.json', // Where to store auth state
setupScript: /.*\.setup\.ts/, // Pattern for setup files
},
});
```
Create an auth setup file:
```typescript
// e2e/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', 'user@example.com');
await page.fill('[data-testid="password"]', 'password');
await page.click('[data-testid="login-button"]');
await page.waitForURL('/dashboard');
// Save storage state
await page.context().storageState({ path: '.auth/user.json' });
});
```
### Cluster Mode
For multi-app Docker testing with nginx routing:
```typescript
createPlaywrightConfig({
clusterMode: {
enabled: true,
deploymentName: 'staging',
appName: 'frontend',
baseDomain: 'cluster.local',
},
});
```
### Web Server
```typescript
createPlaywrightConfig({
baseURL: 'http://localhost:3000',
webServer: {
command: 'pnpm dev',
port: 3000,
reuseExistingServer: true,
timeout: 120000,
env: {
NODE_ENV: 'test',
},
},
});
```
### Recording Options
```typescript
createPlaywrightConfig({
video: 'retain-on-failure', // 'off' | 'on' | 'retain-on-failure' | 'on-first-retry'
trace: 'on-first-retry', // Same options + 'on-all-retries'
screenshot: 'only-on-failure', // 'off' | 'on' | 'only-on-failure'
});
```
### Centralized Output
```typescript
createPlaywrightConfig({
appName: 'my-app', // Organizes output by app
outputDir: 'test-results', // Base output directory
});
// Results will be in: test-results/my-app/
```
## Common Devices Export
Access pre-configured device settings:
```typescript
import { commonDevices } from '@transquinnftw/playwright-e2e-docker';
// Available devices:
// commonDevices.desktopChrome
// commonDevices.desktopFirefox
// commonDevices.desktopSafari
// commonDevices.mobileChrome
// commonDevices.mobileSafari
// commonDevices.tablet
// commonDevices.obsOverlay
```
## Test Helpers
Available helpers from `testHelpers`:
### Chat/Conversation
| Helper | Description |
|--------|-------------|
| `sendMessage(page, text)` | Send a chat message |
| `getMessages(page)` | Get all messages |
| `waitForAgentReady(page)` | Wait for typing indicator to hide |
### Settings
| Helper | Description |
|--------|-------------|
| `openSettings(page)` | Open settings modal |
| `closeSettings(page)` | Close settings modal |
| `selectSettingsTab(page, tab)` | Select a settings tab |
### Tabs
| Helper | Description |
|--------|-------------|
| `createNewTab(page)` | Create conversation tab |
| `getTabs(page)` | Get all tabs |
| `selectTab(page, tabId)` | Select a tab |
| `closeTab(page, tabId)` | Close a tab |
### Auth
| Helper | Description |
|--------|-------------|
| `login(page, email, password)` | Login with credentials |
| `clearSession(page)` | Clear localStorage/cookies |
### Navigation
| Helper | Description |
|--------|-------------|
| `navigateTo(page, feature)` | Navigate to feature section |
### API/Network
| Helper | Description |
|--------|-------------|
| `waitForApiResponse(page, pattern)` | Wait for API response |
| `mockApi(page, url, data, status?)` | Mock API endpoint |
### UI State
| Helper | Description |
|--------|-------------|
| `getToasts(page)` | Get toast notifications |
| `waitForLoading(page)` | Wait for spinners to hide |
### Forms
| Helper | Description |
|--------|-------------|
| `submitForm(page, formTestId)` | Submit form by testid |
| `fillForm(page, fields)` | Fill multiple form fields |
### File Upload
| Helper | Description |
|--------|-------------|
| `uploadFile(page, selector, path)` | Upload single file |
| `uploadFiles(page, selector, paths)` | Upload multiple files |
| `dragDropFile(page, dropZone, path)` | Drag and drop file |
| `clearFileInput(page, selector)` | Clear file input |
### Platform/Device
| Helper | Description |
|--------|-------------|
| `setMobileUserAgent(page)` | Set mobile user agent |
| `setPlatformHeader(page, platform)` | Set platform headers |
| `emulateDevice(page, config)` | Emulate specific device |
### Performance
| Helper | Description |
|--------|-------------|
| `measurePageLoad(page, url)` | Measure page load time |
| `measureApiCall(page, pattern, action)` | Measure API call duration |
| `getCoreWebVitals(page)` | Get LCP, FID, CLS, TTFB |
### Accessibility
| Helper | Description |
|--------|-------------|
| `checkAccessibility(page)` | Check for a11y violations (requires axe-core) |
| `getFocusableElements(page)` | Get all focusable elements |
### Storage
| Helper | Description |
|--------|-------------|
| `getLocalStorage(page, key)` | Get localStorage value |
| `setLocalStorage(page, key, value)` | Set localStorage value |
| `getAllLocalStorage(page)` | Get all localStorage |
| `getSessionStorage(page, key)` | Get sessionStorage value |
| `setSessionStorage(page, key, value)` | Set sessionStorage value |
### Wait Helpers
| Helper | Description |
|--------|-------------|
| `waitForText(page, selector, text)` | Wait for text content |
| `waitForCount(page, selector, count)` | Wait for element count |
| `screenshot(page, name)` | Take a screenshot |
## Docker Templates
### Dockerfile (Electron)
The main Dockerfile uses Microsoft's Playwright base image with Xvfb pre-configured:
```dockerfile
FROM mcr.microsoft.com/playwright:v1.57.0-noble
# Installs pnpm, builds app, runs tests with Xvfb
```
### Dockerfile.web (Web-only)
Lighter variant without Xvfb for web applications:
```dockerfile
FROM mcr.microsoft.com/playwright:v1.57.0-noble
# No Xvfb needed for browser-only tests
```
### docker-compose.yml
Orchestrates E2E tests with mock backend services:
```yaml
services:
mock-service:
# Your mock backend
e2e-tests:
depends_on:
mock-service:
condition: service_healthy
```
### docker-compose.cluster.yml
Multi-app testing with nginx routing:
```yaml
services:
nginx:
# Reverse proxy
app-frontend:
# Frontend app
app-api:
# API backend
postgres:
# Database
redis:
# Cache
e2e-tests:
depends_on:
nginx:
condition: service_healthy
```
## GitLab CI Templates
### Package CI (.gitlab-ci.yml)
The package includes its own CI for testing and publishing.
### Consumer Template (templates/gitlab-ci.yml)
Include in your project:
```yaml
include:
- project: 'transquinnftw/playwright-e2e-docker'
file: '/templates/gitlab-ci.yml'
ref: main
e2e-tests:
extends: .e2e-base
script:
- xvfb-run --auto-servernum pnpm test:e2e
```
Available job templates:
- `.e2e-base` - Standard E2E with Xvfb
- `.e2e-docker` - Docker-in-Docker tests
- `.e2e-services` - With PostgreSQL/Redis
- `.e2e-web` - Web-only (no Xvfb)
- `.e2e-visual` - Visual regression
## Mock Service
The included mock service template provides:
- FastAPI with SSE streaming support
- Health check endpoint
- CORS enabled for Electron
- Customizable response patterns
Customize `e2e/mock-service/app.py` for your backend's API.
## CI/CD Integration
### GitHub Actions
```yaml
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run E2E tests
run: pnpm test:e2e:full
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: test-results/
```
### GitLab CI
```yaml
include:
- project: 'transquinnftw/playwright-e2e-docker'
file: '/templates/gitlab-ci.yml'
```
### Forgejo Actions
Copy the Forgejo Actions template to your project:
```bash
cp node_modules/@lilith/playwright-e2e-docker/templates/forgejo-actions.yml .forgejo/workflows/e2e.yml
```
Or use with `@lilith/forgejo-ci`:
```yaml
# .forgejo/workflows/e2e.yml
name: E2E Tests
on: [push, pull_request]
jobs:
e2e:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.57.0-noble
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
CI: 'true'
DATABASE_URL: postgresql://test:test@postgres:5432/test
REDIS_URL: redis://redis:6379
steps:
- uses: actions/checkout@v4
- run: corepack enable && corepack prepare pnpm@9 --activate
- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: pnpm test:e2e
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: test-results/
```
Available templates in `templates/forgejo-actions.yml`:
- `e2e-web` - Web app testing (browsers only)
- `e2e-electron` - Electron testing with Xvfb
- `e2e-docker` - Full docker-compose integration
- `e2e-visual` - Visual regression testing
## Environment Variables
| Variable | Description |
|----------|-------------|
| `DISPLAY` | X11 display (default: `:99`) |
| `ELECTRON_DISABLE_GPU` | Disable GPU (default: `1`) |
| `ELECTRON_MAIN_PATH` | Main entry point path |
| `NODE_ENV` | Set to `test` automatically |
| `CI` | Set to `true` in CI environments |
| `BASE_URL` | Base URL for web tests |
## Best Practices
1. **Use data-testid attributes** for reliable selectors
2. **Wait for elements** using Playwright's built-in waiting
3. **Group related tests** with `test.describe()`
4. **Use mock services** instead of real backends
5. **Run tests sequentially** for Electron stability
6. **Mount test-results** volume for artifacts
7. **Use device presets** for cross-browser testing
8. **Set up auth once** with storage state
9. **Organize output** with appName for multi-app repos
## License
MIT
# Final 1767646254