.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- 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: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
name: Playwright Tests
on:
push:
branches: [main]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- name: Upload blob report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: blob-report-${{ matrix.shardIndex }}
path: blob-report
retention-days: 1
merge-reports:
if: ${{ !cancelled() }}
needs: [test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Download blob reports
uses: actions/download-artifact@v4
with:
path: all-blob-reports
pattern: blob-report-*
merge-multiple: true
- name: Merge reports
run: npx playwright merge-reports --reporter html ./all-blob-reports
- name: Upload HTML report
uses: actions/upload-artifact@v4
with:
name: html-report
path: playwright-report
retention-days: 14
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
container:
# Use latest or more appropriate playwright version (match package.json)
image: mcr.microsoft.com/playwright:v1.40.0-jammy
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npx playwright test
env:
HOME: /root
FROM mcr.microsoft.com/playwright:v1.40.0-jammy
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npx", "playwright", "test"]
# docker-compose.yml
version: '3.8'
services:
playwright:
build: .
volumes:
- ./playwright-report:/app/playwright-report
- ./test-results:/app/test-results
environment:
- CI=true
- BASE_URL=http://app:3000
depends_on:
- app
app:
build: ./app
ports:
- '3000:3000'
# Build and run
docker build -t playwright-tests .
docker run --rm -v $(pwd)/playwright-report:/app/playwright-report playwright-tests
# With docker-compose
docker-compose run --rm playwright
// playwright.config.ts
export default defineConfig({
reporter: [
// Always generate
['html', {outputFolder: 'playwright-report'}],
// Console output
['list'],
// CI-friendly
['github'], // GitHub Actions annotations
// JUnit for CI integration
['junit', {outputFile: 'results.xml'}],
// JSON for custom processing
['json', {outputFile: 'results.json'}],
// Blob for merging shards
['blob', {outputDir: 'blob-report'}],
],
})
export default defineConfig({
reporter: process.env.CI ? [['github'], ['blob'], ['html']] : [['list'], ['html']],
})
# Split into 4 shards, run shard 1
npx playwright test --shard=1/4
# Run shard 2
npx playwright test --shard=2/4
// playwright.config.ts
export default defineConfig({
// Evenly distribute tests across shards
fullyParallel: true,
// For blob reporter to merge later
reporter: process.env.CI ? [['blob']] : [['html']],
})
# After all shards complete, merge blob reports
npx playwright merge-reports --reporter html ./all-blob-reports
// playwright.config.ts
import {defineConfig} from '@playwright/test'
import dotenv from 'dotenv'
// Load env file based on environment
dotenv.config({path: `.env.${process.env.NODE_ENV || 'development'}`})
export default defineConfig({
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
},
})
# .github/workflows/playwright.yml
jobs:
test:
strategy:
matrix:
environment: [staging, production]
steps:
- name: Run tests
run: npx playwright test
env:
BASE_URL: ${{ matrix.environment == 'staging' && 'https://staging.example.com' || 'https://example.com' }}
TEST_USER: ${{ secrets[format('TEST_USER_{0}', matrix.environment)] }}
# GitHub Actions secrets
- name: Run tests
run: npx playwright test
env:
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
// tests use environment variables
test('login', async ({page}) => {
await page.getByLabel('Email').fill(process.env.TEST_EMAIL!)
await page.getByLabel('Password').fill(process.env.TEST_PASSWORD!)
})
- name: Cache Playwright browsers
uses: actions/cache@v4
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install --with-deps
- name: Install system deps only
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
# Run smoke tests on PR
- name: Run smoke tests
run: npx playwright test --grep @smoke
# Run full regression nightly
- name: Run regression
run: npx playwright test --grep @regression
# Exclude flaky tests
- name: Run stable tests
run: npx playwright test --grep-invert @flaky
# .github/workflows/pr.yml - Fast feedback
- name: Run critical tests
run: npx playwright test --grep "@smoke|@critical"
# .github/workflows/nightly.yml - Full coverage
- name: Run all tests
run: npx playwright test --grep-invert @flaky
// playwright.config.ts
export default defineConfig({
grep: process.env.CI ? /@smoke|@critical/ : undefined,
grepInvert: process.env.CI ? /@flaky/ : undefined,
})
// playwright.config.ts
export default defineConfig({
projects: [
{
name: 'smoke',
grep: /@smoke/,
},
{
name: 'regression',
grepInvert: /@smoke/,
},
],
})
| Practice | Benefit |
|---|---|
Use npm ci | Deterministic installs |
| Run headless in CI | Faster, no display needed |
| Set retries in CI only | Handle flakiness |
| Upload artifacts on failure | Debug failures |
| Use sharding for large suites | Faster execution |
| Cache browsers | Faster setup |
| Use blob reporter for shards | Merge reports correctly |
| Use tags for PR vs nightly | Fast feedback + coverage |
| Exclude @flaky in CI | Stable pipeline |
// playwright.config.ts - CI optimized
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI ? [['github'], ['blob'], ['html']] : [['list'], ['html']],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'on-first-retry',
},
})