[GH-ISSUE #1320] [Bug] Attached browser scenarios silently lose locale, timezone and device-scale overrides before capture #463

Closed
opened 2026-10-15 18:25:16 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @dakjdakd on GitHub (Oct 9, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/1320

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Observed behavior

capture-browser-scenario accepts an environment for an existing Chrome page, successfully completes the requested actions, and returns a DOM capture, but the page executes with its original locale, timezone, device scale and viewport. The overrides have already disappeared by the time REA navigates to start_url.

This is reproducible with browser.mode: "connect" on revision 411498d45f860eba2fbc9d7051af20237b7a4cb1. In a real Chrome 154 run, requesting fr-FR, Pacific/Honolulu, DPR 2 and a 900 × 600 viewport produced the following values from the page's own JavaScript:

Setting Requested Captured before the fix
Intl.DateTimeFormat().resolvedOptions().locale fr-FR zh-CN
Intl.DateTimeFormat().resolvedOptions().timeZone Pacific/Honolulu Asia/Shanghai
devicePixelRatio 2 1
innerWidth × innerHeight 900 × 600 758 × 482

The final capture is an observation of those actual values, not a schema error or a failed action. The command can therefore look successful even though it did not perform the experiment described by its input. The original values in the right-hand column are specific to the test host and browser window; another host may expose different defaults.

Reproduction

Use an already-installed Chrome-family executable, Node.js and a built REA checkout. No remote site, browser extension or service worker is needed. A local page that prints the environment is sufficient.

  1. Run npm run build:cached in the checkout.
  2. Serve this page from a loopback HTTP address, for example http://127.0.0.1:3000/:
<!doctype html>
<html><body>
  <pre id="environment"></pre>
  <button id="refresh-environment">Refresh environment</button>
  <script>
    const refresh = () => {
      document.querySelector('#environment').textContent = JSON.stringify({
        timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
        locale: Intl.DateTimeFormat().resolvedOptions().locale,
        dpr: devicePixelRatio,
        width: innerWidth,
        height: innerHeight
      });
    };
    document.querySelector('#refresh-environment').onclick = refresh;
    refresh();
  </script>
</body></html>
  1. Start a separate Chrome instance with a disposable user-data directory and a loopback debugging port. Open the local page in that instance. For example, the relevant Chrome arguments are:
--headless=new
--remote-debugging-address=127.0.0.1
--remote-debugging-port=9222
--user-data-dir=<absolute path to a disposable profile>
--no-first-run
http://127.0.0.1:3000/
  1. Read http://127.0.0.1:9222/json/list and select the id of that page target. Replace PAGE_TARGET_ID below with that exact ID. Save the request as attached-environment.json:
{
  "browser": {
    "mode": "connect",
    "cdp_endpoint": "http://127.0.0.1:9222",
    "target_id": "PAGE_TARGET_ID"
  },
  "start_url": { "url": "http://127.0.0.1:3000/" },
  "environment": {
    "locale": "fr-FR",
    "timezone": "Pacific/Honolulu",
    "viewport": {
      "width": 900,
      "height": 600,
      "device_scale_factor": 2
    }
  },
  "actions": [
    {
      "step_id": "refresh",
      "action": "click",
      "locator": { "kind": "css", "selector": "#refresh-environment" }
    }
  ],
  "capture": {
    "after_each_step": ["dom"],
    "at_end": ["dom"]
  }
}
  1. Run the public command:
node scripts/rea.mjs capture-browser-scenario ./attached-environment.json --json

Inspect normalized_result.steps[*].artifacts.dom.value.text. The <pre id="environment"> content records the environment on navigation and again after the refresh click. On the affected revision, both captures contain the external browser's defaults rather than the requested values. Passing this same JSON object as arguments to the MCP tool capture_browser_scenario exercises the shared provider path.

The reproduction uses explicit DOM capture because a completed click or a captured final URL cannot establish that locale, timezone or device metrics were in effect. Reading those values inside the rendered document makes the discrepancy visible without interpreting a screenshot.

Expected behavior

The selected environment should remain active while REA navigates, performs actions and captures their results. For the request above, the page should report:

{
  "timezone": "Pacific/Honolulu",
  "locale": "fr-FR",
  "dpr": 2,
  "width": 900,
  "height": 600
}

When the scenario finishes, REA should release its attached connection and its emulation settings while leaving the externally owned browser and page open. That ownership boundary should also hold when configuration fails partway through, an action fails, or the request is cancelled.

Analysis and suspected cause

The relevant code is configureAttachedEnvironment in PlaywrightScenarioBrowser.ts. It creates a page CDP session, sends Emulation.setDeviceMetricsOverride, Emulation.setLocaleOverride and Emulation.setTimezoneOverride, and then unconditionally calls session.detach() in finally.

The CDP commands succeed. Their lifetime is the problem: Chrome resets these session-scoped overrides when the session detaches. A separate direct-CDP control on the same browser showed the requested values while that session remained attached and the original values immediately after detaching it. This control, together with the public CLI reproduction, identifies the early detach as the cause rather than a rejected setting, malformed request or page-specific script.

The source also shows that attached-browser cleanup is already registered before configuration begins. Keeping the emulation session on that connection until the scenario's existing cleanup runs should align the settings' lifetime with the operation's lifetime. A new setting or public contract is unnecessary. The same early detach remains in upstream revision 8b4c3591; the original reproduction predates that revision, and comparison of the attached configuration source shows no intervening change.

Impact and scope

This affects reproducibility of attached-browser investigations. Locale-sensitive formatting, timezone-dependent application behavior and rendering at a selected DPR can all differ from what the scenario requests. It can also invalidate comparisons between captures if callers assume both runs used the specified environment. The DOM still truthfully records what the page rendered; the problem is that the requested experimental conditions were lost before observation.

The affected path is attaching to an existing Chromium page through CDP. The launch path supplies environment options to launchPersistentContext and does not use this temporary emulation session. The report concerns the lifetime of the three CDP overrides and their observable viewport effect; it does not make claims about unrelated browser settings or providers.

Environment and verification context

  • REA source revision: 411498d45f860eba2fbc9d7051af20237b7a4cb1.
  • Runtime: Node.js 24.18.0, npm 11.16.0, Windows x64.
  • Browser: Chrome 154.0.8037.98.
  • Target: an owned, loopback HTTP fixture in a separately started browser.
  • Interface: public CLI reproduction; shared MCP/provider path inspected.
  • Earliest failure stage: attached-page environment configuration, before navigation and capture.

The proposed regression checks the values produced by real page JavaScript, then observes the same external target independently after cleanup. It should cover successful CLI/MCP capture, partial initialization failure, action failure and cancellation, so a fix cannot preserve settings during capture by leaking them into the external browser afterward.

Proposed implementation

The focused fix and its regression coverage are available in https://github.com/morluto/rea/pull/1321. That PR addresses this issue on its own branch.

Originally created by @dakjdakd on GitHub (Oct 9, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1320 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Observed behavior `capture-browser-scenario` accepts an environment for an existing Chrome page, successfully completes the requested actions, and returns a DOM capture, but the page executes with its original locale, timezone, device scale and viewport. The overrides have already disappeared by the time REA navigates to `start_url`. This is reproducible with `browser.mode: "connect"` on revision `411498d45f860eba2fbc9d7051af20237b7a4cb1`. In a real Chrome 154 run, requesting `fr-FR`, `Pacific/Honolulu`, DPR `2` and a `900 × 600` viewport produced the following values from the page's own JavaScript: | Setting | Requested | Captured before the fix | |---|---|---| | `Intl.DateTimeFormat().resolvedOptions().locale` | `fr-FR` | `zh-CN` | | `Intl.DateTimeFormat().resolvedOptions().timeZone` | `Pacific/Honolulu` | `Asia/Shanghai` | | `devicePixelRatio` | `2` | `1` | | `innerWidth × innerHeight` | `900 × 600` | `758 × 482` | The final capture is an observation of those actual values, not a schema error or a failed action. The command can therefore look successful even though it did not perform the experiment described by its input. The original values in the right-hand column are specific to the test host and browser window; another host may expose different defaults. ## Reproduction Use an already-installed Chrome-family executable, Node.js and a built REA checkout. No remote site, browser extension or service worker is needed. A local page that prints the environment is sufficient. 1. Run `npm run build:cached` in the checkout. 2. Serve this page from a loopback HTTP address, for example `http://127.0.0.1:3000/`: ```html <!doctype html> <html><body> <pre id="environment"></pre> <button id="refresh-environment">Refresh environment</button> <script> const refresh = () => { document.querySelector('#environment').textContent = JSON.stringify({ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, locale: Intl.DateTimeFormat().resolvedOptions().locale, dpr: devicePixelRatio, width: innerWidth, height: innerHeight }); }; document.querySelector('#refresh-environment').onclick = refresh; refresh(); </script> </body></html> ``` 3. Start a separate Chrome instance with a disposable user-data directory and a loopback debugging port. Open the local page in that instance. For example, the relevant Chrome arguments are: ```text --headless=new --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222 --user-data-dir=<absolute path to a disposable profile> --no-first-run http://127.0.0.1:3000/ ``` 4. Read `http://127.0.0.1:9222/json/list` and select the `id` of that page target. Replace `PAGE_TARGET_ID` below with that exact ID. Save the request as `attached-environment.json`: ```json { "browser": { "mode": "connect", "cdp_endpoint": "http://127.0.0.1:9222", "target_id": "PAGE_TARGET_ID" }, "start_url": { "url": "http://127.0.0.1:3000/" }, "environment": { "locale": "fr-FR", "timezone": "Pacific/Honolulu", "viewport": { "width": 900, "height": 600, "device_scale_factor": 2 } }, "actions": [ { "step_id": "refresh", "action": "click", "locator": { "kind": "css", "selector": "#refresh-environment" } } ], "capture": { "after_each_step": ["dom"], "at_end": ["dom"] } } ``` 5. Run the public command: ```bash node scripts/rea.mjs capture-browser-scenario ./attached-environment.json --json ``` Inspect `normalized_result.steps[*].artifacts.dom.value.text`. The `<pre id="environment">` content records the environment on navigation and again after the refresh click. On the affected revision, both captures contain the external browser's defaults rather than the requested values. Passing this same JSON object as arguments to the MCP tool `capture_browser_scenario` exercises the shared provider path. The reproduction uses explicit DOM capture because a completed click or a captured final URL cannot establish that locale, timezone or device metrics were in effect. Reading those values inside the rendered document makes the discrepancy visible without interpreting a screenshot. ## Expected behavior The selected environment should remain active while REA navigates, performs actions and captures their results. For the request above, the page should report: ```json { "timezone": "Pacific/Honolulu", "locale": "fr-FR", "dpr": 2, "width": 900, "height": 600 } ``` When the scenario finishes, REA should release its attached connection and its emulation settings while leaving the externally owned browser and page open. That ownership boundary should also hold when configuration fails partway through, an action fails, or the request is cancelled. ## Analysis and suspected cause The relevant code is [`configureAttachedEnvironment` in `PlaywrightScenarioBrowser.ts`](https://github.com/morluto/rea/blob/411498d45f860eba2fbc9d7051af20237b7a4cb1/src/browser/PlaywrightScenarioBrowser.ts#L161-L194). It creates a page CDP session, sends `Emulation.setDeviceMetricsOverride`, `Emulation.setLocaleOverride` and `Emulation.setTimezoneOverride`, and then unconditionally calls `session.detach()` in `finally`. The CDP commands succeed. Their lifetime is the problem: Chrome resets these session-scoped overrides when the session detaches. A separate direct-CDP control on the same browser showed the requested values while that session remained attached and the original values immediately after detaching it. This control, together with the public CLI reproduction, identifies the early detach as the cause rather than a rejected setting, malformed request or page-specific script. The source also shows that attached-browser cleanup is already registered before configuration begins. Keeping the emulation session on that connection until the scenario's existing cleanup runs should align the settings' lifetime with the operation's lifetime. A new setting or public contract is unnecessary. The same early detach remains in [upstream revision `8b4c3591`](https://github.com/morluto/rea/blob/8b4c3591358ee59297d8298ede1b63d08459d817/src/browser/PlaywrightScenarioBrowser.ts#L175-L193); the original reproduction predates that revision, and comparison of the attached configuration source shows no intervening change. ## Impact and scope This affects reproducibility of attached-browser investigations. Locale-sensitive formatting, timezone-dependent application behavior and rendering at a selected DPR can all differ from what the scenario requests. It can also invalidate comparisons between captures if callers assume both runs used the specified environment. The DOM still truthfully records what the page rendered; the problem is that the requested experimental conditions were lost before observation. The affected path is attaching to an existing Chromium page through CDP. The launch path supplies environment options to `launchPersistentContext` and does not use this temporary emulation session. The report concerns the lifetime of the three CDP overrides and their observable viewport effect; it does not make claims about unrelated browser settings or providers. ## Environment and verification context - REA source revision: `411498d45f860eba2fbc9d7051af20237b7a4cb1`. - Runtime: Node.js `24.18.0`, npm `11.16.0`, Windows x64. - Browser: Chrome `154.0.8037.98`. - Target: an owned, loopback HTTP fixture in a separately started browser. - Interface: public CLI reproduction; shared MCP/provider path inspected. - Earliest failure stage: attached-page environment configuration, before navigation and capture. The proposed regression checks the values produced by real page JavaScript, then observes the same external target independently after cleanup. It should cover successful CLI/MCP capture, partial initialization failure, action failure and cancellation, so a fix cannot preserve settings during capture by leaking them into the external browser afterward. ## Proposed implementation The focused fix and its regression coverage are available in https://github.com/morluto/rea/pull/1321. That PR addresses this issue on its own branch.
bsakaguchi 2026-10-15 18:25:16 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#463
No description provided.