How do I correctly wait for a request after an action and validate its response in Playwright?

I’m trying to wait for network requests and validate their responses in Playwright. In Cypress, I could easily do:

cy.server();
cy.route('POST', '/api/contacts').as('newContact');
cy.get('.btn').click();
cy.wait('@newContact').then((response) => {
    expect(response.status).to.eq(400);
    expect(response.responseBody.data.name).to.eq('abcde');
});

But in Playwright, my attempt doesn’t work as expected:

await contacts.clickSaveBtn();

await page.waitForResponse((resp) => {
    resp.url().includes('/api/contacts')
    expect(resp.status()).toBe(400);
});

The validation triggers for a request sent before clicking the save button, so it doesn’t capture the correct request.

The most reliable approach is to start waiting before the action in a Promise.all, so Playwright knows which request to capture:

const [response] = await Promise.all([
  page.waitForResponse(resp => resp.url().includes('/api/contacts') && resp.status() === 400),
  contacts.clickSaveBtn(), // action triggers the request
]);

const data = await response.json();
expect(data.name).toBe('abcde');

:white_check_mark: Key points:

  • waitForResponse waits for the next matching request after the action.

  • Combining it with Promise.all ensures Playwright doesn’t catch a previous request.

If the request might happen multiple times and you want to handle it dynamically:

page.on('response', async resp => {
  if (resp.url().includes('/api/contacts') && resp.status() === 400) {
    const data = await resp.json();
    expect(data.name).toBe('abcde');
  }
});

await contacts.clickSaveBtn();

:white_check_mark: Key points:

  • This sets up a listener before the action.

  • Useful if the request might happen multiple times or asynchronously.

Sometimes you want to capture the exact request first, then validate its response:

const [request, response] = await Promise.all([
  page.waitForRequest(req => req.url().includes('/api/contacts') && req.method() === 'POST'),
  page.waitForResponse(resp => resp.url().includes('/api/contacts')),
  contacts.clickSaveBtn(),
]);

expect(response.status()).toBe(400);
const body = await response.json();
expect(body.name).toBe('abcde');

:white_check_mark: Key points:

  • Gives access to both request and response objects.

  • Helps if you need request payload validation too.