Bitbucket Pipelines for Component Tests
Bitbucket Pipelines gates a pull request the moment you make its build a required merge check — but only if the pipeline itself is fast and reliable enough to run on every pull request. The bitbucket-pipelines.yml file has a smaller vocabulary than most CI systems, and that terseness is a trap: without explicit cache definitions the pipeline reinstalls browsers on every step, without a parallel group the browser matrix runs serially, and without a required build check a red pipeline does nothing to stop a merge. This is the CI/CD gating surface for teams on Bitbucket Cloud.
This page assembles a production bitbucket-pipelines.yml for component snapshot and visual regression tests: definitions:caches for dependencies and browsers, a pipelines:pull-requests section so the work is scoped to what it gates, a parallel step group for cross-browser fan-out, size: and artifacts: on the heavy steps, and the branch-restriction merge check that turns a green pipeline into a hard merge requirement. A focused walkthrough on wiring up component snapshot tests specifically is linked in context below.
Prerequisites
How a Bitbucket build gates a pull request
The moving parts are fewer than other CI systems but connect the same way: declared caches restore into each step, a parallel group fans the browsers out, artifacts carry the diffs back, and a branch-level merge check reads the build result to decide whether the pull request can merge.
Unlike systems with an allow_failure flag per job, Bitbucket gates on the aggregate build result: any failed step in the pipeline fails the build, and the required merge check reads that single build status. Soft tiers are expressed by moving non-blocking work into a separate custom pipeline rather than a per-step flag.
Step-by-step implementation
Step 1 — Pin the image and declare caches
Start with an exact Playwright image so the runner’s fonts and browser binaries match your baselines, then declare custom caches. Bitbucket ships a built-in node cache for node_modules, but the Playwright browser directory needs an explicit definition pointing at a path that persists.
# bitbucket-pipelines.yml
# Exact image tag: it carries the browsers and fonts your baselines assume.
image: mcr.microsoft.com/playwright:v1.44.0-jammy
definitions:
caches:
# Custom cache: browsers are large and slow to download — cache them.
playwright-browsers: /root/.cache/ms-playwright
steps:
- step: &install
name: Install dependencies and browsers
caches:
- node # built-in cache for node_modules
- playwright-browsers # custom cache declared above
script:
- npm ci
- npx playwright install chromium firefox webkit
# Persist the installed tree so parallel steps skip reinstalling.
artifacts:
- node_modules/**
Verify: Push the file and open the pipeline in Bitbucket. The first install step logs “Cache ‘playwright-browsers’: Skipping upload… not found” on the first run, then on the second run logs “Cache ‘playwright-browsers’: Downloading” and the browser install prints “is already installed”.
Step 2 — Scope the gating steps to pull requests
Bitbucket runs different sections depending on the trigger. Put the gating work under pipelines:pull-requests so it fires on pull request events — the exact scope a required merge check evaluates — rather than on every branch push.
pipelines:
pull-requests:
# The '**' glob matches pull requests targeting any branch.
'**':
- step:
name: Unit component tests
caches:
- node
script:
# Fast tier first: fail cheap before launching browsers.
- npm ci
- npm run test:unit -- --reporter=dot
Verify: Open a pull request. The pipeline should appear on the pull request’s page and in the “Builds” tab, and pushing a commit directly to a non-default branch without an open pull request should not trigger this pipeline.
Step 3 — Fan out browsers with a parallel step group
A parallel block schedules each contained step onto its own runner and runs them concurrently. This is the Bitbucket equivalent of a browser matrix — coverage across the engines that drive most cross-browser rendering variance. Each parallel step restores the declared caches independently.
pull-requests:
'**':
- step:
name: Unit component tests
# ... from Step 2 ...
- parallel:
# These three steps run at once, each on its own runner.
- step:
name: Visual — chromium
caches: [node, playwright-browsers]
script:
- npm ci
- npx playwright test --project=chromium --reporter=line
- step:
name: Visual — firefox
caches: [node, playwright-browsers]
script:
- npm ci
- npx playwright test --project=firefox --reporter=line
- step:
name: Visual — webkit
caches: [node, playwright-browsers]
script:
- npm ci
- npx playwright test --project=webkit --reporter=line
Verify: In the pipeline view the three “Visual —” steps should render side by side under one parallel group and start at the same time. Total wall-clock time for the group should approximate the slowest single browser, not the sum of all three.
Step 4 — Size the browser steps and publish artifacts
Headed browsers exhaust the default 4 GB runner, and failing comparisons need their diff PNGs surfaced for review. Add size: 2x for an 8 GB runner and declare artifacts paths so test-results/ is downloadable from each step. Snapshot-specific setup is covered in configuring Bitbucket Pipelines for component snapshot tests.
- step:
name: Visual — chromium
size: 2x # 8 GB runner: browsers are memory-hungry
caches: [node, playwright-browsers]
script:
# Cap workers so concurrent browser contexts fit the runner.
- npm ci
- npx playwright test --project=chromium --workers=2 --reporter=line
artifacts:
# Downloadable from the step on failure for diff review.
- test-results/** # actual / expected / diff PNGs
- playwright-report/**
Verify: Introduce a one-pixel colour change and re-run. The failing “Visual — chromium” step should expose downloadable artifacts; the archive must contain a *-diff.png under test-results/. The step should complete without an out-of-memory “Container killed” message in the log.
Step 5 — Separate a soft advisory tier
Bitbucket has no per-step allow_failure, so express a non-blocking tier by moving flaky-prone work into a custom pipeline that the merge check does not require. Here WebKit — whose font hinting is the most variable — runs advisory-only, while Chromium and Firefox stay in the required pull-requests pipeline. What counts as a failing diff is governed by pipeline gating thresholds.
pipelines:
pull-requests:
'**':
# Blocking tier: chromium + firefox live here and gate the merge.
- parallel:
- step:
name: Visual — chromium
size: 2x
caches: [node, playwright-browsers]
script: [npm ci, npx playwright test --project=chromium]
- step:
name: Visual — firefox
size: 2x
caches: [node, playwright-browsers]
script: [npm ci, npx playwright test --project=firefox]
custom:
# Advisory tier: run manually or on a schedule, not part of the merge gate.
visual-webkit-advisory:
- step:
name: Visual — webkit (advisory)
size: 2x
caches: [node, playwright-browsers]
script: [npm ci, npx playwright test --project=webkit]
Verify: A WebKit-only diff no longer appears in the pull request’s required build because that step is not in pull-requests. Trigger the visual-webkit-advisory custom pipeline manually from Pipelines → Run pipeline and confirm it runs the WebKit project independently.
Step 6 — Make the build a required merge check
The pipeline only gates once Bitbucket refuses to merge without it. In Repository settings → Branch restrictions, add a merge check on the target branch requiring a number of passing builds. With the check active, the pull request’s Merge button stays disabled until the required pull-requests pipeline reports green.
# There is no YAML for the merge check — it is a repository setting.
# Repository settings -> Branch restrictions -> Add a branch restriction
# Branch: main
# Merge checks:
# [x] Minimum number of passing builds: 1
# [x] Reset requested changes when the source branch is modified
# With this set, a failed step in the pull-requests pipeline fails the build,
# and the failed build blocks the Merge button on the pull request.
Verify: With the merge check saved, open a pull request whose Chromium visual step fails. The pull request page should show “1 of 1 passing builds required” unmet and the Merge button disabled. Fix or revert the diff, let the pipeline go green, and the Merge button becomes enabled.
Configuration reference
| Keyword | Scope | Type | Default | Effect |
|---|---|---|---|---|
image |
pipeline/step | string |
Atlassian default | Docker image the step runs in; pin an exact tag for stable font rendering |
definitions:caches |
file | map |
built-ins only | Named cache-to-path mappings referenced by steps |
caches |
step | list |
none | Named caches to restore before and save after the step |
pipelines:pull-requests |
file | map |
none | Steps that run on pull request events; the scope a merge check reads |
parallel |
pipeline | list of steps |
none | Runs contained steps concurrently, each on its own runner |
size |
step | 1x | 2x | 4x |
1x |
Runner resource multiplier; 2x gives 8 GB memory |
artifacts |
step | list of globs |
none | Files passed to later steps and downloadable from the build |
caches (built-in node) |
step | keyword | n/a | Caches node_modules; convenient but does not cover browser binaries |
pipelines:custom |
file | map |
none | Manually or schedule-triggered pipelines, outside the pull request gate |
max-time |
step | number (min) |
120 |
Minutes before the step is force-terminated |
| Merge check “passing builds” | branch restriction | number |
0 |
Minimum green builds required before the Merge button unlocks |
Common pitfalls
1. Relying on the built-in node cache to cover browsers.
The node cache only holds node_modules. Playwright browsers live under /root/.cache/ms-playwright, so without a custom playwright-browsers cache every step re-downloads three engines. Declare the browser path under definitions:caches and reference it in each step that runs tests.
2. Expecting the first run to hit the cache. Bitbucket only saves a cache after a step successfully populates it, so the very first pipeline always shows a cache miss. Treating that first miss as a misconfiguration and rewriting the cache block wastes time — run the pipeline twice before concluding the cache is broken.
3. Omitting size: 2x on browser steps.
The default runner has 4 GB of memory. A parallel group of headed browsers, plus any Docker services, easily exceeds it, and the step dies with “Container killed” partway through capture — which looks like a flaky test, not an OOM. Add size: 2x and cap Playwright --workers so concurrent contexts fit.
4. Assuming a red pipeline blocks the merge on its own. Without a branch restriction requiring passing builds, a failed pipeline is purely informational — the Merge button stays enabled. The gate is the merge check in Branch restrictions, not the pipeline itself. Configure both.
5. Putting advisory steps in the required pull-requests pipeline.
Because Bitbucket gates on the whole build, any failed step in pull-requests fails the required build. A flaky WebKit step placed there will block merges even when you intended it as advisory. Move non-blocking work into a custom pipeline that the merge check does not require.
Integration point
This pipeline is the Bitbucket-specific execution surface for the broader CI/CD gating strategy. Because Bitbucket gates on the aggregate build rather than per-step flags, the decision about which steps belong in the required pull-requests pipeline versus an advisory custom pipeline is the direct equivalent of the strict-versus-soft tiers other CI systems express with a per-job flag. What counts as a failing diff in the first place comes from pipeline gating thresholds — align them so every step in the required pipeline runs against a threshold worth blocking a merge on. As the suite grows past what one runner per browser can finish quickly, the parallel step group here is where test parallelization and sharding splits each browser’s specs across multiple steps.
For the snapshot layer specifically — configuring the runner, wiring toMatchSnapshot, and committing baselines that survive the container — see configuring Bitbucket Pipelines for component snapshot tests. The assertions these steps run are the component unit tests in the fast tier and the Storybook interaction and accessibility checks in the visual tier.
FAQ
How do I run component test steps in parallel in Bitbucket Pipelines?
Wrap the steps in a parallel block inside your pull-requests pipeline. Bitbucket schedules each step in the group onto its own runner and runs them concurrently, so three browser steps finish in roughly the time of the slowest one rather than the sum. Each parallel step restores the same declared caches independently.
How does a required build gate a pull request in Bitbucket?
In Repository settings under Branch restrictions, add a merge check on the target branch that requires a minimum number of passing builds. When set, Bitbucket disables the Merge button on a pull request until the pipeline reports the required number of green builds, so a failing visual step prevents the merge.
Why does my Bitbucket Pipelines step run out of memory during Playwright tests?
The default Bitbucket runner allocates 4 GB of memory, which a headed browser matrix can exhaust. Add size: 2x to the step to double the allocation to 8 GB, and reduce Playwright’s worker count with --workers so the browsers launched in parallel fit within the runner. Docker service memory counts against the same budget, so trim any unused services.
Why is my Bitbucket cache not restoring node_modules between steps?
Bitbucket only saves a cache after the first successful run that populates it, so the very first pipeline shows a cache miss by design. If misses persist, confirm the cache name is declared under definitions:caches and referenced in the step’s caches list, and that the cached path exists when the step ends. Caches are keyed per repository and branch fallback, and expire after one week of disuse.
Related
- CI/CD Test Gating & Pipeline Integration — parent guide covering pipeline gating across GitHub Actions, GitLab, and Bitbucket
- Pipeline Gating Thresholds — set the diff thresholds and required checks that decide when a step should fail
- Test Parallelization & Sharding — split each browser’s specs across steps once the parallel group outgrows one runner per engine
- Configuring Bitbucket Pipelines for Component Snapshot Tests — wire up the snapshot runner and commit baselines that survive the container
- Cross-Browser Matrix — the engine-specific rendering variance the parallel browser steps here cover