Test parallelization and sharding โ
As the E2E suite grows, pipeline time becomes the team's bottleneck: nobody wants to wait 40 minutes to find out whether their PR breaks something. The solution is to run in parallel โ and doing it well is a more interesting problem than it looks.
Two levels of parallelism โ
- Workers within one machine โ Playwright, for instance, runs N tests at a time on the same machine (
workersin the config). Cheap, but limited by the machine's CPU/RAM. - Sharding across machines โ splitting the suite across M CI machines running simultaneously (
--shard=1/4,--shard=2/4โฆ). It scales further, but every extra machine costs money.
Both combine: 4 shards ร 4 workers = 16 simultaneous tests.
The real problem: the split โ
Total pipeline time isn't the average of the shards, it's the slowest shard (makespan). A naive split (same number of tests per shard) produces unbalanced shards, because tests don't take the same time: one takes 8 seconds and another 4 minutes.
Naive split (by test count): Split by duration:
Shard 1: โโโโโโโโโโโโโโโโโโโโ 22 min Shard 1: โโโโโโโโโโโโ 13 min
Shard 2: โโโโโโโโ 9 min Shard 2: โโโโโโโโโโโโ 12.5 min
Shard 3: โโโโโโ 7 min Shard 3: โโโโโโโโโโโโ 13 min
โ total: 22 min โ total: 13 minSame hardware, same tests: 9 minutes less per run, just by splitting better.
How to split better โ
- Greedy by duration (LPT): sort the tests from longest to shortest and assign each one to the least loaded shard. Simple and very effective.
- Exact optimization: the optimal split is a bin packing / makespan minimization problem that can be modeled as mixed-integer linear programming (MILP). I did this in Python for my team's E2E suite: ~40% less validation time in CI/CD without adding infrastructure. That experience gave birth to my project CI Shard Advisor.
- Real-world constraints: tests that can't run in parallel (they share data), expensive setups worth grouping in the same shard, and durations that change โ the split must be recomputed periodically with fresh data from previous runs.
Prerequisite: independent tests โ
None of this works if tests depend on each other. To be able to parallelize:
- Every test creates (or gets injected) its own data โ never rely on what another test left behind.
- Isolated users/accounts per test or per worker โ two tests logged in with the same user at once = false reds.
- No implicit ordering: if
test Bonly passes aftertest A, those aren't two tests, they're one badly split in half. - Cleanup: ideally via API in the teardown, not through the UI.
The symptom
If your suite passes sequentially and fails in parallel, you don't have a parallelization problem: you have coupled tests, and parallelization has merely exposed them.
Checklist for speeding up an E2E pipeline โ
- Measure first: duration per test and per shard (Playwright/JUnit reporters give it to you for free).
- Eliminate the top offenders: can that 5-minute test move down to the API layer?
- Balance shards by duration, not by test count.
- Review the balance periodically: today's suite isn't the one from 3 months ago.
- Only then consider adding machines.