Erzählen Sie uns von Ihrer Umgebung:
Welche Schritte werden das Problem reproduzieren?
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();
Was ist das erwartete Ergebnis?
Count: 0
Open pages: 4
Open pages: 3
Open pages: 2
Open pages: 1
Open pages: 0
Closing empty browser
Browser closed
Was passiert stattdessen?
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
Dies passiert nicht bei jedem Lauf, aber immer wieder. Es ist durchaus möglich, dass ich hier etwas Schlechtes mache!
+1
Nach einigem schnellen Debuggen wird das Ereignis targetDestroyed
ausgelöst, bevor die Nachricht page.close()
bestätigt wurde (oder besser gesagt, der Puppenspieler hat die Bestätigung verarbeitet).
Ich kann mir Möglichkeiten vorstellen, dies zu beheben - z. B. warten Sie auf eine Bestätigung von Target.closeTarget
, bevor Sie das targetDestroyed
-Ereignis behandeln. Wie man das richtig macht, ist eine andere Frage!
Danke @MJPA für die Untersuchung.
Das Problem ergibt sich aus der Tatsache, dass browser
Ziele aus der Liste entfernt, bevor die zugehörigen Seiten geschlossen werden.
Im folgenden Beispiel würde ich erwarten, dass browser.pages()
1 Seite meldet, bis der Aufruf von page.close()
beendet ist:
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();
})();
Die Ausgabe:
closing page...
Target destroyed. Pages count: 0
page closed.
@ JoelEinbinder was denkst du?
Gleiches Problem wie bei @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()
]);
In diesem Fall sind immer die gleichen Fehler aufgetreten.
Vielleicht dieser Puppenspielfehler.
Ich erhalte den gleichen Fehler bei einer etwas ähnlichen Implementierung, aber durch Abhören des Ereignisses close
auf einem page
. Die Dokumentation ist nicht allzu explizit darüber, was "Schließen" bedeutet, aber gemäß der Implementierung wird close
ausgelöst, bevor die Seite geschlossen wird, sodass das folgende Beispiel nicht funktioniert, ähnlich wie das targetdestroyed
Beispiel.
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());
Ich denke, diese Art von Sinn macht Sinn, da das Ereignis close
und nicht closed
. Ich habe das alleine umgangen, indem ich mein eigenes closed
-Ereignis im close()
-Versprechungsrückruf ausgegeben und stattdessen das angehört habe, was funktioniert.
page.close().then(() => {
page.emit('closed');
});
Die Moral der Geschichte scheint zu sein, dass es keine 100% garantierte Methode gibt, um zu wissen, wann ein Browser sicher geschlossen werden kann (ohne die Ausnahme abfangen zu müssen). Bitte lassen Sie mich wissen, wenn mir etwas fehlt.
Möglicherweise verwandt mit # 3423?
Ich erhalte dies, nachdem ich alle Registerkarten in einem Browser geschlossen und dann versucht habe, eine neue Seite zu öffnen
Im folgenden Beispiel würde ich erwarten, dass
browser.pages()
1 Seite meldet, bis der Aufruf vonpage.close()
beendet ist:
Dies würde den Code im obigen Beispiel beschädigen. Die letzte geschlossene Seite, brower.pages (). Länge wäre 1, der Browser würde nicht geschlossen. Dann würden keine vom Ziel zerstörten Ereignisse mehr kommen und der Browser würde für immer leben.
Ich würde sagen, dies ist ein Benutzerfehler, obwohl er subtil ist. Alle Anrufe an page.close
sollten abgewartet werden, bevor browser.close()
angerufen wird.
Wenn keine benutzergesteuerten Seiten vorhanden sind, schließen Sie einfach Promise.all alle Seiten und schließen Sie den Browser. Es sollte nicht nötig sein, die targetdestroyed
-Ereignisse anzuhören. Oder rufen Sie einfach browser.close auf und lassen Sie alle Seiten für sich schließen.
Wenn es benutzergesteuerte Seiten gibt, hören Sie sich die targetdestroyed
-Ereignisse an, aber es sollte nicht erforderlich sein, page.close
aufzurufen.
+1.
Ich habe diesen Fehler gesehen, nachdem ich Popups mit page.on("popup")
. Wenn ein Popup in einem Frame-Kontext ausgelöst wird, kann ich es manchmal nicht mit "popup.close ()" schließen und der Puppenspieler lässt die Registerkarte geöffnet. Wenn ich also versuche, den gesamten Browser zu schließen, wird der Fehler "Ziel geschlossen" ausgegeben. und das Skript stoppt.
Ich benutze Puppenspieler 1.14 mit Chrome 73 (ich benutze kein Chromium)
Der Code, mit dem ich den Browser schließe
async close_browser() {
for (let page of await this.browser.pages()) {
await page.close({
"runBeforeUnload": true
});
}
await this.browser.close();
}