33

This is my first time using playwright and I can't figure out how to wait for requests and validate responses. I've been using cypress for a quite a long time, and it was pretty easy to manage network requests. For example, I need to validate response after clicking a button and this is how I would do it with cypress:

        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')
        })

And this is how I'm trying to do the same thing with playwright, but it validates GET request that was sent long before it even clicked save button. I can't figure out how to manage this request properly and that's a show stopper for my test suite:

        await contacts.clickSaveBtn()

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

Any help or advice would be really appreciated

5 Answers 5

60

What you need to do is first start waiting for the response and then click, so the waitForResponse() can catch the actual response coming as a result of the click.

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

This should handle the possible race condition.

Sign up to request clarification or add additional context in comments.

3 Comments

I suggest the following update: const [response] = await Promise.all([, because often times you'll want to use the response after it arrives.
See one complete end to end usage example - stackoverflow.com/a/79401743/1831456
what about when a request has parameters and ids ( constanlt changing ) in the endpoint
24

Alternatively, you can assign a promise, then later wait for it:

const responsePromise = page.waitForResponse(resp => resp.url().includes('/api/contacts') && resp.status() === 400);
await contacts.clickSaveBtn();
const response = await responsePromise;

It's more readable and you get the response value.

2 Comments

Starting waitForResponse after triggering the action risks missing the network event if it happens too quickly, causing flaky tests; using Promise.all ensures you begin waiting before the action triggers, preventing race conditions.
Even if the promise resolves too quickly, it will still hold the correct value. You don't need to get to await before it's resolved.
3

I was looking for the same, this worked for me:

 const response = await page.waitForResponse((response) => response.url().includes("/api/contacts"));
    
     // Assert the response status and body
     const responseBody = await response.json();
     expect(response.status()).toBe(200);

Comments

1

How about doing it this way?

import { Page } from "playwright";
import _ from 'lodash';

function expectJsonStructure(json: any, structure: any): boolean {
  return _.every(structure, (value, key) => {
    if (_.isString(value)) {
      return _.has(json, value);
    }

    return expectJsonStructure(json[key], value)
  });
}

async function waitForJsonResponse(page: Page, structure: any) {
  const response = await page.waitForResponse(async (response)=> {
    try {
      const json = await response.json();

      return expectJsonStructure(json, structure);
    } catch (error) {
      return false;
    }
  });

  return await response.json();
}


// Example JSON that matches the expected structure:
const exampleJson = {
  data: {
    foo: {
      items: [
        { id: 1, name: "Item 1" },
        { id: 2, name: "Item 2" }
      ],
      total: 2
    },
    bar: {
      items: [
        { id: 3, name: "Item 3" },
        { id: 4, name: "Item 4" },
        { id: 5, name: "Item 5" }
      ],
      total: 3
    }
  }
};

// Usage example
async function performActionAndWaitForResponse(page: Page, element: any) {
  const [, json] = await Promise.all([
    element.click(),
    waitForJsonResponse(page, [
      { data: { foo: ['items', 'total'] } },
      { data: { bar: ['items', 'total'] } },
    ])
  ]);
  
  console.log('Received JSON:', json);
  return json;
}

see link https://gist.github.com/chamyeongdo/cd7b7c0717bfb58548faf2f2be331f49

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
0

The other Responses provide the technical answer - however this tests an implementation detail. When you can try to test what happened to your UI as a result of the response that came back from your API.

In your Example you are adding a new Contact - check that it is visible in the UI now. This way you'd discover a caching issue if there was one e.g. (Endpoint said it added the Contact but the Cache for the Endpoint that Lists the Contacts is not invalidated).

Also your test would still work even when the endpoint was renamed/moved/etc.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.