Playwright reporter on CI providers
The reporter runs inside npx playwright test: give that step the token, and every test is
reported as it runs. No separate report step is needed. On every provider:
- Store an app token from the Playwright card in Workspace › Integrations
(get started) as a secret named
PROBARA_API_TOKEN. The Project ID is not a secret: put it inplaywright.config(projectId) or in the pipeline (PROBARA_PROJECT). - Use Node.js 22.12 or later, like the rest of your Playwright run.
- Failed tests fail the step; reporting problems do not. Playwright’s exit code stays the tests’ own.
- Pin the reporter’s version in
package.json(or the lockfile), so a new release never changes a pipeline by surprise. The CLI is not a dependency of the reporter: the steps below that run it pin it in the command (npx --yes @probara/cli@0.2). With@probara/cliin yourdevDependencies,npx @probara/cliafternpm ciruns the locked version instead. - Keep the secret away from code you do not trust. A build of a pull request from a fork runs that
fork’s code: each section below says how the provider keeps the token out of it. Without the
token, the reporter sends nothing (with the project set, it logs that the token is missing), and
the tests run as usual. The CLI exits 2 without a token, so the recipes turn reporting off
(
PROBARA_ENABLED=false) where a fork’s build would run it: it then exits 0.
For one run across shards, see sharding.
GitHub Actions
Section titled “GitHub Actions”Add the secret
Section titled “Add the secret”In the repository (or organization): Settings → Secrets and variables → Actions → New
repository secret, named PROBARA_API_TOKEN.
The workflow
Section titled “The workflow”name: Playwright
on: push: branches: [main] pull_request:
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v5 with: node-version: 22 - run: npm ci - run: npx playwright install --with-deps - name: Run Playwright tests run: npx playwright test env: PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }} PROBARA_PROJECT: SHOPSharded GitHub Actions jobs
Section titled “Sharded GitHub Actions jobs”Shards write Playwright blob reports, and one job merges them with the reporter: the whole
pipeline becomes one run, attempts and attachments included. Register blob on CI and the
reporter elsewhere:
import { defineConfig } from '@playwright/test';
export default defineConfig({ reporter: process.env.CI ? 'blob' : [['list'], ['@probara/playwright-reporter']],});jobs: test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: shardIndex: [1, 2, 3, 4] shardTotal: [4] steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v5 with: node-version: 22 - run: npm ci - run: npx playwright install --with-deps - run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: blob-report-${{ matrix.shardIndex }} path: blob-report retention-days: 1
report: needs: test if: ${{ !cancelled() }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v5 with: node-version: 22 - run: npm ci - uses: actions/download-artifact@v4 with: path: all-blob-reports pattern: blob-report-* merge-multiple: true - run: npx playwright merge-reports --reporter @probara/playwright-reporter ./all-blob-reports env: PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }} PROBARA_PROJECT: SHOPParallel jobs: one run, closed by the final job
Section titled “Parallel jobs: one run, closed by the final job”In GitHub Actions the jobs of one workflow run report into one run: the run’s source names the
workflow run by its repository, run id and attempt, and the first job to report creates the run. A
job closes its run at its end by default, so the parallel jobs set PROBARA_CLOSE_RUN: false. A
final job closes the run with probara run close, which needs no run ULID there: needs makes it
wait for every test job, and if: always() runs it when they fail.
jobs: test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: shard: [1, 2] steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v5 with: node-version: 22 - run: npm ci - run: npx playwright install --with-deps - run: npx playwright test --shard=${{ matrix.shard }}/2 env: PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }} PROBARA_PROJECT: SHOP # A shard leaves the run open for the others. PROBARA_CLOSE_RUN: false
probara: needs: test if: always() runs-on: ubuntu-latest steps: - uses: actions/setup-node@v5 with: node-version: 22 - run: npx --yes @probara/cli@0.6 run close env: PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }} PROBARA_PROJECT: SHOP- A job after the close is refused with
409 ci_run_closed: the reporter logs why, sends nothing more, and the tests keep their own exit code (troubleshooting). Keep every reporting job inneeds. - A re-run of the workflow is another attempt: it reports into a run of its own.
- One job only needs neither: its run is closed at the end, as anywhere else.
What GitHub Actions fills in
Section titled “What GitHub Actions fills in”| Field | From |
|---|---|
| Run name | GITHUB_WORKFLOW #GITHUB_RUN_NUMBER, such as Playwright #42 |
| Branch | GITHUB_HEAD_REF (the pull request’s branch), else GITHUB_REF_NAME; none for a tag |
| Commit | GITHUB_SHA |
| Build URL | GITHUB_SERVER_URL/GITHUB_REPOSITORY/actions/runs/GITHUB_RUN_ID |
| Repository | GITHUB_REPOSITORY |
| Commit author | GITHUB_ACTOR |
| Pull request | <n> of GITHUB_REF refs/pull/<n>/merge |
| Workflow | GITHUB_WORKFLOW |
| CI run | GITHUB_RUN_ID, attempt GITHUB_RUN_ATTEMPT |
| CI job | GITHUB_JOB |
On pull_request events GITHUB_SHA is the merge commit GitHub made, not the branch’s last
commit. To record the latter, add PROBARA_COMMIT: ${{ github.event.pull_request.head.sha }} to
the step’s env. In GitHub Actions the commit must be a 7 to 40 character hex SHA: another value is
dropped with a warning.
Forks on GitHub Actions
Section titled “Forks on GitHub Actions”Workflows triggered by a pull request from a fork get empty secrets, so fork builds run their tests
and report nothing, with no extra setting. Do not use pull_request_target to get the secret into
a fork build: it runs the fork’s code with your secrets.
GitLab CI
Section titled “GitLab CI”Add the variable
Section titled “Add the variable”Settings → CI/CD → Variables → Add variable: key PROBARA_API_TOKEN, the token as the value,
with Mask variable on. Protect variable limits it to protected branches and tags: turn it on
only if you report from those alone.
The pipeline
Section titled “The pipeline”A .gitlab-ci.yml in Playwright’s image:
stages: - test
playwright: stage: test image: mcr.microsoft.com/playwright:v1.56.1-noble variables: PROBARA_PROJECT: SHOP script: - npm ci - npx playwright test artifacts: when: always paths: - test-results/- Match the image’s Playwright version to the one in your
package.json. - A job without the variable (a fork’s pipeline, an unprotected branch with a protected variable) runs its tests and reports nothing.
Sharded GitLab CI jobs
Section titled “Sharded GitLab CI jobs”parallel: shards write blob reports, and one job merges them with the reporter into one run.
Register blob on CI and the reporter elsewhere, as in
sharding:
stages: - test - report
playwright: stage: test image: mcr.microsoft.com/playwright:v1.56.1-noble parallel: 4 script: - npm ci - npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL artifacts: when: always paths: - blob-report/
report-to-probara: stage: report image: mcr.microsoft.com/playwright:v1.56.1-noble needs: - job: playwright artifacts: true variables: PROBARA_PROJECT: SHOP rules: # After failed shards too, and only where the token is set. - if: $PROBARA_API_TOKEN when: always script: - npm ci - npx playwright merge-reports --reporter @probara/playwright-reporter ./blob-reportA needs: on a parallel: job downloads the artifacts of every instance into the same folder;
each shard’s blob report has its own file name (report-1.zip…), so they sit side by side.
What GitLab CI fills in
Section titled “What GitLab CI fills in”| Field | From |
|---|---|
| Run name | CI_PROJECT_NAME #CI_PIPELINE_IID, such as shop #42 |
| Branch | CI_MERGE_REQUEST_SOURCE_BRANCH_NAME in a merge request pipeline, else CI_COMMIT_REF_NAME; none for a tag pipeline |
| Commit | CI_COMMIT_SHA |
| Build URL | CI_PIPELINE_URL |
Forks on GitLab CI
Section titled “Forks on GitLab CI”A merge request from a fork runs its pipeline in the fork’s project, which does not have your CI/CD variables: the reporter stays off there. If maintainers run fork pipelines in the parent project, those pipelines do get the variable, so review the change before you run it.
Jenkins
Section titled “Jenkins”Add the credential
Section titled “Add the credential”Manage Jenkins → Credentials → (a domain) → Add Credentials: kind Secret text, ID
probara-api-token, the token as the secret.
The pipeline
Section titled “The pipeline”A declarative Jenkinsfile with Node.js 22 on the agent binds the credential into the test stage
with withCredentials:
pipeline { agent any environment { PROBARA_PROJECT = 'SHOP' } stages { stage('Test') { steps { sh 'npm ci' sh 'npx playwright install --with-deps' withCredentials([string(credentialsId: 'probara-api-token', variable: 'PROBARA_API_TOKEN')]) { sh 'npx playwright test' } } } }}Jenkins masks the bound secret in the build log. For parallel stages that share one run, create it
in a first stage and pass PROBARA_RUN_ULID to each
(shared run).
With JENKINS_URL set, the run is named JOB_NAME #BUILD_NUMBER, and its branch, commit and build
URL come from BRANCH_NAME (or GIT_BRANCH), GIT_COMMIT and BUILD_URL.
Forks on Jenkins
Section titled “Forks on Jenkins”Jenkins does not hide credentials from builds of pull requests by itself: a multibranch job that builds forks can hand the token to their code.
- Set Discover pull requests from forks → Trust so that a pull request from anyone else builds
with the
Jenkinsfileof its target branch: in the GitHub branch source, From users with Admin or Write permission (or Nobody); in the Bitbucket branch source, Nobody, or Forks in the same account from plugin version 871 on (the fix for CVE-2024-28152). Everyone builds a fork with its ownJenkinsfile, which can bind the credential itself, and makes the guard below useless. - In the target branch’s
Jenkinsfile, bind the credential only whenenv.CHANGE_FORKis not set (Jenkins sets it for a pull request from a fork), and turn reporting off for a fork build:
pipeline { agent any environment { PROBARA_PROJECT = 'SHOP' } stages { stage('Test') { steps { sh 'npm ci' sh 'npx playwright install --with-deps' script { if (env.CHANGE_FORK) { withEnv(['PROBARA_ENABLED=false']) { sh 'npx playwright test' } } else { withCredentials([string(credentialsId: 'probara-api-token', variable: 'PROBARA_API_TOKEN')]) { sh 'npx playwright test' } } } } } }}For parallel stages that share one run, wrap each withCredentials the same way: with reporting
off, run create writes no ULID and run close exits 0 without a token.
CircleCI
Section titled “CircleCI”Add the token
Section titled “Add the token”Organization Settings → Contexts → Create Context (such as probara-reporting), then add the
environment variable PROBARA_API_TOKEN. A project environment variable (Project Settings →
Environment Variables) works too; then drop context: below.
The pipeline
Section titled “The pipeline”A .circleci/config.yml in Playwright’s image, sharded with parallelism into one run created up
front (shared run);
without parallelism, keep only the test steps:
version: 2.1
jobs: create-run: docker: - image: mcr.microsoft.com/playwright:v1.56.1-noble environment: PROBARA_PROJECT: SHOP steps: - run: | # Without the token (a fork's build), turn reporting off: run create prints nothing. [ -n "$PROBARA_API_TOKEN" ] || export PROBARA_ENABLED=false PROBARA_RUN_ULID=$(npx --yes @probara/cli@0.2 run create) echo "PROBARA_RUN_ULID=$PROBARA_RUN_ULID" > probara.env - persist_to_workspace: root: . paths: [probara.env] test: docker: - image: mcr.microsoft.com/playwright:v1.56.1-noble parallelism: 4 environment: PROBARA_PROJECT: SHOP steps: - checkout - attach_workspace: at: . - run: npm ci - run: | export $(cat probara.env) npx playwright test --shard=$((CIRCLE_NODE_INDEX + 1))/$CIRCLE_NODE_TOTAL close-run: docker: - image: mcr.microsoft.com/playwright:v1.56.1-noble environment: PROBARA_PROJECT: SHOP steps: - attach_workspace: at: . - run: when: always command: | export $(cat probara.env) [ -n "$PROBARA_API_TOKEN" ] || export PROBARA_ENABLED=false npx --yes @probara/cli@0.2 run close
workflows: playwright: jobs: - create-run: context: probara-reporting - test: context: probara-reporting requires: [create-run] - close-run: context: probara-reporting requires: - test: [success, failed]CIRCLE_NODE_INDEXcounts from 0, Playwright’s shards from 1.- With
CIRCLECI=true, the run is namedCIRCLE_PROJECT_REPONAME #CIRCLE_BUILD_NUM, with the branchCIRCLE_BRANCH, the commitCIRCLE_SHA1and the build URLCIRCLE_BUILD_URL.
Forks on CircleCI
Section titled “Forks on CircleCI”CircleCI does not pass contexts or project variables to builds of forked pull requests unless
Pass secrets to builds from forked pull requests is on: leave it off. A fork’s jobs that use a
context then do not run at all (CircleCI shows an authorization error). With project variables they
run without the token: the reporter sends nothing, and the PROBARA_ENABLED=false line of
create-run and close-run makes run create print nothing and both exit 0.
Azure Pipelines
Section titled “Azure Pipelines”Add the variable
Section titled “Add the variable”Pipelines → your pipeline → Edit → Variables → New variable: name PROBARA_API_TOKEN, the
token as the value, Keep this value secret on. A variable group of the library works too. Secret
variables never reach scripts on their own: map them into each step’s env, as below.
The pipeline
Section titled “The pipeline”An azure-pipelines.yml, sharded with parallel into one run created by a first job
(shared run); without
sharding, keep the variables and the test job alone, without strategy and PROBARA_RUN_ULID:
variables: # Off in a fork's build, which gets no secrets (see forks below). PROBARA_ENABLED: $[ ne(variables['System.PullRequest.IsFork'], 'True') ]
jobs: - job: create_run pool: vmImage: ubuntu-latest steps: - task: NodeTool@0 inputs: versionSpec: '22.x' - script: | set -e ulid=$(npx --yes @probara/cli@0.2 run create) echo "##vso[task.setvariable variable=ulid;isOutput=true]$ulid" name: probara env: PROBARA_API_TOKEN: $(PROBARA_API_TOKEN) PROBARA_PROJECT: SHOP
- job: test dependsOn: create_run strategy: parallel: 4 variables: PROBARA_RUN_ULID: $[ dependencies.create_run.outputs['probara.ulid'] ] pool: vmImage: ubuntu-latest steps: - task: NodeTool@0 inputs: versionSpec: '22.x' - script: npm ci - script: npx playwright install --with-deps - script: npx playwright test --shard=$(System.JobPositionInPhase)/$(System.TotalJobsInPhase) env: PROBARA_API_TOKEN: $(PROBARA_API_TOKEN) PROBARA_PROJECT: SHOP PROBARA_RUN_ULID: $(PROBARA_RUN_ULID)
- job: close_run dependsOn: [create_run, test] condition: always() variables: PROBARA_RUN_ULID: $[ dependencies.create_run.outputs['probara.ulid'] ] pool: vmImage: ubuntu-latest steps: - task: NodeTool@0 inputs: versionSpec: '22.x' - script: npx --yes @probara/cli@0.2 run close env: PROBARA_API_TOKEN: $(PROBARA_API_TOKEN) PROBARA_PROJECT: SHOP PROBARA_RUN_ULID: $(PROBARA_RUN_ULID)set -emakes a failedrun createfail the first job: Azure checks only the last command of a script, and the shards would otherwise each create a run of their own.- With
TF_BUILD=True, the run is namedBUILD_DEFINITIONNAME #BUILD_BUILDNUMBER, and its branch, commit and build URL come from the pipeline’s variables.
Forks on Azure Pipelines
Section titled “Forks on Azure Pipelines”Builds of pull requests from forks get no secret variables unless Make secrets available to
builds of forks is on: leave it off. Azure leaves a macro it cannot resolve as it is, so in a
fork’s build $(PROBARA_API_TOKEN) reaches the step as that literal text, a token Probara refuses
with 401: run create would fail the first job, and the reporter would log the 401 on every run
(the tests’ exit code stays their own). The pipeline’s PROBARA_ENABLED variable is False when
System.PullRequest.IsFork is True, so a fork’s build runs its tests with reporting off, and
run create and run close exit 0.
Bitbucket Pipelines
Section titled “Bitbucket Pipelines”Add the variable
Section titled “Add the variable”Repository settings → Pipelines → Repository variables: name PROBARA_API_TOKEN, the token as
the value, Secured on. Bitbucket passes repository variables to every step, so the reporter
finds it in npx playwright test.
The pipeline
Section titled “The pipeline”A bitbucket-pipelines.yml in Playwright’s image, with two parallel shards that merge their blob
reports into one run (merge the blob reports);
without sharding, keep one step running npx playwright test:
image: mcr.microsoft.com/playwright:v1.56.1-noble
pipelines: default: - parallel: - step: name: Shard 1 script: - npm ci - npx playwright test --shard=1/2 || touch shard-1.failed artifacts: - blob-report/** - shard-*.failed - step: name: Shard 2 script: - npm ci - npx playwright test --shard=2/2 || touch shard-2.failed artifacts: - blob-report/** - shard-*.failed - step: name: Report to Probara script: - npm ci - export PROBARA_PROJECT=SHOP - npx playwright merge-reports --reporter @probara/playwright-reporter ./blob-report - if ls shard-*.failed >/dev/null 2>&1; then echo "Tests failed in a shard" >&2; exit 1; fiBitbucket runs no step after a failed one, so a shard that failed its step would keep the report
step from sending anything. Each shard records failed tests in a shard-<n>.failed artifact
instead, and the report step sends every result, then fails the pipeline when a shard left one: the
shards show as passed, and the report step carries the failure.
With BITBUCKET_BUILD_NUMBER set, the run is named BITBUCKET_REPO_SLUG #BITBUCKET_BUILD_NUMBER,
with the branch BITBUCKET_BRANCH, the commit BITBUCKET_COMMIT and a link to the pipeline.
Forks on Bitbucket Pipelines
Section titled “Forks on Bitbucket Pipelines”Pull requests from forks get no secured variables: the reporter sends nothing there.
Buildkite
Section titled “Buildkite”Add the token
Section titled “Add the token”Expose PROBARA_API_TOKEN to the steps from a secret store: an agent environment hook that reads
it from your secrets manager, or Buildkite secrets.
The pipeline
Section titled “The pipeline”A pipeline.yml whose parallel jobs report into one run created by a first step
(shared run):
env: PROBARA_PROJECT: SHOP
steps: - label: Create the Probara run key: create-run command: | PROBARA_RUN_ULID="$(npx --yes @probara/cli@0.2 run create)" if [ -n "$$PROBARA_RUN_ULID" ]; then buildkite-agent meta-data set probara-run "$$PROBARA_RUN_ULID"; fi
- label: Playwright depends_on: create-run parallelism: 4 command: | npm ci export PROBARA_RUN_ULID="$(buildkite-agent meta-data get probara-run --default '')" npx playwright test --shard=$((BUILDKITE_PARALLEL_JOB + 1))/$$BUILDKITE_PARALLEL_JOB_COUNT
- wait: ~ continue_on_failure: true
- label: Close the Probara run command: PROBARA_RUN_ULID="$(buildkite-agent meta-data get probara-run --default '')" npx --yes @probara/cli@0.2 run closebuildkite-agent pipeline uploadfills in$NAMEand${NAME}when it uploads the pipeline, and a variable it does not know then, such asBUILDKITE_PARALLEL_JOB_COUNTthat only the parallel jobs get, becomes blank. A$$is left as one$for the job’s shell, while$(...)and$((...))are left as they are.- The run is created on a line of its own, so a failed
run createfails the first step rather than storing a blank ULID. With reporting off (a fork build on an agent that setsPROBARA_ENABLED=false, as below),run createprints no ULID and exits 0: nothing is stored,meta-data get --default ''gives the other steps an empty ULID instead of an error, and they run with reporting off too. BUILDKITE_PARALLEL_JOBcounts from 0, Playwright’s shards from 1.- With
BUILDKITE=true, the run is namedBUILDKITE_PIPELINE_SLUG #BUILDKITE_BUILD_NUMBER, with the branchBUILDKITE_BRANCH, the commitBUILDKITE_COMMITand the build URLBUILDKITE_BUILD_URL.
Forks on Buildkite
Section titled “Forks on Buildkite”Buildkite builds pull requests from forks only if the pipeline allows it (off by default). Those
builds run on your agents, and pipeline upload reads the fork’s own pipeline.yml, which also
picks the agents’ queue. The fork’s code runs in the job’s shell as the agent’s user, so on an agent
that can read the token from your secret store, the fork’s code can read it too: no hook or step
keeps it out.
Keep fork builds off, or keep them off those agents: their environment hook refuses a build of a
fork (BUILDKITE_PULL_REQUEST_REPO names it), and forks build only on a separate queue or cluster
whose agents cannot read the secret store:
# The environment hook of the agents that can read the token: no build of a fork runs there.if [ -n "$BUILDKITE_PULL_REQUEST_REPO" ]; then case "${BUILDKITE_PULL_REQUEST_REPO#*://}" in github.com/acme/shop.git) ;; *) echo "Refusing a build of a fork: $BUILDKITE_PULL_REQUEST_REPO" >&2 exit 1 ;; esacfiexport PROBARA_API_TOKEN="$(your-secrets-manager get probara-api-token)"BUILDKITE_PULL_REQUEST_REPO is empty outside pull requests, and has been an https:// URL since
2022 (a git:// one before), so the hook compares it without the scheme: put your repository in
place of github.com/acme/shop.git. When it does not match, your own pull requests fail on these
agents too, which the first one shows. On the agents that build forks, set PROBARA_ENABLED=false
in their environment hook: the fork’s tests run, npx playwright test reports nothing, and
run create and run close exit 0 without a token.
Next: troubleshooting, or keep what a failed report could not send with a results file.