--- name: browser-app-testing description: "Test and debug Bricks or SPA apps with browser tools." version: 1.0.0 tags: [browser, testing, spa, bricks, debugging, cdp] --- # Browser App Testing Test and debug web applications through Hermes browser tools. Covers SPA routing verification, framework-specific interaction workarounds, console log capture, and DOM querying when accessibility snapshots are insufficient. ## Quick Diagnostic ``` browser_navigate(url='...') # Load the app browser_console() # Check for JS errors and app logs browser_snapshot() # See rendered elements ``` ## Interaction Workarounds by Framework ### Standard SPAs (Vue, React, etc.) `browser_click` works for standard DOM events. Tested with Vue.js docs — full SPA client-side routing confirmed. ### Bricks Framework **`browser_click` does NOT work with Bricks widgets.** Bricks uses an internal event system that doesn't respond to CDP-level click events. **Workaround: `dispatchEvent` via `browser_console`** (verified on Pipeline 产线平台): ```javascript // Click a specific Bricks menu item const el = document.querySelector('#sidebar_menu .vcontainer').children[0]; el.dispatchEvent(new MouseEvent('click', {bubbles: true, cancelable: true})); ``` Compatibility matrix: | Action | browser_click | .click() via console | dispatchEvent | |--------|:---:|:---:|:---:| | Bricks Menu item | ❌ | ❌ | ✅ | | Bricks toggle button (native DOM) | ❌ | ✅ | ✅ | | Bricks Form submit | ❌ | ❌ | ❌ | | Standard SPAs | ✅ | ✅ | ✅ | | Console log capture | — | ✅ | ✅ | ## Log Capture `browser_console()` without arguments returns all accumulated console messages and uncaught JS errors: ``` browser_console() → console_messages: [{type, text, source}, ...] → js_errors: [{error_message, url, line}, ...] → total_messages, total_errors ``` Bricks log markers: - `idset= id= ` — widget instantiation - `regen_menuitem_event()` — menu click with module/url - `401 unauthorized, opening login` — auth redirect To run JS and capture result simultaneously, pass `expression`: ``` browser_console(expression="document.querySelector('#sidebar').innerText") ``` To clear accumulated logs, use `clear=true`: ``` browser_console(clear=true) ``` ## DOM Querying When Snapshot Is Insufficient `browser_snapshot` may show Bricks widgets as "generic" without text. Query the actual DOM: ```javascript // Get widget text content document.querySelector('#sidebar_menu').innerText // Get current URL (verify SPA routing) window.location.href // Check for specific elements document.querySelectorAll('iframe').length // Inspect element class/state document.querySelector('#sidebar_menu').className ``` ## SPA Routing Verification 1. Take snapshot, record URL via `browser_console`: `window.location.href` 2. Click a nav link 3. Take new snapshot, check URL again 4. Verify: URL changed WITHOUT full page reload = SPA routing works 5. Check `browser_console()` for route-change logs and errors ## CDP Cookie & Session Debugging When apps use HttpOnly cookies (like `AIOHTTP_SESSION`), `document.cookie` can't see them. Use CDP's Storage domain instead: ``` browser_cdp(method='Storage.getCookies', params={}) → lists ALL cookies across ALL domains — find cross-domain session issues browser_cdp(method='Storage.clearCookies', params={}) → clears all cookies (works) browser_cdp(method='Storage.setCookies', params={'cookies': [{...}]}) → inject a cookie (e.g. session from curl login) ``` Note: `Network.deleteCookies` and `Network.clearBrowserCookies` return -32601 (not found) in headless Chrome — use `Storage.*` methods instead. ## Bricks iframe Interaction When a Bricks page is loaded in an iframe (e.g. login overlay), two patterns work: ### Pattern A: Direct CDP into iframe context ```python # From browser_snapshot, find frame_id in frame_tree.children[] browser_cdp(method='Runtime.evaluate', frame_id='542403D3A13B041DE4F59F9635D5307E', params={'expression': 'typeof bricks'}) # → 'object' if Bricks loaded, 'undefined' if not ``` ### Pattern B: Parent-page DOM query ```javascript // Query iframe content from parent page (same-origin only) const iframe = document.getElementById('login-iframe'); const doc = iframe.contentDocument || iframe.contentWindow.document; doc.querySelectorAll('input'); // find form fields ``` ### Bricks PopupWindow form interaction Bricks form fields in PopupWindows are NOT native `` elements. **Critical: PopupWindow runs in an isolated Bricks app context.** When a PopupWindow opens (e.g. login), `bricks.apps` queried from the parent page is empty — the PopupWindow's widgets are NOT accessible via `bricks.getWidgetById()` from the parent page context. The PopupWindow creates its own Bricks app instance. To interact with PopupWindow form fields, you must execute code inside the PopupWindow's context. Approach: 1. Check `browser_snapshot` for the login form's textbox refs (e.g. @e19, @e20) 2. Use `browser_type` on those refs to fill fields (this works for Bricks textboxes) 3. For submit: `browser_click`, `dispatchEvent`, and `.click()` all fail on Bricks Submit/Reset/Cancel buttons. Use the `curl` login + CDP cookie injection approach (see Pitfall #8) as fallback, but note Pitfall #10 below. ## Pitfalls 1. **browser_click silent failure on Bricks**: Always verify with `browser_console()` after clicking — if no new logs appear, the click didn't register. Switch to `dispatchEvent`. 2. **Bricks widget text missing in snapshot**: The accessibility tree shows "generic" for Bricks custom elements. Always use `browser_console` with DOM queries for actual text content. 3. **iframe content**: Interactive examples often live in sandboxed iframes. Use `browser_console(expression='...', frame_id='...')` with the frame_id from `browser_snapshot.frame_tree`. 4. **401 unauthenticated**: Without login session, Bricks apps return 401 and show login popup. Test workflow: first login via `browser_navigate` + `browser_type` + `browser_click` on login form, then navigate to target pages. 5. **CDN resources blocked in China**: See `references/browser-setup-cn.md` for Playwright Chromium setup via npmmirror. 6. **Bricks login page JS redirect**: Some RBAC login pages (`/rbac/user/login.ui`) redirect via JS before the login form renders — even with no cookies. The server HTML is clean; the redirect is client-side in bricks.js. Workaround: use `curl` to call the login DSPY directly, capture the Set-Cookie, inject via CDP `Storage.setCookies`. 7. **HttpOnly cookies invisible to JS**: `document.cookie` won't show HttpOnly session cookies. Use `browser_cdp(method='Storage.getCookies')` to see all cookies including HttpOnly ones. 8. **Bricks Form Submit requires Bricks API**: `browser_click`, `dispatchEvent`, and `.click()` all fail on Bricks Form Submit/Reset/Cancel buttons. These are NOT native `