Runners

Test parallelization Private beta

One workflow step runs your test suite as several parts at the same time. The results come back to the original job: one log, one list of failures, one pass or fail.

Add it to your workflow

Replace your test step with the action and put your test command in run:

- uses: vitko-inc/split-tests@v1
  with:
    run: pytest -q tests/
ToolExample runShared out by
pytestpytest -q tests/test
cargo nextestcargo nextest run --workspace --no-fail-fasttest
Gogo test ./...package
Jestnpx jest --cifile
Vitestnpx vitest runfile

Run the test tool itself in run (for example uv run pytest, not tox or make test), as a single command. A shell script with &&, pipes or several lines runs in one part, and the log says so.

Any other tool: set tool: command. Your command runs in every part with VITKO_PART (1, 2, …) and VITKO_PARTS, and chooses its own share.

- uses: vitko-inc/split-tests@v1
  with:
    tool: command
    run: ./scripts/run-tests.sh --part "$VITKO_PART" --of "$VITKO_PARTS"

Install and build first

The parts start when your tests start and can’t reach the network. Install and build everything before the split: in an earlier step, or in prepare, which runs first in this job, with network.

- uses: vitko-inc/split-tests@v1
  with:
    prepare: uv sync --locked
    run: uv run --locked pytest -q

A command that installs as it starts (such as tox) does that in every part, and fails there.

Tests that need the network

Mark them, leave them out of the split, and run them in a separate step:

- uses: vitko-inc/split-tests@v1
  with:
    run: pytest -q -m "not network"
- run: pytest -q -m network

With Go, use -skip in the split and -run in the separate step. With Jest or Vitest, use a separate config or a file-name pattern.

How many parts

parts: auto (the default) picks a number that suits your plan and the size of your suite. A suite estimated at under a minute runs in one part, because splitting it would cost more than it saves; the log says so. Set parts to a number to split anyway. After the first run, Vitko remembers how long each test took and balances the parts so they finish close together.

Inputs and outputs

InputDefaultWhat it does
runrequiredYour test command.
prepareA command to run first, in this job, with network.
partsautoHow many parts.
toolautopytest, nextest, jest, vitest, go or command.
envExtra environment variables your tests need, by name.
junitvitko-split-tests.xmlJUnit report path; empty to skip.
working-directory.Where to run the tests.
timings-fileOptional test durations, if you keep them.

Outputs: junit, parts and failed.

The environment your tests see

The parts get the usual build and toolchain variables (PATH, HOME, CI, CARGO_*, PYTHONPATH, NODE_OPTIONS, GO* and similar), plus the names you list in env. Names that look like secrets (containing TOKEN, SECRET, PASSWORD, PRIVATE, CREDENTIAL, or ending in _KEY) are never passed. Start services such as a database before this step; each part gets its own copy, already running.

Results

  • Each part’s output in a section you can expand, and every failed test in one place.
  • Failed tests as annotations (up to 10), a table of parts in the run summary, and a JUnit report.
  • The step fails if any test fails, a part stops early, or a test doesn’t report a result. It never passes on partial results.

Limits

  • Up to your plan’s limit of parts at once.
  • One split-tests step at a time in a job, and up to four in one job.
  • Tests that depend on running in a fixed order, or on each other, can fail when split, as with any parallel test runner.

Billing

You pay for the time the parts run. The original job’s wait while its parts run isn’t charged. Short suites gain little, which is why auto doesn’t split them.

On GitHub-hosted runners or your own machines, the step runs your command once with all your tests, like a plain run: step. Keep it in workflows that run in both places. Use @v1, or pin a release such as @v1.0.4.