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.
Read More: Chrome vs Chromium: Core Differences
- 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.
Read More: How to uninstall Playwright
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 –
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 –
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 –
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 –
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.
Read More: Understanding Jest Parameterized Tests
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 –
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.
Read More: Jest vs Playwright
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 –
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 –
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 –
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 –
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 –
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 –
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.
Read More: Playwright with .Net: A 2026 guide
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.










