Timeouts
All browser checks are capped at 60 seconds. This means everything in your script needs to happen within those 60 seconds.
Dealing with timeouts
Setting correct timeout values can mean the difference between a good night’s sleep or alerts bugging you because your site or apps performance dropped by 500 milliseconds.
You can deal with timeouts at two levels, and we recommend you study them in the order below:
- Use the timeout options in Puppeteer/Playwright.
- Set global timeouts on the Mocha suite and test level.
Tip: Good use of the
page.waitForSelector()
can save you a lot of headaches.
Timeouts in Puppeteer/Playwright
In your Puppeteer/Playwright code, you have a range of options to set timeouts for different actions. The default for most actions is 30 seconds. There are two very important ones that you should use in almost every browser check:
page.waitForSelector()
This method waits for an element to appear on the page. This is your bread and butter and should be used whenever something needs to be loaded after clicking, hovering, navigating, etc. You can pass it an options object with a timeout attribute to override the default 30 seconds.
In the example below, we type ‘fluffy kittens’ into an input field on a search page and click “submit”. We then use the
page.waitForSelector()
method and pass in a timeout of 5 seconds because we want this check to fail if it takes any longer.
You can also see we assign a 10-second timeout to the surrounding Mocha test.
it('First search result is my link', async () => {
await page.type('input[name=q]', 'fluffy kittens')
await page.click('input[type="submit"]')
await page.waitForSelector('h3 a', { timeout: 5000 })
const links = await page.evaluate(() =>
Array.from(document.querySelectorAll('h3 a'))
.map(a => a.textContent)
)
assert.equal('my link', links[0])
}).timeout(10000)
it('First search result is my link', async () => {
await page.type('input[name=q]', 'fluffy kittens')
await page.click('input[type="submit"]')
await page.waitForSelector('h3 a', { timeout: 5000 })
const links = await page.evaluate(() =>
Array.from(document.querySelectorAll('h3 a'))
.map(a => a.textContent)
)
assert.equal('my link', links[0])
}).timeout(10000)
Read more in the Puppeteer or Playwright API docs.
Playwright allows you to use XPath selectors in the
page.waitForSelector()
method. Puppeteer provides a separate method for that:page.waitForXpath()
.
page.waitForNavigation()
In both Puppeteer and Playwright you can click on a link that triggers a navigation to a new page.
Use the page.waitForNavigation()
method, although it is slightly unintuitive to use as the associated promise has to be initialized before waiting for it.
This means the following will not work
await page.click('a.some-link')
await page.waitForNavigation() // does not works as expected
await page.click('a.some-link')
await page.waitForNavigation() // does not works as expected
but this will work:
const navigationPromise = page.waitForNavigation()
await page.click('a.some-link')
await navigationPromise
const navigationPromise = page.waitForNavigation()
await page.click('a.some-link')
await navigationPromise
Navigation wait times
The page.waitForNavigation()
method — but also almost all other methods that deal with navigation like page.reload()
and page.goBack()
) —
has a very important set of options that tell the library what it should consider a “navigation”.
These options come in two categories:
DOM event based
These two options are directly related to the events your browser emits when it has reached a certain loading stage. These events are not specific to Puppeteer or Playwright and are used in almost all browsers.
load
- This the most strict: your whole page including all dependent resources, i.e. images, scripts, css etc. More infodomcontentloaded
- less strict: when your HTML has loaded. More info
Note: the load
option is the default. It is pretty strict, so if you have slow dependencies it can be less than optimal.
Heuristic based
These options are based on the heuristic that if (almost) all network connections your browser has are no longer active, your page has probably finished loading.
- Puppeteer:
networkidle0
- consider navigation to be finished when there are no more than 0 network connections for at least 500 ms. - Puppeteer:
networkidle2
- consider navigation to be finished when there are no more than 2 network connections for at least 500 ms. - Playwright:
networkidle
- is the equivalent ofnetworkidle0
in Puppeteer.
So, which one to choose? This really depends on your situation:
- Have an SPA that needs to be fully rendered? Probably go with
load
- Have a server side rendered page but some slow dynamically loaded sections that are not crucial:
go for
networkidle2
/networkidle
Options are set as follows:
page.waitForNavigation({ waitUntil: 'networkidle2' })
page.waitForNavigation({ waitUntil: 'networkidle' })
You can also specify an array of waitUntil
options. Read more in the
Puppeteer
or Playwright
API docs.
page.setDefaultNavigationTimeout(timeout)
You can tweak the navigation timeout with page.setDefaultNavigationTimeout()
. This impact the timeout limits of the
initial load of your page or app and all subsequent navigation.
Read more in the Puppeteer or Playwright API docs.
page.waitFor(timeout) / page.waitForTimeout(timeout)
Some pages can be finicky because of animations or some non-linear behaviour or dynamic content. This method allows you to “just wait” for a set amount of time. The example below passes in a number. The result is the test will wait for five seconds.
await page.waitFor(5000)
await page.waitForTimeout(5000)
Note: using
page.waitFor(timeout)
orpage.waitForTimeout(timeout)
should be a last option. Use it sparingly as it can quickly break your script if load times vary a lot.
Timeouts in Mocha
Default: 2 sec
At the highest level, you can set timeout values in your Mocha code. These timeouts are enforced regardless of what
happens in the Chromium browser started by Puppeteer or Playwright. For Checkly checks, we recommend using timeout
values on each test. In the example below, we added a .timeout(10000)
to the it
function. The default timeout of
2 seconds is almost always too short to run any meaningful check.
describe('Check Google Homepage', () => {
it('has title "Google"', async () => {
await page.goto('https://google.com', { waitUntil: 'networkidle2' })
const title = await page.title()
assert.equal(title, 'Google')
}).timeout(10000)
})
describe('Check Google Homepage', () => {
it('has title "Google"', async () => {
await page.goto('https://google.com', { waitUntil: 'networkidle' })
const title = await page.title()
assert.equal(title, 'Google')
}).timeout(10000)
})
This means this test will fail if it does not resolve within 10 seconds. You can also add a timeout at the suite level,
however, the syntax is a bit different if you want to use arrow functions: you have to pass in the suite
and then set
the timeout on that object.
describe('Check Google Homepage', (suite) => {
suite.timeout(10000)
it('has title "Google"', async () => {
await page.goto('https://google.com', { waitUntil: 'networkidle2' })
const title = await page.title()
assert.equal(title, 'Google')
})
})
describe('Check Google Homepage', (suite) => {
suite.timeout(10000)
it('has title "Google"', async () => {
await page.goto('https://google.com', { waitUntil: 'networkidle' })
const title = await page.title()
assert.equal(title, 'Google')
})
})
Read more on the Mocha site and in this enlightening GitHub issue.