Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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:

https://github.com/GoogleChrome/puppeteer/issues/386 https://github.com/GoogleChrome/puppeteer/issues/3535 https://github.com/GoogleChrome/puppeteer/issues/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)

Note: issue I've created on github: https://github.com/GoogleChrome/puppeteer/issues/3718

like image 342
Dheeraj Bhaskar Avatar asked Jan 02 '19 07:01

Dheeraj Bhaskar


1 Answers

first run the click function first of all and remove "global" inside the promise and declaring browser as constant outside the promise

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;
like image 153
Iggnaxios Avatar answered Sep 22 '22 04:09

Iggnaxios