Playwright has a convenient feature for waiting on responses from requests - waitForResponse.
waitForResponse — playwright.dev
This is helpful when there are no visual changes on the web UI, but you need to verify that a request was actually sent and an entity was successfully created. Instead of:
- Opening the page with that entity and writing checks to verify all data is correct
- Or directly "poking" a specific endpoint to check its fields
You can implement response waiting. Here's an example from the documentation:
Option 1: Declare the expectation first
Start waiting, perform the action, then await the response:
const responsePromise = page.waitForResponse('https://example.com/resource');
await page.getByText('trigger response').click();
const response = await responsePromise;
Option 2: Use a predicate
Declare the predicate with expectations, perform the action, and get the result:
const responsePromise = page.waitForResponse(response =>
response.url() === 'https://example.com' && response.status() === 200
&& response.request().method() === 'GET'
);
await page.getByText('trigger response').click();
const response = await responsePromise;
The Promise Advantage
Promises supposedly give us an advantage over other non-async languages.
Legally, the logic described is correct and logically sound:
- First, we declare that we need to wait for a response from the backend, but we don't block the execution of subsequent actions
- Then we perform the action itself, and wait for it to complete - the browser directly tells Playwright that the action is done, the event is complete, roughly speaking
- Only then do we "receive" the result of the async wait that we declared to the browser
But Here's the Thing...
Your backend doesn't respond to the frontend in 1 millisecond. At best, it'll respond in 100 milliseconds (unless we're talking about gRPC or WebSocket).
The question arises: How much time passes between one promise completing and the next promise starting?
Answer: Microseconds - it's just the time for the JS event loop to process the microtask queue.
So You Can Actually Write It Like This
await page.getByTestId('submit-button').click();
await page.waitForResponse('**/api/users');
It's more compact and simpler!
You're very unlikely to encounter a race condition where a click or fill takes so long that waiting for the response becomes pointless.
So feel free to try writing "synchronous" code like this in your project - it works for me, and it'll work for you.
Proof of Concept
For skeptics, I've prepared an example repository that emulates this behavior on localhost, where everything runs locally and passes successfully:
https://github.com/rmarinsky/trivial_service_for_experiments
Of course, edge cases can occur depending on your frontend implementation.
Top comments (0)