1
const puppeteer = require("puppeteer");

(async () => {
    const browser = await puppeteer.launch({
        args: ["--no-sandbox", "--disable-setuid-sandbox"],
    });
    const page = await browser.newPage();
    await page.setViewport({ width: 3840, height: 2160 });
    await page.goto("https://example.com");
    page.on("console", (message) => {
        console.log(
            `${message.type().substr(0, 3).toUpperCase()} ${message.text()}`
        );
        if (message.text() == "page-completed") {
             process.exit(0);
        }
    });
    await new Promise((r) => setTimeout(r, 10000));

    await page.screenshot({ path: "example.png", fullPage: true });
    await browser.close();
})();

I have trouble with this await/async style of programming. If I move the screenshot code above process.exit(0), it tells me that await can only be used in an async function.

How can I take a screenshot after receiving the log page-completed?

ggorlen
  • 44,755
  • 7
  • 76
  • 106
bilogic
  • 449
  • 3
  • 18

3 Answers3

1
const puppeteer = require("puppeteer");

(async () => {
    const browser = await puppeteer.launch({
        args: ["--no-sandbox", "--disable-setuid-sandbox"],
    });
    const page = await browser.newPage();
    await page.setViewport({ width: 3840, height: 2160 });
    await page.goto("https://example.com");
    page.on("console", async (message) => {
        console.log(
            `${message.type().substr(0, 3).toUpperCase()} ${message.text()}`
        );
        if (message.text() == "page-completed") {
            console.log("taking screenshot");
            await page.screenshot({ path: "example.png", fullPage: true });
            await browser.close();
        }
    });
})();

After some intensive trial and error

bilogic
  • 449
  • 3
  • 18
0

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.

ggorlen
  • 44,755
  • 7
  • 76
  • 106
-1

It seems you forgot to keep await keyword in front of the function. enter image description here

  • thanks but it needed more than that, please see my answer – bilogic Dec 29 '22 at 12:25
  • 1
    [Please don't post pictures of code](https://meta.stackoverflow.com/questions/285551/why-should-i-not-upload-images-of-code-data-errors-when-asking-a-question). This is also incorrect, `page.on` registers a callback and does not return a promise. `await`ing it is misleading. – ggorlen Dec 29 '22 at 15:13