Skip to main content

Test parallelism Playwright: shard strategies

Articolo della serie Playwright Component Testing Avanzato. Pubblicazione prevista 24 November 2027.

What do you think about this article?

Test parallelism Playwright: shard strategies
Playwright ComponentsPhotos:Ruben BoekelooPexels

At a Glance

Articolo della serie Playwright Component Testing Avanzato. Pubblicazione prevista 24 November 2027.

What is Test Parallelism in Playwright

Test parallelism is the ability to execute multiple tests simultaneously on isolated workers, drastically reducing the total execution time of your suite. Playwright supports parallelism through shard strategies, allowing you to distribute your 1000+ tests across multiple processes and machines intelligently and predictably.

This article guides you through the shard strategies supported by Playwright, with practical configurations, trade-offs, and best practices to get the most out of your test suite.

Why Parallelism Matters

Without parallelism, a suite of 500 tests at 100ms each requires ~50 seconds sequentially. With parallelism (10 workers), the same time drops to ~5 seconds: 10x faster.

💡 Benefits of Parallelism

  • Faster feedback: developers get test results in seconds, not minutes
  • Efficient CI/CD: pipelines complete quickly, infrastructure costs reduced
  • Scalability: adding tests doesn't linearly increase execution time
  • Isolation: each worker has an isolated browser context, no cross-test contamination

Playwright Shard Strategies

Playwright offers three sharding strategies, each with different trade-offs between simplicity, uniform distribution, and fine-grained control. Let's explore them in detail.

1. Shard per Test File (Default)

The file-based sharding strategy distributes entire test files across different workers. Each file stays assigned to a single worker, guaranteeing cohesion.

Configuration for 4 shards on 4 workers
{
  "testDir": "./tests",
  "fullyParallel": true,
  "workers": 4,
  "shard": [1, 4]
}

In your CI/CD (GitHub Actions, GitLab CI), launch 4 processes, each with a different shard:

GitHub Actions - 4 Shards in Parallel
jobs:
  test:
    strategy:
      matrix:
        shardIndex: [1, 2, 3, 4]
        shardTotal: [4]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}

Pros: Simple, file cohesion maintained, perfect for <10 test files.
Cons: Unbalanced load if some files have 500 tests and others have 5.

2. Shard per Test (Fine-Grained)

Playwright distributes the load at the single test level, not at the file level. Each test is assigned independently to the shard where it runs, guaranteeing near-uniform distribution.

Fine-grained sharding: execution
# Run only tests in shard 2 of 4
npx playwright test --shard=2/4

Internally, Playwright collects all tests (e.g., 800 tests), assigns each test to a shard using deterministic hashing, and each process executes only its shard's tests.

🔍 Technical Details

  • Algorithm: hash(test.file + test.title) % shardTotal === shardIndex - 1
  • Result: uniform distribution regardless of file size
  • Determinism: same tests always go to the same shard (build repeatability)

3. Shard per Project (Multi-Project)

If your suite uses Playwright projects (e.g., Chrome, Firefox, Safari browsers), you can schedule each project on different workers using the --grep flag combined with sharding.

playwright.config.ts - 3 Projects
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  workers: 4,
  projects: [
    { name: 'chromium', use: { browserName: 'chromium' } },
    { name: 'firefox', use: { browserName: 'firefox' } },
    { name: 'webkit', use: { browserName: 'webkit' } },
  ],
});

In CI, you can launch 3 containers: one for Chrome, one for Firefox, one for Safari. Each container executes the full suite against a different browser, in parallel.

Managing State Sharing Between Shards

When using parallelism, you can't share state between tests (database, cache) without risking race conditions. Here's the correct pattern:

❌ WRONG: Shared state between shards
// test.ts
let sharedCounter = 0; // ❌ Each shard has a different copy

test('test 1', async ({ page }) => {
  sharedCounter++;
  // Shard 1 reads 1, Shard 2 reads 1 (not 2!)
});
✅ CORRECT: Isolated state per shard
// tests/setup.ts
import { test as base } from '@playwright/test';

type Fixtures = {
  shardData: Record;
};

export const test = base.extend({
  shardData: async ({ }, use) => {
    const shardId = process.env.SHARD_ID || '1';
    const data = { shardId, uniqueId: `user_${shardId}_${Date.now()}` };
    await use(data);
  },
});

// test.ts
import { test } from './setup';

test('test 1', async ({ page, shardData }) => {
  console.log(`Shard ${shardData.shardId}, user: ${shardData.uniqueId}`);
  // Each shard generates its own data, no conflicts
});

Monitoring and Troubleshooting

Distributing Load Uniformly

If some shards take 2 minutes and others 30 seconds, the bottleneck is the slowest shard. Analyze execution times:

Analyze Execution Time per Shard
# HTML report shows timing per test
npx playwright show-report

# Alternative: grep the logs
grep "passed\|failed" trace.log | wc -l
echo "Total test count per shard"

If imbalance is excessive, consider:

  • Tag slow tests: @slow, isolate them in a dedicated shard
  • Internal parallelism: increase workers: 8 on fast workers
  • Dynamic rebalancing: use tools like Buildkite with queue to assign tests on-demand

Best Practices

✅ Do's

  • Use fine-grained sharding for suites >100 tests
  • Isolate database/cache per shard (separate instances, different credentials)
  • Monitor shard time in CI, rebalance if outlier >30% average
  • Use fullyParallel: true to maximize parallelism
  • Retry only in CI with retries: 2, no flakiness

❌ Don'ts

  • Don't share state between shards (thread-unsafe database, cache conflicts)
  • Don't use a fixed global seed (Math.random() → unpredictable tests)
  • Don't assume test execution order (parallelism = non-deterministic order)

Hybrid Strategies: Combining Sharding and Workers

True power emerges when you combine horizontal sharding (across machines/containers) with vertical parallelism (workers on a single machine).

Hybrid Setup: CI + Local Workers
jobs:
  test:
    strategy:
      matrix:
        shardIndex: [1, 2, 3, 4]
        shardTotal: [4]
    runs-on: ubuntu-latest
    steps:
      - run: npm ci
      - run: npx playwright test \
          --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} \
          --workers=2
        # Each shard container has 2 local workers
        # Total: 4 shards × 2 workers = 8 parallel workers

Complete Configuration in playwright.config.ts

playwright.config.ts - Production-Ready Setup
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  workers: process.env.CI ? 1 : 4,
  timeout: 30 * 1000,
  expect: { timeout: 5000 },

  retries: process.env.CI ? 2 : 0,

  reporter: [
    ['html'],
    ['github'],
    ['junit', { outputFile: 'junit.xml' }],
  ],

  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
  ],

  webServer: {
    command: 'npm run dev',
    url: 'http://127.0.0.1:3000',
    reuseExistingServer: !process.env.CI,
  },
});

Conclusion

Test parallelism with shard strategies reduces feedback time from minutes to seconds, lowering CI costs and improving developer experience. Choose the right strategy (file-based for simplicity, fine-grained for uniform load, multi-project for cross-browser) and monitor shard load over time.

🎯 Key Points

  • Playwright parallelism reduces execution time by 10x+ with correct sharding
  • 3 shard strategies: file-based (simple), fine-grained (uniform), multi-project (cross-browser)
  • Isolate state per shard, no shared database or cache across shards
  • Monitor load distribution, rebalance if shard is an outlier

Frequently asked questions

What's the difference between file-based and fine-grained sharding?

File-based sharding distributes entire files across shards, fine-grained distributes individual tests. Fine-grained guarantees more uniform load, file-based is simpler for small suites.

Can I share a database between shards?

No. Each shard must have an isolated database instance or separate tables. Sharing a database causes race conditions and unpredictable flakiness.

How can I monitor if load distribution is uniform?

Use the HTML report (npx playwright show-report) to visualize timing per test. If one shard takes significantly longer, rebalance tags or increase workers on that shard.

What's the ideal number of shards and workers?

It depends on your suite size and CI budget. Rule of thumb: num_shard × workers ≤ 2×num_test. Start with 4 shards × 2 workers, monitor total time, scale from there.

Sources and further reading

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects