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());
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"); // ^21.4.1
const html = `<html><body><script>
document.write(confirm("yes or no?") ? "confirmed" : "rejected");
</script></body></html>`;
let browser;
(async () => {
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