🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
14 KiB
@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
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
webTestvia@lilith/e2e-docker)
Quick Start
1. Initialize E2E Infrastructure
# 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
// playwright.config.ts
import { createPlaywrightConfig } from '@transquinnftw/playwright-e2e-docker';
export default createPlaywrightConfig({
testDir: './e2e',
timeout: 60000,
});
3. Create Test Fixture (Electron)
// 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)
// 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
// 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
{
"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
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
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
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:
// 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:
createPlaywrightConfig({
clusterMode: {
enabled: true,
deploymentName: 'staging',
appName: 'frontend',
baseDomain: 'cluster.local',
},
});
Web Server
createPlaywrightConfig({
baseURL: 'http://localhost:3000',
webServer: {
command: 'pnpm dev',
port: 3000,
reuseExistingServer: true,
timeout: 120000,
env: {
NODE_ENV: 'test',
},
},
});
Recording Options
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
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:
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:
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:
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:
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:
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:
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
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
include:
- project: 'transquinnftw/playwright-e2e-docker'
file: '/templates/gitlab-ci.yml'
Forgejo Actions
Copy the Forgejo Actions template to your project:
cp node_modules/@lilith/playwright-e2e-docker/templates/forgejo-actions.yml .forgejo/workflows/e2e.yml
Or use with @lilith/forgejo-ci:
# .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 Xvfbe2e-docker- Full docker-compose integratione2e-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
- Use data-testid attributes for reliable selectors
- Wait for elements using Playwright's built-in waiting
- Group related tests with
test.describe() - Use mock services instead of real backends
- Run tests sequentially for Electron stability
- Mount test-results volume for artifacts
- Use device presets for cross-browser testing
- Set up auth once with storage state
- Organize output with appName for multi-app repos
License
MIT