In Playwright, when do you call route.fallback() instead of route.continue()?
answer
- One goes out, one goes down
- Who acts next after this handler
- Skipping the chain versus deferring to it
- Overrides survive into the next handler
- Last handler falling back performs the request
basics
~20 sCall route.fallback() when this handler declines the request and another registered handler should decide; call route.continue() when the request should go straight to the real network. Continue skips every remaining handler, fallback runs the next one.
solid answer
~40 sBoth settle the route without writing a response, and both accept the same overrides — `url`, `method`, `headers`, `postData`. `route.continue()` sends the request to the network immediately and **skips every other matching handler**. `route.fallback()` passes it to the next matching handler in the chain, with your overrides visible to that handler; only when no handler remains does the request go to the network. Since Playwright runs handlers in the reverse order of registration, `fallback()` is what lets a narrow, test-local handler decline the requests it does not recognise and leave them to a broad handler installed in a fixture. Use `continue()` at the edge of the chain, when you have decided this request really does go to the backend.
code
typescript · 17 linesimport { test, expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.route('**/api/forecast**', route =>
route.fulfill({ json: { city: 'Oslo', tempC: 21, alerts: [] } }));
});
test('severe alert banner', async ({ page }) => {
await page.route('**/api/forecast**', async route => {
const city = new URL(route.request().url()).searchParams.get('city');
if (city !== 'bergen') return route.fallback();
await route.fulfill({ json: { city: 'Bergen', tempC: 9, alerts: ['storm'] } });
});
await page.goto('/dashboard?city=bergen');
await expect(page.getByRole('alert')).toContainText('storm');
});go deeper
Remember that both calls end your handler without writing a response, and that continue means the real request goes out. Falling back means some other handler will deal with it.
Explain the chain: continue skips remaining handlers, fallback invokes the next one with your overrides applied, and an exhausted chain performs the request. Know the four override options.
Show how you layer handlers across fixtures and tests so each claims only what it recognises, and how you stop a stray fallback from sending CI traffic to a real third-party service.
Own how many handler layers a suite is allowed to grow. Every extra layer makes 'who answered this request, and with what' harder to answer in a debugging session, and that cost is paid by everyone.
## Both settle the route, in opposite directions `route.continue()` and `route.fallback()` both end a handler without writing a response of your own, and both accept the same four overrides — `url`, `method`, `headers`, `postData`. The difference is who acts next. - `route.continue()` sends the request **to the network immediately**. Every other matching handler is skipped; the overrides you pass are what actually goes on the wire. - `route.fallback()` hands the request to the **next matching handler in the chain**, with your overrides applied so that handler sees the modified request. Only if no handler remains does the request reach the network. Playwright matches handlers in the order opposite to registration — the most recently registered runs first — so "the next handler" means the one registered before yours. | | `route.continue()` | `route.fallback()` | |---|---|---| | next actor | the network | the next matching handler | | other handlers | skipped | still run | | overrides | applied to the outgoing request | visible to the next handler | | typical use | forward this one request, done | this handler declines; let a broader one decide | ## Why a chain is worth having On a weather dashboard suite the pattern falls out naturally. A `beforeEach` registers a broad handler on `**/api/forecast/**` that fulfils a default payload. One test then registers a narrower handler for the "severe alert" city, checks the request, and calls `route.fallback()` for every other city so the default stub still answers them. Without `fallback()` that narrow handler would have to duplicate the default payload or send the request to the real API. Read as a decision: 1. This handler answers the request → `route.fulfill()`. 2. This handler fails it on purpose → `route.abort()`. 3. This handler is not interested, but another might be → `route.fallback()`. 4. This handler wants the real thing, possibly rewritten → `route.continue()`. ## The redirect caveat on the overrides The overrides do not all behave the same when the request redirects. With `route.continue()`, `headers` applies to the routed request **and to any redirect it initiates**, while `url`, `method` and `postData` apply only to the original request and are not carried into the redirected one. That asymmetry is a real source of confusion when a rewritten URL 302s somewhere and the method reverts. ## Things that catch people out - `route.continue()` is not "let the other mocks have a look" — it is the exit door to the network. - Falling back from the *last* remaining handler is equivalent to continuing: the request is performed. - Neither call gives you the response. If you want to see or change what came back, you need to fetch it yourself and fulfil with the result; `continue()` returns nothing useful. - Overriding `url` to a different origin is legal but changes the request the server sees, including its cross-origin character — the page's own security rules still apply. - A handler that calls `fallback()` when no earlier handler exists is a fine no-op, but if you meant to stub something, the test now silently hits the real service. ## Choosing in review Prefer `route.fallback()` inside layered fixtures, where handlers are registered at different scopes and each one should only claim the requests it recognises. Prefer `route.continue()` at the edge of the chain, when you have decided this request goes to the real backend, with or without a rewrite. A suite whose handlers all end in `continue()` has no chain at all — that is worth noticing, because it usually means someone reached for the wrong verb and the layering below never runs.
- What happens if the last remaining handler in the chain calls route.fallback()?There is nothing left to defer to, so the request is performed against the network — effectively the same outcome as `route.continue()`. That is convenient but worth noticing in review: a handler chain that ends in a fallback silently allows real traffic, which in CI may mean calling a third-party service you meant to stub.
- Do the overrides passed to route.continue() survive a redirect?Only `headers` does. Playwright applies header overrides to the routed request and to any redirect it initiates, while `url`, `method` and `postData` apply to the original request only and are not carried into the redirected one. A rewritten URL that 302s therefore loses the method and body override you set.
- Can you inspect the response after calling route.continue()?No. `continue()` releases the request to the network and returns nothing useful; the response goes straight to the page. If you need to see or edit what came back, fetch the request yourself and fulfil with the result, or observe the response through Playwright's response events instead.
saying these in an interview costs you the question
- Thinks continue lets the other handlers run afterwards
- Expects continue to return the response for inspection
- Believes fallback always sends the request to the network
- Assumes handlers run in registration order
- Uses continue as the default branch inside layered fixtures
- Expects a rewritten method or body to survive a redirect