1. Load your timings
Works with pytest (--junitxml), Jest (jest-junit), Go (gotestsum --junitfile), Maven/Gradle and RSpec (rspec_junit_formatter).
Setup seconds is the fixed cost every shard job pays before its first test (checkout, install, cache restore). It is why adding shards stops paying off. “One report per current shard” needs several reports, one from each shard you run today.
2. What changes
Read this first
Shard by shard
| Shard | Now | Balanced | Units |
|---|
How many shards are worth it
| N | Slowest shard | Wall-clock | Billed minutes | Imbalance |
|---|
Billed minutes = N × (slowest shard + setup), the conservative model. Shards that finish early are billed for less in practice, and your CI provider may round each job up to a whole minute.
Paste into your pipeline
Run it in CI
Same engine, zero dependencies, one file. Fails the job when your current split drifts away from an even one, so it does not quietly decay as tests are added:
node cli.mjs split junit.xml --shards 4 --setup-seconds 60 node cli.mjs split shard-*.xml --current-from-reports # each file = one shard you run today node cli.mjs split junit.xml --shards 6 --group test --matrix # GitHub Actions matrix JSON node cli.mjs split junit.xml --shards 4 --max-imbalance-pct 20 # exit 1 if unbalanced
Get cli.mjs and engine.mjs — Node 18+, nothing to install, no network call, ever. Exit codes: 0 ok, 1 over the imbalance limit, 2 unreadable file or bad option.
Common questions
How do I split tests evenly across CI shards?
Export per-test timings as JUnit XML from a recent run, paste them here, and choose the number of shards. The page treats it as a multiway number-partitioning problem: it places the longest files first on the least-loaded shard, then improves the result by moving and swapping files between shards, and for small suites it searches for the exact optimum. It prints the files for each shard as a runnable command.
Which test reports does it read?
JUnit XML from pytest (--junitxml), Jest through jest-junit, Go through gotestsum or go-junit-report, Maven Surefire and Gradle, and RSpec through rspec_junit_formatter. The format is detected automatically and can be forced.
Why is my slowest shard still slow after balancing?
One test or file may be longer than an even share of the whole suite. It cannot be split across shards, so it sets the floor on wall-clock time no matter how many shards you add. The page flags any such unit and, if you grouped by file, suggests grouping by test instead.
How many shards should I use?
Enter your fixed setup time per shard (checkout, dependency install) and an optional wall-clock target. The page sweeps shard counts, shows slowest shard, wall-clock and billed minutes for each, and suggests the cheapest count that meets the target. Setup time is why more shards stop paying off.
Is my test report uploaded anywhere?
No. The page reads what you paste or load in your browser and makes no network request. The CLI reads local files and makes no network call. There is no model and no randomness, so the same input always gives the same split.
Are the predicted times a guarantee?
No. Test timings vary from run to run and with the machine, so real shards will differ from the prediction. Treat the split as a good starting point and re-run the tool on fresh timings every few weeks.
New to this? Read how to split tests across CI shards. Weighing it against a paid splitting service? See the comparison page. Tests that flip between runs wreck any split, so check for them with the Flaky Test Finder.