Puppeteer: La fermeture du navigateur lorsque la dernière page est fermée génère parfois une erreur

Créé le 27 mars 2018  ·  9Commentaires  ·  Source: puppeteer/puppeteer

Parlez-nous de votre environnement:

  • Version du marionnettiste: 1.2.0
  • Version plate-forme / système d'exploitation: CentOS 7.4.1708
  • URL (le cas échéant): N / A
  • Version Node.js: v8.10.0

Quelles étapes vont reproduire le problème?

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();

quel est le résultat attendu?

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

Que se passe-t-il à la place?

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

Cela ne se produit pas à chaque exécution, mais cela arrive de temps en temps. Il est tout à fait possible que je fasse quelque chose de mauvais ici!

bug chromium

Tous les 9 commentaires

+1

Un peu de débogage rapide, l'événement targetDestroyed est déclenché avant que le message page.close() n'ait été reconnu (ou plutôt, le marionnettiste a traité l'accusé de réception).

Je peux penser à des moyens de résoudre ce problème - par exemple, attendre un accusé Target.closeTarget réception targetDestroyed . Comment le faire correctement est une autre question!

Merci @MJPA pour l'enquête.

Le problème vient du fait que browser supprime les cibles de la liste avant que les pages associées ne soient fermées.

Dans l'exemple suivant, je m'attendrais à ce que browser.pages() signale 1 page jusqu'à ce que l'appel page.close() soit terminé:

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();
})();

Le résultat:

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

@JoelEinbinder qu'en pensez-vous?

Même problème que @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()
    ]);

Dans ce cas, les mêmes erreurs se sont toujours produites.

Peut-être cette erreur de marionnettiste.

J'obtiens la même erreur avec une implémentation légèrement similaire, mais en écoutant l'événement close sur un page . La documentation n'est pas trop explicite sur ce que signifie "fermer", mais selon son implémentation, close est déclenché _avant_ la page est fermée, donc l'exemple suivant ne fonctionne pas, similaire au targetdestroyed exemple.

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());  

Je suppose que ce _sort of_ a du sens, puisque l'événement s'appelle close et non closed . J'ai contourné cela moi-même en émettant mon propre événement closed dans le rappel de promesse close() et en écoutant cela à la place, ce qui fonctionne.

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

La morale de l'histoire semble être qu'il n'y a pas de méthode garantie à 100% pour savoir quand un navigateur peut se fermer en toute sécurité (sans avoir à attraper l'exception). S'il vous plaît laissez-moi savoir si je manque quelque chose.

Peut-être lié à # 3423?

J'obtiens cela après avoir fermé tous les onglets dans un navigateur, puis tenté d'ouvrir une nouvelle page

Dans l'exemple suivant, je m'attendrais à ce que browser.pages() signale 1 page jusqu'à ce que l'appel page.close() soit terminé:

Cela briserait le code dans l'exemple ci-dessus. La dernière page fermée, brower.pages (). Length serait 1, le navigateur ne serait pas fermé. Ensuite, plus aucun événement de cible détruite ne viendrait et le navigateur vivrait pour toujours.

Je dirais que c'est une erreur de l'utilisateur, même si elle est subtile. Tout appel à page.close doit être attendu avant d'appeler browser.close() .

S'il n'y a pas de pages contrôlées par l'utilisateur, promettez simplement.toute la page se ferme, puis fermez le navigateur. Il ne devrait pas être nécessaire d'écouter les événements targetdestroyed . Ou appelez simplement browser.close et laissez toutes les pages se fermer toutes seules.

S'il y a des pages contrôlées par l'utilisateur, écoutez les événements targetdestroyed , mais il ne devrait pas être nécessaire d'appeler page.close .

+1.
J'ai commencé à voir cette erreur après avoir fermé les popups avec page.on("popup") . Lorsqu'une fenêtre contextuelle se déclenche dans un contexte Frame, je ne peux parfois pas la fermer avec "popup.close ()" et que le marionnettiste laisse l'onglet ouvert, donc lorsque j'essaye de fermer tout le navigateur, il lance une erreur "Cible fermée", et l'arrêt du script.

J'utilise marionnettiste 1.14, avec Chrome 73 (je n'utilise pas Chromium)
Le code que j'utilise pour fermer le navigateur

async close_browser() {
    for (let page of await this.browser.pages()) {
      await page.close({
        "runBeforeUnload": true
      });
    }
   await this.browser.close();
}
Cette page vous a été utile?
0 / 5 - 0 notes