javascript - Puppeteer: Jednoduchší spôsob, ako pracovať so stránkami vytvorenými kliknutím na a[target="_blank"]; počkať na načítanie a zahrnúť časové limity


Prehľad

Hľadám jednoduchší spôsob, ako riešiť kliknutie na odkazy, ktoré otvárajú nové stránky (napríklad anchor tagy s target="_blank").

Tu znamená handle:

  • získať nový objekt stránky
  • počkaj, kým sa načíta nová karta (s časovým limitom)

Kroky na reprodukciu

Povedzte nám o vašom prostredí:

  • Verzia Puppeteer: ^1.11.0
  • Platforma/verzia OS: 64-bit, Windows 10 Pro
  • URL adresy (ak je to relevantné): žiadne
  • Verzia Node.js: v10.15.0

Pozrel som sa na súvisiace problémy:

https://github.com/GoogleChrome/puppeteer/issues/386 https://github.com/GoogleChrome/puppeteer/issues/3535 https://github.com/GoogleChrome/puppeteer/issues/978 a ďalšie

Aké kroky spôsobia problém?

Zahrnul som nižšie uvedený úryvok kódu

Snažím sa:

  1. Získajte objekt pre novú stránku, keď kliknutie na odkaz otvorí novú kartu. (Odkazy sú generované dynamicky, zachytenie href nemusí byť najvhodnejší spôsob)

  2. Počkajte, kým sa načíta nová stránka (s časovým limitom). Bol by som rád, keby ste na zachovanie konzistencie mohli použiť page.waitForNavigation

  3. zatvorte kartu a vráťte sa na predchádzajúcu kartu, aby ste pokračovali v ďalších operáciách

Prosím, zahrňte kód, ktorý reprodukuje problém.

// 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;

Aký je očakávaný výsledok?

Jednoduchší a konzistentný spôsob, ako spravovať nové záložky

Čo sa deje namiesto toho?

Vývojár musí písať to, čo vyzerá ako rozvody (interné/nízkoúrovňové) príkazy.

Použitie waitForTarget by to mohlo zjednodušiť, ale nepodarilo sa mi, aby predikát vracal správne typy. Tu je môj nefunkčný kód

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)

Poznámka: issue, ktorú som vytvoril na github: https://github.com/GoogleChrome/puppeteer/issues/3718

Odpoveď č. 1

najprv spustite funkciu click predovšetkým a odstráňte "global" vo vnútri promise a deklarujte browser ako konštantu mimo 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;