Skip to content

fix(browser): scope evaluateWithArgs declarations in an IIFE#1707

Open
MatrixA wants to merge 1 commit into
jackwener:mainfrom
MatrixA:fix/upload-markerattr-shadow
Open

fix(browser): scope evaluateWithArgs declarations in an IIFE#1707
MatrixA wants to merge 1 commit into
jackwener:mainfrom
MatrixA:fix/upload-markerattr-shadow

Conversation

@MatrixA
Copy link
Copy Markdown
Contributor

@MatrixA MatrixA commented May 21, 2026

Bug

opencli browser <session> upload <ref> <file> throws on the second
invocation against the same page:

✖  SyntaxError: Identifier 'markerAttr' has already been declared

Reloading the page does not clear the error — the same execution context is reused.

Minimal repro

opencli browser s open https://www.w3schools.com/howto/howto_html_file_upload_button.asp
opencli browser s wait selector "input[type=file]"
opencli browser s find --css "input[type=file]"   # note the ref, e.g. 1
opencli browser s upload 1 /tmp/any.png           # works (or fails with not_file_input)
opencli browser s upload 1 /tmp/any.png           # SyntaxError: 'markerAttr' has already been declared

Root cause

BasePage.uploadFiles calls evaluateWithArgs with { markerAttr, markerValue }.
evaluateWithArgs (src/browser/base-page.ts:160-170) emits:

const markerAttr = "data-opencli-upload-target";
const markerValue = "...";
(() => { ... el.setAttribute(markerAttr, markerValue); ... })()

wrapForEval correctly classifies this as a "bare expression" and sends it as-is to
Runtime.evaluate. The const declarations land at script top level. With the bridge
reusing the same execution context, the second call hits a duplicate-binding
SyntaxError. Same applies to every other evaluateWithArgs callsite that uses fixed
keys — upload just happens to be the easiest to trip because it always reuses
markerAttr / markerValue.

Fix

Wrap the declarations and the caller-supplied expression in an IIFE so the bindings
are block-scoped. wrapForEval already recognizes this shape as already-IIFE and
forwards it unchanged. The body is parenthesized and returned so Promise return
values are preserved (CDP awaits via awaitPromise: true).

- return this.evaluate(`${declarations}\n${js}`);
+ return this.evaluate(`(() => { ${declarations}\nreturn (${js}); })()`);

Verification

  • New unit test BasePage.evaluateWithArgs > wraps declarations and body in an IIFE …
    asserts the emitted script is IIFE-wrapped and returns the inner expression.
  • npx vitest run --project unit1139 passed, 1 skipped.
  • npm run typecheck → clean.
  • End-to-end against a real page: two consecutive browser upload calls on the same
    session no longer throw the markerAttr SyntaxError. (Whatever underlying
    DOM.setFileInputFiles permission errors a page may still surface are independent
    of this fix.)

Notes

  • All current evaluateWithArgs callsites pass an expression ((() => …)() or
    (async () => …)()), so the return (${js}); wrapper is safe for every caller.
  • The JSDoc on evaluateWithArgs is updated to call out the IIFE requirement so future
    refactors don't silently regress it.

`browser upload` would throw `SyntaxError: Identifier 'markerAttr' has
already been declared` on the second invocation against the same page —
even after a navigation.

Root cause: `BasePage.evaluateWithArgs` prepended raw `const <key> = ...`
statements to the caller-supplied expression and sent the whole script
to `Runtime.evaluate`. Because the upload command always passes a fixed
key set (`markerAttr`, `markerValue`), and the bridge reuses the same
execution context, the second call hit a duplicate top-level `const`.

Wrap the declarations and the caller expression in an IIFE so the
bindings are block-scoped. `wrapForEval` already detects this shape as
already-IIFE and forwards it unchanged. The body is parenthesized and
returned, preserving Promise return values (CDP awaits via
`awaitPromise: true`).

Added a regression test that asserts the emitted script is IIFE-wrapped
with the declarations inside.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant