# @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