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

Use Playwright’s response waiter when one user action should produce one XHR; use a response listener for a stream of requests. In SeleniumBase, the documented approach is CDP Mode: listen for Network.ResponseReceived, retain XHR request IDs, and call Network.getResponseBody for each ID. Register any waiter or handler before navigation or the click that triggers traffic. That ordering prevents the most common race.

Choose the capture pattern first

Need Playwright SeleniumBase
One response caused by one action expect_response() (Python) or waitForResponse() (JavaScript) Install a CDP response handler, then correlate the request ID
Observe many requests page.on("response", handler) Collect Network.ResponseReceived events whose type is XHR
Read the body Use the returned Response body API after it is available Call CDP Network.getResponseBody(request_id); retain its base64 flag

The sources document the APIs and event order, but do not establish that either tool is universally faster or more reliable. Browser version, site behavior and your predicate determine results.

Playwright: capture one XHR caused by an action

Python synchronous API

Enter the response context before clicking. The context records the matching response while the action runs.

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://example.com")

    with page.expect_response(
        lambda response: "/api/items" in response.url
        and response.request.method == "GET"
    ) as response_info:
        page.get_by_role("button", name="Load items").click()

    response = response_info.value
    print("status:", response.status)
    print("url:", response.url)
    print("body:", response.text())
    browser.close()

The predicate can inspect URL, method, status or other response properties. If a glob is used instead, Playwright matches the entire URL; a regular expression or predicate is often clearer when query strings vary. See the Playwright Python network guide.

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.
#1 Best Overall
Sale
Pearson Computer Networking, 8E
  • brand: Pearson
  • Computer Networking, 8e

Python asynchronous API

from playwright.async_api import async_playwright

async with async_playwright() as p:
    browser = await p.chromium.launch()
    page = await browser.new_page()
    await page.goto("https://example.com")

    async with page.expect_response(
        lambda response: "/api/items" in response.url
        and response.request.method == "GET"
    ) as response_info:
        await page.get_by_role("button", name="Load items").click()

    response = await response_info.value
    print(response.status)
    print(await response.text())
    await browser.close()

JavaScript and TypeScript

Save the promise, trigger the request, then await the saved promise. Do not await the waiter before the click.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');

  const responsePromise = page.waitForResponse(response =>
    response.url().includes('/api/items') &&
    response.request().method() === 'GET'
  );
  await page.getByRole('button', { name: 'Load items' }).click();

  const response = await responsePromise;
  console.log(response.status(), response.url());
  console.log(await response.text());
  await browser.close();
})();

The JavaScript API and matching rules are described in the Playwright JavaScript network guide and Page API.

Playwright: capture a stream of responses

Attach a listener before navigation or the action that creates traffic, then filter inside it.

def on_response(response):
    if "/api/" in response.url:
        print(response.status, response.url)

page.on("response", on_response)
page.goto("https://example.com")
# perform actions; the listener remains active

A response event means that status and headers have arrived. For a successful exchange, Playwright’s sequence is request, response, then requestfinished after the body has downloaded. A 404 or 503 is still an HTTP response; requestfailed describes a network- or client-level failure, not an HTTP error status. Use the response body method documented for your installed language binding. The Request API explains this lifecycle.

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

Collecting bodies without losing events

Keep the handler small and hand work to your test’s async task or queue. For a single known call, a waiter is simpler because it gives you one response object and a bounded timeout. For broad capture, record URL, method, status and a timestamp, then consume each matched body after the request has finished.

Service workers and routing gaps

Service workers can make native routing appear to miss requests. For tests that must observe those calls, Playwright’s network guide recommends creating the context with service_workers="block". If you need the service worker to run, use BrowserContext-level events and identify responses handled by a service worker as described in the service-worker guide. Blocking changes application behavior, so choose it deliberately rather than treating it as a universal setting.

SeleniumBase: retrieve XHR bodies through CDP Mode

SeleniumBase’s documented raw_xhr_async.py recipe uses Chrome DevTools Protocol (CDP), not the ordinary WebDriver request API. The flow is:

  1. Register a handler for Network.ResponseReceived.
  2. Keep events whose resource type is XHR.
  3. Store each response URL and its request ID.
  4. Ask CDP for the body with Network.getResponseBody(request_id).
  5. Store both the body and the returned base64 indicator, and handle retrieval exceptions.

The following is the core of that documented async pattern. The names page and mycdp refer to the CDP page/session objects created by your installed SeleniumBase version; initialize them through the version’s cdp_driver.start_async() setup.

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.
results = []

async def on_response(event):
    if event.type != mycdp.network.ResourceType.XHR:
        return
    request_id = event.request_id
    record = {"url": event.response.url, "request_id": request_id}
    try:
        body_result = await page.send(
            mycdp.network.get_response_body(request_id)
        )
        record["body"] = body_result.body
        record["base64_encoded"] = body_result.base64_encoded
    except Exception as exc:
        record["body_error"] = str(exc)
    results.append(record)

page.add_handler(mycdp.network.ResponseReceived, on_response)
# Navigate and perform the action that generates XHR traffic here.
# Wait for your application's completion condition, then inspect results.

Use the complete example in SeleniumBase’s documented raw XHR example for session startup and shutdown. Its quiet-period loop is a batching strategy in that sample, not proof that a fixed delay captures every request. In production, prefer an application-specific completion signal or a bounded timeout.

CDP and WebDriver are different modes

SeleniumBase documents CDP Mode separately from WebDriver operation. Methods available in one mode may redirect, or have no CDP equivalent, when the browser is disconnected from WebDriver. Check the CDP Mode documentation and CDP Mode methods for your installed release instead of copying a WebDriver example into a CDP session.

Prevent races and make captures testable

  • Install the waiter or handler before the click, form submit, navigation, or script call.
  • Match a stable URL path plus HTTP method; include query parameters only when they are significant.
  • Set a bounded timeout and report the predicate when it expires.
  • For repeated calls, collect all matches and assert which one your test expects.
  • Record status separately from transport failure so a 404 is not mislabeled as a missing response.
  • When bodies are compressed, binary, or encoded, preserve the API’s returned representation instead of assuming UTF-8 JSON.

Troubleshooting

The Playwright waiter times out

Most often it was registered after the action, or its URL/method predicate is too narrow. Move registration above the trigger, log every response URL temporarily, and switch from an exact glob to a predicate or regular expression. Confirm that the request actually occurs in the browser context you are observing.

The event has headers but no usable body

The response event precedes body completion. Wait for request completion and then consume the response body through the response API. Do not treat the event callback itself as proof that downloading has finished.

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

A 404 or 503 appears as a response

That is expected: the server returned an HTTP response. Assert the status you require; reserve requestfailed handling for network/client failures.

Service-worker requests are missing

Either block service workers for the test context, as the network guide recommends, or observe BrowserContext events and account for service-worker ownership. Blocking can alter the page, so document the choice.

SeleniumBase cannot return a saved body

Ensure the response handler records the request ID before calling getResponseBody, and catch protocol exceptions. Body availability is timing-dependent; use a completion condition rather than an arbitrary short sleep. Verify that the browser is running in the CDP session used by the handler.

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

Or skip the browser setup

If your goal is a clean image or PDF rather than inspecting network payloads, ScreenshotNeo provides a single HTTP request. It accepts cookie or consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers report the page verdict and billing state. Its MCP server includes take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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 other 63 capture options, including full-page lazy-image loading, CSS-selector elements, device and retina settings, custom JavaScript/CSS, request blocking, headers and cookies, signed links, webhooks and bulk capture.

There is a free plan with 1,000 screenshots per month and no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Playwright or SeleniumBase?

Choose Playwright when your test needs a high-level response waiter, straightforward URL predicates and language-supported response objects. Choose SeleniumBase when your existing suite uses its CDP Mode and you need the protocol-level XHR event plus request-ID body retrieval. In either tool, correctness comes from registering before the trigger, matching deliberately and waiting for the body lifecycle you actually need.

FAQ

Can I capture fetch calls with the same Playwright API?

Yes. Playwright’s page response events cover browser responses; filter by URL or predicate rather than assuming the resource is specifically XHR.

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

Should a 404 fail the capture?

Not automatically. It is a received HTTP response, so capture it and assert status according to the behavior your test defines.

Is SeleniumBase’s quiet-period delay required?

No. It is the example’s batching technique. A task-specific completion signal with a maximum timeout is less dependent on arbitrary timing.

Where can I verify the exact SeleniumBase setup?

Use the raw XHR example together with the CDP Mode guide, because startup APIs vary with the installed SeleniumBase version.

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.

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