Skip to content

Commit 96f3763

Browse files
mreiderclaude
andcommitted
Abort capability run when nav dumps browser on chrome-error page
When a navigation fails with a transport-level error (e.g. ERR_HTTP2_PROTOCOL_ERROR), the browser ends up on chrome-error://chromewebdata/ and every subsequent click/type runs against that error page. Previously the executor kept going, wasting ~10s on selector probes + AI healing calls, and returned a misleading "site_changed" result with an empty data object. Now: after any failed step, check page.url(); if it's a chrome-error page, stop immediately, capture a screenshot, and return a clean 'transient' errorType with "Site unreachable: <original error>". Skips extraction too — nothing to extract from chrome://. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9adcb6a commit 96f3763

2 files changed

Lines changed: 42 additions & 0 deletions

File tree

core/browser/playwright-engine.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export class PlaywrightEngine {
108108
this.addLog(`Optimized: ${resolvedActions.length} actions → ${optimized.length} (removed waits + duplicate navs)`)
109109

110110
let failedSteps = 0
111+
let unreachableError: string | null = null
111112
for (let i = 0; i < optimized.length; i++) {
112113
const action = optimized[i]
113114
this.addLog(`Step ${i + 1}/${optimized.length}: ${action.type} ${action.selector || action.url || ''} ${action.label ? '(' + action.label + ')' : ''}`.trim())
@@ -118,6 +119,29 @@ export class PlaywrightEngine {
118119
} catch (err: any) {
119120
failedSteps++
120121
this.addLog(` -> FAILED (skipping): ${err.message}`)
122+
123+
// If a navigation dumped us onto Chrome's network-error page, no later
124+
// step can succeed (DOM is chrome-error://chromewebdata/). Bail instead
125+
// of spending 10+ seconds running selectors against the error page.
126+
if (this.page.url().startsWith('chrome-error://')) {
127+
unreachableError = err.message
128+
const skipped = optimized.length - i - 1
129+
this.addLog(` -> Site unreachable; aborting${skipped > 0 ? ` remaining ${skipped} step(s)` : ''}.`)
130+
break
131+
}
132+
}
133+
}
134+
135+
if (unreachableError) {
136+
const screenshotBuffer = await this.page.screenshot({ type: 'png' })
137+
return {
138+
success: false,
139+
data: {},
140+
error: `Site unreachable: ${unreachableError}`,
141+
errorType: 'transient',
142+
durationMs: Date.now() - startTime,
143+
screenshot: screenshotBuffer.toString('base64'),
144+
log: this.log
121145
}
122146
}
123147

tests/core/playwright-engine.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,24 @@ describe('PlaywrightEngine', () => {
299299
const result = await engine.execute(actions, [], {}, [])
300300
expect(result.errorType).toBe('site_changed')
301301
})
302+
303+
it('aborts remaining steps when nav dumps browser on chrome-error page', async () => {
304+
mockPage.goto.mockRejectedValueOnce(new Error('page.goto: net::ERR_HTTP2_PROTOCOL_ERROR at https://united.com/'))
305+
mockPage.url.mockReturnValue('chrome-error://chromewebdata/')
306+
const actions = [
307+
buildNavigateAction({ url: 'https://united.com' }),
308+
buildClickAction({ locators: [{ strategy: 'css', value: '#nav' }] }),
309+
buildNavigateAction({ url: 'https://united.com/account' })
310+
]
311+
const result = await engine.execute(actions, [], {}, [])
312+
313+
expect(mockPage.goto).toHaveBeenCalledTimes(1)
314+
expect(result.success).toBe(false)
315+
expect(result.errorType).toBe('transient')
316+
expect(result.error).toMatch(/Site unreachable.*ERR_HTTP2_PROTOCOL_ERROR/)
317+
expect(result.log.some(l => l.includes('Site unreachable; aborting remaining 2 step(s)'))).toBe(true)
318+
expect(result.screenshot).toBeDefined()
319+
})
302320
})
303321

304322
// ════════════════════════════════════════════════════════════════════════════

0 commit comments

Comments
 (0)