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

> Split a Playwright suite across CI jobs with --shard on GitLab CI, Azure Pipelines, CircleCI and Jenkins, balance the shards, and merge one report.

Published 2026-10-31, updated 2026-10-31. Canonical: https://turnsignal.ai/blog/playwright-sharding-gitlab-jenkins-azure

## 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.

```ts
// 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:

```yaml
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:

```yaml
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):

```yaml
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](https://turnsignal.ai/blog/playwright-junit-reporter) 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:

```yaml
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:

```groovy
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](https://turnsignal.ai/blog/playwright-merge-reports) 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](https://turnsignal.ai/blog/playwright-github-actions-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](https://turnsignal.ai/blog/playwright-missing-tests-crashed-shard) shows how.

[TurnSignal](https://turnsignal.ai/) 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](https://turnsignal.ai/docs).

## FAQ

**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](https://turnsignal.ai/blog/playwright-no-tests-found).

**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 docs: Sharding (balancing, merging reports)](https://playwright.dev/docs/test-sharding)
- [Playwright docs: Continuous Integration (GitLab, Azure Pipelines, CircleCI, Jenkins)](https://playwright.dev/docs/ci)
- [GitLab docs: parallel and parallel:matrix](https://docs.gitlab.com/ci/yaml/#parallel)
- [Jenkins docs: Pipeline syntax (parallel stages)](https://www.jenkins.io/doc/book/pipeline/syntax/#parallel)
