puppeteer: waitForNavigation doesn't work after clicking a link
Steps to reproduce
Tell us about your environment:
- Puppeteer version: 0.13.0
- Platform / OS version: Win10x64 / Chromium 64.0.3270.0 r516713
What steps will reproduce the problem?
const puppeteer = require('puppeteer');
(async () => {
try {
const browser = await puppeteer.launch({
headless : false,
executablePath : 'D:/path/to/chromium/chrome.exe',
userDataDir : 'D:/path/to/userdir',
args : ['about:blank']
});
const pages = await browser.pages();
const page = pages[0];
page.on('load', () => console.log('Loaded: ' + page.url()));
// goto: page1.html
await page.goto('https://leo020588.bitbucket.io/page1.html', {waitUntil: 'load'});
// workaround-fix
/*await page.evaluate(() => {
let link = document.querySelector('a');
link.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
window.location = link.href;
});
});*/
// click: link (page2.html)
await page.click('a');
//
await page.waitForNavigation({waitUntil: 'load'});
console.log('page2.html has arrived ... the wait is over');
//
await browser.close();
} catch (e) {
console.log(e);
}
})();
What is the expected result?
page2.html has arrived ... the wait is over
What happens instead?
Error: Navigation Timeout Exceeded: 30000ms exceeded
Note
If I uncomment the workaround-fix works as espected.
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 11
- Comments: 32 (7 by maintainers)
@ebidel Sorry if this is not the place to discuss standard-javascript implementation/understanding. What suggested @vsemozhetbyt works for me. But I still think your suggestion has the same behavior as my code, I would like to clarify if am I wrong for not to confuse other readers before I close the issue.
OPTION-1 (@leo020588) - my original code doesn’t work as expected (Error: Navigation Timeout)
OPTION-2 (@vsemozhetbyt) - works as expected (“The wait is over 2”)
OPTION-3 (@ebidel) - doesn’t work as expected (Error: Navigation Timeout)
Edit: I like @aslushnikov’s approach better, it makes more sense:
@leo020588 another way to think of
waitForNavigationis that it waits for the navigation to start and complete.In your case, the
await page.click('a')returns control to the script too late - the navigation has already started.The handy way to do this is:
FWIW, if the
is replaced by the
the code seems to work as expected. It may be a race between
page.click()andpage.waitForNavigation().@vsemozhetbyt is correct, this is a race condition (or really an async orchestration problem), so you’ll need to have the
page.waitForNavigation()“ready” before the click action completes.FWIW I came across this in the docs and it seems to be the perfect solution, avoiding race conditions and ordering the execution correctly:
So if I understand correctly one needs to start the waitForNavigation before clicking the item like this:
which is better expressed as
as explained earlier.
This is somewhat counter intuitive, as we humans expect to act and then wait for something. Thus it would be nice, if the framework could provide method for this with signature
Currently I have this in my project:
Which gives you all the power to click and wait, but none of the details how it should be orchestrated.
I think all projects need this so every project out there have some form of this helper method duplicated, which makes it prime candidate to be included in the library.
@leo020588 please close if that addresses the issue.
You could also do this if it’s easier to reason about the sequence:
@dowsanjack looks like you’re dealing with a single-page application. The reason the pattern doesn’t work for you is because browser doesn’t do any “navigation” - instead, current page DOM is modified. Thus, using
waitForNavigationwon’t work.It’s not always clear if a website is doing navigation or not. A good rule of a thumb is to rely on page state instead, using methods
waitForSelectorandwaitForFunction.I tried so many things and after about an hour or so this is the only way that it worked for me:
@aslushnikov exposing the ability to wait for 0 network requests that doesn’t depend on navigation would solve this if it’s feasible:
Imagine you click a button and need to wait for images to load
Coming here to say that I was also bit by this sharp edge; maybe a little doc note would be helpful!
So from a “pure promises perspective” :p, I believe my explanation is correct but the reason 3 doesn’t work for the use case is b/c the click actually kicks off a page navigation and you’re
waitForNavigationafter that happens. So you hang forever.What @joelgriffith suggested works great. Just go with that 😃
Thats slightly different. In my example you’re awaiting the resolution of the entire promise chain, not just the
page.clickpromise. It’s the same as:So in the first example below, “2” will probably log before
anotherPromiseThatTakesAwhileToResolveresolves:You can fix that with:
Here is my function to wait for networkidle2 without waiting for navigation first:
@ebidel But, are they not the same?
Both are waiting for
page.click()to resolve. I was thinking if it would be a good idea to implement something like:Because not waiting for
page.click()to resolve, feels someway hackish to me.@joelgriffith how can I do that? Is what @vsemozhetbyt suggested the right way to go?
In this way can be we sure there will be no race condition?
Version: 1.1.1
As mentioned in the linked issue, this seems not to work for an SPA that doesn’t trigger a load event:
Can we perhaps expose something like:
page.waitFor({ waitUntil: 'networkidle0' })?This worked for me as well
It would be nice to have the
ClickOptionsextendNavigationOptionsas to be able to specify awaitUntilfor the.click()method (similarly to the.goto()method)I had the same problem. Here is the solution I went for:
You can also filter requests by any criteria to make have a more efficient solution. In my case, i am interested in all requests.
This still does not work for me. In my use case, I click on the login button, it starts a spinner, makes a network request, on a successful response, takes the user to a dashboard page.
Should I be using a different approach?