To elaborate on the existing answer, the beforeunload event you're trying to deal with causes a dialog box prompt to appear. This dialog can be handled in the same manner as prompts and alerts, with page.on("dialog", dialog => ...).
The dialog object passed to the handler can be checked for the "beforeunload" type, then you can call dismiss() or accept() depending on the behavior you want. dismiss() stays on the current page, aborting the navigation action, while accept() agrees to leave the site.
Your likely solution is to agree to leave the site with accept():
const puppeteer = require("puppeteer");
let browser;
(async () => {
const html = `
<h1>a page that prompts before unload</h1>
<script>
window.addEventListener("beforeunload", function (e) {
e.preventDefault();
(e || window.event).returnValue = "";
return "";
});
</script>
`;
browser = await puppeteer.launch({headless: false});
const [page] = await browser.pages();
const acceptBeforeUnload = dialog =>
dialog.type() === "beforeunload" && dialog.accept()
;
page.on("dialog", acceptBeforeUnload);
await page.setContent(html);
await page.goto("https://www.example.com");
console.log(page.url()); // => => https://www.example.com
})()
.catch(err => console.error(err))
.finally(() => browser.close())
;
Note that using the existing answer's method of simply setting window.beforeunload = null in an evaluate callback doesn't work for this page. You can test that by removing the page.on and adding the evaluate call above page.goto.
However, for more complex behaviors, like enabling and disabling the unload dynamically, the evaluate approach seems to help avoid abort throws in conjunction with removing a dismiss handler, as this example illustrates:
const puppeteer = require("puppeteer");
let browser;
(async () => {
const html = `
<h1>a page that prompts before unload</h1>
<script>
window.addEventListener("beforeunload", function (e) {
e.preventDefault();
(e || window.event).returnValue = "";
return "";
});
</script>
`;
browser = await puppeteer.launch({headless: false});
const [page] = await browser.pages();
const dismissBeforeUnload = dialog =>
dialog.type() === "beforeunload" && dialog.dismiss()
;
page.on("dialog", dismissBeforeUnload);
await page.setContent(html);
// throws Error: net::ERR_ABORTED at https://www.example.com
await page.goto("https://www.example.com").catch(() => {});
console.log(page.url()); // => about:blank
// add to avoid Error: net::ERR_ABORTED when adding a new handler
await page.evaluate(() => {
window.onbeforeunload = null;
});
page.off("dialog", dismissBeforeUnload);
// next unload, we'll accept the dialog
page.on("dialog", dialog =>
dialog.type() === "beforeunload" && dialog.accept()
);
// this navigation will succeed
await page.goto("https://www.example.com");
console.log(page.url()); // => https://www.example.com
})()
.catch(err => console.error(err))
.finally(() => browser.close())
;