Puppeteer: Fechar o navegador quando a última página é fechada às vezes gera um erro

Criado em 27 mar. 2018  ·  9Comentários  ·  Fonte: puppeteer/puppeteer

Conte-nos sobre seu ambiente:

  • Versão do Puppeteer: 1.2.0
  • Versão da plataforma / sistema operacional: CentOS 7.4.1708
  • URLs (se aplicável): N / A
  • Versão Node.js: v8.10.0

Quais passos vão reproduzir o 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();

Qual é o resultado esperado?

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

O que acontece em vez disso?

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

Isso não acontece todas as execuções, mas acontece de vez em quando. É perfeitamente possível que eu esteja fazendo algo _muito_ aqui!

bug chromium

Todos 9 comentários

+1

Efetuou uma pequena depuração rápida, o evento targetDestroyed é disparado antes que a mensagem page.close() tenha sido confirmada (ou melhor, o titereiro processou a confirmação).

Eu posso pensar em maneiras de consertar isso - por exemplo, esperar por uma confirmação de Target.closeTarget antes de lidar com o evento targetDestroyed . Como fazer isso corretamente é outra questão!

Obrigado @MJPA pela investigação.

O problema vem do fato de que browser remove destinos da lista antes que as páginas associadas sejam fechadas.

No exemplo a seguir, esperaria que browser.pages() relatasse 1 página até que a chamada page.close() fosse concluída:

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

A saída:

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

@JoelEinbinder o que você acha?

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

Nesse caso, sempre ocorreram os mesmos erros.

Talvez esse erro do titereiro.

Recebo o mesmo erro com uma implementação ligeiramente semelhante, mas ouvindo o evento close em um page . A documentação não é muito explícita sobre o que "fechar" significa, mas de acordo com sua implementação, close é disparado _antes_ da página ser fechada, então o seguinte exemplo não funciona, semelhante ao targetdestroyed exemplo.

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

Eu acho que este _sort of_ faz sentido, já que o evento é chamado close e não closed . Eu tenho em torno deste em meu próprio, emitindo meu próprio closed evento no close() callback promessa e ouvir que, em vez, que _does_ trabalho.

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

A moral da história parece ser que não existe um método 100% garantido de saber quando um navegador é seguro para fechar (sem ter que detectar a exceção). Avise-me se estiver faltando alguma coisa.

Possivelmente relacionado a # 3423?

Eu recebo isso depois de fechar todas as guias em um navegador e, em seguida, tentar abrir uma nova página

No exemplo a seguir, esperaria que browser.pages() relatasse 1 página até que a chamada page.close() fosse concluída:

Isso quebraria o código do exemplo acima. A última página fechada, brower.pages (). Length seria 1, o navegador não seria fechado. Então, nenhum evento de destino destruído viria e o navegador viveria para sempre.

Eu diria que é um erro do usuário, embora seja sutil. Quaisquer chamadas para page.close devem ser aguardadas antes de chamar browser.close() .

Se não houver páginas controladas pelo usuário, simplesmente prometa. Todas as páginas fecham e feche o navegador. Não deve haver necessidade de ouvir os eventos targetdestroyed . Ou apenas chame browser.close e deixe todas as páginas fecharem sozinhas.

Se houver páginas controladas pelo usuário, ouça os eventos targetdestroyed , mas não haverá necessidade de chamar page.close .

+1.
Comecei a ver esse erro depois de fechar pop-ups com page.on("popup") . Quando um pop-up é disparado dentro de um contexto de Frame, às vezes não consigo fechá-lo com "popup.close ()" e o titereiro deixa a guia aberta, então, quando tento fechar o navegador inteiro, ele exibe um erro de "Alvo fechado", e o script pára.

Eu uso o titereiro 1.14, com Chrome 73 (não uso o Chromium)
O código que uso para fechar o navegador

async close_browser() {
    for (let page of await this.browser.pages()) {
      await page.close({
        "runBeforeUnload": true
      });
    }
   await this.browser.close();
}
Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

barnash picture barnash  ·  3Comentários

kesava picture kesava  ·  3Comentários

sradu picture sradu  ·  3Comentários

paulirish picture paulirish  ·  3Comentários

ryanvincent29 picture ryanvincent29  ·  3Comentários