Skip to content

Performance Testing in Playwright

Playwright is not a load-testing tool, but you can capture navigation timing, wait for network idle, and assert that critical pages load within budget — catching performance regressions before they reach production.

Navigation Timing in Tests

Use page.goto(url, { waitUntil: 'networkidle' }) sparingly — it waits until no network activity for 500ms, which can be slow or hang on apps with polling. Prefer domcontentloaded or load plus explicit expect() on content.

Read performance.timing or the Performance API via page.evaluate() to assert load duration thresholds in smoke tests.

test('homepage loads within 3 seconds', async ({ page }) => {
  const start = Date.now();
  await page.goto('/');
  await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
  expect(Date.now() - start).toBeLessThan(3000);
});

Wall-clock in CI varies — use generous thresholds or dedicated performance environments.

waitUntil Options

page.goto(url, { waitUntil: 'load' })           // load event
page.goto(url, { waitUntil: 'domcontentloaded' }) // DOM ready
page.goto(url, { waitUntil: 'networkidle' })      // no requests 500ms
  • domcontentloaded — fastest, good when you assert specific elements.
  • load — waits for images and stylesheets.
  • networkidle — risky on apps with websockets or polling.
  • Dedicated tools (Lighthouse CI, k6) complement Playwright timing checks.

Performance Patterns

Timing and wait strategies.

Technique Use Case
Wall-clock assert Smoke test load budget
performance.timing Detailed navigation metrics
Avoid networkidle Apps with polling/websockets
waitUntil: 'load' Full resource load
Lighthouse CI Deep Core Web Vitals audits
Dedicated perf env Stable CI timing comparisons

Reading Performance Metrics

Evaluate the Performance API in the browser context for navigation, paint, and resource timing data.

const timing = await page.evaluate(() =>
  JSON.stringify(performance.getEntriesByType('navigation')[0])
);

Playwright vs Load Testing

Playwright drives one browser context at a time per test. For concurrent user simulation at scale, use k6, Artillery, or Gatling — use Playwright for single-user journey performance budgets.

Common Mistakes

  • Using networkidle on SPAs with constant polling — tests hang or timeout.
  • Strict millisecond asserts in shared CI — flaky due to runner load.
  • Confusing Playwright with load testing for 1000 concurrent users.
  • Measuring before critical content is visible — misleading fast times.

Key Takeaways

  • Assert load budgets on critical paths with wall-clock + visible content checks.
  • Prefer domcontentloaded over networkidle for most SPAs.
  • Use Performance API via page.evaluate() for detailed metrics.
  • Complement with Lighthouse CI for Core Web Vitals.

Pro Tip

Track timing asserts in a dedicated nightly job, not every PR — reduces noise while catching trends.