Puppeteer: Cerrar el navegador cuando se cierra la última página a veces arroja un error

Creado en 27 mar. 2018  ·  9Comentarios  ·  Fuente: puppeteer/puppeteer

Cuéntanos sobre tu entorno:

  • Versión titiritero: 1.2.0
  • Versión de la plataforma / SO: CentOS 7.4.1708
  • URL (si corresponde): N / A
  • Versión de Node.js: v8.10.0

¿Qué pasos reproducirán el problema?

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

¿Cuál es el resultado esperado?

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

¿Qué pasa en su lugar?

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

Esto no sucede en todas las ejecuciones, pero sucede de vez en cuando. ¡Es muy posible que esté haciendo algo _mal_ aquí!

bug chromium

Todos 9 comentarios

+1

Hizo un poco de depuración rápida, el evento targetDestroyed se activa antes de que se reconozca el mensaje page.close() (o más bien, el titiritero haya procesado el reconocimiento).

Puedo pensar en formas de solucionarlo, por ejemplo, esperar un reconocimiento de Target.closeTarget antes de manejar el evento targetDestroyed . ¡Sin embargo, cómo hacerlo correctamente es otra cuestión!

Gracias @MJPA por la investigación.

El problema proviene del hecho de que browser elimina los objetivos de la lista antes de que se cierren las páginas asociadas.

En el siguiente ejemplo, esperaría que browser.pages() informe 1 página hasta que finalice la llamada page.close() :

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

La salida:

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

@JoelEinbinder ¿qué opinas?

Mismo problema 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()
    ]);

En este caso, siempre ocurrieron los mismos errores.

Quizás este error del titiritero.

Recibo el mismo error con una implementación ligeramente similar, pero al escuchar el evento close en un page . La documentación no es demasiado explícita sobre lo que significa "cerrar", pero de acuerdo con su implementación, close se activa _antes_ de que se cierre la página, por lo que el siguiente ejemplo no funciona, similar al targetdestroyed ejemplo.

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

Supongo que este _sort of_ tiene sentido, ya que el evento se llama close y no closed . Solucioné esto por mi cuenta emitiendo mi propio evento closed en la devolución de llamada de promesa close() y escuchando eso en su lugar, que _ sí_ funciona.

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

La moraleja de la historia parece ser que no existe un método 100% garantizado para saber cuándo es seguro cerrar un navegador (sin tener que detectar la excepción). Avísame si me falta algo.

¿Posiblemente relacionado con el # 3423?

Recibo esto después de cerrar todas las pestañas en un navegador y luego intentar abrir una nueva página

En el siguiente ejemplo, esperaría que browser.pages() informe 1 página hasta que finalice la llamada page.close() :

Esto rompería el código del ejemplo anterior. La última página cerrada, brower.pages (). Length sería 1, el navegador no se cerraría. Entonces no vendrían más eventos de destrucción de objetivos y el navegador viviría para siempre.

Yo diría que esto es un error del usuario, aunque es sutil. Cualquier llamada a page.close debe esperarse antes de llamar a browser.close() .

Si no hay páginas controladas por el usuario, simplemente Promise.all se cierra toda la página y luego cierra el navegador. No debería ser necesario escuchar los eventos targetdestroyed . O simplemente llame a browser.close y deje que todas las páginas se cierren solas.

Si hay páginas controladas por el usuario, escuche los eventos targetdestroyed , pero no debería ser necesario llamar a page.close .

+1.
Comencé a ver este error después de cerrar las ventanas emergentes con page.on("popup") . Cuando una ventana emergente se dispara dentro de un contexto de Frame, a veces no puedo cerrarla con "popup.close ()" y el titiritero deja la pestaña abierta, así que cuando intento cerrar todo el navegador, arroja un error de "Objetivo cerrado", y el guión se detiene.

Yo uso titiritero 1.14, con Chrome 73 (no uso Chromium)
El código que utilizo para cerrar el navegador.

async close_browser() {
    for (let page of await this.browser.pages()) {
      await page.close({
        "runBeforeUnload": true
      });
    }
   await this.browser.close();
}
¿Fue útil esta página
0 / 5 - 0 calificaciones