Beliebte Suchanfragen
|
//

Playwright tests and API Mocking

10.5.2024 | 4 minutes of reading time

Problem definition

Playwright tests can sometimes depend on external services such as APIs, which might happen to be unavailable at times. In this case there are several options for executing these tests adequately, as described below.

  • Actually call the request, such as a development environment.
    • If the backend cannot be reached, the test will fail.
  • Start your own service such as Wiremock and define the responses.
  • Simulate the corresponding HTTP call.

This blog article will illustrate the latter and describe how to simulate the corresponding HTTP calls.

Example

If you call up a specific website in your test, further implicit requests are often made. This means that the website under test will trigger further HTTP calls on the client side during the page load. The following examples illustrate this procedure step by step.

For upcoming examples, a service of jsonplaceholder is being used.

General test

In the test implemented below, we imagine that a very specific request is carried out implicitly. To make this clear, we lead our test directly to the endpoint in order to develop a basis for further extensions. In this case, it is /todos/1.

This pattern is not practical and only serves the purpose of simplicity.

1import { test } from "@playwright/test";
2
3test.describe('Description', async () => {
4
5test('mytest', async ({ page }) => {
6  // given
7  // no prerequisites
8
9  // when
10  await page.goto('https://jsonplaceholder.typicode.com/todos/1')
11  
12  // then
13  // expectations...
14})
15
16}

At the time of this blog post, GET /todos/1 returns the following response.

1{
2  "userId": 1,
3  "id": 1,
4  "title": "delectus aut autem",
5  "completed": false
6}

Generic extension

Once we implemented the test from the previous example, we can start intercepting the request and executing a callback. The callback itself is a function that is executed as soon as Playwright recognizes this route. The following test, which is a extended test from above, continues the request after a console.log and the response remains the same.

1import { Route, test } from "@playwright/test";
2
3test.describe('Description', async () => {
4  test('mytest', async ({ page }) => {
5
6    // given
7    // for this route ...
8    await page.route('https://jsonplaceholder.typicode.com/todos/1', async (route: Route) => {
9
10      // ... execute this callback
11      console.log('invoked HTTP call to given route')
12      await route.continue()
13    })
14
15    // when
16    await page.goto('https://jsonplaceholder.typicode.com/todos/1')
17
18    // then
19    // expectations ...
20  })
21})

As mentioned above, this form of picking out a request to an endpoint is not common and is used for illustrative purposes. This test does not currently test a website, but only a request where the response is also mocked.

Practical example

The test is now going to be converted. We access the website in our test. This call triggers further requests. One of them is a request that goes to the style.css stylesheet. This should not actually be executed. In this example, the response is replaced and returned as text/plain. This means that no styling is loaded on the page.

For this purpose, the Route interface offers the fulfill() function to define this response.

1import { Route, test } from "@playwright/test";
2
3test.describe('Description', async () => {
4  test('mytest', async ({ page }) => {
5
6    // given
7    // for this route ...
8    await page.route(
9      'https://jsonplaceholder.typicode.com/style.css',
10
11      // ... execute this callback
12      async (route: Route) => {
13        console.log('invoked HTTP call to given route')
14
15        // define this response
16        await route.fulfill({
17          status: 200,
18          contentType: 'text/plain',
19          body: 'No stylesheet for you',
20        })
21      },
22    )
23
24    // when
25    await page.goto('https://jsonplaceholder.typicode.com/')
26    
27    // then
28    // expectations ...
29  })
30})

It is recommended to refactor the function so that it can be used in the

1test.beforeEach(() => {})

block. Therefore it's possible to invoke the function before every test.

Out of scope but an FYI: The route.request() function returns the entire request, making it possible to filter certain requests and act accordingly. In order not to complicate the examples, these will not be discussed in detail.

Playwright UI

The Playwright UI, actually a Chromium browser, also offers the well-known Network tab. Here you can see the mocked request and also analyze the body in more detail.

Actual requestMocked request
Actual styling (actual request)No styling (mocked request)

Limitations

Only network requests that are triggered from the browser can be mocked. These can be found in the network tab of the browser's developer tools. The reason for this is that Playwright is a library that simulates the user and thus the interaction with the browser.

If the web application to be tested is implemented with a full-stack framework such as NextJs or Remix, it is also possible for HTTP calls to be called in the backend.

The browser has no access to these and cannot be mocked.

Conclusion

As described in this blog post, there are not many steps involved in mocking a request. Especially if you do not have any possibility to access the backend, the problem of specific expected responses is being solved.

|

share post

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.