skip to content

Should a Playwright suite's shard split live in playwright.config.ts or in the CI command line?

level: principalimportance: should knowfreq 32%

answer

  1. Two places can declare the split
  2. Flag belongs with the pipeline
  3. Config takes current and total
  4. Command line overrides the config
  5. Mismatched totals fail silently

basics

~20 s

Playwright treats both identically, so it is a question of ownership. The split belongs to the pipeline launching the jobs, so the --shard flag usually wins; the config option earns its place when a wrapper script starts the run.

solid answer

~40 s

Playwright accepts the split either way: `--shard=1/4` on the command line, or `shard: { current: 1, total: 4 }` in `playwright.config.ts`, with command-line options overriding the config. Since the pipeline is what actually launches N jobs, the total lives there whatever you do, and putting `--shard` on the command line keeps one source of truth rather than two that can drift. The config option is worth it when something other than a raw CLI call starts the run, such as a wrapper script or a task runner, and it is then usually written as `total` fixed in code with `current` read from an environment variable. Either way the dangerous failure is the same: a mismatched total is not rejected, it just stops the slices from covering the suite.

code

typescript · 8 lines
typescript
import { defineConfig } from '@playwright/test';

export default defineConfig({
  fullyParallel: true,
  shard: process.env.SHARD_INDEX
    ? { current: Number(process.env.SHARD_INDEX), total: 4 }
    : undefined,
});

go deeper

for a junior

Know that the split can come from the --shard flag or from a shard entry in the Playwright config, and that the flag wins when both are set. Do not hardcode a shard in a config you also run locally.

for a middle

Explain that the config option takes an object with current and total while the flag takes the index/total string, and that either way the pipeline still has to launch one job per shard.

for a senior

Argue from failure modes: a mismatched total is silent, so keep the number in one place, derive the job count from it, and verify at merge time that every expected shard reported.

for a principal

Decide where pipeline configuration is allowed to live and make it consistent across repositories, so that reviewers know which file to read, local runs are never accidentally sharded, and the suite's verdict is owned by the pipeline.

## The two places a split can be declared The runner does not care which you use. `--shard=1/4` and ```ts export default defineConfig({ shard: { current: Number(process.env.SHARD_INDEX), total: 4 }, }); ``` produce the same division, and command-line options override the config when both are present. So this is not a correctness question; it is a question about where a piece of pipeline configuration is allowed to live, and who is accountable when it is wrong. ## What is fixed either way Whichever you pick, the pipeline still has to launch as many jobs as there are shards. The total therefore exists in the pipeline definition no matter what, and the real choice is whether it *also* exists in the repository's test config. - Put it only on the command line and there is exactly one number to change. - Put it only in the config and the pipeline still needs a matching job count, so the number lives in two files that must agree. - Put it in both and the flag silently wins, which is a confusing place for a reader to land. ## What the command line buys 1. **One owner.** The job count and the shard total are written next to each other, so a change is one edit and one review. 2. **A config that serves every caller.** Developers run the same `playwright.config.ts` locally with no shard applied, and no environment variable has to be defined for the config to load. 3. **Obvious provenance.** The run's own command line records the slice it executed, which is the first thing anyone reads when triaging. ## What the config option buys 1. **Programmatic runs.** When a wrapper script, a task runner or a bespoke harness starts Playwright rather than a raw CLI call, the config is the only place the split can go. 2. **Reviewable defaults.** The split lives in versioned code, so it travels with branches and goes through code review with the tests. 3. **Computed values.** The config is code, so the shard index can be derived from whatever the environment provides rather than templated into a shell string. ## Side by side | Concern | --shard flag | shard config option | |---|---|---| | Source of truth | pipeline only | code, duplicated by job count | | Local developer run | unaffected | needs the env var handled | | Non-CLI callers | not covered | covered | | Visible in run logs | yes | only if the config is echoed | | Precedence if both set | wins | ignored | ## The failure mode neither prevents A wrong total does not raise an error. Each shard computes its slice from the numbers it was given, so a job told it is `2/5` inside a four-way split runs the wrong subset while still exiting cleanly. Nothing in Playwright cross-checks the shards, because they are designed never to talk to each other. That is the argument for keeping the total in one place and deriving everything else from it, and for a merge step that fails when the number of collected shard archives does not match the number expected. ## A defensible house rule - Default to `--shard` in the pipeline, with the job count and the total generated from one variable. - Use the config option only where a non-CLI caller makes it necessary, and read `current` from the environment rather than hardcoding it. - Never set both, and say so in the config with a comment, so that nobody edits a `shard` block that the flag is overriding. - Make the pipeline, not any individual shard, the thing that declares the suite passed.

  • If both --shard and a shard block in the config are present, which one applies?
    The command-line flag. Playwright's CLI options override the corresponding config values, so the `shard` block is ignored without warning. That silent override is the main reason to pick one location and keep the other empty.
  • Why can a hardcoded shard in the config break a developer's local run?
    Because the config applies everywhere, not just in CI. A fixed `shard: { current: 1, total: 4 }` means a local run quietly executes a quarter of the tests, and someone concludes the suite is green when three quarters of it never ran. Gate the option on an environment variable instead.

saying these in an interview costs you the question

  • Thinks the config option accepts the string 1/4
  • Believes the config option removes the need for parallel CI jobs
  • Hardcodes a shard index that every job then runs
  • Assumes a mismatched shard total fails loudly
  • Thinks the flag and the config option behave differently at run time
  • Sets both and expects the config to win