question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Feature: Simpler way to handle pages created on clicking a[target="_blank"]; wait for loading and include timeouts

See original GitHub issue

Overview

I’m looking for a simpler way to handle clicking on links which open new pages (like target=“_blank” anchor tags).

Here handle means:

  • get the new page object
  • wait for the new tab to load (with timeout)

Steps to reproduce

Tell us about your environment:

  • Puppeteer version: ^1.11.0
  • Platform / OS version: 64-bit, win 10 pro
  • URLs (if applicable): none
  • Node.js version: v10.15.0

I’ve looked at related issues: #386 #3535 #978 and more

What steps will reproduce the problem? I’ve included the code snippet below

I’m trying to:

  1. Get the object for the new page when clicking on a link opens a new tab. (The links are dynamically generated, capturing href might not be the most elegant way)
  2. Wait till the new page loads (with timeout). I’d like it if you can use page.waitForNavigation for consistency
  3. close the tab and return the earlier tab to continue further operations

Please include code that reproduces the issue.

// as referenced here on #386 : https://github.com/GoogleChrome/puppeteer/issues/386#issuecomment-425109457
    const getNewPageWhenLoaded =  async () => {
        return new Promise(x =>
            global.browser.on('targetcreated', async target => {
                if (target.type() === 'page') {
                    const newPage = await target.page();
                    const newPagePromise = new Promise(y =>
                        newPage.once('domcontentloaded', () => y(newPage))
                    );
                    const isPageLoaded = await newPage.evaluate(
                        () => document.readyState
                    );
                    return isPageLoaded.match('complete|interactive')
                        ? x(newPage)
                        : x(newPagePromise);
                }
            })
        );
    };


const newPagePromise = getNewPageWhenLoaded();
await page.click('my-link'); // or just do await page.evaluate(() => window.open('https://www.example.com/'));
const newPage = await newPagePromise;

What is the expected result? An easier and consistent way to handle new tabs

What happens instead? The developer has to write what looks like plumbing (internal/ low level) commands. Usage of waitForTarget might simplify this, but I’ve not been able to get the predicate to return the right types. Here’s my non-functional code

private async getNewPageWhenLoaded() {
        const newTarget = await this._browser.waitForTarget(async (target) => {
            const newPage = await target.page();
            await newPage.waitForNavigation(this._optionsNavigation);
            // const newPagePromise = new Promise(() => newPage.once('load', () => x(newPage)));
            return await newPage.evaluate("true");
        });
        return await newTarget.page();
    }

// elsewhere in the code
            const newPagePromise = this.getNewPageWhenLoaded();
            await resultItem.element.click();
            const newPage = <Page>await newPagePromise;

//I get the following error
DevTools listening on ws://127.0.0.1:31984/devtools/browser/bf86648d-d52d-42d8-a392-629bf96211d4
(node:5564) UnhandledPromiseRejectionWarning: Error: Navigation failed because browser has disconnected!
    at CDPSession.LifecycleWatcher._eventListeners.helper.addEventListener (<path-to-my-project>\node_modules\puppeteer\lib\FrameManager.js:1181:107)
    at CDPSession.emit (events.js:182:13)
    at CDPSession._onClosed (<path-to-my-project>\node_modules\puppeteer\lib\Connection.js:231:10)
    at Connection._onMessage (<path-to-my-project>\node_modules\puppeteer\lib\Connection.js:103:19)
    at WebSocketTransport._ws.addEventListener.event (<path-to-my-project>\node_modules\puppeteer\lib\WebSocketTransport.js:41:24)
    at WebSocket.onMessage (<path-to-my-project>\node_modules\ws\lib\event-target.js:120:16)
    at WebSocket.emit (events.js:182:13)
    at Receiver.receiverOnMessage (<path-to-my-project>\node_modules\ws\lib\websocket.js:741:20)
    at Receiver.emit (events.js:182:13)
    at Receiver.dataMessage (<path-to-my-project>\node_modules\ws\lib\receiver.js:417:14)
  -- ASYNC --
    at Frame.<anonymous> (<path-to-my-project>\node_modules\puppeteer\lib\helper.js:144:27)
    at Page.waitForNavigation (<path-to-my-project>\node_modules\puppeteer\lib\Page.js:644:49)
    at Page.<anonymous> (<path-to-my-project>\node_modules\puppeteer\lib\helper.js:145:23)
    at newTarget._browser.waitForTarget (<path-to-my-project>\pageObjects\MyPage.js:104:27)
    at process._tickCallback (internal/process/next_tick.js:68:7)
(node:5564) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:5564) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:5564) UnhandledPromiseRejectionWarning: TimeoutError: Navigation Timeout Exceeded: 300000ms exceeded
    at Promise.then (<path-to-my-project>\node_modules\puppeteer\lib\FrameManager.js:1276:21)
  -- ASYNC --
    at Frame.<anonymous> (<path-to-my-project>\node_modules\puppeteer\lib\helper.js:144:27)
    at Page.waitForNavigation (<path-to-my-project>\node_modules\puppeteer\lib\Page.js:644:49)
    at Page.<anonymous> (<path-to-my-project>\node_modules\puppeteer\lib\helper.js:145:23)
    at newTarget._browser.waitForTarget (<path-to-my-project>\pageObjects\MyPage.js:104:27)
    at process._tickCallback (internal/process/next_tick.js:68:7)
(node:5564) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)

Issue Analytics

  • State:open
  • Created 5 years ago
  • Comments:12 (1 by maintainers)

github_iconTop GitHub Comments

36reactions
dheerajbhaskarcommented, Jan 4, 2019

This is the simplest I’ve gotten it to be

const pageTarget = this._page.target(); //save this to know that this was the opener
await resultItem.element.click(); //click on a link
const newTarget = await this._browser.waitForTarget(target => target.opener() === pageTarget); //check that you opened this page, rather than just checking the url
const newPage = await newTarget.page(); //get the page object
// await newPage.once("load",()=>{}); //this doesn't work; wait till page is loaded
await newPage.waitForSelector("body"); //wait for page to be loaded
16reactions
ignacioainolcommented, Mar 31, 2020

run the click function first of all and within the promise remove the global keyword like this

const browser = await puppeteer.launch();
await page.click('my-link'); 

const getNewPageWhenLoaded =  async () => {
        return new Promise(x =>
            browser.on('targetcreated', async target => {
                if (target.type() === 'page') {
                    const newPage = await target.page();
                    const newPagePromise = new Promise(y =>
                        newPage.once('domcontentloaded', () => y(newPage))
                    );
                    const isPageLoaded = await newPage.evaluate(
                        () => document.readyState
                    );
                    return isPageLoaded.match('complete|interactive')
                        ? x(newPage)
                        : x(newPagePromise);
                }
            })
        );
    };


const newPagePromise = getNewPageWhenLoaded();
const newPage = await newPagePromise;
Read more comments on GitHub >

github_iconTop Results From Across the Web

Puppeteer: Simpler way to handle pages created on clicking a ...
first run the click function first of all and remove "global" inside the promise and declaring browser as constant outside the promise
Read more >
How to make puppeteer wait for page to load - Urlbox
It fires when the initial HTML document's DOM has been loaded and parsed. However, this does NOT wait for stylesheets, images, ...
Read more >
Navigating & waiting - Checkly
This method waits for an element to appear in the page. This is your bread and butter and should be used whenever something...
Read more >
Wait For Web Page Content In Power Automate Desktop
In this Video, you will learn about Wait for web page content action in Power Automate Desktop. Please watch this video till the...
Read more >
Login With Bot - NodeJS Scraping with Puppeteer Tutorial #3
Hey everyone, in this series I will teach you guys how to web scrape using JavaScript and a library called Puppeteer.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found