Understanding Playwright waitForResponse
By Priyanka Bhat, Community Contributor - December 9, 2024
Playwright is a popular open-source end-to-end test automation tool developed and managed by Microsoft. Unlike other test automation frameworks, Playwright does not depend on middleware like browser-specific drivers. Because of that, Playwright tests are more stable and faster to execute.
A unique capability of Playwright is it is easy to set up and execute the cross-browser testing scenarios.
The Playwright makes edge-case scenarios simple (For example, file upload and download scenarios). It also has an in-built auto-wait mechanism. However, based on the web page design, you might need to handle waits using additional commands like waitForResponse.
- How Network Responses Impact Web Testing?
- Significance of waits and timeouts for consistent Test Results
- What is waitForResponse in Playwright?
- How waitForResponse improves Test Stability and Accuracy?
- Types of waits in Playwright
- 1. Explicit waits
- 2. Implicit wait
- 3. Conditional waits
How Network Responses Impact Web Testing?
Network responses play a crucial role in web testing. The consistency and flakiness of web testing depend on the network responses.
For example, if the API response is slow, it can cause the slow loading of pages, which may cause delayed loading of web elements. As automation tests are dependent on web elements, they may eventually fail.
These issues can lead to false failures, and testers spend time debugging issues that are not real. The Playwright provides a mechanism to handle such delayed network responses in various ways. One such method is a Playwright waitForResponse.
Read More: Playwright vs Puppeteer: Which one to choose
Significance of waits and timeouts for consistent Test Results
Wait and timeouts are important in automation testing to achieve a stable and reliable output. A web application often involves dynamic contents load or change states asynchronously, such as data fetched from an API or components that render after an interaction.
In handling such scenarios, it is important to use waits and timeouts. If you are writing the UI-based functional tests it’s important to ensure the DOM contents are fully loaded and the relevant API is returned a correct response.
Waits can help to wait until the DOM element is completely rendered on the web page before interacting with the elements. Similarly, timeout allows us to wait for a specific period of time before throwing any exception, such as an Element not found exception or marking the tests as a failure. Setting a value for timeouts can help reduce the flakiness and achieve stable results.
What is waitForResponse in Playwright?
waitForResponse() is a function or method in Playwright that allows you to wait for a specific network call to resolve and provide the responses. Using waitForResponse() you can design the test to pause the execution until the specific network call is resolved before making any further interactions.
Basic Syntax:
await page.waitForResponse(urlOrPredicate);
urlORPredicate: This can be an API URL or regular expression to match the URL
Syntax with options
await page.waitForResponse(urlOrPredicate, options);
urlORPredicate: This can be an API URL or regular expression to match the URL. It can also be a function that takes a Response object and returns a boolean for complex matching.
options:specify timeout in milliseconds; if not met, it throws a timeout error.
Example 1:
await page.waitForResponse('**/api/customerData', { timeout: 5000 });
Example 2:
const response = await page.waitForResponse(response => response.url().includes('/api/customerData') && response.status() === 200 );
In the above example, it waits for /api/customerData API to resolve and ensures it resolves successfully by validating the status code as 200.
How waitForResponse improves Test Stability and Accuracy?
waitForResponse in Playwright improves the stability of test automation results by minimizing network call-related issues.
Rather than assuming the wait time with a hard-coded timeout or using unreliable elements to wait. It awaits for network calls to resolve before performing any interaction with web application elements.
For example, the Playwright might attempt to click on add to cart before the product is loaded, but in such a scenario, tests may fail as the element is not loaded. waitForResponse() with products API waits until the product loads.
Additionally, if you intend to validate only data rather than the UI, you can directly validate from the network response using the waitForResponse() function. As you know, UI tests may be laggy and flakier than API calls. This helps to increase the test’s stability, accuracy, and speed.
Types of waits in Playwright
The Playwright provides different types of wait to handle dynamic content and achieve stable test outputs. This helps to ensure the interaction with the web element happens only when specific elements, events, or network responses are ready.
1. Explicit waits
Explicit wait in Playwright allows us to wait for specific elements to be ready before interacting with such elements. If a specific element or API response is taking a longer time to load you can use the explicit waits in Playwright. There are many different methods available in Playwright to handle the explicit wait.
- locator.waitFor(): Waits for an element to appear, disappear, or be in a certain state
- page.waitForTimeout(): Pauses execution for a specified amount of time
- page.waitForEvent(): Waits for a specific event to be emitted
- page.waitForFunction(): Waits until a custom JavaScript function evaluates to a true
- page.waitForResponse(): Waits for a specific network response
- await pageForLoadState(): Waits for specific page state (ex: load, networkidle, domcontentloaded)
Example:
const submitbtn = await page.locator('#form-submit'); await submitbtn.waitFor({ state: 'visible' });
2. Implicit wait
Unlike popular automation frameworks, Playwright does not support implicit waits. Instead, it provides an auto-waiting mechanism that automatically waits for specific conditions before performing any actions.
These conditions are auto-waiting for elements to be ready, checks to ensure elements behave as expected, automatic retries until the condition is met, timeout triggers, etc.
3. Conditional waits
Conditional waits are typically custom waits, and they loop until a specific condition is met. These conditional waits are helpful while validating dynamic contents. There are many different ways to achieve this. The following are commonly used methods.
- page.waitForFunction(): Waits for a custom JavaScript function to evaluate to true
- page.evaluate(): page.evaluate() can be used to evaluate any custom JavaScript with a wait mechanism. This helps in handling complex scenarios
- locator.waitFor(): locator.waitFor() functions allow parameters like visible, hidden, enabled, or disabled, this enables to conditionally check for specific element state.
Example:
await page.waitForFunction( (labelText) => document.querySelector('#status').innerText === labelText,'Completed');
Limitations of Hard-Coded waits for Network-Dependent Tests
Here are some limitations of hard-coded waits, which can lead to flaky tests and inefficiencies, especially in network-dependent scenarios:
- Variable API Responses: Microservice-based applications may have inconsistent API responses due to network conditions, infrastructure, request load, etc.
- Slow Test Execution: Hardcoded waits lead to longer test times, as they don’t account for dynamically loaded elements.
- Increased Flakiness: Fixed waits often miss timing variations, leading to test failures.
- Difficulty in Debugging: Identifying issues becomes harder with time-based waits due to potential inconsistencies in behavior.
Identifying Network Requests and Responses
Though waitForResponse() is essential to handle some scenarios, you should not use it in every test script unless it is required. Using waitForResponse() throughout the tests can decrease the readability of test scripts and increase maintenance in the long term. You may need to put a strategy to write a test case to standardize the test cases.
Recognizing specific network calls is essential for test accuracy. Recognizing specific network calls is necessary to handle the flakiness in the tests. There are several ways to identify them. One of the easiest methods is the browser’s network tab.
When you manually open the specific web pages in Chrome or any browser developer tools, you find the dedicated tab to track the network requests, which can assist by providing all request and response API calls along with response time information.
You can intelligently look for long API calls and introduce the response waits in Playwright while writing the test cases.
Techniques for Capturing and Identifying Target Responses
There are several ways to capture and identify the target responses in Playwright. Interestingly, you can even validate such API responses using Playwright without any additional libraries.
1. page.waitForResponse()
page.waitForResponse() is one of the most used methods for capturing target responses. This method allows you to wait for a specific API call and validate the response based on the given criteria. For example, if you have ‘api/customerData’ you can easily capture this response using the simple code
const response = await page.waitForResponse(response => response.url().includes('api/ customerData') ); expect(response.status()).toBe(200); page.on(): page.on() is an event it continuesly monitors the request and responses. Based on the specific condition it can be used to validate the specific responses. page.on('response', response => { if (response.url().includes('/api/ customerData')) { let status = response.status(); } }); await page.click('#clickme-button); expect(status).toBe(200);
2. page.route()
page.route() is another method that can be used to intercept network calls. This technique is beneficial for simulating different server responses or testing error handling.
Example:
await page.route('**/api/customerData', route => { console.log(`Intercepted request: ${route.request().url()}`); route.continue(); });
When to Use waitForResponse in Tests?
waitForResponse() is one of the most helpful methods in Playwright, and it allows you to wait for a specific network response. It allows for the reduction of flakiness, especially in delayed network response scenarios.
1. Scenarios where waitForResponse Improves Test Accuracy
- API-based web applications
Modern web applications are built with a large set of APIs. After each interaction to load the data, the APIs will be called. waitForResponse() helps to perform the interactions after all the required API calls are resolved.
- Dynamic content
Dynamic content handling is one of the complex methods, modern frameworks like React, Angular, VueJS use the dynamic content loading mechanism. Before you make any assertion in such scenarios you can ensure API calls are resolved.
2. Instances for Using waitForResponse Over Element-Based Waits
- Unreliable UI state
The application UI state may not always be reliable, and it may change dynamically based on various scenarios. Waiting for network response can help in achieving more reliable and stable tests.
- Data validation
If the test case intention is only to validate the data rather than the UI in such cases using the network response may be appropriate it not only helps in consistent test results but also helps in accuracy and speed.
Waiting for Specific Responses in Playwright
Consider a scenario where the user logs in with valid credentials to bsdemo.com. After logging in, all items are displayed on the home page.
In the above scenario, loading the items may take some time. If you try to capture the items on the home page immediately after the login test, it may fail. One good approach is waiting for an API call that is responsible for displaying the items.
If you carefully look into the network tab manually, you can observe that “/api/products?userName” is the call that holds all the product details. The same can be simulated using the Playwright tests as given below:
import { test, expect } from '@Playwright/test'; test('Wait for Response Demo', async ({ page }) => { await test.setTimeout(500000); await page.goto("https://bstackdemo.com/"); await page.locator('#signin').click(); await page.locator('#username').click(); await page.getByText("demouser").nth(1).click(); await page.locator('#password').click(); await page.getByText('testingisfun99').nth(1).click(); const responsePromise = page.waitForResponse('**/api/products?userName*'); await page.locator('#login-btn').click(); await responsePromise; console.log(await(await responsePromise).status()) console.log(await(await responsePromise).text()) const items = await page.locator("p[class='shelf-item__title']").allInnerTexts(); console.log(items); });
In the above code,
You navigate to bstackdemo.com and enter your credentials.
Once you submit the credentials. you wait for the **/api/products?userName* network call using page.waitForResponse() This helps to ensure all the items are loaded before capturing them.
Best Practices for Using waitForResponse
Implementing best practices for using waitForResponse ensures reliable, efficient, and maintainable network-dependent tests in your automation framework.
1. Avoid Excessive waitForResponse Calls: Use the waitForResponse() only when required. Excessive usage may slow down the test execution it may also make it difficult to maintain the tests
2. Use Specific URLs or URL patterns: construct the URL pattern that matches your specific requirement. Waiting for unintended network calls may slow the test execution.
3. Use assertion: Check the response code whenever required, typically 200 OK responses helps to ensure the API call is successful.
4. Fail fast: Set the timeout to fail fast. By default, Playwright reads timeout from the configuration file, and in API scenarios, you may be expecting it to resolve faster. In such scenarios, you can use the time-outs
Example:
await page.waitForResponse('**/api/customerData', { timeout: 5000 });
5. Enable logging: If you are debugging the tests log the API responses to ensure all required details are coming as a response.
6. Avoid Overlapping waitForResponse Calls: When waiting for multiple responses, ensure that calls don’t overlap unnecessarily, which can lead to unpredictable results or slower execution. For instance, use specific conditions or predicates to ensure that only relevant responses are captured.
7. Use waitForResponse in Combination with Other Wait Methods: For more complex scenarios, combining waitForResponse with other Playwright waiting methods (like waitForSelector or waitForFunction) can improve reliability and accuracy.
8. Timeout Management in Slow Networks: If tests are running on a slower network or are known to have variable response times, adjust the timeout settings accordingly to avoid unnecessary test failures.
Why should you execute Playwright Tests on Real Devices?
The end-to-end test output depends on various factors, such as the browser that you use, network conditions, operating system, etc. While performing test automation, you need to simulate such scenarios.
Having a test automation infrastructure to handle all these scenarios is difficult and it incurs a lot of cost. The best approach is using the cloud-based test execution providers.
BrowserStack Automate is a cloud-based test execution tool that provides 20,000+ real devices across which to run the test execution. It supports integration with many automation frameworks, including the Playwright. It has many benefits. Below are a few.
- Allows to simulate the real user machine
- Supports parallel testing on multiple devices and browsers
- Supports reliable network conditions like 4G, 3G, etc.
- Allows to validate devices/browser-specific behaviors
- Improved logging and debugging with screenshots, test execution video, etc.
Read More: Real Devices vs Emulators and SImulators
Conclusion
Modern automation tools, like Playwright, offer powerful features such as waitForResponse(), which enables tests to wait for network calls instead of relying on UI elements, enhancing test speed, accuracy, and consistency.
However, executing tests across multiple platforms and browsers under real user conditions requires a robust testing environment that simulates a variety of devices, operating systems, and browser versions.
BrowserStack Automate provides a cloud-based testing platform with over 20,000 real devices and browsers, ensuring tests run in authentic conditions and boosting confidence in production releases. With parallel test execution, Playwright tests can run simultaneously across different devices and configurations, reducing execution time significantly.
The integration with BrowserStack is seamless and requires no modification of existing test scripts, making it easy to adopt and maintain high test coverage while ensuring the code is reliable across platforms.