Skip to main content

Overview

Playwright tests integrate seamlessly with CI/CD pipelines. This guide covers best practices for running tests in continuous integration environments across popular platforms.

GitHub Actions

Basic Configuration

Create .github/workflows/playwright.yml:
name: Playwright Tests

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      
      - uses: actions/setup-node@v6
        with:
          node-version: 20
      
      - name: Install dependencies
        run: npm ci
      
      - name: Install Playwright Browsers
        run: npx playwright install --with-deps
      
      - name: Run Playwright tests
        run: npx playwright test
      
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

Matrix Strategy

Test across multiple browsers and Node.js versions:
name: Playwright Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        browser: [chromium, firefox, webkit]
        node-version: [18, 20, 22]
    steps:
      - uses: actions/checkout@v6
      
      - uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}
      
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test --project=${{ matrix.browser }}
      
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report-${{ matrix.browser }}-node${{ matrix.node-version }}
          path: playwright-report/

Sharding Tests

Run tests in parallel across multiple machines:
name: Playwright Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4]
        shardTotal: [4]
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 20
      
      - run: npm ci
      - run: npx playwright install --with-deps
      
      - name: Run Playwright tests
        run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
      
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report-${{ matrix.shardIndex }}
          path: playwright-report/

GitLab CI

Basic Configuration

Create .gitlab-ci.yml:
stages:
  - test

playwright-tests:
  stage: test
  image: mcr.microsoft.com/playwright:v1.40.0-focal
  script:
    - npm ci
    - npx playwright test
  artifacts:
    when: always
    paths:
      - playwright-report/
      - test-results/
    expire_in: 1 week

Multiple Browsers

stages:
  - test

.test-template:
  stage: test
  image: mcr.microsoft.com/playwright:v1.40.0-focal
  script:
    - npm ci
    - npx playwright test --project=$BROWSER
  artifacts:
    when: always
    paths:
      - playwright-report/
    expire_in: 1 week

test-chromium:
  extends: .test-template
  variables:
    BROWSER: chromium

test-firefox:
  extends: .test-template
  variables:
    BROWSER: firefox

test-webkit:
  extends: .test-template
  variables:
    BROWSER: webkit

Jenkins

Jenkinsfile Configuration

pipeline {
  agent {
    docker {
      image 'mcr.microsoft.com/playwright:v1.40.0-focal'
    }
  }
  
  stages {
    stage('Install Dependencies') {
      steps {
        sh 'npm ci'
      }
    }
    
    stage('Run Tests') {
      steps {
        sh 'npx playwright test'
      }
    }
  }
  
  post {
    always {
      archiveArtifacts artifacts: 'playwright-report/**', allowEmptyArchive: true
      publishHTML([
        reportDir: 'playwright-report',
        reportFiles: 'index.html',
        reportName: 'Playwright Test Report'
      ])
    }
  }
}

CircleCI

Configuration

Create .circleci/config.yml:
version: 2.1

orbs:
  node: circleci/[email protected]

jobs:
  playwright-tests:
    docker:
      - image: mcr.microsoft.com/playwright:v1.40.0-focal
    steps:
      - checkout
      - node/install-packages:
          pkg-manager: npm
      - run:
          name: Run Playwright tests
          command: npx playwright test
      - store_artifacts:
          path: playwright-report
      - store_test_results:
          path: test-results

workflows:
  test:
    jobs:
      - playwright-tests

Docker

Using Playwright Docker Image

FROM mcr.microsoft.com/playwright:v1.40.0-focal

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

CMD ["npx", "playwright", "test"]
Build and run:
docker build -t playwright-tests .
docker run -it playwright-tests

Docker Compose

docker-compose.yml
version: '3.8'

services:
  playwright:
    image: mcr.microsoft.com/playwright:v1.40.0-focal
    working_dir: /app
    volumes:
      - .:/app
    command: npm run test:ci
    environment:
      - CI=true
Run tests:
docker-compose run playwright

Best Practices

Optimize CI Configuration

1

Cache dependencies

- name: Cache dependencies
  uses: actions/cache@v3
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
2

Cache Playwright browsers

- name: Cache Playwright browsers
  uses: actions/cache@v3
  with:
    path: ~/.cache/ms-playwright
    key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
3

Use fail-fast: false

strategy:
  fail-fast: false  # Continue other jobs even if one fails
  matrix:
    browser: [chromium, firefox, webkit]
4

Set appropriate timeouts

jobs:
  test:
    timeout-minutes: 60  # Prevent hanging jobs

CI-Specific Configuration

Create playwright.ci.config.ts:
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  
  // CI-specific settings
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  
  reporter: [
    ['html'],
    ['junit', { outputFile: 'test-results/junit.xml' }],
    ['json', { outputFile: 'test-results/results.json' }],
  ],
  
  use: {
    trace: 'retain-on-failure',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
});
Run in CI:
npx playwright test --config=playwright.ci.config.ts

Environment Variables

Set Secrets

env:
  API_TOKEN: ${{ secrets.API_TOKEN }}
  DATABASE_URL: ${{ secrets.DATABASE_URL }}

Use in Tests

import { test, expect } from '@playwright/test';

test.use({
  extraHTTPHeaders: {
    'Authorization': `Bearer ${process.env.API_TOKEN}`,
  },
});

test('authenticated request', async ({ request }) => {
  const response = await request.get('/api/profile');
  expect(response.ok()).toBeTruthy();
});

Reporting

HTML Report

Automatically generated after test runs:
- name: Upload HTML report
  uses: actions/upload-artifact@v4
  if: always()
  with:
    name: playwright-report
    path: playwright-report/

JUnit Report

For CI integration:
import { defineConfig } from '@playwright/test';

export default defineConfig({
  reporter: [
    ['junit', { outputFile: 'results.xml' }],
  ],
});

Slack Notifications

- name: Notify Slack
  if: failure()
  uses: slackapi/slack-github-action@v1
  with:
    payload: |
      {
        "text": "Playwright tests failed on ${{ github.ref }}"
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Real-World Example from Playwright Repo

From .github/workflows/tests_primary.yml:
name: "tests 1"

on:
  push:
    branches:
      - main
      - release-*
  pull_request:
    branches:
      - main
      - release-*

env:
  FORCE_COLOR: 1
  ELECTRON_SKIP_BINARY_DOWNLOAD: 1

jobs:
  test_linux:
    name: ${{ matrix.os }} (${{ matrix.browser }} - Node.js ${{ matrix.node-version }})
    strategy:
      fail-fast: false
      matrix:
        browser: [chromium, firefox, webkit]
        os: [ubuntu-22.04]
        node-version: [20]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm run build
      - run: npx playwright install --with-deps
      - run: npm run test -- --project=${{ matrix.browser }}-*

Troubleshooting

Always install browsers with --with-deps flag in CI to ensure all system dependencies are installed.

Tests timing out in CI

Increase timeout in configuration:
import { defineConfig } from '@playwright/test';

export default defineConfig({
  timeout: 60000, // 60 seconds per test
  expect: {
    timeout: 10000, // 10 seconds for assertions
  },
});

Browser installation fails

Use the official Playwright Docker image:
container:
  image: mcr.microsoft.com/playwright:v1.40.0-focal

Flaky tests in CI

Enable retries:
export default defineConfig({
  retries: process.env.CI ? 2 : 0,
});
Use Playwright Docker images: Official images include all dependencies and are optimized for CI environments.

Performance Tips

  • Use sharding: Split tests across multiple machines for faster execution
  • Cache dependencies: Cache npm packages and Playwright browsers
  • Run tests in parallel: Use multiple workers
  • Use headed: false: Headless mode is faster
  • Minimize retries: Only retry flaky tests in CI
  • Upload artifacts conditionally: Only on failure to save storage
CI runs typically take longer than local runs. Optimize for reliability over speed.

Build docs developers (and LLMs) love