warn agents when popup windows are detected during execution

When window.open() creates a popup, agents have no visibility into it
and can't control it via playwriter. This adds a [WARNING] line to the
executor output telling agents the popup page index, URL, and to repeat
the interaction without popups or navigate to the URL directly.

Detection uses page.opener() on newly created pages (context 'page'
event), not on existing pages during reconnection to avoid false
positives.
This commit is contained in:
Tommy D. Rossi
2026-02-18 22:58:27 +01:00
parent 17fd10b50f
commit 03a70fb9fe
+31 -2
View File
@@ -219,6 +219,7 @@ export class PlaywrightExecutor {
private browserLogs: Map<string, string[]> = new Map()
private lastSnapshots: WeakMap<Page, string> = new WeakMap()
private lastRefToLocator: WeakMap<Page, Map<string, string>> = new WeakMap()
private popupWarnings: string[] = []
private scopedFs: ScopedFS
private sandboxedRequire: NodeRequire
@@ -305,6 +306,28 @@ export class PlaywrightExecutor {
}
}
private setupPageListeners(page: Page) {
this.setupPageConsoleListener(page)
this.setupPopupDetection(page)
}
private setupPopupDetection(page: Page) {
// Check if this page was opened by window.open() (has an opener)
const opener = page.opener()
if (!opener) {
return
}
const context = page.context()
const pages = context.pages()
const pageIndex = pages.indexOf(page)
const url = page.url()
this.popupWarnings.push(
`Popup window detected (page index ${pageIndex}, url: ${url}). ` +
`Popup windows cannot be controlled by playwriter. ` +
`Repeat the interaction in a way that does not open a popup, or navigate to the URL directly in a new tab.`,
)
}
private setupPageConsoleListener(page: Page) {
// Use targetId() if available, fallback to internal _guid for CDP connections
const targetId = page.targetId() || (page as any)._guid as string | undefined
@@ -410,7 +433,7 @@ export class PlaywrightExecutor {
const context = contexts.length > 0 ? contexts[0] : await browser.newContext()
context.on('page', (page) => {
this.setupPageConsoleListener(page)
this.setupPageListeners(page)
})
context.pages().forEach((p) => this.setupPageConsoleListener(p))
@@ -485,7 +508,7 @@ export class PlaywrightExecutor {
const context = contexts.length > 0 ? contexts[0] : await browser.newContext()
context.on('page', (page) => {
this.setupPageConsoleListener(page)
this.setupPageListeners(page)
})
context.pages().forEach((p) => this.setupPageConsoleListener(p))
@@ -861,6 +884,12 @@ export class PlaywrightExecutor {
}
}
// Drain any popup warnings that fired during execution
if (this.popupWarnings.length > 0) {
responseText += this.popupWarnings.map((w) => `[WARNING] ${w}`).join('\n') + '\n'
this.popupWarnings = []
}
if (!responseText.trim()) {
responseText = 'Code executed successfully (no output)'
}