1

As it is stated in the documentation of Puppeteer, the basic usage of "dialog" event is the following:

page.on('dialog', async (dialog) => {
  await dialog.dismiss() 
  // or await dialog.accept()
 })

I want to loop through a list of URLs each of them firing a confirm dialog. But I want to accept or dismiss the dialog depending of the page content.

I wonder if it is possible?

When I use it in a loop I get an error: "Cannot dismiss dialog which is already handled!"

for (let url in urls) {
  if (condition) {
    page.on("dialog", async (dialog) => {
      await dialog.accept();
    });
  } else {
    page.on("dialog", async (dialog) => {
      await dialog.dismiss();
    });
  }
}

I'm adding a listener on every loop, so I'm getting an error.

But when I move the "dialog" listener out of the loop, I get a "dialog is undefined" error.

 page.on("dialog", async (dialog) => {

    for (let url in urls) {
      if (condition) {
        await dialog.accept();
      } else {
        await dialog.dismiss();
      }
    }
});

I tried to make a custom event listener.

await page.exposeFunction("test", async (e) => {
  // But I don't know how to dismiss or accept the confirm dialog here.
});

await page.evaluate(() => {
  window.addEventListener("confirm", window.test());
});

The problem with this approach is that I don't have access to handleJavaScriptDialog which is responsible for handling the confirm dialog returns: https://pub.dev/documentation/puppeteer/latest/puppeteer/Dialog/dismiss.html

So far I think the only solution I have is to emulate Enter key press to accept the confirm dialog, or to just go to the next page when I want to dismiss the confirm dialog.

Are there any solutions to using dialog events in a loop like this with Puppeteer?

======

Update

======

//Example for @ggorlen

for (let url in urls) {
  await page.goto(url);

  const dialogDismissed = new Promise((resolve, reject) => {
    const handler = async (dialog) => {
      await dialog.dismiss();
      resolve(dialog.message());
    };
    page.on("dialog", handler);
  });

  const dialogAccepted = new Promise((resolve, reject) => {
    const handler = async (dialog) => {
      await dialog.accept();
      resolve(dialog.message());
    };
    page.on("dialog", handler);
  });

  await page.evaluate(() => window.confirm("Yes or No?"));

  if (condition) {
    //want to accept
    //how to handle the dialog promise here?
  } else {
    //want to dismiss
    //how to handle the dialog promise here?
  }
}

======

Update 2

======

//Based on @ggorlen answer but without promisifing the handler

const puppeteer = require("puppeteer");

let browser;
(async () => {
  const html = `<html><body><script>
    document.write(confirm("yes or no?") ? "confirmed" : "rejected");
  </script></body></html>`;
  browser = await puppeteer.launch({
    headless: true,
  });
  const [page] = await browser.pages();
  const urls = ["just", "a", "demo", "replace", "this"];

  for (const url of urls) {
    const someCondition = Math.random() < 0.5; // for example

    //This bloc is in question.
    //Is there a need to promisify?
    page.once("dialog", async (dialog) => {
      console.log(dialog.message());
      await (someCondition ? dialog.accept() : dialog.dismiss());
    });

    //await page.goto(url, {waitUntil: "networkidle0"});
    await page.setContent(html);
    console.log(await page.$eval("body", (el) => el.innerText));
  }
})()
  .catch((err) => console.error(err))
  .finally(() => browser?.close());
Olek
  • 13
  • 5
  • You might promisify the handler as shown in [Puppeteer not picking up dialog box](https://stackoverflow.com/questions/68585704/puppeteer-not-picking-up-dialog-box/68587534#68587534) and wrap a loop around it. – ggorlen Nov 18 '21 at 01:24
  • @ggorlen I have updated the question using your suggestion. But I still don't understand how use promisified handlers in my case. Can you please explain a bit more? – Olek Nov 25 '21 at 00:48

1 Answers1

0

This answer is a variant of Puppeteer not picking up dialog box. A quick summary of that answer: .on handlers can be promisified to make it easy to integrate waiting for them into control flow without a mess of callbacks. An important nuance that seems lost in OP's code is that if you're only waiting once, use .once rather than .on, or use .off to remove the listener. After it's been resolved, the listener becomes stale.

In this case, let's say you have a bunch of URLs to pages that show confirmation dialogs (or you inject your own confirmation dialog), and for each URL, you want to add a handler for the dialog that lets you accept or dismiss it based on a condition. You might also want to collect the message from the dialog, which is shown below.

Here's a simple example of this:

const puppeteer = require("puppeteer");

let browser;
(async () => {
  const html = `<html><body><script>
    document.write(confirm("yes or no?") ? "confirmed" : "rejected");
  </script></body></html>`;
  browser = await puppeteer.launch({headless: true});
  const [page] = await browser.pages();
  const urls = ["just", "a", "demo", "replace", "this"];

  for (const url of urls) {
    const someCondition = Math.random() < 0.5; // for example

    const dialogHandled = new Promise((resolve, reject) => {
      const handler = async dialog => {
        await (someCondition ? dialog.accept() : dialog.dismiss());
        resolve(dialog.message());
      };
      page.once("dialog", handler);
    });
    
    //await page.goto(url, {waitUntil: "networkidle0"});
    await page.setContent(html); // for demonstration
    const msg = await dialogHandled;
    console.log(msg, await page.$eval("body", el => el.innerText));
  }
})()
  .catch(err => console.error(err))
  .finally(() => browser?.close())
;

A sample run looks something like:

yes or no? confirmed
yes or no? confirmed
yes or no? confirmed
yes or no? rejected
yes or no? rejected
ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • Thank you! Your example was very helpful. I got a clue that I should put the condition inside the dialog function. However, I have also noticed that the ultimate reason of my problem was using page.on() instead of page.once(), as it is shown in your example. I will update the question with your example, but without the promise. Can you please explain, why there is a need to promisify the handler? – Olek Nov 26 '21 at 00:50
  • Do I understand you correct that promisifing here is only needed for assigning the dialog message to the variable? – Olek Nov 26 '21 at 00:59
  • Without promisifcation, how are you going to maintain the desired sequential control flow between the main loop and the asynchronous handler callback? You'd have to chain the next cycle of the loop from the callback. Entirely possible, just less pleasant than promises. – ggorlen Nov 26 '21 at 04:14
  • I see. Thank you. – Olek Nov 28 '21 at 23:29