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.

Bitbucket Pipelines gating a pull request via a required build An install step populates the declared node and browser caches. A parallel step group runs three browser steps at once, each restoring those caches. Failing steps publish diff artifacts. A required merge check reads the overall build status and allows or blocks the pull request. install step parallel step group merge check npm ci fills caches caches step: chromium size: 2x step: firefox size: 2x step: webkit size: 2x artifacts merge? blocked / allowed

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.