Thin orchestrator for Playwright test execution with parallel worker support.
Owns the browser lifecycle, per-test loop (sequential or parallel), trace management, and final status transition. Delegates heavy sub-tasks to focused modules:
| Module | Responsibility |
|---|---|
runner/config.js |
Env constants, artifact dir setup |
runner/codeParsing.js |
extractTestBody (hasCode check) |
runner/executeTest.js |
Single-test execution |
runner/feedbackIntegration.js |
Post-run AI feedback loop |
Parallel execution
When parallelWorkers > 1, tests run in concurrent browser contexts within
a single Chromium instance. Each worker picks the next queued test, executes
it in its own isolated BrowserContext, and reports back. The shared browser
process keeps memory usage lower than launching N separate browsers.
Concurrency is controlled by:
PARALLEL_WORKERSenv var (default for all runs)- Per-run override via
options.parallelWorkers(from Test Dials / API)
Exports
runTests— Execute an array of approved tests against a project.
- Source:
Methods
(static) computeShardSizes(total, shardCount) → {Array.<number>}
CAP-002 — Compute per-shard sizes using Playwright's --shard=N/M
algorithm: the first total % shardCount shards receive one extra item,
so shard sizes differ by at most one. Pure function — no I/O, no
mutation, no allocations beyond the returned array. The single source
of truth for the partition shape; both partitionTestsIntoShards
(stamps _shardIndex on test objects) and partitionTestIdsForShards
(slices plain-string arrays) derive their output from this.
Parameters:
| Name | Type | Description |
|---|---|---|
total |
number | Total item count. |
shardCount |
number | 1..MAX_WORKERS (caller is responsible for clamp). |
- Source:
Returns:
sizes[shardIndex] — per-shard item count. Empty
trailing shards (possible when shardCount > total) yield 0.
- Type
- Array.<number>
(static) partitionTestIdsForShards(testIds, shardCount) → {Array.<Array.<string>>}
CAP-002 Phase 2 — Partition test IDs into shardCount contiguous slices
using the shared computeShardSizes algorithm. The route layer
calls this at enqueue time to pre-compute each BullMQ shard job's
testIds payload — the coordinator is the single source of truth for
the split; workers never re-derive the partition (avoids drift if a
future test sort changes the approved-test order between enqueue and
worker pickup).
Implementation note: slices the input array directly rather than allocating
a tagged-copy. computeShardSizes is the single source of truth shared
with partitionTestsIntoShards, so the algorithm cannot drift between
the two callers.
Parameters:
| Name | Type | Description |
|---|---|---|
testIds |
Array.<string> | Approved test IDs in dispatch order. |
shardCount |
number | 1..MAX_WORKERS (caller is responsible for clamp). |
- Source:
Returns:
slices[shardIndex] is the array of test IDs for
that shard. Empty shards (possible when shardCount > testIds.length)
yield [] at their slot.
- Type
- Array.<Array.<string>>
(static) partitionTestsIntoShards(tests, shardCount) → {Object}
CAP-002 — Partition tests into shardCount contiguous slices using the
shared computeShardSizes algorithm. Tags each test with
_shardIndex in place (callers rely on this to attribute results to the
correct shard at completion time) and returns the sizes[] array so the
runner can detect "last test in shard S has finished" without re-deriving
the partition. Pure function — no DB or side effects — so the partition
contract can be exercised in isolation by backend/tests/run-sharding.test.js.
Parameters:
| Name | Type | Description |
|---|---|---|
tests |
Array.<Object> | Tests in dispatch order (post-smoke-pin). |
shardCount |
number | 1..MAX_WORKERS (caller is responsible for clamp). |
- Source:
Returns:
per-shard test counts.
- Type
- Object
(static) runTests(project, tests, run, optionsopt) → {Promise.<void>}
Execute an array of approved tests against a project using Playwright. Launches Chromium, runs each test with self-healing (optionally in parallel), collects results, saves traces/videos, runs the AI feedback loop, and finalises the run.
Parameters:
| Name | Type | Attributes | Description | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
project |
Object | The project |
|||||||||||||||||||||||||||||||||||||
tests |
Array.<Object> | Array of test objects to execute. |
|||||||||||||||||||||||||||||||||||||
run |
Object | The run record (mutated in place). |
|||||||||||||||||||||||||||||||||||||
options |
Object |
<optional> |
Properties
|
- Source:
Returns:
- Type
- Promise.<void>
(static) shardTraceArtifactPath(runId, shardIndex) → {string}
CAP-002 Phase 2 (Prerequisite #2) — Compute the public artifact URL for a
shard's trace zip. The path mirrors the on-disk layout in TRACES_DIR so
signArtifactUrl and the trace-viewer static-file mount can resolve nested
paths without special-casing. shardIndex == null (legacy / single-shard
runs) returns the flat ${runId}.zip URL — zero regression for every
existing consumer of run.tracePath. Pure: no I/O, no DB, exported so
backend/tests/run-sharding.test.js can assert the contract directly.
Parameters:
| Name | Type | Description |
|---|---|---|
runId |
string | |
shardIndex |
number | null | 0-based shard index, or null for legacy single-path runs. |
- Source:
Returns:
/artifacts/traces/<runId>.zip or /artifacts/traces/<runId>/shard-<idx>.zip
- Type
- string