QA in CI/CD Pipelines

Learn how to write production-ready CI/CD pipeline configurations for Playwright tests. As a QA engineer, your role is to create the YAML files, configure test stages, and hand them off to DevOps engineers for integration into the organization's main pipeline infrastructure.

QA's Role in CI/CD

Why Automated Testing in Pipelines Matters

Continuous Integration and Continuous Delivery (CI/CD) pipelines are the backbone of modern software delivery. As a QA engineer, your automated Playwright tests are only valuable if they run automatically on every code change. Running tests manually defeats the purpose of automation — the pipeline ensures tests execute consistently, reliably, and without human intervention.

The Pipeline Lifecycle from a QA Perspective

  • Code Push / PR — A developer pushes code or opens a pull request. This triggers the pipeline automatically.
  • Lint and Build — The code is checked for style issues and compiled. QA tests should only run if this stage passes.
  • Unit Tests — Fast, isolated tests run first. If they fail, there is no point running slower E2E tests.
  • E2E Tests — Your Playwright tests run against a deployed staging environment. This is where QA's pipeline work lives.
  • Reporting — Test results, screenshots, videos, and traces are collected as artifacts for debugging and review.
  • Notification — The team is notified of results via Slack, email, or the CI platform's dashboard.
  • Deployment — If all tests pass, the code is deployed to production (or promoted to the next environment).

What Does a QA Engineer Actually Write?

As a QA engineer, you write the YAML pipeline configuration that defines how your tests run in CI. You do not need to manage the infrastructure, runners, or deployment pipelines — that is the DevOps engineer's responsibility. Your job is to:

  1. Write the YAML file that installs dependencies and runs your tests
  2. Configure reporters, artifacts, and test sharding
  3. Define which tests run on which triggers (smoke on every PR, full regression nightly)
  4. Document the required secrets and environment variables
  5. Hand the YAML file to DevOps with clear instructions for integration
Key TakeawayYou do not need to be a DevOps expert. You need to understand enough YAML and CI/CD concepts to write a pipeline file that runs your tests correctly. DevOps engineers will handle the infrastructure, runner management, and integration with deployment workflows.

GitHub Actions

GitHub Actions is the most common CI/CD platform for projects hosted on GitHub. Workflows are defined in YAML files placed in the .github/workflows/directory. Each workflow file defines one or more jobs that run on GitHub-hosted runners.

Production-Ready Workflow

This workflow triggers on every push and pull request, installs dependencies, runs Playwright tests, and uploads all artifacts (reports, screenshots, videos, traces) for debugging:

.github/workflows/playwright.ymlyaml
1# .github/workflows/playwright.yml
2name: Playwright E2E Tests
3
4on:
5  push:
6    branches: [main, develop]
7  pull_request:
8    branches: [main]
9
10jobs:
11  e2e-tests:
12    timeout-minutes: 30
13    runs-on: ubuntu-latest
14
15    steps:
16      - name: Checkout repository
17        uses: actions/checkout@v4
18
19      - name: Setup Node.js
20        uses: actions/setup-node@v4
21        with:
22          node-version: 20
23          cache: 'npm'
24
25      - name: Install dependencies
26        run: npm ci
27
28      - name: Install Playwright browsers
29        run: npx playwright install --with-deps
30
31      - name: Run Playwright tests
32        run: npx playwright test
33        env:
34          CI: true
35          BASE_URL: ${{ secrets.STAGING_URL }}
36
37      - name: Upload HTML report
38        uses: actions/upload-artifact@v4
39        if: always()
40        with:
41          name: playwright-html-report
42          path: playwright-report/
43          retention-days: 14
44
45      - name: Upload test results (screenshots, videos, traces)
46        uses: actions/upload-artifact@v4
47        if: failure()
48        with:
49          name: playwright-test-results
50          path: test-results/
51          retention-days: 7
TipThe if: always() condition on the report upload step ensures the HTML report is uploaded even when tests fail. Without it, the upload step is skipped on failure, and you lose the most important debugging information.

Matrix Strategy: Multi-Browser and Sharding

For larger test suites, use a matrix strategy to run tests across browsers and shards in parallel. This can reduce a 30-minute test suite to under 10 minutes:

.github/workflows/playwright-matrix.ymlyaml
1# .github/workflows/playwright-matrix.yml
2name: Playwright Cross-Browser Tests
3
4on:
5  push:
6    branches: [main]
7  pull_request:
8    branches: [main]
9
10jobs:
11  e2e-tests:
12    timeout-minutes: 30
13    runs-on: ubuntu-latest
14
15    strategy:
16      fail-fast: false
17      matrix:
18        project: [chromium, firefox, webkit]
19        shard: [1/3, 2/3, 3/3]
20
21    steps:
22      - name: Checkout
23        uses: actions/checkout@v4
24
25      - name: Setup Node.js
26        uses: actions/setup-node@v4
27        with:
28          node-version: 20
29          cache: 'npm'
30
31      - name: Install dependencies
32        run: npm ci
33
34      - name: Install Playwright browsers
35        run: npx playwright install --with-deps ${{ matrix.project }}
36
37      - name: Run Playwright tests
38        run: npx playwright test --project=${{ matrix.project }} --shard=${{ matrix.shard }}
39        env:
40          CI: true
41
42      - name: Upload test results
43        uses: actions/upload-artifact@v4
44        if: always()
45        with:
46          name: results-${{ matrix.project }}-${{ strategy.job-index }}
47          path: |
48            playwright-report/
49            test-results/
50          retention-days: 14
How Sharding WorksSharding splits your test files across multiple parallel workers. With --shard=1/3, each worker runs approximately one-third of the test files. Combined with the browser matrix (3 browsers x 3 shards = 9 parallel jobs), you get significant speed improvements.

GitLab CI

GitLab CI/CD is configured through a single .gitlab-ci.yml file at the root of your repository. It supports stages, parallel execution, Docker-based runners, and built-in artifact management with GitLab Pages for report hosting.

Complete Pipeline with Stages

This configuration defines four stages: lint, unit tests, E2E tests (with parallel sharding), and report publishing via GitLab Pages:

.gitlab-ci.ymlyaml
1# .gitlab-ci.yml
2stages:
3  - lint
4  - test
5  - e2e
6  - report
7
8variables:
9  npm_config_cache: "$CI_PROJECT_DIR/.npm"
10  PLAYWRIGHT_BROWSERS_PATH: "$CI_PROJECT_DIR/.cache/ms-playwright"
11
12# Cache node_modules and Playwright browsers across pipelines
13cache:
14  key: $CI_COMMIT_REF_SLUG
15  paths:
16    - .npm/
17    - .cache/ms-playwright/
18    - node_modules/
19
20# ------------------------------------------------------------------
21# Stage 1: Lint
22# ------------------------------------------------------------------
23lint:
24  stage: lint
25  image: node:20-alpine
26  script:
27    - npm ci --cache .npm --prefer-offline
28    - npm run lint
29  rules:
30    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
31    - if: $CI_COMMIT_BRANCH == "main"
32
33# ------------------------------------------------------------------
34# Stage 2: Unit Tests
35# ------------------------------------------------------------------
36unit-tests:
37  stage: test
38  image: node:20-alpine
39  script:
40    - npm ci --cache .npm --prefer-offline
41    - npm run test:unit -- --coverage
42  artifacts:
43    reports:
44      coverage_report:
45        coverage_format: cobertura
46        path: coverage/cobertura-coverage.xml
47  rules:
48    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
49    - if: $CI_COMMIT_BRANCH == "main"
50
51# ------------------------------------------------------------------
52# Stage 3: E2E Tests with Playwright
53# ------------------------------------------------------------------
54e2e-tests:
55  stage: e2e
56  image: mcr.microsoft.com/playwright:v1.48.0-jammy
57  parallel: 3
58  script:
59    - npm ci --cache .npm --prefer-offline
60    - npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL
61  artifacts:
62    when: always
63    paths:
64      - playwright-report/
65      - test-results/
66    reports:
67      junit: results/junit.xml
68    expire_in: 7 days
69  rules:
70    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
71    - if: $CI_COMMIT_BRANCH == "main"
72
73# ------------------------------------------------------------------
74# Stage 4: Publish Report
75# ------------------------------------------------------------------
76pages:
77  stage: report
78  dependencies:
79    - e2e-tests
80  script:
81    - mkdir -p public
82    - cp -r playwright-report/* public/
83  artifacts:
84    paths:
85      - public
86  rules:
87    - if: $CI_COMMIT_BRANCH == "main"

Key GitLab CI Features for QA

  • Playwright Docker Image — Using mcr.microsoft.com/playwright:v1.48.0-jammy ensures all browser dependencies are pre-installed, eliminating setup issues.
  • parallel keyword — GitLab natively supports parallel job execution. Setting parallel: 3 creates three copies of the job, and Playwright's --shard flag distributes tests across them.
  • JUnit reports — The reports: junit directive tells GitLab to parse the XML file and display test results directly in merge request widgets.
  • GitLab Pages — The pages job publishes the HTML report to a URL like https://your-group.gitlab.io/your-project.
WarningKeep the Playwright Docker image version in sync with your package.jsonversion. A mismatch (e.g., Docker image v1.48 but npm package v1.45) will cause browser binary compatibility errors.

Azure DevOps

Azure DevOps Pipelines use YAML files (typically azure-pipelines.yml) and provide tight integration with Azure Test Plans, making it popular in enterprise environments. Pipelines are organized into stages, jobs, and steps.

Complete Azure Pipeline

azure-pipelines.ymlyaml
1# azure-pipelines.yml
2trigger:
3  branches:
4    include:
5      - main
6      - develop
7
8pr:
9  branches:
10    include:
11      - main
12
13pool:
14  vmImage: 'ubuntu-latest'
15
16variables:
17  nodeVersion: '20.x'
18  npm_config_cache: $(Pipeline.Workspace)/.npm
19
20stages:
21  # ------------------------------------------------------------------
22  # Stage 1: Install & Lint
23  # ------------------------------------------------------------------
24  - stage: Prepare
25    displayName: 'Install & Lint'
26    jobs:
27      - job: LintJob
28        displayName: 'Lint Code'
29        steps:
30          - task: UseNode@1
31            inputs:
32              version: $(nodeVersion)
33            displayName: 'Setup Node.js'
34
35          - task: Cache@2
36            inputs:
37              key: 'npm | "$(Agent.OS)" | package-lock.json'
38              restoreKeys: |
39                npm | "$(Agent.OS)"
40              path: $(npm_config_cache)
41            displayName: 'Cache npm'
42
43          - script: npm ci
44            displayName: 'Install dependencies'
45
46          - script: npm run lint
47            displayName: 'Run linter'
48
49  # ------------------------------------------------------------------
50  # Stage 2: Playwright E2E Tests
51  # ------------------------------------------------------------------
52  - stage: E2E
53    displayName: 'E2E Tests'
54    dependsOn: Prepare
55    jobs:
56      - job: PlaywrightTests
57        displayName: 'Run Playwright Tests'
58        timeoutInMinutes: 30
59        steps:
60          - task: UseNode@1
61            inputs:
62              version: $(nodeVersion)
63            displayName: 'Setup Node.js'
64
65          - script: npm ci
66            displayName: 'Install dependencies'
67
68          - script: npx playwright install --with-deps
69            displayName: 'Install Playwright browsers'
70
71          - script: npx playwright test --reporter=junit,html
72            displayName: 'Run E2E tests'
73            env:
74              CI: 'true'
75              BASE_URL: $(STAGING_URL)
76
77          - task: PublishTestResults@2
78            displayName: 'Publish test results'
79            condition: always()
80            inputs:
81              testResultsFormat: 'JUnit'
82              testResultsFiles: 'results/junit.xml'
83              mergeTestResults: true
84              testRunTitle: 'Playwright E2E Tests'
85
86          - task: PublishPipelineArtifact@1
87            displayName: 'Publish HTML report'
88            condition: always()
89            inputs:
90              targetPath: 'playwright-report'
91              artifact: 'playwright-report'
92              publishLocation: 'pipeline'
93
94          - task: PublishPipelineArtifact@1
95            displayName: 'Publish traces and screenshots'
96            condition: failed()
97            inputs:
98              targetPath: 'test-results'
99              artifact: 'test-results'
100              publishLocation: 'pipeline'
101
102  # ------------------------------------------------------------------
103  # Stage 3: Integration with Azure Test Plans (optional)
104  # ------------------------------------------------------------------
105  - stage: TestPlanSync
106    displayName: 'Sync with Test Plans'
107    dependsOn: E2E
108    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
109    jobs:
110      - job: SyncResults
111        displayName: 'Sync Test Results to Azure Test Plans'
112        steps:
113          - task: PublishTestResults@2
114            inputs:
115              testResultsFormat: 'JUnit'
116              testResultsFiles: 'results/junit.xml'
117              testRunTitle: 'E2E Regression - $(Build.BuildNumber)'
118              publishRunAttachments: true

Azure DevOps Highlights for QA

  • PublishTestResults task — This built-in task parses JUnit XML and displays results in the Azure DevOps test tab. You can see pass/fail counts, test duration trends, and failure details without downloading artifacts.
  • Pipeline Artifacts — The PublishPipelineArtifacttask stores the HTML report and traces. Team members can download and view them directly from the pipeline run page.
  • Azure Test Plans Integration — The optional third stage syncs test results with Azure Test Plans, enabling traceability between automated tests and manual test cases.
  • Variable Groups — Sensitive values like API keys are stored in Variable Groups (Pipelines > Library) and referenced with $(VARIABLE_NAME) syntax.
TipUse condition: always() on the PublishTestResults task to ensure test results are published even when the test step fails. The default behavior skips subsequent steps on failure, which means you lose visibility into what went wrong.

Jenkins

Jenkins uses Groovy-based Jenkinsfile definitions for pipeline-as-code. The declarative pipeline syntax is the most common approach. Playwright tests run inside Docker agents that have all browser dependencies pre-installed.

Declarative Pipeline

A complete Jenkinsfile that installs dependencies, lints, runs unit tests, executes Playwright E2E tests, and publishes results:

Jenkinsfilegroovy
1// Jenkinsfile (Declarative Pipeline)
2pipeline {
3    agent {
4        docker {
5            image 'mcr.microsoft.com/playwright:v1.48.0-jammy'
6            args '-u root'
7        }
8    }
9
10    environment {
11        CI = 'true'
12        HOME = '/root'
13        npm_config_cache = '${WORKSPACE}/.npm'
14    }
15
16    options {
17        timeout(time: 30, unit: 'MINUTES')
18        timestamps()
19        disableConcurrentBuilds()
20    }
21
22    stages {
23        stage('Install') {
24            steps {
25                sh 'npm ci'
26            }
27        }
28
29        stage('Lint') {
30            steps {
31                sh 'npm run lint'
32            }
33        }
34
35        stage('Unit Tests') {
36            steps {
37                sh 'npm run test:unit'
38            }
39        }
40
41        stage('E2E Tests') {
42            steps {
43                sh 'npx playwright test --reporter=junit,html'
44            }
45        }
46    }
47
48    post {
49        always {
50            // Publish JUnit test results to Jenkins
51            junit 'results/junit.xml'
52
53            // Archive the HTML report
54            publishHTML(target: [
55                allowMissing: true,
56                alwaysLinkToLastBuild: true,
57                keepAll: true,
58                reportDir: 'playwright-report',
59                reportFiles: 'index.html',
60                reportName: 'Playwright Report'
61            ])
62
63            // Archive screenshots and traces
64            archiveArtifacts artifacts: 'test-results/**/*',
65                             allowEmptyArchive: true,
66                             fingerprint: true
67        }
68
69        failure {
70            // Send notification on failure
71            slackSend(
72                channel: '#qa-alerts',
73                color: 'danger',
74                message: """
75                    E2E Tests Failed!
76                    Job: ${env.JOB_NAME} #${env.BUILD_NUMBER}
77                    Branch: ${env.GIT_BRANCH}
78                    <${env.BUILD_URL}|View Build>
79                """.stripIndent()
80            )
81        }
82
83        success {
84            slackSend(
85                channel: '#qa-results',
86                color: 'good',
87                message: "E2E Tests Passed - ${env.JOB_NAME} #${env.BUILD_NUMBER}"
88            )
89        }
90
91        cleanup {
92            cleanWs()
93        }
94    }
95}

Parallel Browser Testing

For faster execution, run each browser project as a parallel stage. Jenkins executes them simultaneously on separate Docker agents:

Jenkinsfilegroovy
1// Jenkinsfile with Parallel Browser Testing
2pipeline {
3    agent none
4
5    stages {
6        stage('Install') {
7            agent {
8                docker {
9                    image 'mcr.microsoft.com/playwright:v1.48.0-jammy'
10                    args '-u root'
11                }
12            }
13            steps {
14                sh 'npm ci'
15                stash includes: 'node_modules/**', name: 'deps'
16            }
17        }
18
19        stage('E2E Tests') {
20            parallel {
21                stage('Chromium') {
22                    agent {
23                        docker {
24                            image 'mcr.microsoft.com/playwright:v1.48.0-jammy'
25                            args '-u root'
26                        }
27                    }
28                    steps {
29                        unstash 'deps'
30                        sh 'npx playwright test --project=chromium'
31                    }
32                    post {
33                        always {
34                            junit 'results/junit.xml'
35                        }
36                    }
37                }
38
39                stage('Firefox') {
40                    agent {
41                        docker {
42                            image 'mcr.microsoft.com/playwright:v1.48.0-jammy'
43                            args '-u root'
44                        }
45                    }
46                    steps {
47                        unstash 'deps'
48                        sh 'npx playwright test --project=firefox'
49                    }
50                    post {
51                        always {
52                            junit 'results/junit.xml'
53                        }
54                    }
55                }
56
57                stage('WebKit') {
58                    agent {
59                        docker {
60                            image 'mcr.microsoft.com/playwright:v1.48.0-jammy'
61                            args '-u root'
62                        }
63                    }
64                    steps {
65                        unstash 'deps'
66                        sh 'npx playwright test --project=webkit'
67                    }
68                    post {
69                        always {
70                            junit 'results/junit.xml'
71                        }
72                    }
73                }
74            }
75        }
76    }
77}

Jenkins Plugin Requirements

When handing off a Jenkinsfile to DevOps, include a list of required Jenkins plugins. Common plugins needed for Playwright test reporting:

  • HTML Publisher Plugin — For hosting the Playwright HTML report
  • JUnit Plugin — For parsing and displaying test results (usually pre-installed)
  • Docker Pipeline Plugin — For running stages inside Docker containers
  • Slack Notification Plugin — For sending Slack alerts on failure
  • Pipeline Utility Steps — For archive and stash operations
WarningPlaywright requires the container to run as root (or a user with sufficient permissions) due to browser sandbox requirements. The args '-u root'flag in the Docker agent configuration is necessary. Inform your DevOps engineer about this requirement, as some organizations restrict root container usage.

Pipeline Best Practices

Writing the YAML file is only part of the job. These best practices ensure your pipeline is maintainable, reliable, and efficient.

Environment Variables for Test Configuration

Never hardcode URLs, timeouts, or credentials in your test code or YAML files. Use environment variables to make your pipeline adaptable to different environments:

.env filesbash
1# Environment variables for test configuration
2# playwright.config.ts reads these to adapt behavior per environment
3
4# .env.ci (committed - non-sensitive defaults)
5CI=true
6BASE_URL=https://staging.example.com
7TEST_TIMEOUT=60000
8RETRIES=2
9WORKERS=4
10
11# .env.local (NOT committed - developer overrides)
12BASE_URL=http://localhost:3000
13TEST_TIMEOUT=30000
14RETRIES=0
15WORKERS=undefined
playwright.config.tstypescript
1// playwright.config.ts - Reading environment variables
2import { defineConfig } from '@playwright/test';
3import dotenv from 'dotenv';
4import path from 'path';
5
6// Load the appropriate .env file based on CI flag
7dotenv.config({
8  path: path.resolve(__dirname, process.env.CI ? '.env.ci' : '.env.local'),
9});
10
11export default defineConfig({
12  timeout: Number(process.env.TEST_TIMEOUT) || 30_000,
13  retries: Number(process.env.RETRIES) || 0,
14  workers: process.env.WORKERS === 'undefined'
15    ? undefined
16    : Number(process.env.WORKERS) || 1,
17
18  use: {
19    baseURL: process.env.BASE_URL || 'http://localhost:3000',
20  },
21});

Secrets Management

API keys, passwords, and tokens must never appear in YAML files or be committed to version control. Each CI platform has its own secrets management system:

secrets-examples.ymlyaml
1# GitHub Actions - Using secrets for sensitive values
2# Secrets are configured in: Settings > Secrets and variables > Actions
3
4- name: Run Playwright tests
5  run: npx playwright test
6  env:
7    CI: true
8    BASE_URL: ${{ secrets.STAGING_URL }}
9    API_KEY: ${{ secrets.API_KEY }}
10    TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
11    TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
12
13# GitLab CI - Using CI/CD variables
14# Variables are configured in: Settings > CI/CD > Variables
15e2e-tests:
16  script:
17    - npx playwright test
18  variables:
19    BASE_URL: $STAGING_URL
20    API_KEY: $API_KEY
21
22# Azure DevOps - Using pipeline variables and variable groups
23# Variable groups are managed in: Pipelines > Library
24- script: npx playwright test
25  env:
26    BASE_URL: $(STAGING_URL)
27    API_KEY: $(API_KEY)
WarningNever log secrets in pipeline output. Avoid commands like echo $API_KEY for debugging. CI platforms mask known secrets in logs, but custom logging can accidentally expose them.

Test Parallelization and Sharding

Sharding is the most effective way to reduce pipeline execution time. Playwright's built-in --shard flag distributes test files across multiple parallel CI jobs:

sharding-examples.ymlyaml
1# Sharding distributes tests across multiple parallel workers
2# This significantly reduces total execution time
3
4# GitHub Actions - 4 shards running in parallel
5strategy:
6  fail-fast: false
7  matrix:
8    shard: [1/4, 2/4, 3/4, 4/4]
9
10steps:
11  - name: Run tests (shard ${{ matrix.shard }})
12    run: npx playwright test --shard=${{ matrix.shard }}
13
14# Merge shard reports into a single HTML report
15  - name: Merge reports
16    if: always()
17    run: npx playwright merge-reports --reporter=html ./blob-reports
18
19# GitLab CI - Uses built-in parallel keyword
20e2e-tests:
21  parallel: 4
22  script:
23    - npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL

Retry Strategies and Test Tagging

Retries handle transient failures (network issues, timing problems) without hiding real bugs. Tags let you create different test suites for different pipeline triggers:

playwright.config.tstypescript
1// playwright.config.ts - Retry and tagging strategies
2import { defineConfig } from '@playwright/test';
3
4export default defineConfig({
5  // Retry failed tests 2 times in CI, 0 times locally
6  retries: process.env.CI ? 2 : 0,
7
8  // Projects can use grep to filter by tags
9  projects: [
10    {
11      name: 'smoke',
12      grep: /@smoke/,
13      retries: 0, // Smoke tests should never be flaky
14    },
15    {
16      name: 'regression',
17      grep: /@regression/,
18      retries: 2,
19    },
20    {
21      name: 'flaky-known',
22      grep: /@flaky/,
23      retries: 3,
24    },
25  ],
26});

Apply tags directly in your test names using the @tag convention:

tests/checkout.spec.tstypescript
1// tests/checkout.spec.ts - Using tags in test names
2import { test, expect } from '@playwright/test';
3
4test('user can log in @smoke @regression', async ({ page }) => {
5  await page.goto('/login');
6  await page.getByLabel('Email').fill('user@example.com');
7  await page.getByLabel('Password').fill('password123');
8  await page.getByRole('button', { name: 'Sign In' }).click();
9  await expect(page).toHaveURL(/.*dashboard/);
10});
11
12test('user can add item to cart @regression', async ({ page }) => {
13  // ... test implementation
14});
15
16test('payment gateway timeout handling @regression @flaky', async ({ page }) => {
17  // This test is known to be flaky due to third-party gateway
18  // ... test implementation
19});
TipKeep smoke tests under 5 minutes. These run on every PR and should provide fast feedback. Reserve full regression (all browsers, all tests) for nightly runs or merges to main.

Conditional Execution

Run different test suites based on the branch, trigger event, or changed files. This prevents unnecessary resource usage and speeds up developer feedback:

conditional-examples.ymlyaml
1# Run different test suites based on branch or event
2# GitHub Actions - conditional execution
3
4jobs:
5  smoke-tests:
6    # Smoke tests run on every push and PR
7    if: always()
8    runs-on: ubuntu-latest
9    steps:
10      - uses: actions/checkout@v4
11      - run: npm ci
12      - run: npx playwright install --with-deps chromium
13      - run: npx playwright test --project=smoke
14
15  regression-tests:
16    # Full regression only on main branch or manual trigger
17    if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
18    runs-on: ubuntu-latest
19    steps:
20      - uses: actions/checkout@v4
21      - run: npm ci
22      - run: npx playwright install --with-deps
23      - run: npx playwright test --project=regression
24
25  # Only run visual tests when UI files change
26  visual-tests:
27    if: contains(github.event.head_commit.modified, 'src/components')
28    runs-on: ubuntu-latest
29    steps:
30      - uses: actions/checkout@v4
31      - run: npm ci
32      - run: npx playwright install --with-deps chromium
33      - run: npx playwright test --grep @visual

Handing Off to DevOps

Writing the YAML is only half the work. The other half is clearly communicating everything the DevOps engineer needs to integrate your pipeline into the organization's CI/CD infrastructure. A good handoff reduces back-and-forth and gets your tests running in CI faster.

Documentation Checklist

Prepare a concise document that covers all integration requirements. Here is a template you can fill out and attach alongside your YAML file:

qa-handoff-checklist.mdtext
1# QA-to-DevOps Handoff Checklist
2# Include this document alongside your YAML pipeline file
3
4## Pipeline File
5- [ ] YAML file location: .github/workflows/playwright.yml
6- [ ] Tested locally with 'act' or equivalent tool
7- [ ] All jobs complete successfully in isolation
8
9## Required Secrets / Environment Variables
10| Variable Name       | Description                   | Where to Get It        |
11|---------------------|-------------------------------|------------------------|
12| STAGING_URL         | Staging environment base URL  | DevOps / Infra team    |
13| TEST_USER_EMAIL     | Test account email            | QA (shared vault)      |
14| TEST_USER_PASSWORD  | Test account password         | QA (shared vault)      |
15| API_KEY             | API key for test environment  | Backend team           |
16| SLACK_WEBHOOK_URL   | Slack notification webhook    | Slack admin            |
17
18## Infrastructure Requirements
19- Node.js 20.x
20- Ubuntu runner (ubuntu-latest)
21- Playwright Docker image: mcr.microsoft.com/playwright:v1.48.0-jammy
22- Minimum 7 GB RAM for parallel browser execution
23- Estimated storage: ~500 MB for browser binaries
24
25## Execution Details
26- Expected run time: 8-12 minutes (with 4 shards)
27- Test count: ~150 E2E tests
28- Browsers: Chromium, Firefox, WebKit
29- Artifacts generated: HTML report, JUnit XML, screenshots, traces
30
31## Triggers
32- Runs on: push to main, pull requests to main
33- Manual trigger: workflow_dispatch enabled
34- Schedule: nightly regression at 2:00 AM UTC (if configured)

Communication Template

Here is a sample message you can adapt when sending your pipeline file to the DevOps team:

handoff-message.txttext
1# Sample Message to DevOps Engineer
2# ---------------------------------
3
4Subject: Ready for Integration - Playwright E2E Pipeline
5
6Hi [DevOps Engineer],
7
8I have prepared the CI/CD pipeline configuration for our Playwright E2E
9test suite. Here is everything you need to integrate it:
10
111. PIPELINE FILE
12   - File: .github/workflows/playwright-e2e.yml
13   - Attached to this message / PR #XXX
14
152. REQUIRED SECRETS (please add to GitHub Actions secrets):
16   - STAGING_URL: https://staging.ourapp.com
17   - TEST_USER_EMAIL: (in shared vault, path: /qa/test-accounts)
18   - TEST_USER_PASSWORD: (in shared vault, path: /qa/test-accounts)
19   - SLACK_WEBHOOK_URL: (QA channel webhook)
20
213. INFRASTRUCTURE NEEDS
22   - Standard GitHub-hosted runner (ubuntu-latest) is sufficient
23   - No self-hosted runner required
24   - No additional services (databases, etc.) needed
25     (tests run against the existing staging environment)
26
274. EXPECTED BEHAVIOR
28   - Triggers on push to main and PRs to main
29   - Runs ~150 tests across 3 browsers in ~10 minutes
30   - Produces an HTML report artifact (retained 14 days)
31   - Sends Slack notification on failure
32
335. TESTING
34   - I have validated the YAML syntax
35   - Tests pass locally against staging
36   - The pipeline has been tested with a dry run
37
38Please let me know if you need any changes or have questions.
39
40Thanks,
41[QA Engineer Name]

What to Expect After Handoff

  • Infrastructure Review — DevOps may adjust runner types, resource limits, or caching strategies based on organizational standards.
  • Secrets Configuration — DevOps will add the required secrets to the CI platform. They may use a vault integration instead of platform-native secrets.
  • Integration with Main Pipeline — Your test stage will likely be incorporated into a larger deployment pipeline rather than running as a standalone workflow.
  • Monitoring and Alerts — DevOps may add additional monitoring, custom dashboards, or escalation policies for test failures.
  • Feedback Loop — Expect questions about timeout values, retry counts, and resource requirements. Be prepared to iterate on the configuration.
Pro TipEstablish a regular sync with the DevOps team (even 15 minutes weekly) to discuss pipeline performance, flaky test trends, and infrastructure needs. This proactive communication prevents pipeline issues from becoming blockers.

Integration with Test Reports

Test reports are the primary way your team consumes test results. In a CI/CD context, reports must be generated, stored as artifacts, and made accessible to developers, QA leads, and project managers.

Reporter Configuration for Pipelines

Configure multiple reporters simultaneously to serve different purposes: terminal output for live CI logs, HTML for detailed interactive reports, JUnit XML for CI dashboard integration, and JSON for custom tooling:

playwright.config.tstypescript
1// playwright.config.ts - Reporter configuration for CI pipelines
2import { defineConfig } from '@playwright/test';
3
4export default defineConfig({
5  reporter: [
6    // Terminal output for live CI logs
7    ['list'],
8
9    // HTML report - self-contained, downloadable from artifacts
10    ['html', {
11      open: 'never',
12      outputFolder: 'playwright-report',
13    }],
14
15    // JUnit XML - parsed by CI systems (Jenkins, GitLab, Azure DevOps)
16    // Shows test results directly in the CI dashboard
17    ['junit', {
18      outputFile: 'results/junit.xml',
19      embedAnnotationsAsProperties: true,
20    }],
21
22    // JSON - for custom dashboards or trend analysis
23    ['json', {
24      outputFile: 'results/results.json',
25    }],
26  ],
27});

Allure Reporter

Allure is a popular open-source reporting framework that provides rich, interactive reports with trends, categories, and environment information. It integrates with all major CI platforms:

playwright.config.tstypescript
1// For Allure reporting, install the adapter:
2// npm install -D allure-playwright
3
4// playwright.config.ts
5import { defineConfig } from '@playwright/test';
6
7export default defineConfig({
8  reporter: [
9    ['list'],
10    ['allure-playwright', {
11      detail: true,
12      outputFolder: 'allure-results',
13      suiteTitle: false,
14    }],
15  ],
16});

Allure in CI Pipelines

allure-ci-examples.ymlyaml
1# GitHub Actions - Generate and publish Allure report
2- name: Run Playwright tests
3  run: npx playwright test
4
5- name: Generate Allure report
6  if: always()
7  run: |
8    npm install -g allure-commandline
9    allure generate allure-results --clean -o allure-report
10
11- name: Upload Allure report
12  uses: actions/upload-artifact@v4
13  if: always()
14  with:
15    name: allure-report
16    path: allure-report/
17    retention-days: 14
18
19# GitLab CI - Allure with GitLab Pages
20e2e-tests:
21  script:
22    - npx playwright test
23  artifacts:
24    paths:
25      - allure-results/
26    expire_in: 7 days
27
28allure-report:
29  stage: report
30  script:
31    - allure generate allure-results --clean -o public
32  artifacts:
33    paths:
34      - public
35  dependencies:
36    - e2e-tests

Where Artifacts Go

CI PlatformHow to View ReportsRetention
GitHub ActionsDownload from the Actions tab > Artifacts sectionConfigurable (default 90 days)
GitLab CIBrowse in job artifacts or publish via GitLab PagesConfigurable via expire_in
Azure DevOpsDownload from pipeline run > Published artifactsFollows org retention policy
JenkinsHTML Publisher Plugin sidebar link on build pageUntil build is deleted
TipFor the best debugging experience, always enable traces in CI. Configure trace: 'on-first-retry' in your Playwright config. When a test fails and retries, the trace captures a complete timeline of DOM snapshots, network requests, and console logs that make root cause analysis dramatically faster.

Full Pipeline Example

Below is a complete, production-ready GitHub Actions workflow that you can copy and adapt for your project. It implements the full pipeline lifecycle: lint, unit tests, E2E tests (sharded across browsers), report merging, and Slack notification.

Features of This Pipeline

  • Multi-trigger — Runs on push, PR, manual dispatch (with test suite selection), and nightly schedule.
  • Gated execution — E2E tests only run after lint and unit tests pass.
  • Full browser matrix — Tests run across Chromium, Firefox, and WebKit with 2 shards each (6 parallel jobs).
  • Dynamic test suite selection — Manual triggers let you choose between smoke, regression, or all tests.
  • Report merging — All shard reports are merged into a single HTML report for easy review.
  • Slack notification — The team receives a structured Slack message with pass/fail status, branch info, and a direct link to the pipeline run.
.github/workflows/playwright-full-pipeline.ymlyaml
1# .github/workflows/playwright-full-pipeline.yml
2# Complete production-ready pipeline:
3# lint -> unit tests -> E2E (sharded, multi-browser) -> report -> notify
4
5name: Full Test Pipeline
6
7on:
8  push:
9    branches: [main, develop]
10  pull_request:
11    branches: [main]
12  workflow_dispatch:
13    inputs:
14      test_suite:
15        description: 'Test suite to run'
16        required: false
17        default: 'all'
18        type: choice
19        options:
20          - all
21          - smoke
22          - regression
23  schedule:
24    # Nightly regression at 2:00 AM UTC
25    - cron: '0 2 * * *'
26
27env:
28  NODE_VERSION: '20'
29  PLAYWRIGHT_VERSION: '1.48.0'
30
31jobs:
32  # ================================================================
33  # Job 1: Lint
34  # ================================================================
35  lint:
36    name: Lint
37    runs-on: ubuntu-latest
38    timeout-minutes: 5
39    steps:
40      - uses: actions/checkout@v4
41
42      - uses: actions/setup-node@v4
43        with:
44          node-version: ${{ env.NODE_VERSION }}
45          cache: 'npm'
46
47      - run: npm ci
48      - run: npm run lint
49      - run: npx tsc --noEmit
50
51  # ================================================================
52  # Job 2: Unit Tests
53  # ================================================================
54  unit-tests:
55    name: Unit Tests
56    runs-on: ubuntu-latest
57    timeout-minutes: 10
58    steps:
59      - uses: actions/checkout@v4
60
61      - uses: actions/setup-node@v4
62        with:
63          node-version: ${{ env.NODE_VERSION }}
64          cache: 'npm'
65
66      - run: npm ci
67      - run: npm run test:unit -- --coverage
68
69      - name: Upload coverage
70        uses: actions/upload-artifact@v4
71        with:
72          name: coverage-report
73          path: coverage/
74
75  # ================================================================
76  # Job 3: E2E Tests (sharded across browsers)
77  # ================================================================
78  e2e-tests:
79    name: E2E (${{ matrix.project }}, shard ${{ matrix.shard }})
80    needs: [lint, unit-tests]
81    runs-on: ubuntu-latest
82    timeout-minutes: 30
83
84    strategy:
85      fail-fast: false
86      matrix:
87        project: [chromium, firefox, webkit]
88        shard: [1/2, 2/2]
89
90    steps:
91      - uses: actions/checkout@v4
92
93      - uses: actions/setup-node@v4
94        with:
95          node-version: ${{ env.NODE_VERSION }}
96          cache: 'npm'
97
98      - name: Install dependencies
99        run: npm ci
100
101      - name: Install Playwright browser
102        run: npx playwright install --with-deps ${{ matrix.project }}
103
104      - name: Determine test grep
105        id: grep
106        run: |
107          if [ "${{ github.event.inputs.test_suite }}" = "smoke" ]; then
108            echo "grep=--grep @smoke" >> $GITHUB_OUTPUT
109          elif [ "${{ github.event.inputs.test_suite }}" = "regression" ]; then
110            echo "grep=--grep @regression" >> $GITHUB_OUTPUT
111          else
112            echo "grep=" >> $GITHUB_OUTPUT
113          fi
114
115      - name: Run Playwright tests
116        run: >
117          npx playwright test
118          --project=${{ matrix.project }}
119          --shard=${{ matrix.shard }}
120          ${{ steps.grep.outputs.grep }}
121        env:
122          CI: true
123          BASE_URL: ${{ secrets.STAGING_URL }}
124          API_KEY: ${{ secrets.API_KEY }}
125          TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
126          TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
127
128      - name: Upload blob report
129        uses: actions/upload-artifact@v4
130        if: always()
131        with:
132          name: blob-report-${{ matrix.project }}-${{ strategy.job-index }}
133          path: blob-report/
134          retention-days: 3
135
136  # ================================================================
137  # Job 4: Merge Reports
138  # ================================================================
139  merge-reports:
140    name: Merge Test Reports
141    needs: e2e-tests
142    if: always()
143    runs-on: ubuntu-latest
144    timeout-minutes: 10
145
146    steps:
147      - uses: actions/checkout@v4
148
149      - uses: actions/setup-node@v4
150        with:
151          node-version: ${{ env.NODE_VERSION }}
152          cache: 'npm'
153
154      - run: npm ci
155
156      - name: Download all blob reports
157        uses: actions/download-artifact@v4
158        with:
159          path: all-blob-reports
160          pattern: blob-report-*
161          merge-multiple: true
162
163      - name: Merge into HTML report
164        run: npx playwright merge-reports --reporter=html ./all-blob-reports
165
166      - name: Upload final HTML report
167        uses: actions/upload-artifact@v4
168        with:
169          name: playwright-html-report
170          path: playwright-report/
171          retention-days: 14
172
173  # ================================================================
174  # Job 5: Slack Notification
175  # ================================================================
176  notify:
177    name: Notify
178    needs: [lint, unit-tests, e2e-tests, merge-reports]
179    if: always()
180    runs-on: ubuntu-latest
181    timeout-minutes: 5
182
183    steps:
184      - name: Determine status
185        id: status
186        run: |
187          if [ "${{ needs.e2e-tests.result }}" = "success" ]; then
188            echo "color=good" >> $GITHUB_OUTPUT
189            echo "status=PASSED" >> $GITHUB_OUTPUT
190            echo "emoji=white_check_mark" >> $GITHUB_OUTPUT
191          else
192            echo "color=danger" >> $GITHUB_OUTPUT
193            echo "status=FAILED" >> $GITHUB_OUTPUT
194            echo "emoji=x" >> $GITHUB_OUTPUT
195          fi
196
197      - name: Send Slack notification
198        uses: slackapi/slack-github-action@v1
199        with:
200          payload: |
201            {
202              "text": ":${{ steps.status.outputs.emoji }}: E2E Tests ${{ steps.status.outputs.status }}",
203              "blocks": [
204                {
205                  "type": "header",
206                  "text": {
207                    "type": "plain_text",
208                    "text": ":${{ steps.status.outputs.emoji }}: E2E Tests ${{ steps.status.outputs.status }}"
209                  }
210                },
211                {
212                  "type": "section",
213                  "fields": [
214                    { "type": "mrkdwn", "text": "*Repository:*\n${{ github.repository }}" },
215                    { "type": "mrkdwn", "text": "*Branch:*\n${{ github.ref_name }}" },
216                    { "type": "mrkdwn", "text": "*Triggered by:*\n${{ github.actor }}" },
217                    { "type": "mrkdwn", "text": "*Run:*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View>" }
218                  ]
219                }
220              ]
221            }
222        env:
223          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
224          SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
Customization GuideTo adapt this pipeline for your project: (1) Update the NODE_VERSION and PLAYWRIGHT_VERSION environment variables, (2) Add your required secrets in GitHub repository settings, (3) Adjust the shard matrix based on your test count (more shards = faster, but more CI minutes), (4) Modify the Slack payload to match your team's notification preferences.
WarningThis pipeline uses fail-fast: false in the matrix strategy. This means all browser/shard combinations run to completion even if one fails. Change to fail-fast: true if you want to cancel remaining jobs on the first failure (saves CI minutes but provides less complete results).