<!-- Markdown mirror of https://runners.vitko.inc/docs/split-tests/ -->

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/
```

| Tool | Example run | Shared out by |
| --- | --- | --- |
| pytest | `pytest -q tests/` | test |
| cargo nextest | `cargo nextest run --workspace --no-fail-fast` | test |
| Go | `go test ./...` | package |
| Jest | `npx jest --ci` | file |
| Vitest | `npx vitest run` | file |

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

| Input | Default | What it does |
| --- | --- | --- |
| `run` | required | Your test command. |
| `prepare` |  | A command to run first, in this job, with network. |
| `parts` | `auto` | How many parts. |
| `tool` | `auto` | `pytest`, `nextest`, `jest`, `vitest`, `go` or `command`. |
| `env` |  | Extra environment variables your tests need, by name. |
| `junit` | `vitko-split-tests.xml` | JUnit report path; empty to skip. |
| `working-directory` | `.` | Where to run the tests. |
| `timings-file` |  | Optional 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.
