Parlez-nous de votre environnement:
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!
+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'appelpage.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();
}