DEV Community

Phil
Phil

Posted on

Intercepting network requests in Playwright

I recall some time ago that the folks behind WebDriver made their intentions very clear: If an action can be taken in the browser viewport with a mouse and keyboard, they would provide an API for it. Anything else beyond that was merely an afterthought. As testing evolved into a more complex, technically challenging discipline in its own right, the tooling had to meet that demand.

Fast forward to the present day and we now have tools like WebDriver's BiDi API, as well as Playwright's support for network events.

I had described previously that intercepting network requests added to an effective waiting strategy for the DOM, and it still does. Slow network responses combined with very fast interactions in the UI can result in some fairly nasty, difficult-to-troubleshoot race conditions.

But one thing I had failed to mention was this: Ultimately, is that button that you're clicking actually sending the proper request? Playwright allows us to check on that with ease, and adds a nice wrinkle of defensiveness to our tests.

Playwright's documentation is pretty straightforward about this. Set up some promises, go about your Playwright Actions, and then retrieve the result of those promises. Here, we've written a higher order function that can be used throughout our test suite:

export const expectRequest = async (page: Page, requests: {url: string, method: string}[], action: () => Promise<void>) => {
  const promises = requests.map(({url, method}) => {
    const predicate = (response: Response) =>
      response.url().includes(url) &&
      response.request().method() === method &&
      response.status() === 200
    return page.waitForResponse(predicate)
  })

  await action()

  return await Promise.all(promises)
}
Enter fullscreen mode Exit fullscreen mode

And here's an example of how we would use this helper:

const loginRequest = {url: '/auth', method: 'POST'}
const profileRequest = {url: '/profile', method: 'GET}
await expectRequest(page, [loginRequest, profileRequest], async () => {
  await page.getByRole('button', { name: 'Log In' }).click()
})
Enter fullscreen mode Exit fullscreen mode

So there we have a pretty simple example of a button click to 'Log In', and we are expecting multiple requests to succeed. A POST request to /auth, and a GET request to /profile. This is a fairly arbitrary example, but I have come across some areas in our app where this has made a nice positive impact in our tests, and increases our confidence that the frontend and backend are in complete lockstep.

Happy testing!

Top comments (0)