Lesson ยท 25 min
Mock the network
The real server is hard to control
You cannot ask a real server to be empty, to fail, or to be slow, on demand. You would have to break it. But a good app must handle these cases, and a tester must check them.
Playwright can stand between the page and the server. It can intercept a request, and it can answer the request itself. The page does not know the difference. This is called mocking the network.
Routes
page.route(pattern, handler) tells Playwright: "When the page sends a request to an address that matches this pattern, call this function." The function receives a route. You decide what happens to the request.
await page.route('**/api/todos', (route) => route.fulfill({ json: [] }));
The pattern **/api/todos is a glob. ** stands for any start of the address, so this matches http://localhost:3000/api/todos.
A route has three main answers.
| Call | What the page sees |
|---|---|
route.fulfill({ ... }) |
An answer that you write. Use json, status, body, contentType, and headers. |
route.abort() |
A network failure, as if the connection broke. |
route.continue() |
The real request goes to the server. You can change its headers or body first. |
Set the route first
A route works for requests that come after you set it. The todo app asks for its list when the page opens, so set the route before page.goto.
await page.route('**/api/todos', (route) => route.abort());
await page.goto('/');
A route stays until the test ends. page.unroute(pattern) removes it earlier. The option { times: 1 } makes the route work one time only, and then it goes away. That is useful for "the first try fails, and the second works".
Good uses of a mock
- Rare states: an empty list, a list with 1,000 items, a very long name.
- Errors: a server error with the status
500, a missing item with404, a broken connection. - A page that does not depend on a server: the test runs fast and the same way every time.
A limit of a mock
A mock tests how the page reacts. It does not test the server. If you replace the answer, you do not know whether the real server would give that answer. A set of tests needs both: tests with a real server, and tests with mocks for the cases that are hard to make.
Your turn
Write five tests. Each one changes the answer of the server in another way.
Tasks
5 to do
Task 1, not checked yet. Add a test named
shows a message for an empty list. Before you open the page, answer the request to**/api/todoswith an empty list:route.fulfill({ json: [] }). Check that the textNo todos yet. Add one above.is visible.Task 2, not checked yet. Add a test named
shows the todos of the mock. Answer with two todos:Mock one(not done) andMock two(done). Each todo is an object withid,title, anddone. Check that the list has 2 todos and that the count shows1 item left.Task 3, not checked yet. Add a test named
shows an error when the server fails. Answer the request with the status500. Check that the alert has the textCould not load your todos.and that the buttonTry againis visible.Task 4, not checked yet. Add a test named
shows an error when the network fails. Make the request fail withroute.abort(). Check that the alert has the textCould not reach the server.Task 5, not checked yet. Add a test named
loads the todos when the user tries again. Make the first request fail, and only the first: add the option{ times: 1 }as the third argument ofpage.route. Check the alert. ClickTry again. Check that the list has 3 todos.
Task 1 of 5
Add a test named shows a message for an empty list. Before you open the page, answer the request to **/api/todos with an empty list: route.fulfill({ json: [] }). Check that the text No todos yet. Add one above. is visible.