Allgemeinen, können Sie nicht auf await tempPage.click("a.orange")
verlassen Ausführung zu pausieren, bis tempPage
hat „beenden [ed] seine Aufgaben“. Für super einfachen Code, der synchron ausgeführt wird, kann es funktionieren. Aber im Allgemeinen können Sie sich nicht darauf verlassen.
Wenn der Klick eine AJAX-Operation auslöst oder eine CSS-Animation startet oder eine Berechnung startet, die nicht sofort berechnet werden kann, oder eine neue Seite usw. öffnet, ist das Ergebnis asynchron und die .click
Die Methode wartet nicht auf die Beendigung dieses asynchronen Vorgangs.
Was können Sie tun? In einigen Fällen können Sie möglicherweise in den auf der Seite ausgeführten Code eingreifen und auf ein Ereignis warten, das für Sie von Bedeutung ist. Wenn Sie beispielsweise warten möchten, dass eine Ajax-Operation ausgeführt wird und der Code auf der Seite jQuery verwendet, können Sie ajaxComplete
verwenden, um festzustellen, wann die Operation abgeschlossen ist. Wenn Sie sich nicht in ein Ereignissystem einklinken können, um festzustellen, wann die Operation abgeschlossen ist, müssen Sie möglicherweise die Seite abfragen, um auf den Nachweis zu warten, dass die Operation ausgeführt wurde. Hier
ist ein Beispiel, das das Problem zeigt:
const puppeteer = require('puppeteer');
function getResults(page) {
return page.evaluate(() => ({
clicked: window.clicked,
asynchronousResponse: window.asynchronousResponse,
}));
}
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
await page.goto("https://example.com");
// We add a button to the page that will click later.
await page.evaluate(() => {
const button = document.createElement("button");
button.id = "myButton";
button.textContent = "My Button";
document.body.appendChild(button);
window.clicked = 0;
window.asynchronousResponse = 0;
button.addEventListener("click",() => {
// Synchronous operation
window.clicked++;
// Asynchronous operation.
setTimeout(() => {
window.asynchronousResponse++;
}, 1000);
});
});
console.log("before clicks", await getResults(page));
const button = await page.$("#myButton");
await button.click();
await button.click();
console.log("after clicks", await getResults(page));
await page.waitForFunction(() => window.asynchronousResponse === 2);
console.log("after wait", await getResults(page));
await browser.close();
});
Der setTimeout
Code simuliert jede Art von asynchronem Betrieb durch den Klick gestartet.
Wenn Sie diesen Code ausführen, werden Sie auf der Konsole sehen:
before click { clicked: 0, asynchronousResponse: 0 }
after click { clicked: 2, asynchronousResponse: 0 }
after wait { clicked: 2, asynchronousResponse: 2 }
Sie sehen, dass clicked
sofort zweimal durch die beiden Klicks erhöht wird. Es dauert jedoch eine Weile, bevor asynchronousResponse
inkrementiert wird. Die Anweisung await page.waitForFunction(() => window.asynchronousResponse === 2)
fragt die Seite ab, bis die Bedingung erfüllt ist, auf die wir warten.
Sie haben in einem Kommentar erwähnt, dass die Schaltfläche die Registerkarte schließt. Öffnen und Schließen von Registerkarten sind asynchrone Vorgänge.Hier ein Beispiel:
puppeteer.launch().then(async browser => {
let pages = await browser.pages();
console.log("number of pages", pages.length);
const page = pages[0];
await page.goto("https://example.com");
await page.evaluate(() => {
window.open("https://example.com");
});
do {
pages = await browser.pages();
// For whatever reason, I need to have this here otherwise
// browser.pages() always returns the same value. And the loop
// never terminates.
await page.evaluate(() => {});
console.log("number of pages after evaluating open", pages.length);
} while (pages.length === 1);
let tempPage = pages[pages.length - 1];
// Add a button that will close the page when we click it.
tempPage.evaluate(() => {
const button = document.createElement("button");
button.id = "myButton";
button.textContent = "My Button";
document.body.appendChild(button);
window.clicked = 0;
window.asynchronousResponse = 0;
button.addEventListener("click",() => {
window.close();
});
});
const button = await tempPage.$("#myButton");
await button.click();
do {
pages = await browser.pages();
// For whatever reason, I need to have this here otherwise
// browser.pages() always returns the same value. And the loop
// never terminates.
await page.evaluate(() => {});
console.log("number of pages after click", pages.length);
} while (pages.length > 1);
await browser.close();
});
Wenn ich die oben laufen, erhalte ich:
number of pages 1
number of pages after evaluating open 1
number of pages after evaluating open 1
number of pages after evaluating open 2
number of pages after click 2
number of pages after click 1
Sie können sehen, es dauert ein wenig, bevor window.open()
und window.close()
haben nachweisbare Effekte.
In Ihrem Kommentar schrieb auch:
Ich dachte await
war im Grunde, was eine asynchrone Funktion in ein einer synchronen gedreht
würde ich nicht sagen, dass es asynchrone Funktionen in synchronem dreht Einsen. Der aktuelle Code wartet darauf, dass die Zusage einer asynchronen Operation aufgelöst oder zurückgewiesen wird. Wichtiger für das hier vorliegende Problem ist jedoch, dass Sie zwei virtuelle Maschinen haben, die JavaScript-Code ausführen: Es gibt einen Knoten, auf dem puppeteer
läuft, und das Skript, das den Browser steuert, und es gibt den Browser selbst, der über eine eigene virtuelle JavaScript-Maschine verfügt. Alle await
, die Sie auf der Node-Seite verwenden, wirken sich nur auf den Node-Code aus: Sie haben keinen Einfluss auf den Code, der im Browser ausgeführt wird.
Es kann verwirrend werden, wenn Sie Dinge wie await page.evaluate(() => { some code; })
sehen. Es sieht so aus, als ob es aus einem Stück besteht und alle in derselben virtuellen Maschine ausgeführt werden, aber das ist es nicht. puppeteer
nimmt den übergebenen Parameter an .evaluate
, serialisiert es und sendet es an den Browser, wo es ausgeführt wird. Versuchen Sie, im obigen Skript nach const button = ...
etwas wie await page.evaluate(() => { button.click(); });
hinzuzufügen. Etwas wie folgt aus:
const button = await tempPage.$("#myButton");
await button.click();
await page.evaluate(() => { button.click(); });
Im Skript wird button
vor page.evaluate
definiert, aber Sie werden ein ReferenceError
wenn page.evaluate
läuft, weil button
nicht auf der Browser-Seite definiert erhalten!