fix(browser): make Lightpanda backend usable end-to-end

Live testing surfaced three issues:

- Lightpanda numbers targets per-browser, and each conn is its own
  browser, so every tab gets the same upstream targetID
  ("FID-0000000001"). Synthesize globally-unique "lp-N" keys for our
  internal map so multi-tenant tab tracking works.
- page.Info() returns valid data once post-open then errors on
  subsequent calls, which made ListTabs silently drop tabs. Cache URL
  and Title at OpenTab time and read from the cache in ListTabs.
- rod.Browser.Close() calls Browser.close which Lightpanda doesn't
  implement; the WS drops cleanly anyway. Swallow the error to quiet
  the noisy log line.

Two Lightpanda upstream bugs are documented in docs/browser-backends.md
and exercised by TestLightpanda_KnownUpstreamGaps:
  1. Accessibility.getFullAXTree returns nodeId as a JSON number
     (CDP spec: string)
  2. Runtime.evaluate rejects go-rod's function-apply wrapper
This commit is contained in:
Pierre Tachoire committed 2026-07-01 10:23:20 +02:00
1 parent 890ced810d
commit 4147fee4e5
4 files changed
+81 -21

No files matched your search

+12 -3
View File
@@ -26,17 +26,26 @@ Set `GOCLAW_BROWSER_BACKEND=chrome|lightpanda` to pick the backend explicitly. I
| Feature | Chrome | Lightpanda | Notes |
|---|---|---|---|
| Navigate / reload | ✅ | ✅ | |
| AX snapshot (`Accessibility.getFullAXTree`) | ✅ | ✅ | Primary "see the page" path for the agent |
| AX snapshot (`Accessibility.getFullAXTree`) | ✅ | ⚠️ | Lightpanda upstream bug: returns `nodeId` as a JSON number, but CDP spec defines `AXNodeId` as a string. go-rod's typed decoder rejects it. Tracked as a known gap (see below) |
| Click / type / hover / press | ✅ | ✅ | |
| Wait (text / URL / stable) | ✅ | ✅ | |
| Evaluate JS | ✅ | ✅ | Subset on Lightpanda — see upstream docs |
| Evaluate JS | ✅ | ⚠️ | Lightpanda upstream bug: go-rod wraps eval JS in a function-apply call (`(function(){…}).apply(this, args)`); Lightpanda's runtime rejects it with "is not a function". Direct `Runtime.evaluate` works; go-rod's `Page.Eval` does not |
| Screenshot (`Page.captureScreenshot`) | ✅ | ❌ | Lightpanda returns a placeholder image. The tool returns an error on Lightpanda directing the agent to use `snapshot` instead |
| Multiple tabs per connection | ✅ | ❌ | Lightpanda: 1 CDP connection = 1 tab. Goclaw opens a fresh connection per tab transparently |
| Browser contexts / incognito | ✅ | Implicit | On Lightpanda every connection is already a fresh browser — isolation is automatic, no `Target.createBrowserContext` multiplexing |
| Cookies / localStorage shared across tabs | ✅ within a context | ❌ | Lightpanda: each tab is a fresh browser. A login on one tab is not visible to another |
| List open tabs from server | ✅ | ❌ | Lightpanda: no `/json/list`. Goclaw tracks tabs in its local map |
| List open tabs from server | ✅ | ❌ | Lightpanda: no `/json/list`. Goclaw tracks tabs in its local map (URL/title cached at OpenTab time, since `page.Info()` is also unreliable post-open) |
| Auto-reconnect on WS drop | ✅ | ❌ | Lightpanda: connection death = that tab is gone server-side. Goclaw drops the tab from the map and surfaces a clear error |
## Known upstream Lightpanda gaps
The two ⚠️ rows above are **Lightpanda-side bugs** that goclaw cannot work around without writing a custom CDP decoder. They prevent the agent's primary "see the page" workflow from functioning on Lightpanda today:
1. **`Accessibility.getFullAXTree` non-conformance.** CDP defines `AXNodeId` as a string; Lightpanda emits it as a JSON number. The `Snapshot` tool action fails with `cannot unmarshal number into Go struct field AccessibilityAXNode.nodes.nodeId of type proto.AccessibilityAXNodeID`.
2. **`Runtime.evaluate` function-wrapper.** go-rod's `page.Eval()` issues `Runtime.callFunctionOn` with a wrapped function expression. Lightpanda's runtime cannot invoke it (`TypeError: ... is not a function`). The `Evaluate` tool action and any internal `WaitStable`/`WaitNavigation` paths that use eval are affected.
Both gaps are exercised by `TestLightpanda_KnownUpstreamGaps` in `tests/integration/browser_lightpanda_test.go` — when Lightpanda fixes them, that test will start logging "remove this gap test" and we can drop the workarounds.
## When to choose which
**Lightpanda:**