Hero Background

Next-Gen App & Browser Testing Cloud

Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

Next-Gen App & Browser Testing Cloud

Why are Playwright tests running slow or timing out?

Playwright tests run slow or time out mainly because of broad or unstable locators that force Playwright to retry until a timeout fires, manual waitForTimeout() hard waits instead of auto-waiting web-first assertions, default timeouts (30s per test, 5s for expect) that are too tight for slow network or CI, running headed instead of headless, no parallelism or sharding, and trace or video overhead on an underpowered runner. The fix is to tune the right timeout type in playwright.config.ts, use stable locators, remove hard waits, and parallelize, ideally on a cloud grid.

Why Playwright Tests Slow Down or Time Out

Most Playwright slowness traces back to a small set of root causes. Unlike a generic WebDriver suite, Playwright has its own auto-waiting model, layered timeouts, and built-in parallelism, so the causes (and fixes) are Playwright-specific:

  • Broad or multi-match locators like page.locator('div') force extra element-resolution work and retries until the timeout window closes.
  • Hard waits such as await page.waitForTimeout(5000) stack a fixed delay onto every run, even when the app is already ready.
  • Default timeouts too tight for slow CI or network: the test timeout is 30,000ms and the expect timeout is 5,000ms, while action and navigation timeouts fall back to the test timeout unless set.
  • Headed mode in CI (headless: false) adds rendering overhead that headless avoids.
  • Serial execution with workers: 1 or no sharding leaves CPU cores idle.
  • Trace, video, and screenshot set to 'on' for every test multiply I/O and CPU cost.
  • Re-logging in for every test instead of reusing storageState wastes seconds on each spec.
  • waitUntil: 'networkidle' on pages with long-polling or analytics that never go idle, which hangs until the navigation timeout.
  • An underpowered or throttled CI runner with fewer vCPUs and less RAM than your laptop.

Understand the Playwright Timeout Types

Playwright has several distinct timeouts, and raising the wrong one just hides the real problem. Know what each one covers before you change anything:

TimeoutDefaultWhere setWhat it covers
Test timeout30,000mstimeoutThe whole test body
Expect timeout5,000msexpect.timeoutWeb-first assertions
Action timeoutUnset (uses test timeout)use.actionTimeoutclick, fill, and similar actions
Navigation timeoutUnset (uses test timeout)use.navigationTimeoutgoto and navigations
Global timeoutUnsetglobalTimeoutThe entire run

Configure these explicitly in playwright.config.ts so each timeout matches your app's real behavior:

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

export default defineConfig({
  timeout: 60_000,            // per-test timeout (default 30_000)
  globalTimeout: 30 * 60_000, // whole run cap (CI safety net)
  expect: {
    timeout: 10_000,          // web-first assertions (default 5_000)
  },
  use: {
    actionTimeout: 15_000,        // click/fill/etc.
    navigationTimeout: 30_000,    // goto / navigations
  },
});

When a single test legitimately needs longer, override it locally instead of relaxing the global value:

test('slow report export', async ({ page }) => {
  test.setTimeout(120_000);       // or test.slow() to triple it
  // ...
});

await expect(page.getByRole('button', { name: 'Submit' }))
  .toBeVisible({ timeout: 10_000 });

await page.getByRole('link', { name: 'Reports' }).click({ timeout: 8_000 });

Raise the specific timeout for the slow step, not the blanket global test timeout, which only masks the real cause.

Use Auto-Waiting and Web-First Assertions

The single biggest speed and stability win in Playwright is to stop hard-waiting. Playwright already auto-waits for an element to be actionable before it acts, so a fixed delay is wasted time on every run.

// Anti-pattern: blind 5s wait every run
await page.waitForTimeout(5000);
await page.fill('#username', 'testuser');

// Better: Playwright auto-waits for actionability
await page.getByLabel('Username').fill('testuser');

// Wait for a condition, not a clock
await expect(page.getByTestId('dashboard')).toBeVisible(); // auto-retries
await page.waitForResponse(r => r.url().includes('/api/orders') && r.ok());

Web-first assertions like toBeVisible and toHaveText auto-retry until the expect timeout, so they pass the instant the app is ready instead of always burning a fixed delay.

Fix Slow or Failing Locators

Prefer role-based and accessible locators such as getByRole, getByLabel, and getByTestId over broad CSS or XPath. Broad selectors that match many nodes force Playwright to do extra resolution work and are far more brittle.

// Slow / fragile
await page.locator('//div[@class="content"]//button').click();

// Fast / stable
await page.getByRole('button', { name: 'Checkout' }).click();

Speed Up the Suite with Parallelism and Sharding

Playwright runs test files in parallel across worker processes by default. Enable full parallelism and match the worker count to your machine's cores. For background on the approach, see What Is Parallel Testing and Why to Adopt It.

export default defineConfig({
  fullyParallel: true,
  workers: process.env.CI ? 4 : undefined, // match CI vCPUs
});

To go beyond a single machine, shard the suite across multiple CI runners:

npx playwright test --shard=1/4
npx playwright test --shard=2/4   # on a second runner, and so on

There is a ceiling to what your own hardware can do, which is exactly where a cloud grid takes over.

Reduce Trace, Video, and Screenshot Overhead

Capturing artifacts for every test is expensive. Capture them only when a test retries or fails so you keep the debugging value without paying the cost on every green run.

export default defineConfig({
  use: {
    trace: 'on-first-retry',     // not 'on'
    video: 'retain-on-failure',
    screenshot: 'only-on-failure',
  },
  retries: process.env.CI ? 2 : 0,
});

Reuse Auth with storageState

Logging in through the UI on every test is one of the most common hidden time sinks. Log in once in a global setup, save the authenticated state, and reuse it everywhere.

// global-setup.ts - log in once, save state
import { chromium } from '@playwright/test';
export default async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://app.example.com/login');
  await page.getByLabel('Email').fill('[email protected]');
  await page.getByLabel('Password').fill('secret');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.context().storageState({ path: 'state.json' });
  await browser.close();
};
// playwright.config.ts
export default defineConfig({
  globalSetup: require.resolve('./global-setup'),
  use: { storageState: 'state.json' },
});

CI, Network, and the networkidle Trap

CI runners have fewer vCPUs and less RAM than your laptop, so set a realistic workers count and slightly higher timeouts for CI only. Just as important, avoid waitUntil: 'networkidle' on pages with persistent connections (analytics, web sockets, long-polling) because the network may never go idle and the navigation will hang until it times out. Prefer 'domcontentloaded' plus a specific assertion:

await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();

Run headless in CI (headless: true), and use slowMo only for local debugging, never in a pipeline.

Run Playwright at Scale on the TestMu AI Cloud Grid

Once you have removed hard waits, fixed locators, and tuned timeouts, the remaining bottleneck is usually raw machine capacity. Running parallel and sharded Playwright tests across a cloud grid of browser and OS combinations removes that local-hardware ceiling and cuts wall-clock time dramatically. You can run the same configuration unchanged, just pointed at the cloud. See Parallel Testing with Playwright and the Playwright Testing platform for setup details. For very large suites, HyperExecute can further cut end-to-end execution time by orchestrating runs across many machines at once.

Running Selenium instead? See Why Are Selenium Tests Running Slow or Timing Out?.

Frequently Asked Questions

What is the default timeout in Playwright?

Playwright uses a 30,000ms (30s) timeout per test and a 5,000ms (5s) timeout for expect web-first assertions. Action and navigation timeouts are unset by default and fall back to the test timeout. All of these can be changed in playwright.config.ts.

How do I fix the "Test timeout of 30000ms exceeded" error?

Find the slow step first, then fix the underlying cause: replace a broad locator with getByRole, remove a waitForTimeout hard wait, or wait on a response. Only after that should you raise the specific timeout with test.setTimeout or a per-action timeout, rather than blanket-raising the global test timeout.

Should I use waitForTimeout() in Playwright?

No, except for brief local debugging. waitForTimeout() is a blind hard wait that always burns a fixed delay and adds flakiness. Use auto-waiting web-first assertions such as expect().toBeVisible(), or wait on a real signal with waitForResponse or locator.waitFor.

Why are my tests slower in CI than locally?

CI runners typically have fewer vCPUs and less RAM than a laptop, and network is often throttled. Tune the workers count to the CI machine, reduce trace and video artifacts, run headless, and offload execution to a cloud grid to remove the local-hardware ceiling.

How do I make Playwright tests run faster?

Run headless, enable fullyParallel with a sensible workers count, shard across machines in CI, reuse authentication with storageState, capture trace and video only on failure or retry, and parallelize across a cloud grid for large suites.

Does headless mode speed up Playwright?

Yes. Headed mode adds rendering and window-management overhead, so it is slower than headless. Keep headless set to true in CI and only run headed when you are actively debugging a test locally.

Related Questions

Test Your Website on 3000+ Browsers

Get 100 minutes of automation test minutes FREE!!

Test Now...

KaneAI - Testing Assistant

World’s first AI-Native E2E testing agent.

...

TestMu AI forEnterprise

Get access to solutions built on Enterprise
grade security, privacy, & compliance

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests