Puppeteer: Das Schließen des Browsers beim Schließen der letzten Seite führt manchmal zu einem Fehler

Erstellt am 27. März 2018  ·  9Kommentare  ·  Quelle: puppeteer/puppeteer

Erzählen Sie uns von Ihrer Umgebung:

  • Puppenspieler-Version: 1.2.0
  • Plattform- / Betriebssystemversion: CentOS 7.4.1708
  • URLs (falls zutreffend): N / A.
  • Node.js Version: v8.10.0

Welche Schritte werden das Problem reproduzieren?

const puppeteer = require('puppeteer');

const run = async () => {
  const browser = await puppeteer.launch();

  // close any open pages
  let pages = await browser.pages();
  await Promise.all(pages.map(async page => await page.close()));

  // handle a page being closed
  browser.on('targetdestroyed', async target => {
    const openPages = await browser.pages();
    console.log('Open pages:', openPages.length);
    if (openPages.length == 0) {
      console.log('Closing empty browser');
      await browser.close();
      console.log('Browser closed');
    }
  });

  pages = await browser.pages();
  for (let i = 0; i < 5; i++) {
    await browser.newPage();
  }

  pages = await browser.pages();
  pages.forEach((page) => page.close());
};

run();

Was ist das erwartete Ergebnis?

Count: 0
Open pages: 4
Open pages: 3
Open pages: 2
Open pages: 1
Open pages: 0
Closing empty browser
Browser closed

Was passiert stattdessen?

Open pages: 4
Open pages: 3
Open pages: 2
Open pages: 1
Open pages: 0
Closing empty browser
(node:29074) UnhandledPromiseRejectionWarning: Error: Protocol error (Target.closeTarget): Target closed.
    at Promise (node_modules/puppeteer/lib/Connection.js:86:56)
    at new Promise (<anonymous>)
    at Connection.send (node_modules/puppeteer/lib/Connection.js:85:12)
    at Page.close (node_modules/puppeteer/lib/Page.js:802:36)
    at pages.forEach (test.js:27:32)
    at Array.forEach (<anonymous>)
    at run (test.js:27:9)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
(node:29074) 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:29074) [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.
Browser closed

Dies passiert nicht bei jedem Lauf, aber immer wieder. Es ist durchaus möglich, dass ich hier etwas Schlechtes mache!

bug chromium

Alle 9 Kommentare

+1

Nach einigem schnellen Debuggen wird das Ereignis targetDestroyed ausgelöst, bevor die Nachricht page.close() bestätigt wurde (oder besser gesagt, der Puppenspieler hat die Bestätigung verarbeitet).

Ich kann mir Möglichkeiten vorstellen, dies zu beheben - z. B. warten Sie auf eine Bestätigung von Target.closeTarget , bevor Sie das targetDestroyed -Ereignis behandeln. Wie man das richtig macht, ist eine andere Frage!

Danke @MJPA für die Untersuchung.

Das Problem ergibt sich aus der Tatsache, dass browser Ziele aus der Liste entfernt, bevor die zugehörigen Seiten geschlossen werden.

Im folgenden Beispiel würde ich erwarten, dass browser.pages() 1 Seite meldet, bis der Aufruf von page.close() beendet ist:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const [page] = await browser.pages();
  browser.on('targetdestroyed', async () => console.log('Target destroyed. Pages count: ' + (await browser.pages()).length));
  console.log('closing page...');
  await page.close();
  console.log('page closed.');
  await browser.close();
})();

Die Ausgabe:

closing page...
Target destroyed. Pages count: 0
page closed.

@ JoelEinbinder was denkst du?

Gleiches Problem wie bei @MJPA

        await page.waitForSelector('aside.aside a[href="/checkout"]')
    .then(() => {
        logMessage('Checkout is available');
    })
    .catch(err => {
        log('Checkout not available');
        success = false;
        browser.close();
    });
    // click checkout button
    await Promise.all([
        page.click('aside.aside a[href="/checkout"]'),
        page.waitForNavigation()
    ]);

In diesem Fall sind immer die gleichen Fehler aufgetreten.

Vielleicht dieser Puppenspielfehler.

Ich erhalte den gleichen Fehler bei einer etwas ähnlichen Implementierung, aber durch Abhören des Ereignisses close auf einem page . Die Dokumentation ist nicht allzu explizit darüber, was "Schließen" bedeutet, aber gemäß der Implementierung wird close ausgelöst, bevor die Seite geschlossen wird, sodass das folgende Beispiel nicht funktioniert, ähnlich wie das targetdestroyed Beispiel.

const puppeteer = require('puppeteer');
const browser = await puppeteer.launch();

// create 5 test pages
for(let i=0; i<5; i++){

  let page = await browser.newPage();

  // listen to page close event and close browser
  // when all get closed
  page.on('close', async () => {
    let pages = await browser.pages();
    if(!pages.length){
      // will not get here!
      browser.close();
    }
  });

}

let pages = await browser.pages();

pages.forEach(async (page) => await page.close());  

Ich denke, diese Art von Sinn macht Sinn, da das Ereignis close und nicht closed . Ich habe das alleine umgangen, indem ich mein eigenes closed -Ereignis im close() -Versprechungsrückruf ausgegeben und stattdessen das angehört habe, was funktioniert.

page.close().then(() => {
  page.emit('closed');
});

Die Moral der Geschichte scheint zu sein, dass es keine 100% garantierte Methode gibt, um zu wissen, wann ein Browser sicher geschlossen werden kann (ohne die Ausnahme abfangen zu müssen). Bitte lassen Sie mich wissen, wenn mir etwas fehlt.

Möglicherweise verwandt mit # 3423?

Ich erhalte dies, nachdem ich alle Registerkarten in einem Browser geschlossen und dann versucht habe, eine neue Seite zu öffnen

Im folgenden Beispiel würde ich erwarten, dass browser.pages() 1 Seite meldet, bis der Aufruf von page.close() beendet ist:

Dies würde den Code im obigen Beispiel beschädigen. Die letzte geschlossene Seite, brower.pages (). Länge wäre 1, der Browser würde nicht geschlossen. Dann würden keine vom Ziel zerstörten Ereignisse mehr kommen und der Browser würde für immer leben.

Ich würde sagen, dies ist ein Benutzerfehler, obwohl er subtil ist. Alle Anrufe an page.close sollten abgewartet werden, bevor browser.close() angerufen wird.

Wenn keine benutzergesteuerten Seiten vorhanden sind, schließen Sie einfach Promise.all alle Seiten und schließen Sie den Browser. Es sollte nicht nötig sein, die targetdestroyed -Ereignisse anzuhören. Oder rufen Sie einfach browser.close auf und lassen Sie alle Seiten für sich schließen.

Wenn es benutzergesteuerte Seiten gibt, hören Sie sich die targetdestroyed -Ereignisse an, aber es sollte nicht erforderlich sein, page.close aufzurufen.

+1.
Ich habe diesen Fehler gesehen, nachdem ich Popups mit page.on("popup") . Wenn ein Popup in einem Frame-Kontext ausgelöst wird, kann ich es manchmal nicht mit "popup.close ()" schließen und der Puppenspieler lässt die Registerkarte geöffnet. Wenn ich also versuche, den gesamten Browser zu schließen, wird der Fehler "Ziel geschlossen" ausgegeben. und das Skript stoppt.

Ich benutze Puppenspieler 1.14 mit Chrome 73 (ich benutze kein Chromium)
Der Code, mit dem ich den Browser schließe

async close_browser() {
    for (let page of await this.browser.pages()) {
      await page.close({
        "runBeforeUnload": true
      });
    }
   await this.browser.close();
}
War diese Seite hilfreich?
0 / 5 - 0 Bewertungen