Navigation and loading
Playwright logically splits the process of showing a new document in the page into navigation and loading.
Navigation
Page navigation can be either initiated by the Playwright call:
// Load a page
await page.goto('https://example.com');
// Reload a page
await page.reload();
// Click a link
await page.click('text="Continue"');
or by the page itself:
// Programmatic navigation
window.location.href = 'https://example.com';
// Single page app navigation
history.pushState({}, 'title', '#deep-link');
Navigation intent may result in being canceled, for example transformed into a download or hitting an unresolved DNS address. Only when the navigation succeeds, page starts loading the document.
Loading
Page load takes time retrieving the response body over the network, parsing, executing the scripts and firing the events. Typical load scenario goes through the following load states:
page.url()
is set to the new url- document content is loaded over network and parsed
domcontentloaded
event is fired- page executes some scripts and loads resources like stylesheets and images
load
event is fired- page executes dynamically loaded scripts
networkidle
is fired - no new network requests made for at least500
ms
Common scenarios
By default, Playwright handles navigations seamlessly so that you did not need to think about them. Consider the following scenario, where everything is handled by Playwright behind the scenes:
await page.goto('http://example.com');
// If the page does a client-side redirect to 'http://example.com/login'.
// Playwright will automatically wait for the login page to load.
// Playwright waits for the lazy loaded #username and #password inputs
// to appear before filling the values.
await page.fill('#username', 'John Doe');
await page.fill('#password', '********');
// Playwright waits for the login button to become enabled and clicks it.
await page.click('text=Login');
// Clicking the button navigates to the logged-in page and Playwright
// automatically waits for that.
Explicit loading handling may be required for more complicated scenarios though.
Loading a popup
When popup is opened, explicitly calling page.waitForLoadState()
ensures that popup is loaded to the desired state.
const [ popup ] = await Promise.all([
page.waitForEvent('popup'),
page.click('a[target="_blank"]'), // <-- opens popup
]);
await popup.waitForLoadState('load');
await popup.evaluate(() => window.globalVariableInitializedByOnLoadHandler);
Unusual client-side redirects
Usually, the client-side redirect happens before the load
event, and page.goto()
method automatically waits for the redirect. However, when redirecting from a link click or after the load
event, it would be easier to explicitly waitForNavigation()
to a specific url.
await Promise.all([
page.waitForNavigation({ url: '**/login' }),
page.click('a'), // Triggers a navigation with a script redirect.
]);
Notice the Promise.all
to click and wait for navigation. Awaiting these methods one after the other is racy, because navigation could happen too fast.
Click triggers navigation after a timeout
When onclick
handler triggers a navigation from a setTimeout
, use an explicit waitForNavigation()
call as a last resort.
await Promise.all([
page.waitForNavigation(), // Waits for the next navigation.
page.click('a'), // Triggers a navigation after a timeout.
]);
Notice the Promise.all
to click and wait for navigation. Awaiting these methods one after the other is racy, because navigation could happen too fast.
Unpredictable patterns
When the page has a complex loading pattern, the custom waiting function is most reliable.
await page.goto('http://example.com');
await page.waitForFunction(() => window.amILoadedYet());
// Ready to take a screenshot, according to the page itself.
await page.screenshot();
When clicking on a button triggers some asynchronous processing, issues a couple GET requests and pushes a new history state multiple times, explicit waitForNavigation()
to a specific url is the most reliable option.
await Promise.all([
page.waitForNavigation({ url: '**/invoice#processed' }),
page.click('text=Process the invoice'), // Triggers some complex handling.
]);
Lazy loading, hydration
TBD