Conte-nos sobre seu ambiente:
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!
+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 chamadapage.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();
}