Appearance
Best practices
You will find on this page a collection of best practices that we discussed internally over time but lacks a centralized reference page.
- Difference between step type:
change,write, andkeyDown. - The internal workings of the
waitForNavigationattribute - The difference between
waitForandwaitForNavigation - Why use waitForNavigation Instead of waitFor
- Why Isn’t There a Generic Way to Determine if a Page Has Successfully Loaded?
Difference between step type: change, write, and keyDown.
Summarizing below the difference between the three different steps:
| Step Type | Description | Puppeteers underlying method name |
|---|---|---|
| change | Fills out an input field, which could be a selector, input, or textarea field. | Locator.fill(). See https://pptr.dev/api/puppeteer.locator.fill |
| write | Sends a keydown, keypress/input, and keyup event for each character in the text. There's a 200ms pause between each character type to simulate human typing behaviour. Useful when simulating typing in an autosuggest input field. | Keyboard.type(). See https://pptr.dev/api/puppeteer.keyboard.type |
| keyDown | Dispatches a keydown event. Use it to sent ENTER, SHIFT, ALT, CTRL command | Keyboard.down. See https://pptr.dev/api/puppeteer.keyboard.down |
Main differences between change and write:
The
changesteps work withinput,textarea,andselectfields. While you can also use awritestep for aninputortextareafield, you cannot use it for aselectfield to pick up values.The
changestep is faster to fill values, while thewritestep has a 200ms delay between each character typed which can slow down the screenshot process if there are a lot of forms to fill or a lot of text to type.The
writestep should be the preferable method when typing into a field that relies on keyboardEvent to trigger. A typical use case would be a search box with autosuggestion/autocomplete functionality.
The internal workings of the waitForNavigation attribute
The waitForNavigation attribute ensures that a page fully loads before executing the next step whenever there is a URL change, such as when clicking a hyperlink or a login button.
In Puppeteer, using waitForNavigation: 5000 translates to the following:
js
// Wait for the page to load completely using the 'networkidle2' algorithm
await page.waitForNavigation({waitUntil: 'networkidle2'})
// Then, wait an additional 5 seconds
await new Promise((resolve) => setTimeout(resolve, 5000));In most cases, using only page.waitForNavigation({ waitUntil: 'networkidle2' }) is sufficient to ensure that a page has loaded successfully.
However, in some situations, the page might appear to be loaded before all elements are fully rendered. To address this, the second line, new Promise((resolve) => setTimeout(resolve, 5000)), adds a precautionary 5-second delay. This additional wait time helps prevent issues where page.waitForNavigation({ waitUntil: 'networkidle2' }) completes too early.
This approach is based on observations documented here, where adding a small delay proved beneficial in specific scenarios.
Difference Between waitFor and waitForNavigation
waitFor introduces a fixed delay before continuing, while waitForNavigation waits based on page load completion using the networkidle2 algorithm, plus an additional delay.
The delay value in waitForNavigation should typically be small (e.g., 500 to 5000 ms) because the primary waiting time is already handled by await page.waitForNavigation({ waitUntil: 'networkidle2' }). In many cases, a short delay like waitForNavigation:500 is enough.
To clarify, consider the following values:
waitFor:5000
waitForNavigation:2000- Total waiting time for
waitFor= 5000 ms (fixed) - Total waiting time for
waitForNavigation= page load time based on networkidle2 + 2000 ms
This distinction helps optimize loading times by combining flexible navigation waiting with minimal additional delay.
Why Use waitForNavigation Instead of waitFor
Consider the following delay values for waitFor and waitForNavigation:
waitFor: 5000 ms
waitForNavigation: 2000 msWhen Using waitFor:
- If the page takes
10,000 msto load,waitForwill stop waiting after 5000 ms, which causes subsequent steps to fail or behave inaccurately since the page hasn’t fully loaded. - If the page loads quickly, say in
2000 ms,waitForstill waits for the full 5000 ms, resulting in an unnecessary delay of 3000 ms.
When Using waitForNavigation:
- If the page takes
10,000ms to load,waitForNavigationwaits until it fully loads, resulting in a total wait time of 12,000 ms (10,000 ms for loading + 2000 ms delay). - If the page loads in
1000 ms, waitForNavigation only waits for3000 ms(1000 msfor loading +2000 msdelay), saving time compared to waitFor. - If the page takes
40,000ms to load,waitForNavigationwill wait the entire time until it loads, totaling42,000ms (40,000ms for loading +2000ms delay).
Using waitForNavigation provides greater accuracy, executes more efficiently, and therefore reduces AWS Lambda infrastructure costs over time by avoiding unnecessary delays and handling varying load times more gracefully.
Using waitFor should be discouraged. Here's a great article explaining why.
Why Isn’t There a Generic Way to Determine if a Page Has Successfully Loaded?
There’s no universal method to determine when a page has fully loaded. Puppeteer offers several options, each suited for different scenarios:
page.waitForNavigation({ waitUntil: 'domcontentloaded' })
page.waitForNavigation({ waitUntil: 'load' })
page.waitForNavigation({ waitUntil: 'networkidle0' })
page.waitForNavigation({ waitUntil: 'networkidle2' })
page.waitForNetworkIdle({ idleTime: 250 })
page.waitForSelector("form.register", { visible: true })Each of these methods waits for a different condition:
domcontentloadedwaits until the HTML is parsed and the DOM is ready.loadwaits until all resources like images and subframes are loaded.networkidle0andnetworkidle2wait for network activity to reduce to 0 or 2 connections, respectively.waitForNetworkIdleallows setting a custom idle time for finer control.waitForSelectorwaits for a specific element to appear on the page, which can be useful for pages with specific content.
At some point, we may consider introducing a similar feature, but this would mean that the application itself would need to select the appropriate waiting algorithm. This approach could potentially be confusing for users or customers, who may not be familiar with the nuances of each method.
For now, a combination of waitForNetworkIdle followed by a brief additional delay seems to be the most reliable and generic way to ensure pages load fully before moving to the next step.