(args: z.infer<typeof NavigateArgs>, _ctx: ToolContext)
| 47 | argsSchema = NavigateArgs; |
| 48 | |
| 49 | async execute(args: z.infer<typeof NavigateArgs>, _ctx: ToolContext): Promise<ToolResult> { |
| 50 | const waitUntil = args.wait_until ?? 'domcontentloaded'; |
| 51 | const timeout = args.timeout_ms ?? 30_000; |
| 52 | const s = await getSession(); |
| 53 | clearBuffers(s); |
| 54 | |
| 55 | // Try the navigation. Catch Playwright's TimeoutError and fall through to a |
| 56 | // best-effort recovery — many real pages never finish according to `load` |
| 57 | // (trackers, analytics, prefetch beacons), and even `domcontentloaded` can |
| 58 | // hang on giant SPAs. The user almost always prefers a partial render they |
| 59 | // can inspect over a hard error. |
| 60 | let status: number | undefined; |
| 61 | let timedOut = false; |
| 62 | let phaseError: string | undefined; |
| 63 | try { |
| 64 | const r = await s.page.goto(args.url, { waitUntil, timeout }); |
| 65 | status = r?.status(); |
| 66 | } catch (e: any) { |
| 67 | const msg = e?.message ?? String(e); |
| 68 | const isTimeout = |
| 69 | e?.name === 'TimeoutError' || |
| 70 | /Timeout \d+ms exceeded/i.test(msg) || |
| 71 | /navigation timeout/i.test(msg); |
| 72 | if (!isTimeout) { |
| 73 | return { content: `[BROWSER_ERROR] navigate failed: ${msg}`, isError: true }; |
| 74 | } |
| 75 | timedOut = true; |
| 76 | phaseError = msg.split('\n')[0]; |
| 77 | logger.info('browser_navigate timed out; returning partial state', { url: args.url, waitUntil, timeout }); |
| 78 | } |
| 79 | |
| 80 | // Each accessor can itself fail if the page is in a weird state; wrap individually |
| 81 | // so one failure doesn't wipe out the others. |
| 82 | let title = ''; |
| 83 | try { title = await s.page.title(); } catch { /* keep '' */ } |
| 84 | const finalUrl = (() => { try { return s.page.url(); } catch { return args.url; } })(); |
| 85 | |
| 86 | let htmlSection = ''; |
| 87 | if (args.return_html === true || timedOut) { |
| 88 | try { |
| 89 | const html = await s.page.content(); |
| 90 | const max = 25_000; |
| 91 | const slice = html.length > max |
| 92 | ? html.slice(0, max) + `\n\n…[truncated, ${html.length - max} more chars]` |
| 93 | : html; |
| 94 | htmlSection = `\n\n--- HTML (${html.length} chars) ---\n${slice}`; |
| 95 | } catch (e: any) { |
| 96 | htmlSection = `\n\n--- HTML unavailable: ${e?.message ?? String(e)} ---`; |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | const banner = timedOut |
| 101 | ? `[PARTIAL_LOAD] navigation timed out after ${timeout}ms (waitUntil=${waitUntil}); returning whatever the DOM has so far. Reason: ${phaseError ?? 'timeout'}` |
| 102 | : `Loaded ${finalUrl}`; |
| 103 | |
| 104 | return { |
| 105 | content: |
| 106 | `${banner}\n` + |
nothing calls this directly
no test coverage detected