Cases / #24 · 2026-10-06 · Easy
New tabs, window.open and the three blocking dialogs: everything that interrupts a script
A page that opens tabs and throws alert, confirm and prompt at you, handled on a remote browser: catching the new page, answering dialogs, and what happens when you don't.
Run on production on 2026-10-06: ✓ Node.js ✓ Python ✓ Java ✓ C# ✓ Go
The problem
Real sites open things: a link with target="_blank", a login that pops a window, a "Delete 3 items?" confirm, a prompt. Each one changes what your script's page object refers to or blocks it until someone answers. On a local browser you'd see it happen; on a remote browser you see a timeout. The handling isn't hard, but it has to be in place *before* the click, and the default behaviour when nothing is in place isn't what most people assume.
What we used, and why
| What | Why |
|---|---|
page.setContent(HTML) | The test page is served by the browser itself: a new-tab link, a window.open button, and buttons that call alert, confirm and prompt. No third-party site to flake. |
context.waitForEvent("page") started before the click | The new page can appear before the click resolves; waiting afterwards can miss it. |
popup.waitForLoadState(), popup.opener() | A popup starts empty; wait for it to load before reading url() or title(). opener() ties it back to the page that opened it. |
page.on("dialog", …) with accept(), accept(text) | You answer the dialog; the page gets true, your text, or false/null if you dismiss. |
Clicking confirm once with no handler | To show the default: Playwright dismisses dialogs it has no handler for, so confirm() returns false. |
How it works
- Load the test page into a fresh context.
- Click the
target="_blank"link and thewindow.openbutton; each time, capture the new page via the context event, wait for it to load, read URL and title, checkopener(), close it. - Click
confirmwith no dialog handler and read what the page got back. - Install a dialog handler that accepts everything (and types a name into the prompt); click
alert,confirm,prompt; read what the page got back.
The code
The same program in five languages (also on GitHub, with the raw output). Set these environment variables first:
CDPFLEET_API_KEY— your API key (dashboard → API keys)PROXY_URL— your proxy, e.g.http://user:[email protected]:8000
// npm install [email protected]
// env: CDPFLEET_API_KEY, PROXY_URL
import { chromium } from 'playwright';
const KEY = process.env.CDPFLEET_API_KEY;
// A page with everything that interrupts a script: a new-tab link, window.open, and the
// three blocking dialogs. Served from the browser itself, so the case needs no third party.
const HTML = `<!doctype html><title>Interruptions</title>
<a id="blank" href="https://example.com/" target="_blank">open in a new tab</a>
<button id="open" onclick="window.open('https://example.com/?popup', 'pop', 'width=480,height=320')">window.open</button>
<button id="alert" onclick="alert('Saved!')">alert</button>
<button id="confirm" onclick="document.body.dataset.confirm = String(confirm('Delete 3 items?'))">confirm</button>
<button id="prompt" onclick="document.body.dataset.prompt = String(prompt('Your name?', 'anonymous'))">prompt</button>`;
const res = await fetch('https://starter.cdpfleet.com/chromium/session', {
method: 'POST',
headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
body: JSON.stringify({ proxy: process.env.PROXY_URL, headless: 'new' }),
});
if (!res.ok) throw new Error(`launch ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
try {
const context = await browser.newContext();
const page = await context.newPage();
await page.setContent(HTML);
const out = [];
// New pages: listen on the context BEFORE the click, then wait for the popup to load.
for (const [label, selector] of [['link with target=_blank', '#blank'], ['window.open()', '#open']]) {
const [popup] = await Promise.all([context.waitForEvent('page'), page.click(selector)]);
await popup.waitForLoadState('load', { timeout: 60000 });
out.push({ event: label, what_happened: `new page: ${popup.url()} — "${await popup.title()}"`, handled_with: 'context.waitForEvent("page") + popup.waitForLoadState()', pages_open: context.pages().length, opener_is_main_page: (await popup.opener()) === page });
await popup.close();
}
// Dialogs: without a handler Playwright dismisses them (confirm → false, prompt → null).
await page.click('#confirm');
out.push({ event: 'confirm() with no dialog handler', what_happened: `page saw confirm() return ${await page.evaluate(() => document.body.dataset.confirm)}`, handled_with: 'nothing — auto-dismissed', pages_open: context.pages().length, opener_is_main_page: null });
// With a handler you decide: accept, dismiss, or type an answer.
const seen = [];
page.on('dialog', async (d) => {
seen.push(`${d.type()}: "${d.message()}"`);
if (d.type() === 'prompt') await d.accept('Ada Lovelace');
else await d.accept();
});
await page.click('#alert');
await page.click('#confirm');
await page.click('#prompt');
const results = await page.evaluate(() => ({ confirm: document.body.dataset.confirm, prompt: document.body.dataset.prompt }));
out.push({ event: 'alert()', what_happened: seen[0], handled_with: 'dialog.accept()', pages_open: context.pages().length, opener_is_main_page: null });
out.push({ event: 'confirm() with a handler', what_happened: `${seen[1]} → page saw ${results.confirm}`, handled_with: 'dialog.accept()', pages_open: context.pages().length, opener_is_main_page: null });
out.push({ event: 'prompt()', what_happened: `${seen[2]} → page saw "${results.prompt}"`, handled_with: 'dialog.accept("Ada Lovelace")', pages_open: context.pages().length, opener_is_main_page: null });
console.log(JSON.stringify(out, null, 2));
} finally {
await browser.close();
}
# pip install playwright==1.60.0 requests
# env: CDPFLEET_API_KEY, PROXY_URL
import json
import os
import requests
from playwright.sync_api import sync_playwright
KEY = os.environ["CDPFLEET_API_KEY"]
# A page with everything that interrupts a script: a new-tab link, window.open, and the
# three blocking dialogs. Served from the browser itself, so the case needs no third party.
HTML = """<!doctype html><title>Interruptions</title>
<a id="blank" href="https://example.com/" target="_blank">open in a new tab</a>
<button id="open" onclick="window.open('https://example.com/?popup', 'pop', 'width=480,height=320')">window.open</button>
<button id="alert" onclick="alert('Saved!')">alert</button>
<button id="confirm" onclick="document.body.dataset.confirm = String(confirm('Delete 3 items?'))">confirm</button>
<button id="prompt" onclick="document.body.dataset.prompt = String(prompt('Your name?', 'anonymous'))">prompt</button>"""
res = requests.post("https://starter.cdpfleet.com/chromium/session", headers={"x-api-key": KEY}, timeout=60,
json={"proxy": os.environ["PROXY_URL"], "headless": "new"})
if not res.ok:
raise SystemExit(f"launch {res.status_code} {res.text}")
session = res.json()
with sync_playwright() as p:
browser = p.chromium.connect(session["wsUrl"], headers={"x-api-key": KEY})
try:
context = browser.new_context()
page = context.new_page()
page.set_content(HTML)
out = []
# New pages: listen on the context BEFORE the click, then wait for the popup to load.
for label, selector in [("link with target=_blank", "#blank"), ("window.open()", "#open")]:
with context.expect_page() as popup_info:
page.click(selector)
popup = popup_info.value
popup.wait_for_load_state("load", timeout=60000)
out.append({"event": label, "what_happened": f'new page: {popup.url} — "{popup.title()}"',
"handled_with": 'context.waitForEvent("page") + popup.waitForLoadState()',
"pages_open": len(context.pages), "opener_is_main_page": popup.opener() is page})
popup.close()
# Dialogs: without a handler Playwright dismisses them (confirm → false, prompt → null).
page.click("#confirm")
out.append({"event": "confirm() with no dialog handler",
"what_happened": f"page saw confirm() return {page.evaluate('() => document.body.dataset.confirm')}",
"handled_with": "nothing — auto-dismissed", "pages_open": len(context.pages), "opener_is_main_page": None})
# With a handler you decide: accept, dismiss, or type an answer.
seen = []
def on_dialog(d):
seen.append(f'{d.type}: "{d.message}"')
if d.type == "prompt":
d.accept("Ada Lovelace")
else:
d.accept()
page.on("dialog", on_dialog)
page.click("#alert")
page.click("#confirm")
page.click("#prompt")
results = page.evaluate("() => ({ confirm: document.body.dataset.confirm, prompt: document.body.dataset.prompt })")
out.append({"event": "alert()", "what_happened": seen[0], "handled_with": "dialog.accept()",
"pages_open": len(context.pages), "opener_is_main_page": None})
out.append({"event": "confirm() with a handler", "what_happened": f"{seen[1]} → page saw {results['confirm']}",
"handled_with": "dialog.accept()", "pages_open": len(context.pages), "opener_is_main_page": None})
out.append({"event": "prompt()", "what_happened": f"{seen[2]} → page saw \"{results['prompt']}\"",
"handled_with": 'dialog.accept("Ada Lovelace")', "pages_open": len(context.pages), "opener_is_main_page": None})
print(json.dumps(out, indent=2, ensure_ascii=False))
finally:
browser.close()
// Maven: com.microsoft.playwright:playwright:1.60.0, com.google.code.gson:gson:2.11.0
// Run with PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1. env: CDPFLEET_API_KEY, PROXY_URL
import com.google.gson.*;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.LoadState;
import java.net.URI;
import java.net.http.*;
import java.util.*;
public class Main {
static final String KEY = System.getenv("CDPFLEET_API_KEY");
static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().serializeNulls().create();
// A page with everything that interrupts a script: a new-tab link, window.open, and the
// three blocking dialogs. Served from the browser itself, so the case needs no third party.
static final String HTML = "<!doctype html><title>Interruptions</title>\n"
+ "<a id=\"blank\" href=\"https://example.com/\" target=\"_blank\">open in a new tab</a>\n"
+ "<button id=\"open\" onclick=\"window.open('https://example.com/?popup', 'pop', 'width=480,height=320')\">window.open</button>\n"
+ "<button id=\"alert\" onclick=\"alert('Saved!')\">alert</button>\n"
+ "<button id=\"confirm\" onclick=\"document.body.dataset.confirm = String(confirm('Delete 3 items?'))\">confirm</button>\n"
+ "<button id=\"prompt\" onclick=\"document.body.dataset.prompt = String(prompt('Your name?', 'anonymous'))\">prompt</button>";
static JsonObject row(String event, String what, String handledWith, int pagesOpen, Boolean opener) {
JsonObject row = new JsonObject();
row.addProperty("event", event);
row.addProperty("what_happened", what);
row.addProperty("handled_with", handledWith);
row.addProperty("pages_open", pagesOpen);
if (opener == null) row.add("opener_is_main_page", JsonNull.INSTANCE); else row.addProperty("opener_is_main_page", opener);
return row;
}
public static void main(String[] args) throws Exception {
String body = "{\"proxy\": " + GSON.toJson(System.getenv("PROXY_URL")) + ", \"headless\": \"new\"}";
HttpResponse<String> res = HttpClient.newHttpClient().send(HttpRequest.newBuilder(URI.create("https://starter.cdpfleet.com/chromium/session"))
.header("x-api-key", KEY).header("content-type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)).build(), HttpResponse.BodyHandlers.ofString());
if (res.statusCode() != 200) throw new RuntimeException("launch " + res.statusCode() + " " + res.body());
JsonObject session = JsonParser.parseString(res.body()).getAsJsonObject();
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().connect(session.get("wsUrl").getAsString(),
new BrowserType.ConnectOptions().setHeaders(Map.of("x-api-key", KEY)));
try {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.setContent(HTML);
JsonArray out = new JsonArray();
// New pages: listen on the context BEFORE the click, then wait for the popup to load.
for (String[] target : new String[][] {{"link with target=_blank", "#blank"}, {"window.open()", "#open"}}) {
Page popup = context.waitForPage(() -> page.click(target[1]));
popup.waitForLoadState(LoadState.LOAD, new Page.WaitForLoadStateOptions().setTimeout(60000));
out.add(row(target[0], "new page: " + popup.url() + " — \"" + popup.title() + "\"",
"context.waitForEvent(\"page\") + popup.waitForLoadState()", context.pages().size(), popup.opener() == page));
popup.close();
}
// Dialogs: without a handler Playwright dismisses them (confirm → false, prompt → null).
page.click("#confirm");
out.add(row("confirm() with no dialog handler", "page saw confirm() return " + page.evaluate("() => document.body.dataset.confirm"),
"nothing — auto-dismissed", context.pages().size(), null));
// With a handler you decide: accept, dismiss, or type an answer.
List<String> seen = new ArrayList<>();
page.onDialog(d -> {
seen.add(d.type() + ": \"" + d.message() + "\"");
if (d.type().equals("prompt")) d.accept("Ada Lovelace"); else d.accept();
});
page.click("#alert");
page.click("#confirm");
page.click("#prompt");
JsonObject results = GSON.toJsonTree(page.evaluate("() => ({ confirm: document.body.dataset.confirm, prompt: document.body.dataset.prompt })")).getAsJsonObject();
out.add(row("alert()", seen.get(0), "dialog.accept()", context.pages().size(), null));
out.add(row("confirm() with a handler", seen.get(1) + " → page saw " + results.get("confirm").getAsString(), "dialog.accept()", context.pages().size(), null));
out.add(row("prompt()", seen.get(2) + " → page saw \"" + results.get("prompt").getAsString() + "\"", "dialog.accept(\"Ada Lovelace\")", context.pages().size(), null));
System.out.println(GSON.toJson(out));
} finally {
browser.close();
}
}
}
}
// dotnet add package Microsoft.Playwright --version 1.60.0
// env: CDPFLEET_API_KEY, PROXY_URL
using System.Net.Http.Json;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Playwright;
var key = Environment.GetEnvironmentVariable("CDPFLEET_API_KEY")!;
// A page with everything that interrupts a script: a new-tab link, window.open, and the
// three blocking dialogs. Served from the browser itself, so the case needs no third party.
const string Html = @"<!doctype html><title>Interruptions</title>
<a id=""blank"" href=""https://example.com/"" target=""_blank"">open in a new tab</a>
<button id=""open"" onclick=""window.open('https://example.com/?popup', 'pop', 'width=480,height=320')"">window.open</button>
<button id=""alert"" onclick=""alert('Saved!')"">alert</button>
<button id=""confirm"" onclick=""document.body.dataset.confirm = String(confirm('Delete 3 items?'))"">confirm</button>
<button id=""prompt"" onclick=""document.body.dataset.prompt = String(prompt('Your name?', 'anonymous'))"">prompt</button>";
static JsonObject Row(string ev, string what, string handledWith, int pagesOpen, bool? opener) => new()
{
["event"] = ev,
["what_happened"] = what,
["handled_with"] = handledWith,
["pages_open"] = pagesOpen,
["opener_is_main_page"] = opener is null ? null : JsonValue.Create(opener.Value),
};
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("x-api-key", key);
var res = await http.PostAsJsonAsync("https://starter.cdpfleet.com/chromium/session",
new { proxy = Environment.GetEnvironmentVariable("PROXY_URL"), headless = "new" });
if (!res.IsSuccessStatusCode) throw new Exception($"launch {(int)res.StatusCode} {await res.Content.ReadAsStringAsync()}");
var session = await res.Content.ReadFromJsonAsync<JsonElement>();
using var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.ConnectAsync(session.GetProperty("wsUrl").GetString()!,
new() { Headers = new Dictionary<string, string> { ["x-api-key"] = key } });
try
{
var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.SetContentAsync(Html);
var output = new JsonArray();
// New pages: listen on the context BEFORE the click, then wait for the popup to load.
foreach (var (label, selector) in new[] { ("link with target=_blank", "#blank"), ("window.open()", "#open") })
{
var popup = await context.RunAndWaitForPageAsync(async () => await page.ClickAsync(selector));
await popup.WaitForLoadStateAsync(LoadState.Load, new() { Timeout = 60000 });
output.Add(Row(label, $"new page: {popup.Url} — \"{await popup.TitleAsync()}\"",
"context.waitForEvent(\"page\") + popup.waitForLoadState()", context.Pages.Count, await popup.OpenerAsync() == page));
await popup.CloseAsync();
}
// Dialogs: without a handler Playwright dismisses them (confirm → false, prompt → null).
await page.ClickAsync("#confirm");
output.Add(Row("confirm() with no dialog handler", $"page saw confirm() return {await page.EvaluateAsync<string>("() => document.body.dataset.confirm")}",
"nothing — auto-dismissed", context.Pages.Count, null));
// With a handler you decide: accept, dismiss, or type an answer.
var seen = new List<string>();
page.Dialog += async (_, d) =>
{
seen.Add($"{d.Type}: \"{d.Message}\"");
if (d.Type == "prompt") await d.AcceptAsync("Ada Lovelace"); else await d.AcceptAsync();
};
await page.ClickAsync("#alert");
await page.ClickAsync("#confirm");
await page.ClickAsync("#prompt");
var results = await page.EvaluateAsync<JsonElement>("() => ({ confirm: document.body.dataset.confirm, prompt: document.body.dataset.prompt })");
output.Add(Row("alert()", seen[0], "dialog.accept()", context.Pages.Count, null));
output.Add(Row("confirm() with a handler", $"{seen[1]} → page saw {results.GetProperty("confirm").GetString()}", "dialog.accept()", context.Pages.Count, null));
output.Add(Row("prompt()", $"{seen[2]} → page saw \"{results.GetProperty("prompt").GetString()}\"", "dialog.accept(\"Ada Lovelace\")", context.Pages.Count, null));
Console.WriteLine(output.ToJsonString(new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }));
}
finally
{
await browser.CloseAsync();
}
// go get github.com/playwright-community/[email protected]
// Driver: build playwright-core 1.60.0 from npm and set PLAYWRIGHT_DRIVER_PATH (see /docs/quickstart).
// env: CDPFLEET_API_KEY, PROXY_URL
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"github.com/playwright-community/playwright-go"
)
var key = os.Getenv("CDPFLEET_API_KEY")
// A page with everything that interrupts a script: a new-tab link, window.open, and the
// three blocking dialogs. Served from the browser itself, so the case needs no third party.
const html = "<!doctype html><title>Interruptions</title>\n" +
"<a id=\"blank\" href=\"https://example.com/\" target=\"_blank\">open in a new tab</a>\n" +
"<button id=\"open\" onclick=\"window.open('https://example.com/?popup', 'pop', 'width=480,height=320')\">window.open</button>\n" +
"<button id=\"alert\" onclick=\"alert('Saved!')\">alert</button>\n" +
"<button id=\"confirm\" onclick=\"document.body.dataset.confirm = String(confirm('Delete 3 items?'))\">confirm</button>\n" +
"<button id=\"prompt\" onclick=\"document.body.dataset.prompt = String(prompt('Your name?', 'anonymous'))\">prompt</button>"
type row struct {
Event string `json:"event"`
WhatHappened string `json:"what_happened"`
HandledWith string `json:"handled_with"`
PagesOpen int `json:"pages_open"`
OpenerIsMainPage *bool `json:"opener_is_main_page"`
}
func must[T any](v T, err error) T {
if err != nil {
log.Fatal(err)
}
return v
}
func main() {
body, _ := json.Marshal(map[string]any{"proxy": os.Getenv("PROXY_URL"), "headless": "new"})
req, _ := http.NewRequest("POST", "https://starter.cdpfleet.com/chromium/session", bytes.NewReader(body))
req.Header.Set("x-api-key", key)
req.Header.Set("content-type", "application/json")
res := must(http.DefaultClient.Do(req))
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
msg, _ := io.ReadAll(res.Body)
log.Fatalf("launch %s %s", res.Status, msg)
}
var session map[string]any
must(0, json.NewDecoder(res.Body).Decode(&session))
pw := must(playwright.Run(&playwright.RunOptions{SkipInstallBrowsers: true}))
defer pw.Stop()
browser := must(pw.Chromium.Connect(session["wsUrl"].(string), playwright.BrowserTypeConnectOptions{Headers: map[string]string{"x-api-key": key}}))
defer browser.Close()
context := must(browser.NewContext())
page := must(context.NewPage())
must(0, page.SetContent(html))
out := []row{}
// New pages: listen on the context BEFORE the click, then wait for the popup to load.
for _, target := range [][2]string{{"link with target=_blank", "#blank"}, {"window.open()", "#open"}} {
selector := target[1]
popup := must(context.ExpectPage(func() error { return page.Click(selector) }))
must(0, popup.WaitForLoadState(playwright.PageWaitForLoadStateOptions{State: playwright.LoadStateLoad, Timeout: playwright.Float(60000)}))
opener := must(popup.Opener()) == page
out = append(out, row{target[0], "new page: " + popup.URL() + " — \"" + must(popup.Title()) + "\"",
"context.waitForEvent(\"page\") + popup.waitForLoadState()", len(context.Pages()), &opener})
must(0, popup.Close())
}
// Dialogs: without a handler Playwright dismisses them (confirm → false, prompt → null).
must(0, page.Click("#confirm"))
out = append(out, row{"confirm() with no dialog handler", fmt.Sprintf("page saw confirm() return %v", must(page.Evaluate("() => document.body.dataset.confirm"))),
"nothing — auto-dismissed", len(context.Pages()), nil})
// With a handler you decide: accept, dismiss, or type an answer.
seen := []string{}
page.OnDialog(func(d playwright.Dialog) {
seen = append(seen, d.Type()+": \""+d.Message()+"\"")
if d.Type() == "prompt" {
d.Accept("Ada Lovelace")
} else {
d.Accept()
}
})
must(0, page.Click("#alert"))
must(0, page.Click("#confirm"))
must(0, page.Click("#prompt"))
results := must(page.Evaluate("() => ({ confirm: document.body.dataset.confirm, prompt: document.body.dataset.prompt })")).(map[string]any)
out = append(out, row{"alert()", seen[0], "dialog.accept()", len(context.Pages()), nil})
out = append(out, row{"confirm() with a handler", fmt.Sprintf("%s → page saw %v", seen[1], results["confirm"]), "dialog.accept()", len(context.Pages()), nil})
out = append(out, row{"prompt()", fmt.Sprintf("%s → page saw \"%v\"", seen[2], results["prompt"]), "dialog.accept(\"Ada Lovelace\")", len(context.Pages()), nil})
text, _ := json.MarshalIndent(out, "", " ")
fmt.Println(string(text))
}
What we got
| Event | What happened | Handled with | Pages open | opener() is the main page |
|---|---|---|---|---|
| link with target=_blank | new page: https://example.com/ — "Example Domain" | context.waitForEvent("page") + popup.waitForLoadState() | 2 | yes |
| window.open() | new page: https://example.com/?popup — "Example Domain" | context.waitForEvent("page") + popup.waitForLoadState() | 2 | yes |
| confirm() with no dialog handler | page saw confirm() return false | nothing — auto-dismissed | 1 | — |
| alert() | alert: "Saved!" | dialog.accept() | 1 | — |
| confirm() with a handler | confirm: "Delete 3 items?" → page saw true | dialog.accept() | 1 | — |
| prompt() | prompt: "Your name?" → page saw "Ada Lovelace" | dialog.accept("Ada Lovelace") | 1 | — |
From the Node.js run on 2026-10-06. IP addresses are replaced with placeholders (203.0.113.x); equal addresses stay equal. The other languages produced the same findings.
Raw output (Node.js)
[
{
"event": "link with target=_blank",
"what_happened": "new page: https://example.com/ — \"Example Domain\"",
"handled_with": "context.waitForEvent(\"page\") + popup.waitForLoadState()",
"pages_open": 2,
"opener_is_main_page": true
},
{
"event": "window.open()",
"what_happened": "new page: https://example.com/?popup — \"Example Domain\"",
"handled_with": "context.waitForEvent(\"page\") + popup.waitForLoadState()",
"pages_open": 2,
"opener_is_main_page": true
},
{
"event": "confirm() with no dialog handler",
"what_happened": "page saw confirm() return false",
"handled_with": "nothing — auto-dismissed",
"pages_open": 1,
"opener_is_main_page": null
},
{
"event": "alert()",
"what_happened": "alert: \"Saved!\"",
"handled_with": "dialog.accept()",
"pages_open": 1,
"opener_is_main_page": null
},
{
"event": "confirm() with a handler",
"what_happened": "confirm: \"Delete 3 items?\" → page saw true",
"handled_with": "dialog.accept()",
"pages_open": 1,
"opener_is_main_page": null
},
{
"event": "prompt()",
"what_happened": "prompt: \"Your name?\" → page saw \"Ada Lovelace\"",
"handled_with": "dialog.accept(\"Ada Lovelace\")",
"pages_open": 1,
"opener_is_main_page": null
}
]Takeaways
- Both kinds of new page arrive the same way —
context.waitForEvent("page")catches atarget="_blank"link and awindow.opencall alike,opener()points back at the main page, and afterwaitForLoadStatethe popup is a normalpageyou can drive. Both counted as a second page in the context, not a second session: tabs are free, threads are per session. - Unhandled dialogs are dismissed, not accepted: with no handler,
confirm("Delete 3 items?")returnedfalseto the page — the script continued, and the site silently took the "cancel" path. That is the opposite of the usual assumption. - With a handler you choose the answer:
alertacknowledged,confirm→true,prompt→"Ada Lovelace". The handler must exist before the click, and it must callaccept()ordismiss(), or the page stays blocked. - Nothing here depends on the browser being remote — the events travel over the same connection as everything else; the only remote-specific cost is that a missed popup looks like a timeout rather than a visible window.