How to Run Playwright Tests in Parallel

Playwright parallel testing runs multiple tests at once using workers. Learn how to configure workers, parallel mode, sharding, and CI execution.

Written by Sourabh G Sourabh G
Reviewed by Siddhi Rao Siddhi Rao
Last updated: 13 August 2026 19 min read

Key Takeaways

  • Playwright runs test files across worker processes, while tests within each file remain sequential unless parallel mode or fullyParallel is enabled.
  • Keep tests independent, assign unique data, and set worker counts according to CPU, memory, database connections, and API limits.
  • Use Playwright Test for native parallel features, then add sharding when a single CI runner can no longer reduce execution time.

Large Playwright test suites take longer to complete when independent tests run sequentially. Parallel execution reduces this time by distributing test files across multiple worker processes.

In a 2026 case study, PayPay Card reduced its web test execution time from about 30 minutes to 11 minutes after migrating to Playwright and optimizing parallel execution.

However, effective parallel testing requires more than increasing the worker count. Tests need isolated data, enough CPU and memory, and controlled access to shared accounts, databases, files, and external services.

The following sections explain how Playwright runs parallel tests and how to configure them for local and CI environments.

Why Parallel Testing is Essential?

A Playwright suite becomes slower as more test files, browser projects, and environment checks are added. When these tests run through a limited number of workers, each file waits for an available worker before execution begins. The delay comes from the total queue, not only from slow individual tests.

Parallel testing reduces this queue by allowing multiple workers to process independent test files at the same time.

For example, if 200 test files take an average of one minute each, running them through one worker would require about 200 minutes. Ten workers could reduce the theoretical runtime to about 20 minutes. The actual runtime will be higher because test duration varies and setup, retries, reporting, and CI overhead also take time.

This matters in Playwright for several reasons:

  • Regression suites grow quickly: Adding coverage for new workflows increases the number of files waiting for execution. Parallel workers prevent runtime from growing at the same rate as the test count.
  • Browser projects multiply executions: A test configured for Chromium, Firefox, and WebKit may run three times. Without enough parallel capacity, cross-browser validation can become the longest part of the CI pipeline.
  • Slow feedback blocks development: Developers cannot confidently merge, deploy, or investigate failures until the suite finishes. Reducing total runtime shortens the period between a code change and a usable test result.
  • CI runners often have unused capacity: A runner with several CPU cores can handle more than one Playwright worker. Sequential execution leaves much of that capacity unused.
  • Sharding becomes easier to justify: Once one CI machine reaches its CPU or memory limit, the suite can be divided across multiple machines while Playwright continues running tests in parallel within each shard.

How Playwright Handles Parallel Execution

Playwright supports parallel test execution out of the box, allowing tests to run concurrently, reducing overall execution time. It achieves this through worker processes and isolated browser contexts, ensuring tests do not interfere with each other.

Key Features of Playwright Parallel Execution

1. Parallel Execution with Workers

Playwright runs tests in separate worker processes, enabling parallel execution without waiting for each test to finish. This reduces test runtime significantly.

2. Test Parallelization

Use test.parallel() and test.describe.parallel() to group and run tests concurrently.

test.describe.parallel(‘Parallel tests’, () => {
test(‘Test 1’, async ({ page }) => { … });
test(‘Test 2’, async ({ page }) => { … });
});

3. Isolated Browser Contexts

Each parallel test runs in its own isolated browser context, ensuring no shared state or interference between tests.

4. Configuring Parallelism

Control parallel execution by setting the number of workers in the playwright.config.ts file.

module.exports = {
workers: 4, // Run tests in 4 parallel workers
};

Setting Up Parallel Execution in Playwright

Setting up parallel execution in Playwright is straightforward and involves configuring a few settings. By running tests concurrently, you can significantly reduce overall test execution time and optimize your CI pipeline.

1. Install Playwright and Set Up Your Test Environment

Ensure Playwright is installed and your test environment is set up:

npm install playwright

Create a basic Playwright test suite to get started, ensuring tests are isolated and independent for parallel execution.

2. Configure Parallel Execution in playwright.config.ts

In the playwright.config.ts file, you can configure the number of workers (parallel test processes) to run.

Example configuration:

module.exports = {
workers: 4, // Run 4 tests in parallel
};

This configures Playwright to run tests in 4 parallel worker processes. You can adjust this number based on the available resources (CPU, memory).

3. Use test.parallel() and test.describe.parallel() for Grouping Tests

Playwright provides test.parallel() and test.describe.parallel() to execute tests concurrently. These methods allow you to group tests or individual test cases to run in parallel.

Example of grouping tests:

test.describe.parallel(‘Parallel tests’, () => {
test(‘Test 1’, async ({ page }) => {
await page.goto(‘https://example.com’);
await expect(page).toHaveTitle(‘Example Domain’);
});

test(‘Test 2’, async ({ page }) => {
await page.goto(‘https://example.com’);
await expect(page).toHaveTitle(‘Example Domain’);
});
});

4. Consider Browser Contexts for Test Isolation

Each test should run in its own browser context to avoid shared state. Playwright ensures this by creating a separate browser context for each parallel test, providing an isolated environment.

Example of creating independent contexts:

const context1 = await browser.newContext();
const context2 = await browser.newContext();

5. Run Tests in Parallel on CI/CD

In your CI/CD pipeline, ensure that your infrastructure supports parallel test execution. Most CI tools like Jenkins, GitHub Actions, and GitLab support parallel execution by default.

Make sure to configure your CI settings to allocate enough resources for the specified number of parallel tests.

Running Playwright Tests in Parallel with Test Runners

The test runner decides how test files are assigned to workers, how setup and teardown are handled, and whether failed tests can be retried without affecting other tests. Playwright Test provides these capabilities natively. Jest and Mocha can also run Playwright scripts in parallel, but you must manage more of the browser lifecycle and test isolation yourself.

1. Using Playwright Test

Playwright Test is the recommended runner for Playwright end-to-end tests. By default, it runs test files in parallel across worker processes. Tests inside one file run sequentially unless you enable parallel mode for that file or for the entire configuration.

Set the worker limit in playwright.config.ts:

import { defineConfig } from '@playwright/test';



export default defineConfig({

  workers: process.env.CI ? 2 : undefined,

});

This configuration limits CI runs to two workers while allowing Playwright to select the local worker count. You can also pass a fixed number or a percentage through the command line:

npx playwright test --workers=4

npx playwright test --workers=50%

Output –

snippet 01 playwright test worker config parallel output snap

Playwright currently uses 50% of the available logical CPU cores by default. The correct worker count depends on the CPU, memory, browser projects, database capacity, and external services used by the tests.

To run the tests inside one describe block concurrently, use:

import { test, expect } from '@playwright/test';



test.describe.configure({ mode: 'parallel' });



test('checks the home page', async ({ page }) => {

  await page.goto('/');

  await expect(page).toHaveTitle(/Home/);

});



test('checks the pricing page', async ({ page }) => {

  await page.goto('/pricing');

  await expect(page.getByRole('heading')).toContainText('Pricing');

});

Output –

snippet 02 describe configure parallel mode within file output snap

Each test receives its own browser context and page fixture. Do not store data from one test in variables that another parallel test expects to use.

You can enable parallel execution for every test in the project with fullyParallel: true. Use this only when all tests have independent setup, data, and cleanup.

export default defineConfig({

  fullyParallel: true,

  workers: 4,

});

For larger CI suites, use sharding to divide the test suite across separate machines or jobs:

npx playwright test --shard=1/3

npx playwright test --shard=2/3

npx playwright test --shard=3/3

Output –

snippet 03 fullyparallel sharding ci suite output snap

Workers provide concurrency within one machine. Shards distribute the suite across multiple machines. When fullyParallel is enabled, Playwright can distribute individual tests across shards instead of assigning complete files, which can produce a more even workload.

2. Using Jest

Jest runs test files through a worker pool and lets you control the pool with maxWorkers.

// jest.config.js

module.exports = {

  maxWorkers: '50%',

};

Output –

snippet 04 jest maxworkers parallel config output snap

Jest is suitable when Playwright browser checks form part of an existing Jest suite. However, using the Playwright library with Jest does not provide the fixtures, projects, tracing, retries, and browser management built into Playwright Test.

Avoid creating one shared page for several parallel tests. Create an isolated browser context for each test and close it during teardown. Otherwise, cookies, local storage, navigation, and page state can leak between tests.

Use –runInBand only when debugging concurrency problems because it disables the worker pool and runs tests serially.

3. Using Mocha

Modern Mocha versions support parallel execution natively through the –parallel option. The separate mocha-parallel-tests package is not required.

npx mocha "tests/**/*.spec.js" --parallel --jobs 4

Output –

snippet 05 mocha parallel jobs native execution output snap

Mocha places test files in a queue and assigns them to worker processes. Tests inside each file still run sequentially. A worker may process several files, so those files can share process-level state such as global variables and the Node.js module cache.

Create browser contexts and test data within each file or test rather than relying on mutable global state. Also avoid setup logic that depends on test files running in a specific order. Mocha does not guarantee file order or which worker will execute a file in parallel mode.

Choosing a Test Runner

Use Playwright Test for new Playwright end-to-end suites because parallel workers, browser fixtures, retries, projects, traces, and sharding work together within the same runner.

Use Jest when Playwright checks must remain part of an established Jest codebase and your team can manage browser contexts and cleanup explicitly.

Use Mocha when an existing Mocha suite already contains browser tests. Enable its native parallel mode and review all hooks, global variables, and shared browser instances before increasing the worker count.

Managing Test Dependencies in Parallel Execution

When running tests in parallel, managing dependencies becomes critical to ensure that tests do not interfere with one another and produce consistent, reliable results.

Since parallel tests run simultaneously in separate worker processes or browser contexts, any shared state or data can lead to race conditions or flaky tests.

Here’s how to effectively manage dependencies when executing tests in parallel:

1. Ensure Test Independence

Each test should be independent and not rely on the state of others. Avoid sharing global variables or mutable state between tests. If tests require data (like login credentials or session tokens), make sure each test has its own set to avoid conflicts.

Example:

test(‘Test 1 – Login’, async ({ page }) => {
await page.goto(‘https://example.com/login’);
await page.fill(‘input[name=”username”]’, ‘user1’);
await page.fill(‘input[name=”password”]’, ‘password1’);
await page.click(‘button[type=”submit”]’);
expect(await page.url()).toBe(‘https://example.com/dashboard’);
});

Output –

snippet 06 test independence no shared state parallel output snap

2. Use Browser Contexts for Isolation

Playwright allows tests to run in isolated browser contexts using browser.newContext(). This ensures no shared state (like cookies, local storage, or session data) between tests.

Example:

const context1 = await browser.newContext(); // Independent context
const page1 = await context1.newPage();
await page1.goto(‘https://example.com’);

const context2 = await browser.newContext(); // Another independent context
const page2 = await context2.newPage();
await page2.goto(‘https://example.com’);

Output –

snippet 07 browser context isolation no shared cookies output snap

3. Use Test Hooks for Setup and Teardown

Use beforeAll() and afterAll() hooks for setup and teardown tasks that should only run once for all tests in a suite. This prevents redundancy and improves performance.

Example:

let browser;

beforeAll(async () => {
browser = await chromium.launch();
});

afterAll(async () => {
await browser.close();
});

test(‘Test 1’, async () => { /* Test code */ });
test(‘Test 2’, async () => { /* Test code */ });

Use beforeEach() and afterEach() hooks to initialize things like page navigation or element state before and after each test. This ensures that each test starts with a clean slate.

Output –

snippet 08 test hooks beforeall afterall setup teardown output snap

4. Handle Shared Resources Carefully

If tests interact with shared databases or APIs, use test-specific data or mock responses to prevent data conflicts. Mocking external services can be useful for isolating tests and avoiding race conditions.

Example using a mock:

test(‘Test API call’, async ({ page }) => {
await page.route(‘**/api/**’, route => route.fulfill({ status: 200, body: ‘{“key”: “value”}’ }));
await page.goto(‘https://example.com’);
// Test assertions here
});

Output –

snippet 09 shared resources mock api route fulfill output snap

Best Practices for Running Playwright Tests in Parallel

Parallel execution changes how tests interact with accounts, databases, files, APIs, and CI resources. A suite that passes with one worker may fail when several workers modify the same data or compete for limited resources.

The following practices focus on keeping parallel runs both fast and repeatable.

1. Make Every Test Independently Executable

A parallel test must not depend on another test to create data, complete a workflow, or leave the application in a specific state. Playwright does not guarantee the order in which test files run. Different workers may also execute dependent files at the same time.

Each test should create the state it needs and remove or reset that state after execution. For example, a checkout test should not depend on a separate login test or product-creation test running first. Use fixtures or API calls to prepare those conditions directly.

Avoid these patterns:

  • One test creates a user and another test edits that user.
  • Several tests update the same account settings.
  • Tests rely on a shared cart, order, or database record.
  • A test expects files from another test to remain available.
  • Test files depend on alphabetical execution order.

Serial mode can preserve dependencies, but it reduces parallel capacity and causes related tests to be retried as a group. Playwright recommends isolated tests instead of serial groups.

2. Assign Unique Data to Each Worker

Separate browser contexts isolate cookies, local storage, and session data. They do not isolate application data stored on the server.

Two tests can still conflict if they log in with the same account, edit the same database row, or upload a file with the same name. Playwright provides parallelIndex so you can assign stable data to each parallel worker. The value remains the same when a failed worker is replaced.

import { test as base } from '@playwright/test';



type WorkerFixtures = {

  workerUser: {

    email: string;

    password: string;

  };

};



export const test = base.extend<{}, WorkerFixtures>({

  workerUser: [

    async ({}, use, workerInfo) => {

      const index = workerInfo.parallelIndex;



      await use({

        email: `test-user-${index}@example.com`,

        password: process.env.TEST_PASSWORD!,

      });

    },

    { scope: 'worker' },

  ],

});

Output –

snippet 10 unique data per worker parallelindex fixture output snap

Each worker receives a different account. Tests executed by that worker can reuse the account without colliding with tests assigned to other workers.

Use the same approach for database schemas, tenant IDs, storage buckets, queues, and other shared resources. Generate data per test instead when concurrent tests running inside the same worker may still modify it.

3. Set Workers According to the Entire Test Environment

A higher worker count creates more browser sessions, network traffic, database connections, and application requests. CPU cores are only one part of the limit.

Increase workers gradually while tracking:

  • Total suite duration
  • CPU and memory consumption
  • Database connection usage
  • API throttling and HTTP 429 responses
  • Application response times
  • Retry and flaky-test rates
  • CI runner crashes or browser-launch failures

A worker increase has stopped adding value when suite duration no longer decreases or intermittent failures begin to rise.

Local machines and CI runners may need different limits. You can set a lower worker count in CI while retaining Playwright’s local default:

import { defineConfig } from '@playwright/test';




export default defineConfig({

  workers: process.env.CI ? 4 : undefined,

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

  maxFailures: process.env.CI ? 10 : undefined,

  use: {

    trace: 'on-first-retry',

  },

});

maxFailures stops a severely broken run after it reaches the configured failure count. This prevents the remaining workers from consuming CI resources when an application deployment or test environment is already unusable.

4. Enable fullyParallel Only After Removing Shared State

By default, Playwright runs test files in parallel while tests inside each file run sequentially. Setting fullyParallel: true allows individual tests within files to run in separate workers.

This can reduce runtime when a few large files contain many independent tests. It can also expose hidden dependencies involving module-level variables, shared beforeAll data, or reused accounts.

Before enabling it across the suite, check that:

  • Tests do not share mutable variables declared outside test functions.
  • Each test can execute its own setup and cleanup.
  • beforeAll does not create state that several tests modify.
  • Accounts and server-side records are partitioned.
  • Tests do not write to the same output path.

Start with one independent test group:

import { test } from '@playwright/test';




test.describe('independent search tests', () => {

  test.describe.configure({ mode: 'parallel' });




  test('finds a product by name', async ({ page }) => {

    // Test steps

  });




  test('filters products by price', async ({ page }) => {

    // Test steps

  });

});

Output –

Snippet 11 Playwright Independent Search Tests

Expand parallel mode only after the group runs reliably under repeated CI execution.

5. Make Worker-Scoped Setup Safe to Repeat

Playwright closes the entire worker process after a test failure and starts a fresh worker for the remaining tests. Hooks and worker-scoped fixtures may therefore execute again during the same test run.

Setup code must handle repeated execution. For example, account creation should check whether the account already exists or generate a unique identity. Cleanup should tolerate resources that were already removed or were never fully created.

Do not assume that a value stored only in worker memory will survive a failure. Save required identifiers in a place the replacement worker can recover, or recreate the resource when the fixture starts again.

This behavior is especially important for:

  • Temporary database schemas
  • Seeded test users
  • Mock servers
  • Authentication state files
  • Queues and background jobs
  • Environment-level feature flags

6. Use Sharding After a Single Runner Reaches Its Limit

Workers provide parallelism within one machine. Sharding divides the suite across multiple CI jobs or machines.

Add shards when increasing workers on one runner no longer reduces execution time because CPU, memory, or browser capacity has been reached:

npx playwright test --shard=1/4

npx playwright test --shard=2/4

npx playwright test --shard=3/4

npx playwright test --shard=4/4

Without fullyParallel, Playwright distributes complete test files between shards. A shard containing several slow files may finish later than the others. With fullyParallel, Playwright can distribute individual tests, which usually produces a more balanced workload.

Check each shard’s duration rather than only the combined suite duration. One slow shard determines when the pipeline finishes.

Use the blob reporter to preserve results from each shard and merge them into one report after all jobs complete. Blob reports are designed for merging distributed Playwright runs.

Conclusion

Parallel execution can reduce Playwright test time when tests are independent and the worker count matches available resources. Configure workers based on CPU, memory, browser load, database capacity, and API limits rather than using the highest possible value.

Keep test data isolated, avoid shared mutable state, and use sharding when one CI runner reaches its limit. Track runtime, retries, flaky failures, and resource usage after each change so faster execution does not come at the cost of test reliability.

https://blog.paypay.ne.jp/en/modernising-e2e-testing-by-migrating-from-cypress-to-playwright/

Version History

  1. Aug 05, 2026 Current Version

    Reworked selected sections to remove AI-style phrasing, correct outdated Playwright guidance, and add practical examples for parallel test execution.

    Siddhi Rao
    Reviewed by Siddhi Rao Lead Customer Engineer
Tags
Automation Testing Real Device Cloud Website Testing
Sourabh G
Sourabh G

Senior Software Engineer

Sourabh Gome is a Senior Software Engineer with 5+ years of experience building scalable, high-performance software systems. He specializes in test automation, quality engineering, and developer productivity, helping teams deliver reliable applications with greater speed and confidence.

Slow Tests Delaying Every Build?
Run tests in parallel across real browsers, devices, and OSs.