TurnSignal

Playwright sharding in GitLab CI, Azure Pipelines, CircleCI and Jenkins

By · October 31, 2026 · 5 min read

In short

  • Every CI system shards the same way: run N copies of the job and give each one npx playwright test --shard=<index>/<total>, where the index starts at 1.
  • GitLab CI has parallel: N with $CI_NODE_INDEX/$CI_NODE_TOTAL; Azure Pipelines uses a strategy: matrix; CircleCI numbers from 0, so add 1 to CIRCLE_NODE_INDEX; Jenkins uses parallel stages.
  • Turn on fullyParallel: true so shards are split by test, not by file. Otherwise one large file can make one shard much slower than the rest.
  • Each shard writes its own report. Use the blob reporter on every shard and merge the zips in a final job, and upload them even when tests fail.

How --shard works

npx playwright test --shard=2/4 runs the second quarter of the suite. Run four jobs, each with its own number, and the suite finishes in roughly a quarter of the time. Playwright decides which tests belong to which shard from the test list, so every job must see the same tests, the same config and the same Playwright version.

Balancing: fullyParallel decides the split

Playwright's sharding guide explains that without fullyParallel, whole test files are assigned to shards. If one file has 200 tests and the others have 10, the shard that gets the big file runs far longer, and some shards may get no tests at all. With fullyParallel: true, tests are split one by one and the shards come out even. The guide recommends it for sharding in CI.

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

export default defineConfig({
  fullyParallel: true,
  reporter: process.env.CI ? 'blob' : 'html',
});

Tests in a serial group stay together on one shard either way, because they depend on each other. Splitting is by count, not by duration: a shard with many slow tests still takes longer.

GitLab CI

GitLab's parallel keyword runs the same job N times and names the copies tests 1/7 … tests 7/7. Each copy gets CI_NODE_INDEX (starting at 1) and CI_NODE_TOTAL, which match what --shard expects. This is the example from Playwright's CI docs, with the Docker image pinned to the Playwright version in your lockfile:

stages:
  - test

tests:
  stage: test
  image: mcr.microsoft.com/playwright:v1.63.0-noble
  parallel: 7
  script:
    - npm ci
    - npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL

To shard several browser projects, parallel:matrix gives each job its own variables. Playwright's example runs two projects with ten shards each, 20 jobs in total:

tests:
  stage: test
  image: mcr.microsoft.com/playwright:v1.63.0-noble
  parallel:
    matrix:
      - PROJECT: ['chromium', 'webkit']
        SHARD: ['1/10', '2/10', '3/10', '4/10', '5/10', '6/10', '7/10', '8/10', '9/10', '10/10']
  script:
    - npm ci
    - npx playwright test --project=$PROJECT --shard=$SHARD

To keep one report, save blob-report/ as an artifact of each job with artifacts: when: always, so failed shards still upload it. A job in a later stage receives the artifacts of earlier stages by default; the blob file names contain the shard number, so they sit side by side in blob-report/ and npx playwright merge-reports --reporter html ./blob-report combines them.

Azure Pipelines

Playwright's docs shard on Azure Pipelines with a strategy: matrix that lists every project and shard. Each entry becomes its own job (shortened here to one project):

strategy:
  matrix:
    chromium-1:
      project: chromium
      shard: 1/3
    chromium-2:
      project: chromium
      shard: 2/3
    chromium-3:
      project: chromium
      shard: 3/3
steps:
- task: UseNode@1
  inputs:
    version: '22'
- script: npm ci
- script: npx playwright install --with-deps
- script: npx playwright test --project=$(project) --shard=$(shard)
  env:
    CI: 'true'

Azure DevOps can also show results in its own Tests tab. Playwright's docs use the JUnit reporter with PublishTestResults@2, testResultsFormat: 'JUnit' and mergeTestResults: true, run with condition: succeededOrFailed() so failures are published too. With shards, each job publishes its part of the run. The JUnit reporter guide covers the reporter options.

CircleCI

CircleCI's parallelism starts its node index at 0, while --shard starts at 1. Playwright's docs add one before passing it on:

playwright-job-name:
  executor: pw-noble-development
  parallelism: 4
  steps:
    - run: SHARD="$((${CIRCLE_NODE_INDEX}+1))"; npx playwright test --shard=${SHARD}/${CIRCLE_NODE_TOTAL}

Passing CIRCLE_NODE_INDEX directly is a common mistake: the first node asks for shard 0 and the last shard never runs. The same docs warn that on the medium resource class Playwright detects 2 cores and uses 2 workers; setting more workers than that causes timeouts and failures.

Jenkins

Playwright's Jenkins docs show a single job in the Playwright Docker image and no sharding example. Jenkins' own declarative syntax runs stages in parallel, so each shard can be a stage. Each one archives its blob report even when tests fail:

pipeline {
  agent none
  stages {
    stage('e2e') {
      parallel {
        stage('shard 1') {
          agent { docker { image 'mcr.microsoft.com/playwright:v1.63.0-noble' } }
          steps {
            sh 'npm ci'
            sh 'npx playwright test --shard=1/3'
          }
          post { always { archiveArtifacts artifacts: 'blob-report/**', allowEmptyArchive: true } }
        }
        stage('shard 2') {
          agent { docker { image 'mcr.microsoft.com/playwright:v1.63.0-noble' } }
          steps {
            sh 'npm ci'
            sh 'npx playwright test --shard=2/3'
          }
          post { always { archiveArtifacts artifacts: 'blob-report/**', allowEmptyArchive: true } }
        }
        stage('shard 3') {
          agent { docker { image 'mcr.microsoft.com/playwright:v1.63.0-noble' } }
          steps {
            sh 'npm ci'
            sh 'npx playwright test --shard=3/3'
          }
          post { always { archiveArtifacts artifacts: 'blob-report/**', allowEmptyArchive: true } }
        }
      }
    }
  }
}

Each stage gets its own agent, so the stages really run on separate executors. To produce one HTML report, bring the zips from all stages into one workspace (for example with stash in each shard and unstash in a later stage) and run npx playwright merge-reports there.

Merging the shards into one report

Whatever the CI system, the steps are the same: blob reporter on every shard, upload blob-report/ even on failure, put every .zip in one folder, run npx playwright merge-reports. The merge-reports guide covers JUnit and JSON output and the errors you can hit, such as "No report files found" when the zips sit in subfolders. On GitHub Actions, the setup guide has the full matrix workflow.

When a shard dies

More shards means more machines that can run out of memory, hit a time limit or lose their runner. A shard that is killed before the run ends writes no blob report, and the merged report then simply has fewer tests, often all green. If the total test count drops between runs, look for a shard that ended early; this guide shows how.

TurnSignal records which tests each shard planned to run when it starts, and lists any that never reported as Missing on the run page, on every CI system, with GitLab, Azure Pipelines, CircleCI, Jenkins and Bitbucket detected automatically. Results from all shards land in one run without a merge job. See the docs.

Questions people ask

How many shards should I use?

Enough that each shard runs for a few minutes after setup. Every shard pays for checkout, npm ci and browser setup, so many tiny shards spend most of their time installing. Measure one shard, then divide.

Can I combine --shard with --project or --grep?

Yes. The filters pick the tests first and the shard takes its part of what is left, so every job must use the same filters for the shards to add up to the whole set.

Why does one shard report "No tests found"?

It does not in Playwright 1.63: when a shard is set, an empty shard is not an error. If you see "No tests found", the filters or testDir match nothing at all; see the guide to that error.

Should every shard upload the HTML report?

No. Upload the blob report from each shard and produce the HTML report once, in the merge job. Shards then do not need the HTML reporter at all in CI.

Sources

Playwright details were checked against Playwright 1.63 and its documentation on October 31, 2026.

Try TurnSignal on your next run

Add one reporter line next to your existing reporters. Free to start, no credit card, no repository access.

Get started free Read the docs