There is nothing more frustrating than pushing a critical hotfix only to realize your CI pipeline is stuck in a 25-minute test run. I’ve been there. For a long time, I thought the only solution was to write fewer tests or buy a more expensive runner. But the real answer is sharding tests in GitHub Actions.

Sharding is the process of splitting your test suite into smaller ‘shards’ and running them simultaneously across multiple virtual machines. Instead of one runner doing 1,000 tests in 20 minutes, you have five runners doing 200 tests each in roughly 4 minutes. In this deep dive, I’ll show you how to move from a monolithic test step to a high-performance parallelized architecture.

The Challenge: The Linear Bottleneck

Most developers start with a simple npm test or pytest command in their YAML file. This works great for a few dozen tests. However, as your project grows, your CI time grows linearly. This creates a massive productivity bottleneck. When developers have to wait 20 minutes for a PR check, they switch contexts, lose focus, and slow down the entire team’s velocity.

I remember a project where our integration tests grew so large that we stopped running them on every commit. This led to a surge in regressions. If you’ve already tried to reduce CI/CD build time for tests through caching and you’re still hitting a wall, sharding is your next logical step.

Solution Overview: How Sharding Actually Works

At its core, sharding relies on the GitHub Actions Matrix strategy. Instead of defining a single job, you define a matrix of jobs. Each job is assigned an index (e.g., shard 1 of 4, shard 2 of 4), and your test runner is told to only execute the tests corresponding to that index.

Most modern test frameworks (Playwright, Jest, Pytest) have built-in support for this. If yours doesn’t, you can implement a simple script to divide your test files into buckets based on the matrix index.

Techniques for Implementation

1. Using Playwright (Built-in Sharding)

Playwright makes this incredibly easy. It provides a --shard=x/y flag that handles the logic for you. Here is how I set it up in my production pipelines:


jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4]
        shardTotal: [4]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}

2. Using Jest (Custom Logic)

Jest doesn’t have a native --shard flag like Playwright, but you can achieve the same result using jest-shard or a simple bash script. In my experience, using a dedicated package is cleaner:


- run: npx jest-shard --shard=${{ matrix.shardIndex }} --total=${{ matrix.shardTotal }}

3. Generic Bash Sharding (The “Manual” Way)

If you’re using a tool that doesn’t support sharding, you can split your test directory using a bash script. As shown in the image below, the goal is to distribute files evenly across your runners.

I typically use a script that lists all test files, calculates the modulus of the file index against the total shards, and executes only the matching files.

Comparison of sequential vs sharded test execution flow
Comparison of sequential vs sharded test execution flow

Looking to automate more? Check out my GitHub Actions test automation tutorial to master the basics before diving into advanced scaling.

Performance Benchmarks: Before vs. After

I ran a benchmark on a suite of 400 E2E tests. The results were staggering. By increasing the shard count from 1 to 4, I didn’t just get a linear speedup; I significantly reduced the probability of “flaky” timeouts caused by resource contention on a single VM.

Configuration Total Execution Time Cost (GitHub Minutes) Feedback Loop
Single Runner (No Sharding) 22m 14s 22m Slow
4 Shards (Parallel) 6m 45s 27m (Combined) Fast
10 Shards (Parallel) 3m 12s 35m (Combined) Instant

Notice the tradeoff: as you increase shards, your total “billed minutes” increase slightly due to the overhead of booting multiple VMs and installing dependencies on each. However, the wall-clock time (the time you actually wait) drops drastically.

Pitfalls and Common Mistakes

Sharding isn’t magic; it introduces new complexities that I’ve spent many late nights debugging:

Final Verdict

Sharding tests in GitHub Actions is the single most effective way to scale a CI pipeline. While it increases the consumption of GitHub Action minutes, the gain in developer productivity far outweighs the cost. If your build takes longer than 10 minutes, stop optimizing your code and start sharding your infrastructure.