playwright-e2e-docker/README.md
Lilith fbc8e5772e 🔧 migrate to @lilith namespace, remove gitlab-ci.yml
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 01:35:26 -08:00

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 webTest via @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 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