In Playwright, how do testInfo.workerIndex and testInfo.parallelIndex differ?
answer
- One counts processes, one counts slots
- One is bounded, one keeps growing
- Restarts reuse the slot, not the process id
- Range is zero to workers minus one
- Pools versus per-process labels
basics
~20 sworkerIndex names a process and never repeats: every worker a run starts, including replacements after failures, gets a new one. parallelIndex names a slot between zero and workers minus one, and a replacement worker inherits it.
solid answer
~40 sBoth come from `testInfo`, and both are stable for the duration of a test, but they count different things. `testInfo.workerIndex` identifies the **process**: it is unique for every worker the run ever starts, so a suite with many failures climbs far past the configured worker count as restarts pile up. `testInfo.parallelIndex` identifies the **slot**: it always lies between `0` and `workers - 1`, and workers running at the same moment always hold different values, so a replacement worker takes over the slot its predecessor vacated. The practical rule follows from the lifetimes. Use `parallelIndex` to pick from a bounded pool sized to the worker count — one payroll schema per slot, for example. Use `workerIndex` when you want a label no other process in the run will reuse, such as a per-process log file.
code
typescript · 11 linesimport { test } from '@playwright/test';
test('payroll ledger writes into its own slot', async ({ page }) => {
const info = test.info();
// 0 .. workers-1, inherited by the replacement if this worker is discarded
const schema = `payroll_slot_${info.parallelIndex}`;
// Unique for every process the run starts, so no restart overwrites this file
const logFile = `logs/worker-${info.workerIndex}.log`;
console.log(schema, logFile);
await page.goto('/payroll/ledger');
});go deeper
Remember both live on testInfo and that one of them stays small: parallelIndex is always between zero and the worker count minus one, while workerIndex keeps climbing as processes are replaced.
Explain the lifetimes. A restart gives a new workerIndex but inherits the parallelIndex, which is why bounded pools are keyed on the slot and per-process artefacts on the process.
Show the failure modes you have hit: a thirteenth schema requested after restarts, or a replacement worker overwriting the log of the process whose failure you were trying to debug.
Own the convention across the suite. Decide which resources are provisioned per slot, how they are named, and how that provisioning stays in step when the worker count is changed for a different machine.
## Two counters, two lifetimes Playwright exposes both numbers on `testInfo` (reachable as `test.info()` inside a test), and they are easy to confuse because in a clean run with no failures they are often equal. They diverge the moment a worker is restarted. - **`testInfo.workerIndex`** identifies a **process**. Every worker the run starts is given the next index, and none is ever reused. A replacement worker started after a failure gets a brand-new, higher index. - **`testInfo.parallelIndex`** identifies a **slot**. It is always in the range `0` to `workers - 1`, and two workers running at the same instant never share one. When a worker is discarded, its replacement occupies the same slot and reports the same `parallelIndex`. | | `workerIndex` | `parallelIndex` | |---|---|---| | Identifies | the process | the concurrency slot | | Range | grows with every process started | `0` to `workers - 1` | | Reused after a restart | never | yes, by the replacement | | Unique among live workers | yes | yes | | Good for | a label no process repeats | claiming one of a fixed pool | ## Why the distinction matters Suppose the payroll regression suite gives each worker its own database schema, so that concurrent runs of the pay-run tests cannot collide. There are two very different questions hiding here: 1. **How many schemas do I have to create?** That is bounded by how many workers run at once — the `parallelIndex` range. Provision `workers` schemas and let each worker claim `payroll_slot_<parallelIndex>`. A restarted worker walks into the slot its predecessor left and reuses that schema. 2. **How do I label this one process?** That is `workerIndex`. A per-process log file named `worker-<workerIndex>.log` never has two writers, even when a restart means two processes have occupied the same slot during the run. Getting these the wrong way round produces two classic bugs. Keying a fixed pool on `workerIndex` means the twelfth restart asks for a thirteenth schema that was never provisioned. Keying a per-process artefact on `parallelIndex` means the replacement worker silently overwrites the log or trace of the process it replaced — exactly the process whose failure you wanted to investigate. ## Reading them outside a test Playwright also puts both values in the worker's environment as `TEST_WORKER_INDEX` and `TEST_PARALLEL_INDEX`, which is how code that never receives a `testInfo` — a helper module, a database connection factory imported at module load — can still tell which worker it is running in without threading the value through every call. ## Practical rules of thumb - **Bounded resources go on `parallelIndex`.** Schemas, ports, seeded tenants, licence slots: anything you provisioned exactly `workers` of. - **Per-process artefacts go on `workerIndex`.** Log files, dumps, anything you want to keep from the process that failed. - **Neither is a test identity.** A worker runs many tests in turn, so both values are shared by every test that process handles; do not use either to name something per-test. - **Neither is stable across runs.** Which slot runs which test depends on timing, so nothing durable — a golden file, a snapshot name — should be keyed on them. - **Both are zero-based.** Off-by-one bugs here are quiet: `payroll_slot_4` simply never gets created when four workers only produce indexes `0` through `3`. ## A quick diagnostic If you are unsure which number a run is producing, log both from a `beforeEach` and look at the shape of the output. Values where the two agree, run after run, mean a clean run. Values where `workerIndex` runs ahead of `parallelIndex` mean workers were replaced — and the size of the gap is a rough count of how many failures the run has taken so far.
- A run configured for four workers reports workerIndex 11. What does that tell you?That the run has started twelve processes so far, so eight of them were replacements for workers discarded after failures. The configured ceiling still holds — never more than four run at once — but the run has repeatedly paid browser start-up again, and the report should show failures to match.
- How does code that never sees testInfo find out which worker it is in?Playwright sets `TEST_WORKER_INDEX` and `TEST_PARALLEL_INDEX` in each worker's environment. A helper module or connection factory imported at load time can read them directly, which avoids threading the value through every function just so a low-level module can pick its schema or port.
saying these in an interview costs you the question
- Treats the two indexes as interchangeable
- Thinks workerIndex stays below the worker count
- Keys a fixed resource pool on workerIndex
- Names per-process log files by parallelIndex
- Assumes an index identifies a single test
- Expects the same index across separate runs