Although OP's answer of adding async
to the page.on
callback solves the immediate problem, a more general approach is to promisify the callback. The trouble with OP's solution is that if many actions need to occur after the log, all of them must be nested inside the callback's if
statement or moved to a separate function.
Here's a general helper function that works like a normal waitForX
call in Puppeteer, cleaning up its event listener and integrating nicely into the normal control flow:
const puppeteer = require("puppeteer"); // ^19.0.0
const waitForConsoleLog = async (page, text) => {
let resolve;
const consoleP = new Promise(r => resolve = r);
const handleConsole = message =>
message.text() === text && resolve();
page.on("console", handleConsole);
await consoleP;
page.off("console", handleConsole);
};
const html = `
<script>
setTimeout(() => console.log("page-not-completed"), 2000);
setTimeout(() => {
console.log("page-completed");
document.body.innerHTML = "done";
}, 5000);
</script>
`;
let browser;
(async () => {
browser = await puppeteer.launch();
const [page] = await browser.pages();
await page.setContent(html);
await waitForConsoleLog(page, "page-completed");
console.log(await page.evaluate(() => document.body.textContent)); // => done
})()
.catch(err => console.error(err))
.finally(() => browser?.close());
Currently, waitForConsoleLog
blocks forever, so a good next step would be to add a timeout to it. For example:
const waitForConsoleLog = async (
page,
text,
config={timeout: 30000}
) => {
const msg =
`Waiting for console.log failed (${config.timeout}ms timeout exceeded)`;
let resolve;
let reject;
const consoleP = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = () => _reject(msg);
});
const handleConsole = message =>
message.text() === text && resolve();
page.on("console", handleConsole);
const timeoutId = setTimeout(reject, config.timeout);
await consoleP;
clearTimeout(timeoutId);
page.off("console", handleConsole);
};
Note that message.text()
has some unexpected behavior edge cases that can make the text you see in the console different from what .text()
returns. This answer offers a way to handle some of these edge cases.
A similar approach can be used to promisify dialog handlers, among other things.