Intercepting API events in your Playwright functional tests

Have you ever found yourself in a situation where you wish you could leverage an API request or response to help solve a automation issue when writing your functional tests? Whether you looking to solve those problems, or just looking to add some enhanced event logging, Playwright has some great tools to help.

Use cases for intercepting API data during the test:

  • Creating a new object in the UI and wish to capture the created object id
  • Waiting on specific network event before continuing test
  • Logging and analyzing which endpoints are being hit during scenarios
  • Assertions on API data

Implementation

We can make use of the page or context waitForResponse() or waitForRequest() function to capture expected calls. In the example below, we will wait for response to the test-runs endpoint so we can confirm that the object was created before continuing then capture the id so we can use it in future steps to filter elements to the specific run we created.

When('user clicks the Run Test button', async ({ page, createTestRunPage }) => {
  const waitForTestRunId: Promise<Response> = page.waitForResponse('*/**/api/test-runs', { timeout: 10_000 })
  await createTestRunPage.runTestsButton.click()
  const response: Response = await waitForTestRunId
  const id: string = (await response.json()).id
  TEST_RUN_ID = id
})
Then('user verifies test run has started', async ({ testRunsPage }) => {
  const testRunRow: Locator = await testRunsPage.getTestRunRowById(TEST_RUN_ID)
  const status: string = await testRunRow.locator(testRunsPage.status).textContent() ?? 'null'
  const running: boolean = ['SUBMITTED', 'QUEUED'].includes(status)
  expect(running).toBe(true)
})

If you wish to implement a broad capture for logging or event checking, you can add a listener to one of the hooks. For example if you wanted to capture every response from test-runs you could do add a fixture similar as below.

  captureAPI: [async ({ page }, use) => {
    page.on('request', request => {
      const url: string = request.url()
      if (url.includes('/api/test-runs')) {
        console.log('>>', request.method(), url)
      }
    })
    page.on('response', response => {
      const url: string = response.url()
      if (url.includes('/api/test-runs')) {
        console.log('<<', response.status(), url)
      }
    })
    await use()
    // if you save all network info to an array you could write to a file afterwards
  },
  { auto: true }],

Console output example:

>> POST https://api.testery.io/api/test-runs
<< 201 https://api.testery.io/api/test-runs
>> GET https://api.testery.io/api/test-runs?offset=0&limit=10
<< 200 https://api.testery.io/api/test-runs?offset=0&limit=10
>> GET https://api.testery.io/api/test-runs/226252/results
<< 200 https://api.testery.io/api/test-runs/226252/results

I hope this shines some light on API related scenarios your working on!

Happy Testing.