Version: 1.0.0

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 least 500 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