Practice Hub
Score: 0 / 12
Challenge 1Beginnerpage.route
Solved
What does ** match in a page.route glob pattern?
You write page.route('**/api/**', handler). What does the ** glob match?
Answer: B — any number of segments including slashes
In Playwright's URL glob syntax, ** matches any sequence of characters, including path separators (slashes). So '**/api/**' matches any URL containing /api/ at any depth: https://app.com/api/users, https://app.com/v2/api/orders/123, etc. A single * matches any characters but NOT slashes — so */api/* would only match URLs with exactly one segment before and after /api/.
Challenge 2Beginnerroute.fulfill
Solved
What does route.fulfill() do?
You call route.fulfill({ status: 200, body: JSON.stringify({ users: [] }) }). What happens to the intercepted request?
Answer: B — mock response returned, server never called
route.fulfill() returns a synthetic HTTP response from Playwright itself — the real server is never contacted. You control: status (number), headers (object), body (string/Buffer), contentType. This is the primary pattern for mocking API responses in UI tests: no real backend needed, tests run faster and are fully deterministic.
Challenge 3Beginnerroute.abort
Solved
What does route.abort() simulate?
You call route.abort() in a route handler. What does the page experience?
Answer: B — network-level failure, not an HTTP error
route.abort() causes the request to fail at the network level — the browser sees a network error, not an HTTP response with an error status code. This simulates scenarios like: server unreachable, DNS failure, or connection refused. You can pass an error reason: route.abort('failed') or route.abort('connectionrefused'). Use this to test how your app handles network failures, like showing "offline" messages.
Challenge 4Intermediateroute.continue
Solved
What does route.continue() with modifications do?
You call route.continue({ headers: { ...route.request().headers(), 'X-Test': 'true' } }). What happens?
Answer: B — forwarded to real server with modifications
route.continue(overrides) forwards the request to the actual server but with the specified overrides applied — you can change: url, method, headers, postData. The server still processes the request and returns a real response. This is useful for injecting test headers, redirecting requests, or changing request bodies before they reach the server.
Challenge 5IntermediateWaiting
Solved
What does page.waitForResponse() return?
You use const response = await page.waitForResponse('**/api/users'). What is response?
Answer: B — a Response object
page.waitForResponse() resolves with a Response object once a matching response is received. You can then inspect: response.status(), await response.json(), response.headers(), response.url(). The typical pattern is to start the wait before the action that triggers it: const [response] = await Promise.all([page.waitForResponse('**/api/users'), page.click('#load-users')]).
Challenge 6IntermediateWaiting
Solved
When does page.waitForRequest() resolve?
What is the key timing difference between waitForRequest() and waitForResponse()?
Answer: B — request resolves on send; response resolves on receive
waitForRequest() resolves as soon as the matching request is sent by the browser — before the server has responded. You get a Request object. waitForResponse() resolves when the full response is received back from the server. Use waitForRequest() when you want to inspect what the browser sends (headers, body). Use waitForResponse() when you want to inspect what the server returns.
Challenge 7Beginnerpage.route
Solved
How do you intercept ALL requests from a page?
You want to log every outgoing request from a page without blocking any of them. What pattern achieves this?
await page.route('???', route => {
  console.log(route.request().url());
  route.continue();
});
Answer: B — '**/*' matches every URL
'**/*' is the catch-all glob pattern — ** matches any prefix (protocol + domain + path segments) and * ensures at least one character at the end. This intercepts every network request from the page. You must call route.continue(), route.fulfill(), or route.abort() on every intercepted request — otherwise the request hangs forever. You can also pass a RegExp to page.route() for more precise matching.
Challenge 8AdvancedHAR
Solved
What does page.routeFromHAR() do?
You call await page.routeFromHAR('./fixtures/api.har'). What is the effect?
Answer: B — replays recorded responses from HAR
A HAR (HTTP Archive) file records all network activity. page.routeFromHAR('./api.har') intercepts requests that match entries in the HAR file and returns the recorded responses — no real network calls made. To record a HAR: await context.recordHAR({ path: './api.har' }). This is powerful for: testing with external APIs (no API key needed), creating deterministic tests, and running tests offline. Use { update: true } to re-record.
Challenge 9IntermediateSimulation
Solved
How do you simulate an offline network in Playwright?
You want to test how your app behaves when the user loses internet connection. Which Playwright API achieves this?
Answer: B — context.setOffline(true)
context.setOffline(true) puts the browser context into offline mode — all network requests fail immediately with a network error, simulating a complete loss of connectivity. Restore with context.setOffline(false). This is cleaner than aborting routes individually because it truly simulates the OS-level offline state. Use it to test offline-first features, service worker fallbacks, or error states.
Challenge 10BeginnerCookies
Solved
How do you programmatically add a cookie to a browser context?
You want to add a session cookie before navigating to a page. Which API call is correct?
await context.addCookies([
  {
    name: 'session',
    value: 'abc123',
    url: 'https://app.example.com'
  }
]);
Answer: B — context.addCookies()
context.addCookies() is the Playwright API for setting cookies on a browser context. Each cookie object requires at minimum name, value, and either url or domain + path. Cookies can be set before any navigation. To remove all cookies: context.clearCookies(). To read cookies: await context.cookies(). Setting cookies via document.cookie in page.evaluate only works after navigation.
Challenge 11IntermediateEvents vs Routes
Solved
What is the key difference between page.on('request') and page.route()?
Both page.on('request', handler) and page.route('**/*', handler) receive requests. What is the fundamental difference?
Answer: B — on() observes; route() intercepts
page.on('request', handler) is a passive event listener — you can read request data but cannot change or block the request. The request continues regardless. page.route(pattern, handler) actively intercepts the request — execution pauses and you MUST handle it by calling fulfill(), continue(), or abort(). Use on('request') for logging/monitoring. Use route() for mocking/modifying.
Challenge 12AdvancedSimulation
Solved
How do you add a delay to a mocked response?
You want to simulate slow API responses (e.g., 2-second delay) using route.fulfill(). What is the correct approach in Playwright?
await page.route('**/api/data', async route => {
  // Add 2-second delay before responding
  ???
  await route.fulfill({ status: 200, body: '{"data": []}' });
});
Answer: B — await new Promise(r => setTimeout(r, 2000))
Playwright's route handler is an async function, so you can use any async delay before calling fulfill(). The standard approach is await new Promise(resolve => setTimeout(resolve, 2000)). Note: page.waitForTimeout() is a page method and doesn't work inside route handlers. There is no route.delay() API. Adding delays simulates slow network conditions without needing actual slow servers — useful for testing loading spinners and skeleton screens.