Understanding Playwright waitforloadstate
By Priyanka Bhat, Community Contributor - December 6, 2024
Playwright is a modern open-source framework developed and maintained by Microsoft. Due to its advanced architecture, Playwright tests are faster and more stable. Playwright has many features in-built, auto waiting is one of the unique features.
The Playwright automatically waits for element stability and actionability before performing any interactions. This reduces the additional code and complex logic of implicit waits.
Playwright also supports different mechanisms to wait for specific elements. WaitForLoadState() is one such advanced method that allows waiting for DOM content to load, network calls to resolve, or complete a web page to load.
- Why do Load States Matter in Web Testing?
- What is waitForLoadState in Playwright?
- How waitForLoadState improves Test Stability and Accuracy?
- Dealing with waits and timeouts in Playwright
- Types of waits in Playwright
- 1. Explicit waits
- 2. Implicit wait
- 3. Conditional waits
Why do Load States Matter in Web Testing?
Load states are indications or different states of web applications. By understating and handling these intelligently, you can achieve highly reliable automation execution output.
Modern web development uses this advanced mechanism to provide a smoother user experience. Dynamic loading is one such approach where the contents are loaded dynamically after the initial payload. This can have multiple API calls, or loading images from resources etc.
Waiting for specific load states, such as DOM content loaded, network idle, or load, tests can verify that the content and elements they depend on are ready before interacting. This reduces test failures that occur because of incomplete loading or interaction that happens before page load.
Read More: Playwright vs Puppeteer: Which one to choose
What is waitForLoadState in Playwright?
The waitForLoadState() is a method in Playwright that waits for the page to trigger specific states such as load, DOMcontentloaded, and networkidle.
waitForLoadState is particularly useful in the navigation of web pages. Instead of using the hard-coded waits, you can dynamically wait for the page to be loaded completely. For example, if you want to interact with one of the UI elements after the page loads, then if you put the hard wait, the Playwright might look for the element as soon as navigation happens or as soon as the timeout occurs.
Playwright may attempt to interact with the element before it loads. If the element is not loaded, then the tests may fail, which can cause flaky tests.
How waitForLoadState improves Test Stability and Accuracy?
waitForLoadState() can greatly influence the test stability and accuracy, as the page load is the initial step to perform any action on the page.
waitForLoadState() allows us to ensure the page is completely loaded before proceeding further. The test flakiness is a common challenge in automation testing.
Most of the flakiness originates from the element loading or presence. The waitForLoadState() reduces such errors. Additionally, waitForLoadState() can be placed inside the script based on scenarios.
Playwright also allows definition of custom timeout for load state, which can be helpful in specific pages where it takes longer than usual time.
When waitForLoadState() is used with networkidle, it ensures there is no ongoing network call in the backend. These waiting strategies in Playwright can reduce the flakiness drastically. However, it may need to be handled on a case-by-case basis.
Dealing with waits and timeouts in Playwright
The Playwright allows different ways to handle the wait and timeouts. This mechanism includes element-level waits, network call waits, and the complete DOM tree-level waits. Based on the specific requirements these waits and timeouts can be used intelligently.
1. Element level waits and timeouts
Locator.waitFor(): This method enables waiting for a specific element to be visible or hidden in the DOM tree. If the specific element is taking a lot of time to load, you can use this method before interacting with such elements. The locator.waitFor() accepts timeout in milliseconds as a parameter that can be used for a timeout before throwing an exception.
Example:
const subBtn = page.locator('#btn') subBtn.waitFor({ state: 'visible', timeout: 5000 })
2. Page level waits
waitForLoadState(): This method provides a wait at page level with state-based waits. You can wait for specific states of a web page such as domcontentloaded, networkidle, load, etc. This is helpful when you freshly navigate to pages.
The waitForLoadState() also accepts timeout as a parameter along with state.
Example:
await page.waitForLoadState('load', { timeout: 10000 });
3. Network call wait
waitForResponse(): This method helps to wait for specific API calls to resolve before proceeding further. For example, if your UI test depends on a specific API call, you can wait for that API to be called and resolved before performing action on that UI element.
The waitForResponse() can be combined with the timeout option to achieve highly reliable tests
Example:
await page.waitForResponse('https://example.com/api/getCustomerDetails, { timeout: 10000 });
4. Hard waits
Playwright allows the execution to be paused for a specific duration using the page.waitForTimeout(). This method is helpful in debugging. However, it is not recommended to use this for real-time scripts as tests may become flaky.
Example:
await page.waitForTimeout(90000);
Types of waits in Playwright
Playwright provides different types of wait to handle dynamic content loading and achieve stable test outputs; this helps to ensure the interaction with web elements 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 takes a longer time to load, you can use the explicit waits in Playwright.
There are many different methods available to the 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
Example
const submitbtn = await page.locator('#form-submit'); await submitbtn.waitFor({ state: 'visible' });
2. Implicit wait
Unlike traditional 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 automatically 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, including the following 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');
What are the problems with Hard Wait?
The Playwright offers hardcoded waits using page.waitForTimeout() function. The limitation of these hard-coded waits is increased flakiness, especially if the web application is developed on a microservice-based architecture.
The API response may vary based on various factors like network, infrastructure, number of requests, etc. In such cases, using hard-coded waits causes slower test execution, test flakiness, difficulty debugging, etc.
Additionally, this may even bypass the real user simulation as the user doesn’t wait a specific time rather, he tries to wait for a particular component before interacting.
Playwright clearly documents with caution on hard-coded waits, mentioning: “Discouraged Never wait for timeout in production. Tests that wait for time are inherently flaky. Use Locator actions and web assertions that wait automatically”.
Take a scenario of a web page loading after navigation, depending on network speed, and application architecture it may take 60 seconds or 30 seconds.
If you put a code page.waitForTimeout(45000) the tests may still fail when it takes longer than 45 seconds. The best approach in such a scenario is to use the page.waitForLoadState() where it dynamically waits for the web page to load.
Types of Load States in Playwright
There are different types of load statuses in Playwright, and one can use them based on the scenario and requirements.
1. domcontentloaded
The domcontentloaded state indicates that the DOMContentLoaded event has fired, meaning the HTML document has been completely loaded and parsed. However, that doesn’t mean that all stylesheets and resources are loaded. This can be considered as the initial page load.
2. load
The load state indicates that the entire page, including all dependent resources such as images, stylesheets, scripts, and other resources, has finished loading. This state is useful when you want to ensure that all content is fully available before interacting with the page. However, this doesn’t track the ongoing network requests. When the load event is fired, there can still be ongoing network requests in the backend.
3. networkidle
Network idle state indicates that there is no ongoing network request of at least 500 milliseconds. This state is beneficial for applications that make multiple asynchronous requests, as it ensures that all network activity has been completed before proceeding. This state is mostly useful in single-page applications.
Note: The Playwright clearly mentions not to use the networkidle unless required by stating official website “Discouraged wait until there are no network connections for at least 500 ms. Don’t use this method for testing, rely on web assertions to assess readiness instead”.
Basic Usage of waitForLoadState
Syntax:
await page.waitForLoadState(loadStateParameters, options);
loadStateParamters can be one of the below
‘load‘ – wait for the load event to be fired. This is the default.
‘domcontentloaded‘ – wait for the DOMContentLoaded event to be fired
‘networkidle‘ – wait until there are no network connections for at least 500 ms
Options can be one of the below
Timeout: number in milliseconds. Maximum operation time in milliseconds. Defaults to 0 – no timeout.
Example
Consider you want to navigate to bstackdemo.com, and before performing UI interactions, you want to ensure the page is loaded then you can use the below code
test('Wait For Load State - Load', async ({ page }) => { await page.goto("https://bstackdemo.com/"); await page.waitForLoadState('load'); expect(await page.locator('small[class="products-found"]').innerText()).toEqual("25 Product(s) found."); });
In the above code, you navigate to bstackdemo.com and wait for the page to load before capturing the inner text which is available on the home page.
Waiting for Specific Load States
As mentioned earlier, playwright waitForLoadState() provides waiting options with different states namely domconentloaded, load, networkidle.
Waiting for DOMContentLoaded
The DOMContentLoaded state indicates that the HTML document has been completely loaded and parsed. This method is suitable when you want to work with DOM-level actions such as title, meta, etc
test('Wait For Load State - Load - DOM Content Loaded', async ({ page }) => { await page.goto("https://bstackdemo.com/"); await page.waitForLoadState('domcontentloaded'); console.log("Page Title is: "+await page.title()) expect(await page.title()).toContain('StackDemo') });
In the above code, navigate to bstackdomo.com and wait for ‘domcontentloaded’ event to be fired before validating the page title.
Waiting for Load
The load state indicates that the page is completely loaded, including stylesheets, images, and other JavaScript. This is the right way to ensure that the page is ready to take any actions. This is suitable for while interacting with UI based elements
test('Wait For Load State - Load', async ({ page }) => { await page.goto("https://bstackdemo.com/"); await page.waitForLoadState('load'); const productCount = await page.locator("//p[@class='shelf-item__title']").count(); expect(await page.locator('small[class="products-found"]').innerText()).toEqual("25 Product(s) found."); console.log("Total Products in the page is: "+productCount) });
In the above code, navigate to bstackdemo.com and wait until the page is completely loaded, and then take the count of the product and assert the count text.
Waiting for Network Idle
The networkidle state indicates no more than 0 network connections for at least 500 milliseconds. This is useful when applications make multiple asynchronous requests. However, Playwright does not recommend using networkidle status.
test('Wait for Response Demo', async ({ page }) => { await test.setTimeout(500000); await page.goto("https://bstackdemo.com/signin"); await page.locator('#username').click(); await page.getByText("demouser").nth(1).click(); await page.locator('#password').click(); await page.getByText('testingisfun99').nth(1).click(); await page.locator('#login-btn').click(); await page.waitForLoadState('networkidle',{timeout:30000}); await page.locator("//p[@class='shelf-item__title'][text()='One Plus 6T']/../div[text()='Add to cart']").click(); console.log("Added One Plus 6T to cart") });
In the above code navigate to the sign page and log in as a demo user by entering a username and password. Once you hit the submit button, you wait for the load state – network idle, and then perform the click operation on the One Plus 6T Add to Cart button.
Best Practices for Using waitForLoadState
Mastering waitForLoadState in Playwright is key to building reliable, efficient, and robust automated tests that handle dynamic web applications seamlessly. Here are some best practices to follow:
- Do not use long timeouts on the page.waitForLoadState() method, this can cause the test execution to slow and may hide the actual loading issues that can be faced by the end-user.
- Use appropriate load states based on the requirement, such as load, domcontentloaded, etc.
- Avoid using the networkidle load state. Instead, rely on web assertions to assess readiness.
- To get consistent test results, try to combine waitForLoadState() with element-level waits.
- Use custom timeouts based on each scenario or use case
- Review the page load time regularly and optimize it more frequently
Why should you execute Playwright Tests on Real Devices?
The user experience of the web application may change as the platform and browser change. It is important to test all possible operating systems, browsers, and devices before deploying code to production. Executing test automation scripts on multiple operating systems and browsers has its own challenges.
One of the main challenges is infrastructure. Cross-platform testing requires an environment that supports such kind of testing. This challenge can be overcome easily by using the cloud-based execution provider BrowserStack.
BrowserStack Automate is designed to integrate popular testing frameworks without modifying the actual test script. It requires minimal configuration to execute your tests on multiple browsers and operating systems.
Additionally, BrowserStack manages all of the testing infrastructure, so testers do not need to bother about infrastructure setup or monitoring. The BrowserStack Automate infrastructure is highly scalable, it allocates the resources on a demand basis. This can help in saving the cost considerably. As of today, BrowserStack has 20000+ real devices on the cloud which can be used for test execution.
Conclusion
Playwright is an advanced testing framework that provides different ways to handle complex scenarios. It has an inbuilt auto-waiting mechanism. However, if anyone wants to handle waiting explicitly it supports them using the various methods. waitForLoadState() is one such method where one can put the logic to wait for a specific page status.
Irrespective of the logic you use, it is important to execute the tests on the real devices, so that it helps to simulate the real user scenarios under different network conditions. BrowserStack is a recommended tool to validate such scenarios with ease.