Network steps put Playwright's page.route() interception API directly into your feature files: stub any URL with canned JSON, force error status codes, block third-party scripts, add artificial latency, or take the whole browser offline. That lets QA cover error states, slow-3G behaviour, and dependency outages without touching staging - the page under test never knows its API was replaced.
The group also includes a lightweight request recorder. Once you start recording network requests, every outgoing request's URL, method, and resource type is logged on the scenario, and you can assert afterwards that a specific call was (or was not) made - for example that pressing "Buy now" fired a POST to the checkout endpoint, or that no tracking beacon left the page.
Order matters: register stubs, blocks, and delays before the navigation or click that triggers the request. Stub patterns use Playwright glob matching (**/api/users, *.gif), while the recording assertions use a simpler matcher that converts * to .* internally - both stay BDD-friendly. Pair delayed responses with the AJAX wait steps to verify loading-state UI.
Quick reference
| Step | What it does |
|---|---|
Given the URL "**/api/users" returns the JSON: | Stub a URL pattern with a JSON response body. |
Given the URL "**/api/login" returns status 401 with body "Unauthorized" | Stub a URL pattern with a status code and plain-text body. |
Given the URL "**/analytics.js" is blocked | Block a URL pattern entirely (network failure simulation). |
Given the URL "**/api/**" is delayed by 1500 ms | Delay matching requests by N milliseconds (slow-network simulation). |
Given the network is offline | Simulate offline mode for the rest of the scenario. |
Given the network is online | Restore network connectivity after the network is offline. |
Given I start recording network requests | Start recording every outgoing request for later assertion. |
Then a request to "**/api/users" should have been made | Assert at least one request matching a URL pattern was recorded. |
Then a GET request to "**/api/users" should have been made | Assert at least one HTTP-method-specific request was recorded. |
Then no request to "**/tracking" should have been made | Assert NO request matching a URL pattern was recorded. |
Steps in detail
Given the URL "..." returns the JSON:
Registers a route for the glob pattern and fulfils every matching request with HTTP 200, Content-Type: application/json, and the doc-string body sent verbatim. The body is not validated as JSON, so you can stub exactly what a broken backend would send. Query strings need their own wildcard: "**/api/products?*".
Given the URL "**/api/users" returns the JSON:
"""
{"users": }
"""
When I go to "/dashboard"
And I wait for AJAX to finish
Then "<#user-list>" should contain text "Alice"
Given the URL "..." returns status <code> [with body "..."]
Fulfils matching requests with the given status code and an optional plain-text body (empty string if omitted). This is the fastest way to exercise error handling - 401 login rejection, 404 not-found, 500 server error, 503 maintenance - without breaking anything server-side.
Given the URL "**/api/login" returns status 401 with body "Unauthorized"
When I press "Log in"
Then I should see "Unauthorized"
Given the URL "**/api/health" returns status 503
Given the URL "..." is blocked
Aborts every matching request, so the page sees a network failure. Ideal for verifying the site still works when analytics, error-reporting, or ad scripts cannot load, or for stripping heavy assets ("*.gif") to speed a suite up.
Given the URL "**/google-analytics.com/**" is blocked
And the URL "**/sentry.io/**" is blocked
When I go to the homepage
Then I should see "Welcome"
Given the URL "..." is delayed by <n> ms
Holds matching requests for N milliseconds and then lets them continue to the real upstream - the response is genuine, only late. Use it to prove spinners, skeletons, and disabled-while-loading buttons actually appear.
Given the URL "**/api/**" is delayed by 1500 ms
When I press "Search"
Then "<.spinner>" should be visible
When I wait for AJAX to finish
Then "<.spinner>" should not be visible
Given the network is offline / Given the network is online
Toggles Playwright's context-level offline mode. Everything the page tries to fetch fails until you switch back online, so you can walk through an offline/retry flow inside a single scenario. If the browser context has already been closed the step fails with a hint to start a fresh scenario.
When the network is offline
And I press "Sync"
Then I should see "Cannot reach server"
When the network is online
And I press "Retry"
Then I should see "Synced"
Given I start recording network requests
Attaches a request listener that logs the URL, HTTP method, and resource type of every outgoing request from that point on. Call it before the action you want to observe; the three assertion steps below read the same log. The recorder is installed at most once per scenario, so repeating the step is harmless.
Given I start recording network requests
When I press "Buy now"
Then a POST request to "**/api/checkout" should have been made
Then a request to "..." should have been made / a <METHOD> request to "..." / no request to "..."
Assert against the recorded log. The pattern's * wildcards are converted to .* and matched as a regex against each logged URL. The method-specific form accepts GET, POST, PUT, PATCH, or DELETE and requires both method and URL to match the same request. The no request form passes only when nothing in the log matches - perfect for privacy and tracking audits. Each assertion also ensures the recorder is active, but requests made before recording started are never seen, so put start recording first.
Given I start recording network requests
When I follow "Pricing"
Then a GET request to "**/api/plans" should have been made
And no request to "**/tracking" should have been made
And no request to "**/google-analytics.com/**" should have been made
Complete example
Feature: Checkout resilience
Cover mocked data, error states, and request auditing without staging changes.
Scenario: Dashboard renders mocked data and survives a slow, tracked checkout
Given the URL "**/api/users" returns the JSON:
"""
{"users": }
"""
And the URL "**/tracking" is blocked
And the URL "**/api/checkout" is delayed by 2000 ms
And I start recording network requests
Given I am on the homepage
When I go to "/dashboard"
And I wait for AJAX to finish
Then "<#user-list>" should contain text "Alice"
When I press "Buy now"
Then "<.spinner>" should be visible
When I wait for AJAX to finish
Then a POST request to "**/api/checkout" should have been made
And no request to "**/tracking" should have been made
Tips
- Register routes before the navigation or click that fires the request; routes registered afterwards see nothing.
- Stub patterns are Playwright globs:
**spans path segments,*stays within one. A full URL like"https://api.stripe.com/v1/charges"matches exactly. - Stubs, blocks, and delays last for the rest of the scenario; each scenario starts with a clean slate.
- Delayed requests still hit the real upstream - combine with a stub on the same pattern if you need both latency and a canned body.
- For native
alert/confirm/prompthandling, see the dialog steps; for direct API calls and JSON assertions, see the API steps.