hero_browser MCP — bugs and improvements from real-world SPA testing #86

Closed
opened 2026-03-24 16:56:14 +00:00 by mik-tf · 2 comments
Owner

Context

Used hero_browser MCP extensively for automated testing of a Dioxus WASM SPA (25+ page smoke test, API verification, auth flow testing). Found several bugs and missing features.

Bugs

1. element_set_value reuses variable name el

Calling element_set_value twice on the same page throws:

SyntaxError: Identifier 'el' has already been declared

The injected JS declares const el = ... — calling it twice redeclares in the same scope. Fix: wrap in IIFE or use unique variable names.

2. network_install / console_install return empty arrays

Installed both interceptors before navigation, waited 10+ seconds, but network_requests and console_messages both returned []. WASM-generated fetch() calls were not captured. This was the most limiting issue — had to work around with manual js_evaluate + fetch() calls.

Possible causes:

  • Interceptors don't survive page navigations
  • WASM fetch() calls bypass the interceptor layer
  • Timing issue with when interceptors attach

3. page_screenshot returns base64 inline (219K+ chars)

Exceeds token limits for LLM context. The tool should either:

  • Default to file output (page_screenshot_save)
  • Auto-detect when result exceeds a size threshold and save to file
  • Document that page_screenshot_save should be preferred

Missing Features

4. No page_wait_for_idle or page_wait_for_network_idle

WASM SPAs need time to load (5-15s). Currently must use manual sleep between every navigation. A "wait for network idle" or "wait for DOM to stabilize" primitive would be the single most impactful addition for SPA testing workflows.

element_wait exists but doesn't help when you need to wait for the entire app to initialize.

5. No top-level await in js_evaluate

await fetch('/api/health')  // SyntaxError

Had to use Promise chains with window.__result globals, then a separate js_evaluate call to read results. Either support top-level await or add js_evaluate_async that returns the resolved Promise value.

Skill Doc Recommendations

6. Pattern: WASM SPA automated testing

Document the pattern we developed:

  • Login via fetch() in js_evaluate, store token in localStorage
  • Navigate via page_navigate (full reload) or history.pushState + popstate (SPA routing)
  • Wait with sleep (until #4 is fixed)
  • Check DOM with js_evaluate (querySelector for errors, cards, content)
  • Test APIs from browser context to verify CORS/proxy behavior

7. Pattern: Framework-reactive input handling

element_set_value doesn't trigger Dioxus/React signal updates. Must use:

var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
nativeInputValueSetter.call(el, 'value');
el.dispatchEvent(new Event('input', { bubbles: true }));

This should be documented or handled automatically by element_set_value.

Priority

Issue Impact Effort
#1 element_set_value bug High (blocks multi-input forms) Low
#2 network/console empty High (blocks debugging) Medium
#4 wait_for_idle High (every SPA test needs it) Medium
#5 async js_evaluate Medium (workaround exists) Low
#3 screenshot size Low (use _save variant) Low
#7 reactive inputs Medium (document or fix) Low

Signed-off-by: mik-tf

## Context Used hero_browser MCP extensively for automated testing of a Dioxus WASM SPA (25+ page smoke test, API verification, auth flow testing). Found several bugs and missing features. ## Bugs ### 1. `element_set_value` reuses variable name `el` Calling `element_set_value` twice on the same page throws: ``` SyntaxError: Identifier 'el' has already been declared ``` The injected JS declares `const el = ...` — calling it twice redeclares in the same scope. Fix: wrap in IIFE or use unique variable names. ### 2. `network_install` / `console_install` return empty arrays Installed both interceptors before navigation, waited 10+ seconds, but `network_requests` and `console_messages` both returned `[]`. WASM-generated `fetch()` calls were not captured. This was the most limiting issue — had to work around with manual `js_evaluate` + `fetch()` calls. Possible causes: - Interceptors don't survive page navigations - WASM `fetch()` calls bypass the interceptor layer - Timing issue with when interceptors attach ### 3. `page_screenshot` returns base64 inline (219K+ chars) Exceeds token limits for LLM context. The tool should either: - Default to file output (`page_screenshot_save`) - Auto-detect when result exceeds a size threshold and save to file - Document that `page_screenshot_save` should be preferred ## Missing Features ### 4. No `page_wait_for_idle` or `page_wait_for_network_idle` WASM SPAs need time to load (5-15s). Currently must use manual `sleep` between every navigation. A "wait for network idle" or "wait for DOM to stabilize" primitive would be the **single most impactful addition** for SPA testing workflows. `element_wait` exists but doesn't help when you need to wait for the entire app to initialize. ### 5. No top-level `await` in `js_evaluate` ```js await fetch('/api/health') // SyntaxError ``` Had to use Promise chains with `window.__result` globals, then a separate `js_evaluate` call to read results. Either support top-level await or add `js_evaluate_async` that returns the resolved Promise value. ## Skill Doc Recommendations ### 6. Pattern: WASM SPA automated testing Document the pattern we developed: - Login via `fetch()` in `js_evaluate`, store token in `localStorage` - Navigate via `page_navigate` (full reload) or `history.pushState` + `popstate` (SPA routing) - Wait with `sleep` (until #4 is fixed) - Check DOM with `js_evaluate` (querySelector for errors, cards, content) - Test APIs from browser context to verify CORS/proxy behavior ### 7. Pattern: Framework-reactive input handling `element_set_value` doesn't trigger Dioxus/React signal updates. Must use: ```js var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; nativeInputValueSetter.call(el, 'value'); el.dispatchEvent(new Event('input', { bubbles: true })); ``` This should be documented or handled automatically by `element_set_value`. ## Priority | Issue | Impact | Effort | |-------|--------|--------| | #1 element_set_value bug | High (blocks multi-input forms) | Low | | #2 network/console empty | High (blocks debugging) | Medium | | #4 wait_for_idle | High (every SPA test needs it) | Medium | | #5 async js_evaluate | Medium (workaround exists) | Low | | #3 screenshot size | Low (use _save variant) | Low | | #7 reactive inputs | Medium (document or fix) | Low | Signed-off-by: mik-tf
Author
Owner

Implementation Summary — commit 88e2c4b

All 5 technical items from this issue have been addressed in a single squash commit on development in hero_browser_mcp.

Bug #1: Variable Name Collision in element_set_value — FIXED

  • Wrapped JS in an IIFE to prevent const el redeclaration across multiple calls
  • Added native property setter via Object.getOwnPropertyDescriptor for React/Dioxus signal compatibility
  • Now dispatches both input and change events for framework reactivity

Bug #3: Screenshot Size Limitations — FIXED

  • page_screenshot now auto-saves to /tmp/hero_browser-{timestamp}.png when base64 exceeds 100KB
  • Returns file path instead of inline data, avoiding MCP token limit issues
  • Both MCP tool and JSON-RPC handler updated

Feature #5: Async JavaScript Evaluation — ADDED

  • New js_evaluate_async tool wraps expression in async IIFE with CDP awaitPromise: true
  • Supports top-level await — e.g. return await fetch('/api/health').then(r => r.json())
  • Available via both MCP and JSON-RPC

Feature #4: Network Idle Waiting — ADDED

  • New page_wait_for_network_idle tool — the single most impactful addition for SPA testing
  • Injects a tracker that monitors in-flight fetch and XMLHttpRequest calls
  • Resolves when no requests are in-flight for idle_ms (default: 500ms)
  • Configurable timeout (default: 30s)
  • Eliminates reliance on manual sleep calls

Bug #2: Interceptor Navigation Loss — DOCUMENTED

  • Root cause: JS-injected interceptors (window.__hero*) are destroyed on full page navigation
  • OpenRPC descriptions now document that network_install and console_install must be re-installed after full page navigations
  • A proper CDP-level fix (using Fetch.enable / Runtime.consoleAPICalled events) is a larger architectural change for a follow-up

Files Changed

  • crates/hero_browser_core/src/browser/page.rs — core fixes + new methods
  • crates/hero_browser_server/src/rpc_handler.rs — JSON-RPC handlers
  • crates/hero_browser_server/src/server.rs — MCP tool definitions
  • crates/hero_browser_server/src/openrpc.rs — OpenRPC spec

Browse: lhumina_code/hero_browser_mcp@88e2c4b

## Implementation Summary — commit `88e2c4b` All 5 technical items from this issue have been addressed in a single squash commit on `development` in `hero_browser_mcp`. ### Bug #1: Variable Name Collision in `element_set_value` — FIXED - Wrapped JS in an IIFE to prevent `const el` redeclaration across multiple calls - Added native property setter via `Object.getOwnPropertyDescriptor` for React/Dioxus signal compatibility - Now dispatches both `input` and `change` events for framework reactivity ### Bug #3: Screenshot Size Limitations — FIXED - `page_screenshot` now auto-saves to `/tmp/hero_browser-{timestamp}.png` when base64 exceeds 100KB - Returns file path instead of inline data, avoiding MCP token limit issues - Both MCP tool and JSON-RPC handler updated ### Feature #5: Async JavaScript Evaluation — ADDED - New `js_evaluate_async` tool wraps expression in async IIFE with CDP `awaitPromise: true` - Supports top-level `await` — e.g. `return await fetch('/api/health').then(r => r.json())` - Available via both MCP and JSON-RPC ### Feature #4: Network Idle Waiting — ADDED - New `page_wait_for_network_idle` tool — the single most impactful addition for SPA testing - Injects a tracker that monitors in-flight `fetch` and `XMLHttpRequest` calls - Resolves when no requests are in-flight for `idle_ms` (default: 500ms) - Configurable timeout (default: 30s) - Eliminates reliance on manual sleep calls ### Bug #2: Interceptor Navigation Loss — DOCUMENTED - Root cause: JS-injected interceptors (`window.__hero*`) are destroyed on full page navigation - OpenRPC descriptions now document that `network_install` and `console_install` must be re-installed after full page navigations - A proper CDP-level fix (using `Fetch.enable` / `Runtime.consoleAPICalled` events) is a larger architectural change for a follow-up ### Files Changed - `crates/hero_browser_core/src/browser/page.rs` — core fixes + new methods - `crates/hero_browser_server/src/rpc_handler.rs` — JSON-RPC handlers - `crates/hero_browser_server/src/server.rs` — MCP tool definitions - `crates/hero_browser_server/src/openrpc.rs` — OpenRPC spec Browse: https://forge.ourworld.tf/lhumina_code/hero_browser_mcp/commit/88e2c4b
Author
Owner

Closing Summary

All items from this issue have been resolved in two commits on development in hero_browser_mcp:

Commit 1: 88e2c4b — Bug fixes + new features

# Item Status Verified
1 element_set_value variable collision Fixed — IIFE wrapper + native property setter for React/Dioxus Live test: two consecutive calls, both succeed
2 Interceptors lost on navigation Documented — OpenRPC descriptions warn to re-install after navigation. CDP-level fix deferred (larger arch change) N/A
3 Screenshot size blows token limits Fixed — auto-saves to /tmp when base64 > 100KB Live test: 7.1MB Wikipedia screenshot saved to file
4 No page_wait_for_network_idle Added — tracks in-flight fetch/XHR, resolves when idle for N ms Live test: detects fetch completion correctly
5 No async JS evaluation Addedjs_evaluate_async with CDP awaitPromise Live test: await fetch(), promises, complex returns all work

Commit 2: 2d7c57c — Doctest fixes

  • Fixed 25 pre-existing doctest failures (references to hero_browser_sdkhero_browser_core)
  • Fixed hero_browser_core/Makefile referencing wrong crate
  • Full test suite now passes: 10 unit tests + 26 doctests = 36/36 green

Testing performed

  • cargo check — clean build
  • cargo test — 36/36 pass (0 failures)
  • Live integration tests against running hero_browser_server with headless Chromium:
    • Browser create/destroy lifecycle
    • element_set_value called twice on same page (Bug #1 regression test)
    • js_evaluate_async with fetch, Promise, and complex returns (Feature #5)
    • page_wait_for_network_idle after navigation and fetch (Feature #4)
    • page_screenshot auto-save on large page (Bug #3)

Remaining for follow-up

  • Bug #2 (interceptor navigation loss): Documented but not fully fixed. A proper CDP-level solution using Fetch.enable and Runtime.consoleAPICalled events would survive page navigations but requires a larger architectural change to hero_browser_core.
  • Patterns #6 and #7 (documentation): SPA testing workflow docs and framework-reactive input handling docs can be added to docs/ once patterns are validated in more real-world usage.
## Closing Summary All items from this issue have been resolved in two commits on `development` in [hero_browser_mcp](https://forge.ourworld.tf/lhumina_code/hero_browser_mcp): ### Commit 1: [`88e2c4b`](https://forge.ourworld.tf/lhumina_code/hero_browser_mcp/commit/88e2c4b) — Bug fixes + new features | # | Item | Status | Verified | |---|------|--------|----------| | 1 | `element_set_value` variable collision | **Fixed** — IIFE wrapper + native property setter for React/Dioxus | Live test: two consecutive calls, both succeed | | 2 | Interceptors lost on navigation | **Documented** — OpenRPC descriptions warn to re-install after navigation. CDP-level fix deferred (larger arch change) | N/A | | 3 | Screenshot size blows token limits | **Fixed** — auto-saves to `/tmp` when base64 > 100KB | Live test: 7.1MB Wikipedia screenshot saved to file | | 4 | No `page_wait_for_network_idle` | **Added** — tracks in-flight fetch/XHR, resolves when idle for N ms | Live test: detects fetch completion correctly | | 5 | No async JS evaluation | **Added** — `js_evaluate_async` with CDP `awaitPromise` | Live test: `await fetch()`, promises, complex returns all work | ### Commit 2: [`2d7c57c`](https://forge.ourworld.tf/lhumina_code/hero_browser_mcp/commit/2d7c57c) — Doctest fixes - Fixed 25 pre-existing doctest failures (references to `hero_browser_sdk` → `hero_browser_core`) - Fixed `hero_browser_core/Makefile` referencing wrong crate - Full test suite now passes: 10 unit tests + 26 doctests = 36/36 green ### Testing performed - `cargo check` — clean build - `cargo test` — 36/36 pass (0 failures) - **Live integration tests** against running `hero_browser_server` with headless Chromium: - Browser create/destroy lifecycle - `element_set_value` called twice on same page (Bug #1 regression test) - `js_evaluate_async` with fetch, Promise, and complex returns (Feature #5) - `page_wait_for_network_idle` after navigation and fetch (Feature #4) - `page_screenshot` auto-save on large page (Bug #3) ### Remaining for follow-up - **Bug #2 (interceptor navigation loss)**: Documented but not fully fixed. A proper CDP-level solution using `Fetch.enable` and `Runtime.consoleAPICalled` events would survive page navigations but requires a larger architectural change to hero_browser_core. - **Patterns #6 and #7 (documentation)**: SPA testing workflow docs and framework-reactive input handling docs can be added to `docs/` once patterns are validated in more real-world usage.
Sign in to join this conversation.
No labels
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
lhumina_code/home#86
No description provided.