From c47601bd7ba1974da756c2bf062f3d6bb3f3cf38 Mon Sep 17 00:00:00 2001 From: leeguooooo Date: Wed, 17 Jun 2026 02:08:11 +0900 Subject: [PATCH] fix(eval): replMode only for sync let/const decls, keep awaitPromise for async (#38) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit replMode and awaitPromise are mutually exclusive in Chrome — under replMode a returned promise serialises to {} instead of being awaited, which broke every fetch/async eval (e2e_domain_filter, e2e_headers, e2e_react_tree all regressed). Enable replMode only for synchronous scripts that declare a top-level let/const (the #38 case); promise-returning scripts keep awaitPromise — restoring the pre-#38 await behaviour while still fixing the let-redeclaration collision. --- cli/src/native/browser.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/cli/src/native/browser.rs b/cli/src/native/browser.rs index 8075191..b6bf8da 100644 --- a/cli/src/native/browser.rs +++ b/cli/src/native/browser.rs @@ -1208,13 +1208,21 @@ impl BrowserManager { pub async fn evaluate(&self, script: &str, _args: Option) -> Result { let session_id = self.active_session_id()?.to_string(); - // `replMode: true` matches the DevTools console: top-level `let`/`const` - // can be re-declared across successive `eval`s instead of throwing - // "Identifier 'x' has already been declared" (issue #38 — independent - // `eval` steps in a test suite collided in the page's shared lexical - // scope), and top-level `await` is allowed. Completion-value and - // main-world semantics are unchanged. Built as raw params so the other - // ~28 EvaluateParams literals don't all need a new field. + // `replMode: true` lets successive `eval`s re-declare top-level + // `let`/`const` instead of throwing "Identifier 'x' has already been + // declared" (issue #38 — independent `eval` steps in a test suite collided + // in the page's shared lexical scope). BUT replMode and `awaitPromise` are + // mutually exclusive in Chrome: under replMode a returned promise is NOT + // awaited (it serialises to `{}`), which breaks `fetch(...).then(...)` and + // every other async eval. So enable replMode ONLY for synchronous scripts + // that declare a top-level `let`/`const`; promise-returning scripts keep + // `awaitPromise` (no replMode) — exactly the pre-#38 behaviour. + let mentions_async = script.contains("await") + || script.contains(".then(") + || script.contains("fetch(") + || script.contains("Promise"); + let declares = script.contains("let ") || script.contains("const "); + let repl_mode = declares && !mentions_async; let result: EvaluateResult = self .client .send_command_typed( @@ -1222,8 +1230,8 @@ impl BrowserManager { &json!({ "expression": script, "returnByValue": true, - "awaitPromise": true, - "replMode": true, + "awaitPromise": !repl_mode, + "replMode": repl_mode, }), Some(&session_id), )