In Playwright, what is the difference between the requestfailed and requestfinished page events?
answer
- One request, several events
- Terminal event is finished or failed
- HTTP errors still count as success
- failure() is null for a completed call
- Status checks live on the response event
basics
~20 srequestfinished fires when a request completes and its response body has downloaded. requestfailed fires when the exchange never completed at all, such as a DNS or connection error. An HTTP 500 counts as finished, not failed.
solid answer
~40 sA page emits `request` when the browser issues a request, `response` when its status and headers arrive, and then exactly one terminal event: `requestfinished` once the body has downloaded, or `requestfailed` if the exchange never completed. **Failure here means network-level failure, not an error status.** A forecast endpoint answering `500` still emits `response` and `requestfinished`, and `request.failure()` is `null` for it. `requestfailed` covers DNS errors, refused connections, TLS problems, cancelled requests and requests aborted by a route handler; `request.failure().errorText` carries the browser's own reason string, whose exact wording differs between engines. The practical consequence: a listener meant to catch broken API calls must inspect `response.status()` on the `response` event, because counting `requestfailed` alone reports zero on a dashboard whose forecast API is returning 503s.
code
typescript · 17 linesconst failures: string[] = [];
const errorStatuses: string[] = [];
page.on('requestfailed', (request) => {
failures.push(`${request.url()} ${request.failure()?.errorText}`);
});
page.on('response', (response) => {
if (response.status() >= 400) {
errorStatuses.push(`${response.url()} ${response.status()}`);
}
});
await page.goto('/dashboard');
expect(failures).toEqual([]);
expect(errorStatuses).toEqual([]);go deeper
Learn the event order for one request: request, then response, then requestfinished, or requestfailed if it never completed. A 4xx or 5xx is a completed request.
Explain why an error status is still a finished request, and where each check belongs: statuses on the response event, transport failures on requestfailed, with request.failure() returning null for anything completed.
Show a health check built from both halves, and explain how you keep deliberately aborted traffic out of the failure list so the assertion stays trustworthy across a suite.
Decide what a browser suite should enforce about network health at all. A blanket no-failed-requests rule turns third-party outages into red builds, so scope it to first-party origins and treat the rest as reporting.
## The life of one request For a single exchange, a Playwright `Page` emits events in a fixed order: 1. `request` — the browser has issued it. The handler receives a `Request` describing method, URL, headers and body. 2. `response` — status and headers have come back. The handler receives a `Response`; the body may still be streaming at this point. 3. `requestfinished` — the response body has finished downloading. This is the terminal event for a completed exchange. If the exchange never completes, step 3 is replaced by `requestfailed` and `requestfinished` never fires. Redirects add hops: each one emits its own `request`/`response` pair, and the chain is walkable with `request.redirectedFrom()` and `request.redirectedTo()`. ## Failure means the network, not the status This is the distinction interviewers are actually probing. From HTTP's point of view a `404` or a `503` is a perfectly successful exchange — the server was reached, it answered, the body arrived — so Playwright reports it as finished. | Situation | Terminal event | `request.failure()` | | --- | --- | --- | | Forecast API answers `200` | `requestfinished` | `null` | | Forecast API answers `503` | `requestfinished` | `null` | | Host name does not resolve | `requestfailed` | an object with `errorText` | | Connection refused or TLS rejected | `requestfailed` | an object with `errorText` | | Request cancelled by a navigation | `requestfailed` | an object with `errorText` | | A route handler aborts the request | `requestfailed` | an object with `errorText` | `request.failure()` returns `null` for anything that completed, and otherwise an object whose `errorText` is the browser's own reason — Chromium reports strings such as `net::ERR_CONNECTION_REFUSED`, while other engines word it differently. Assert on the fact of the failure and the URL rather than on that text. ## Catching real API errors Because of that split, a "no broken calls" check needs both halves: - `page.on('requestfailed')` catches the exchanges that never happened — a mistyped API origin, a service that is down, a blocked asset. - `page.on('response')` with a `status() >= 400` filter catches the exchanges that happened and went wrong. - A route handler that aborts analytics or ad traffic deliberately produces `requestfailed` events, so a strict "no failures" assertion must exclude the URLs you abort on purpose. - Collect into arrays and assert once at the end of the test; the arrays give a readable failure message listing the offending URLs. ## When requestfinished is the event you want - **Timing.** `request.timing()` exposes resource timing, and the end-of-request numbers only become available once the request has finished. - **Body size or content.** `response.body()` resolves when the body is available, so awaiting it inside a `response` handler effectively waits for the finish. - **Counting completed work.** A test that needs "the dashboard issued exactly three forecast calls and all three completed" counts `requestfinished` rather than `request`. ## Listener hygiene - Register listeners before the traffic they should record — a handler added after `page.goto` misses the navigation's own requests. - Use `page.once` for a single expected event, and `page.off` to detach a handler that should not outlive a step. - Handlers are called by Playwright but not awaited, so async work started inside one can still be running when the test ends. Keep them to pushing values into an array. - Events are per page. When a popup can issue the traffic, listen on the browser context instead so both pages are covered.
- How do you tell a deliberately aborted request apart from a real network failure?Both surface as `requestfailed`, so distinguish them by URL. Keep the list of patterns your route handlers abort — analytics, fonts, ad beacons — and filter those out of the failure collector before asserting; anything left is a genuine problem.
- Which event should a test listen on to read a response body?Listen on `response` and await `response.text()`, `json()` or `body()`. The event fires when status and headers arrive, and those accessors await the remaining download — which is exactly the gap that `requestfinished` marks the end of.
saying these in an interview costs you the question
- Expecting requestfailed to fire for a 500 response
- Believing requestfinished means the response was successful
- Asserting on the exact errorText string across browsers
- Counting only failed requests to prove no API errors occurred
- Assuming requestfinished fires before the response event