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:
- Write the YAML file that installs dependencies and runs your tests
- Configure reporters, artifacts, and test sharding
- Define which tests run on which triggers (smoke on every PR, full regression nightly)
- Document the required secrets and environment variables
- Hand the YAML file to DevOps with clear instructions for integration
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:
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: 7if: 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:
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--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:
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-jammyensures all browser dependencies are pre-installed, eliminating setup issues. - parallel keyword — GitLab natively supports parallel job execution. Setting
parallel: 3creates three copies of the job, and Playwright's--shardflag distributes tests across them. - JUnit reports — The
reports: junitdirective tells GitLab to parse the XML file and display test results directly in merge request widgets. - GitLab Pages — The
pagesjob publishes the HTML report to a URL likehttps://your-group.gitlab.io/your-project.
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
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: trueAzure 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.
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:
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:
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
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:
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=undefined1// 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:
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)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:
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_TOTALRetry 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:
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:
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});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:
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 @visualHanding 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:
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:
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.
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:
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:
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
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-testsWhere Artifacts Go
| CI Platform | How to View Reports | Retention |
|---|---|---|
| GitHub Actions | Download from the Actions tab > Artifacts section | Configurable (default 90 days) |
| GitLab CI | Browse in job artifacts or publish via GitLab Pages | Configurable via expire_in |
| Azure DevOps | Download from pipeline run > Published artifacts | Follows org retention policy |
| Jenkins | HTML Publisher Plugin sidebar link on build page | Until build is deleted |
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.
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_WEBHOOKNODE_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.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).