diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/client.ts | 14 | ||||
| -rw-r--r-- | src/domains/form.ts | 2 | ||||
| -rw-r--r-- | src/domains/keyboard.ts | 11 |
3 files changed, 18 insertions, 9 deletions
diff --git a/src/client.ts b/src/client.ts index 3c79f1a..724a711 100644 --- a/src/client.ts +++ b/src/client.ts @@ -170,12 +170,14 @@ export const createBrowserClient = (opts: BrowserClientOptions): BrowserClient = const evaluateJs = async (expression: string, sessionId?: string): Promise<Result<unknown, CdpError>> => { // Heuristic IIFE wrap: legacy convenience that lets agents write - // `return foo` instead of `(() => foo)()`. False-positive on substring - // `"return "` inside string literals or comments — agents that hit this - // can pass an explicit IIFE instead. - const wrapped = expression.includes("return ") && !expression.trim().startsWith("(") - ? `(function(){${expression}})()` - : expression; + // `return foo` instead of `(() => foo)()`. Only wrap when the trimmed + // source *starts with* a return statement — a bare expression, or one that + // merely mentions `return` inside a string literal or comment, is passed + // through untouched (previously any substring `"return "` triggered a wrap, + // silently turning such expressions into `undefined`). + const trimmed = expression.trim(); + const isReturnStatement = /^return[\s(]/.test(trimmed); + const wrapped = isReturnStatement ? `(function(){${expression}})()` : expression; const r = sessionId ? await session.callOnTarget("Runtime.evaluate", { expression: wrapped, returnByValue: true, awaitPromise: true }, sessionId) : await session.call("Runtime.evaluate", { expression: wrapped, returnByValue: true, awaitPromise: true }); diff --git a/src/domains/form.ts b/src/domains/form.ts index a10646c..063cfaf 100644 --- a/src/domains/form.ts +++ b/src/domains/form.ts @@ -119,6 +119,8 @@ export const fillTool = defineBrowserTool({ "Pass the desired value; no browser_click is needed first.", "A 'ref is stale' error means the page changed — re-run browser_snapshot to get fresh refs.", "Use browser_type instead only for keystroke-sensitive widgets (autocomplete, masked/segmented inputs).", + "Fill sets the value but does not focus or submit. To submit a tag/autocomplete input, follow with browser_dispatch_key({ ref, key: 'Enter' }) — not browser_press_key, which targets the focused element and may miss this field.", + "For a custom (div-based) dropdown, browser_fill won't validate against its option list — open it, run browser_snapshot, then browser_click the option's ref.", "The result reports the field's value after writing and an appended page-changes diff — confirm both match what you intended before moving on.", ], parameters: FillArgs, diff --git a/src/domains/keyboard.ts b/src/domains/keyboard.ts index 69a7396..24b7833 100644 --- a/src/domains/keyboard.ts +++ b/src/domains/keyboard.ts @@ -129,12 +129,17 @@ export const dispatchKeyTool = defineBrowserTool({ "PREFER `ref` from browser_snapshot over a CSS selector — survives re-renders.", "Dispatches a synthetic DOM KeyboardEvent — for React/Vue synthetic event listeners. Does NOT insert text (use browser_type or browser_press_key for actual typing).", "Try browser_press_key first; only use browser_dispatch_key when the page ignores raw CDP key events.", + "The event carries keyCode/which (e.g. 13 for Enter) for legacy handlers, but is untrusted (isTrusted === false) — a few libraries may still ignore it.", "eventType defaults to 'keydown'.", ], parameters: DispatchKeyArgs, async handler(args, { client }): Promise<Result<ToolOk, ToolErr>> { const eventType = args.eventType ?? "keydown"; const target = args.ref ?? args.selector ?? ""; + // Legacy handlers commonly branch on e.keyCode/e.which (e.g. === 13 for + // Enter) rather than e.key, so populate both. virtualKeyCode maps named + // keys and single chars; 0 for anything unknown. + const code = virtualKeyCode(args.key); // Ref path: dispatch on the single resolved node. Selector path: dispatch on // all matches (preserves the original multi-match behavior). if (args.ref !== undefined) { @@ -142,8 +147,8 @@ export const dispatchKeyTool = defineBrowserTool({ if (!objectId.success) return objectId; const r = await client.session().call("Runtime.callFunctionOn", { objectId: objectId.data, - functionDeclaration: `function (type, key) { this.dispatchEvent(new KeyboardEvent(type, { key, bubbles: true, cancelable: true })); return 1; }`, - arguments: [{ value: eventType }, { value: args.key }], + functionDeclaration: `function (type, key, keyCode) { this.dispatchEvent(new KeyboardEvent(type, { key, keyCode, which: keyCode, bubbles: true, cancelable: true })); return 1; }`, + arguments: [{ value: eventType }, { value: args.key }, { value: code }], returnByValue: true, }); if (!r.success) return err({ kind: "cdp_error", message: r.error.message }); @@ -160,7 +165,7 @@ export const dispatchKeyTool = defineBrowserTool({ const els = document.querySelectorAll(${args.selector}); if (els.length === 0) return 0; for (const el of els) { - el.dispatchEvent(new KeyboardEvent(${eventType}, { key: ${args.key}, bubbles: true, cancelable: true })); + el.dispatchEvent(new KeyboardEvent(${eventType}, { key: ${args.key}, keyCode: ${code}, which: ${code}, bubbles: true, cancelable: true })); } return els.length; })() |
