Playwright makes what used to be complicated in end-to-testing almost too easily, especially waiting for network requests.
However, in the case of single-page applications (SPA) where a click on a link or button maybe fire a request but not a page load, you can encounter a situation where Playwright encounters a race condition between clicking and waiting for a response.
The issue is sometimes the waitForResponse
in situations where the request might be faster than the click event finishing, it acts as if it never took place and times out. This issue is acknowledged in the Playwright documentation for page.waitForResponse.
The Playwright docs give an example of using Promise.all
, but the syntax is a little less clean for my tastes.
// Note that Promise.all prevents a race condition // between clicking and waiting for the response. const [response] = await Promise.all([ // Waits for the next response with the specified url page.waitForResponse('https://example.com/resource'), // Triggers the response page.click('button.triggers-response'), ]);
The proposed solution works, but we can make it cleaner by wrapping it in an asynchronous function instead:
export async function clickWait(page: Page, locator: string, url: string) { const [response] = await Promise.all([ page.waitForResponse(url), page.click(locator) ]); return response; }
And then, inside of your test cases, use it like this:
test('load button is clicked and WordPress posts are loaded', async ({ page }) => { await page.goto('http://localhost:8080/'); await clickWait(page, '#load-posts', 'https://myapp.com/wp-json/wp/v1/post'); })
This approach also works for waitForRequest
and any other race condition situation you might encounter in Playwright. Just wrap in a function.