How to use Playwright Library for synchronized UI Automation
Flaky failures can be a major headache in UI automation while interacting with web components. I’ve been using Playwright .NET for almost a year and discovered many useful features to solve synchronization issues. Playwright offers numerous functions that can delay actions until specific events without an explicit timeout, and I find some of them useful based on my experience.
- Wait for pre-action stability
First, it’s always better to ensure the page content is loaded and stable to proceed with the test steps. WaitForLoadState is the function for this purpose and it makes sure that the page is stable and ready to take the next action.
// Wait until initial page is loaded and parsed
await Page.WaitForLoadStateAsync(LoadState.Load);
// Wait until all HTML content is loaded
await Page.WaitForLoadStateAsync(LoadState.DOMContentLoaded)
2. Wait for element state
The best feature of Playwright is the auto-wait functionality: meaning you don’t have to wait for every interaction with an element! The action functions check automatically if the element is visible, stable, enabled, editable, and receiving events. You can check what functions are waiting for these states in the Playwright documentation.
However, in some cases, we need further time to catch synchronization. WaitForSelector can be used if there is a need for a specific element (or its state) to proceed with the steps. You can choose to wait until the selector is Visible, Hidden, Attached, or Detached by using WaitForSelectorState. I mostly use these functions when the action changes the state of an element other than the one I interact with.
// Waiting for a specific selector is present
await Page.WaitForSelectorAsync(".selector");
// Locator-based waiting
await locator.WaitForAsync(new() { State = WaitForSelectorState.Visible });
await locator.WaitForAsync(new() { State = WaitForSelectorState.Hidden});
3. Wait for post-action events
If the action fires an API request, the best practice is to wait for the response in return for the action. WaitForRequestFinished, WaitForResponse, or RunAndWaitForResponse are some useful functions I use for these steps.
// Waiting for a response
await Page.WaitForResponseAsync( response => response.Request.Method == "GET"
&& response.Status == 200 );
// Running an action and waiting for response
await Page.RunAndWaitForResponseAsync( async () => await element.ClickAsync(),
response => response.Request.Method == "GET" && response.Status == 200 );
4. Define custom timeout
It is possible to change the amount of time to be waited before throwing a TimeoutException if the promised event is not captured. You can pass the timeout in the function you’re using, or you can add a global waiting time with SetDefaultTimeout(timeout) method. Pass the default timeout whenever you set up your IBrowserContext to specify all the waiting times in your project.
Deciding how & when to use these wait functions requires a strategic and specific focus on test steps. When in doubt, you can always check the Playwright's documentation for straightforward answers.