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
waitForNavigation
attribute - The difference between
waitFor
andwaitForNavigation
- 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
change
steps work withinput
,textarea,
andselect
fields. While you can also use awrite
step for aninput
ortextarea
field, you cannot use it for aselect
field to pick up values.The
change
step is faster to fill values, while thewrite
step 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
write
step 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 the case of TIC, a 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 ms
When Using waitFor
:
- If the page takes
10,000 ms
to load,waitFor
will 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
,waitFor
still waits for the full 5000 ms, resulting in an unnecessary delay of 3000 ms.
When Using waitForNavigation
:
- If the page takes
10,000
ms to load,waitForNavigation
waits 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 ms
for loading +2000 ms
delay), saving time compared to waitFor. - If the page takes
40,000
ms to load,waitForNavigation
will wait the entire time until it loads, totaling42,000
ms (40,000
ms for loading +2000
ms 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:
domcontentloaded
waits until the HTML is parsed and the DOM is ready.load
waits until all resources like images and subframes are loaded.networkidle0
andnetworkidle2
wait for network activity to reduce to 0 or 2 connections, respectively.waitForNetworkIdle
allows setting a custom idle time for finer control.waitForSelector
waits 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.