Reputation: 645
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
Upvotes: 29
Views: 79008
Reputation: 4222
In Playwright, you can wait for a request and validate its response using the page.waitForResponse()
method. Here’s the one simple and effective end to end example to do it:
const { test, expect } = require('@playwright/test');
test('Validate API response after clicking a button', async ({ page }) => {
await page.goto('https://example.com');
// Intercept and wait for the request
const response = await Promise.all([
page.waitForResponse(resp =>
resp.url().includes('/api/data') && resp.status() === 200
),
page.locator('#fetch-button').click() // Trigger the request
]);
// Parse JSON response
const responseBody = await response[0].json();
// Validate response content
expect(responseBody).toHaveProperty('success', true);
});
Upvotes: -1
Reputation: 1917
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.
Upvotes: 1
Reputation: 21
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
Upvotes: 1
Reputation: 1079
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);
Upvotes: 3
Reputation: 1125
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.
Upvotes: 22
Reputation: 8362
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.
Upvotes: 53