Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When a click or other user action should make a network call, create the wait promise before triggering the action. Use page.waitForRequest() when you need the outgoing request, page.waitForResponse() when you need status, headers, or response data, and then assert the resulting page state.

The reliable Playwright pattern

Start the wait without awaiting it, perform the action, and await the saved promise afterward. Waiting first would block the action that is supposed to create the request.

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

test('submits an order', async ({ page }) => {
  const responsePromise = page.waitForResponse(response =>
    response.url().includes('/api/orders') &&
    response.request().method() === 'POST'
  );

  await page.getByRole('button', { name: 'Submit order' }).click();

  const response = await responsePromise;
  expect(response.status()).toBe(201);
  await expect(page.getByText('Order confirmed')).toBeVisible();
});

The predicate is deliberately narrow: it checks both the endpoint and HTTP method so an unrelated request cannot satisfy the wait. The official Playwright Network guide and Page API reference document this ordering and matching approach.

Choose the event your test actually needs

API or event What it gives you Use it when
page.waitForRequest() A matching Request You need to verify that the browser issued a call, inspect its URL or method, or read request data.
page.waitForResponse() A matching Response You need the HTTP status, headers, response body, or the request associated with the response.
requestfinished A request whose response body has finished downloading The test depends on download completion rather than merely receiving status and headers.
page.on('request'), page.on('response') Ongoing event notifications You are observing many calls or collecting diagnostics instead of waiting for one specific call.

A response event occurs when status and headers arrive. A requestfinished event comes later, after the body download completes. A transport-level problem emits requestfailed instead of requestfinished and may have no response at all; see the Request API.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Wait for a request when the outgoing call is the assertion

waitForRequest() resolves with the browser’s Request object. That makes it suitable for checking an HTTP method, URL, posted data, or a request header.

test('sends the search term', async ({ page }) => {
  const requestPromise = page.waitForRequest(request =>
    request.url().includes('/api/search') &&
    request.method() === 'GET'
  );

  await page.getByRole('button', { name: 'Search' }).click();

  const request = await requestPromise;
  expect(request.url()).toContain('q=playwright');
});

Use an exact URL if it is stable. If query parameters, hosts, or paths vary, use a regular expression or predicate. A predicate can combine URL, method, and any request property exposed by the API.

Wait for a response when status or data matters

waitForResponse() returns the matching Response. Check the request method through response.request(), then assert the status explicitly.

test('loads the account', async ({ page }) => {
  const responsePromise = page.waitForResponse(response =>
    response.url().endsWith('/api/account') &&
    response.request().method() === 'GET'
  );

  await page.getByRole('link', { name: 'Account' }).click();

  const response = await responsePromise;
  expect(response.status()).toBe(200);
  const account = await response.json();
  expect(account).toHaveProperty('id');
});

An HTTP 404, 401, or 503 is still a response and can satisfy waitForResponse(). It is not the same as requestfailed; assert the success status your scenario requires.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Match traffic precisely

Exact URL

Use a string when the complete URL is deterministic:

const responsePromise = page.waitForResponse('https://example.test/api/profile');

This is simple but can be brittle when the application adds a query string or changes origin between environments.

Glob patterns

Playwright’s simplified glob syntax supports * for characters other than /, ** for characters including /, ? as a literal question mark, and brace lists such as {png,jpg}. For example, **/*.js matches JavaScript files at the root and in nested directories.

const responsePromise = page.waitForResponse('**/api/orders?status=paid');

Choose a pattern that cannot also match analytics, prefetch, or polling traffic.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Regular expressions

const responsePromise = page.waitForResponse(//api/orders/d+$/);

Regular expressions are useful when an identifier changes but the endpoint shape is stable. Add a predicate when the method or status also matters.

Predicates

const responsePromise = page.waitForResponse(response => {
  return response.url().includes('/api/checkout') &&
    response.request().method() === 'POST' &&
    response.status() === 201;
});

Do not make a predicate broader than the behavior under test. A generic check such as response => response.url().includes('/api') can resolve on the wrong call.

Handle actions that create more than one request

Register every relevant wait before the action. Keep each matcher distinct so the promises do not accidentally observe the same request.

test('publishes and refreshes the feed', async ({ page }) => {
  const publishPromise = page.waitForResponse(response =>
    response.url().endsWith('/api/posts') &&
    response.request().method() === 'POST'
  );
  const feedPromise = page.waitForResponse(response =>
    response.url().includes('/api/feed') &&
    response.request().method() === 'GET'
  );

  await page.getByRole('button', { name: 'Publish' }).click();

  const [publishResponse, feedResponse] = await Promise.all([
    publishPromise,
    feedPromise
  ]);
  expect(publishResponse.status()).toBe(201);
  expect(feedResponse.status()).toBe(200);
});

If only one of those calls is required for the test’s contract, wait for that one and use a locator assertion for the visible result. Waiting for every incidental request makes tests slower and more fragile.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Understand the request lifecycle

  1. request: the browser issues the request.
  2. response: status and headers arrive.
  3. requestfinished: the response body has downloaded.

Redirects finish the original request and issue a new request to the redirected URL. A network or client-side failure emits requestfailed; because there may be no HTTP response, a response wait cannot report a status for that failure. The lifecycle details are described in the Request API.

Set timeouts intentionally

Timeout behavior is API-version-sensitive. The Page API documents a 30-second default for waitForRequest() and a 0 ms default for waitForResponse(); verify the reference for the Playwright version installed in your project. Pass an explicit timeout when the test has a known limit instead of relying on a changing default.

const responsePromise = page.waitForResponse(
  response => response.url().includes('/api/report'),
  { timeout: 15_000 }
);

await page.getByRole('button', { name: 'Generate report' }).click();
const response = await responsePromise;

You can also configure relevant page or context defaults. Keep the timeout long enough for the environment’s normal latency, but short enough that a broken endpoint fails promptly. A timeout usually means the matcher never saw the intended event, the action did not run, or the application made a different URL or method than expected.

Do not use networkidle as a substitute for an API wait

Playwright defines networkidle as at least 500 ms with no active network connections and discourages it for testing. Pages with analytics, polling, advertisements, service workers, or long-lived connections may never become idle. When the requirement is an API result, wait for that response and then assert the user-visible condition:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const responsePromise = page.waitForResponse(response =>
  response.url().includes('/api/results') && response.status() === 200
);
await page.getByRole('button', { name: 'Run' }).click();
await responsePromise;
await expect(page.getByRole('heading', { name: 'Results' })).toBeVisible();

This expresses both parts of the behavior: the server call completed successfully and the interface rendered the result.

Diagnose missed or unexpected requests

Temporary listeners show what the page is actually doing. Log the URL and method for requests, the status for responses, and failure details for transport errors.

page.on('request', request => {
  console.log('request', request.method(), request.url());
});
page.on('response', response => {
  console.log('response', response.status(), response.url());
});
page.on('requestfailed', request => {
  console.log('failed', request.method(), request.url(), request.failure());
});

Remove or scope verbose listeners in normal test runs. A 404 appears through the response listener, while a DNS, connection, or other network-level failure appears through requestfailed.

Service workers and routing interception

If page.route() or browserContext.route() appears to miss traffic, a service worker may be handling the request before the route sees it. The official Network guide recommends setting serviceWorkers: 'block' for those routing and interception scenarios. Treat that as a targeted diagnostic setting, not a requirement for every waitForRequest() or waitForResponse() call.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { test } from '@playwright/test';

test.use({ serviceWorkers: 'block' });

Mock Service Worker can take over requests for the same reason. Confirm whether the test is observing the browser’s network, a service worker response, or an application mock before changing the matcher.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and reliability practices

  • Install the wait immediately before the action that triggers it; this minimizes the window in which an unrelated request can match.
  • Match endpoint, method, and—when useful—status. Stable predicates are safer than broad URL fragments.
  • Prefer one meaningful network assertion plus a locator assertion over waiting for a page-wide idle state.
  • Use explicit timeouts for slow but expected operations and keep diagnostics enabled only while investigating.
  • When an application retries an endpoint, include a unique path, method, request payload, or response status in the predicate so the first retry cannot satisfy the wrong assertion.
  • Keep response-body parsing after the wait; the wait itself should identify the event, while normal assertions verify its content.

Or skip the browser setup

If your goal is a clean image or PDF of a page rather than an end-to-end browser assertion, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. Its capture flow accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each step can be disabled.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo documentation for the request options and response headers. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed as clean shots, and each response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Runnable alternatives in Python and Node.js

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

Troubleshooting common failures

Symptom Likely cause Fix
The wait times out The promise was created after the click, the action did not fire, or the matcher is wrong. Create the promise first, verify the locator action, and log requests to compare the real URL and method.
A different request satisfies the wait The URL fragment or glob is too broad. Add the HTTP method, an exact path, a regular expression for the identifier, or a status check.
The test treats a 404 as a network failure An HTTP error response and a transport failure are different events. Read the response and assert response.status(); use requestfailed diagnostics for network-level errors.
The response arrives but the UI assertion fails The API completed before rendering finished, or the application rejected the payload. Assert the expected status and data, then wait for a specific locator that represents the rendered state.
Routing does not intercept the call A service worker or Mock Service Worker handled it. For routing diagnostics, try serviceWorkers: 'block' and confirm which layer owns the request.
The request URL includes unexpected query parameters The application adds cache, locale, or tracking parameters. Use a predicate or regular expression that matches the stable path and checks the method instead of comparing the complete URL.
A redirect appears to be missing The original request finished and a new request was issued for the redirect target. Match the expected request in the redirect chain, and inspect request and response events while diagnosing.
Waiting for networkidle never completes Polling, analytics, a service worker, or a persistent connection keeps traffic active. Wait for the specific response and then assert the relevant UI state.

FAQ

Frequently Asked Questions

Does a successful response guarantee that the page has finished rendering?

No. A response wait observes network progress. Rendering can still be pending or can fail in application code, so pair it with a locator assertion for the state the user must see.

How can I test an endpoint that is retried by the application?

Make the predicate identify the intended attempt with stable details such as the endpoint path, HTTP method, request payload, or required status. Avoid a broad predicate that can match an earlier retry.

Which Playwright documentation should I check when defaults differ?

Use the Page API reference for wait methods and timeout options, the Network guide for matching and event examples, and the Request API for lifecycle and failure semantics.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.