Playwright

Lesson 13 of 17

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 with 404, 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

  1. 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/todos with an empty list: route.fulfill({ json: [] }). Check that the text No todos yet. Add one above. is visible.

  2. Task 2, not checked yet. Add a test named shows the todos of the mock. Answer with two todos: Mock one (not done) and Mock two (done). Each todo is an object with id, title, and done. Check that the list has 2 todos and that the count shows 1 item left.

  3. Task 3, not checked yet. Add a test named shows an error when the server fails. Answer the request with the status 500. Check that the alert has the text Could not load your todos. and that the button Try again is visible.

  4. Task 4, not checked yet. Add a test named shows an error when the network fails. Make the request fail with route.abort(). Check that the alert has the text Could not reach the server.

  5. 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 of page.route. Check the alert. Click Try 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.

All tasks

example.spec.js

Ctrl+Enter runs your tests. Ctrl+Shift+Enter checks it. Press Esc, then Tab, to leave the editor.

Practice browser

The sample app is ready

The results of your tests show here. Press Run tests to watch them run in the practice browser.