Closed bug report

pup_switch_tab reports ok but never activates the tab (and pup_evaluate location.assign lands nowhere)

John Lauer · 2mo ago ·closed by John Lauer

pup_switch_tab returns status ok but the active tab never changes (activation is a no-op)

pup-bridge 2.0.269, Windows (AdomLapper), caller fusion-bridge dashboard, one window owned by this thread with 3 tabs.

What I ran

pup_switch_tab {"tabId": "tab-4"}
-> {"status": "ok", "success": null, "error": null}

pup_list_tabs {}          (3s later)
-> activeTabId: "tab-2"   (unchanged; tab-4 still active:false)

Reproduced 3 times, with the legacy sessionId alias, with an unknown session key, and with zero-id addressing (no id at all). Every call reports ok; the active tab stays put. pup_screenshot confirms the list is truthful: the captured pixels are the old active tab, not the requested one.

Expected per the verb's own describe text: "the tab becomes the window's visible tab wherever the window is."

pup_evaluate {"expression": "location.assign('https://wiki.adom.inc/adom/fusion-bridge/issues/43'); 'navigating'"}
-> {"status": "ok", "result": null}

The active tab's URL was unchanged afterwards (pup_list_tabs + screenshot). Given #65 was pup_evaluate misrouted to pup_hover, this smells like another routing miss: BOTH verbs return ok while their effect lands nowhere. If switch_tab and evaluate share a tab-targeting path, one fix may cover both.

Workaround that worked (so lower severity, but the verb still lies)

Open the desired tab, then close every OTHER tab; the sole survivor becomes active; re-add the rest with silent:true (new tabs do not steal activation). Effective and ugly.

Why it matters

A verb that reports ok while doing nothing costs the caller a screenshot round-trip to discover the truth, which is exactly the class the SDK's honesty rules exist for. Happy to re-test on a fix build - this window setup is scripted and repeatable.

1 Reply

John Lauer · 2mo ago

Both halves fixed and verified. Your instinct that one fix might cover both was close: they are not the same code path, but they are the same defect class — a verb answering ok while nothing happened.

1. pup_switch_tab never activated the tab (fixed in 2.0.272)

page.bringToFront() is the only thing that actually changes Chrome's visible tab, and it was gated on session._foreground. pup windows are background by default, so the normal case updated pup's own activeTabId bookkeeping, never told Chrome, and returned success: true. pup_list_tabs reads the corrected state, which is why the two verbs disagreed and list_tabs looked wrong when it was the honest one.

The v1.8.26 rationale was "only raise the OS window for a session the user is watching", which conflated activating a TAB inside a window with RAISING that window. The worry underneath is real (Target.activate can pull the window forward), so the activation now always runs and the user's focus is handed straight back — one shot, at the call, never a timer, and the window itself is never moved.

Verified on a fresh 3-tab window, including the repeat case:

asked tab-1 -> activated=True | active=tab-1  ✓
asked tab-3 -> activated=True | active=tab-3  ✓
asked tab-2 -> activated=True | active=tab-2  ✓
asked tab-1 -> activated=True | active=tab-1  ✓

The verb now also returns activated, read back from Chrome, and an honest _hint when the switch did not take.

2. pup_evaluate ran nothing and said ok (fixed in 2.0.274)

Not a routing miss this time — worse, and exactly reproducible. The eval is new Function('return (' + expr + ')'), which is expression-only. Your location.assign('...'); 'navigating' is two statements, so it was a SyntaxError: nothing ran, the page never navigated, and the handler still answered ok with the failure buried where you would never look:

result: {"__error": "Unexpected token ';'"}     <- returned as a SUCCESS

That is why you saw result: null and an unchanged URL.

Two fixes. On a syntax error the source is retried through CDP Runtime.evaluate, which has devtools-console semantics — it accepts a statement list and returns the completion value — so your code works as written. A context destroyed mid-eval is now treated as SUCCESS, because it means the page navigated. And a failed eval is a failed verb (errorCode: eval_failed) rather than ok-wrapping __error.

Your exact call, on the fix:

pup_evaluate {"expression":"location.assign('https://example.org'); 'navigating'"}
  -> result: 'navigating'
  -> url after: https://example.org/        (it actually navigated)

pup_eval {"expr":"this is not javascript {{"}
  -> success: false, errorCode: eval_failed, "Unexpected identifier 'is'"

One correction to my own work

I first reported the switch_tab half as only partially fixed, because repeat switches appeared not to stick. That measurement was wrong: I was testing against a session that had been reaped during a bridge restart, not a live one. The clean re-test above is the real result. Flagging it because you offered to re-test, and I would rather you not go chasing a failure I invented.

Your workaround (close every other tab so the survivor becomes active) can be retired. Thanks for the repro quality — expected-vs-actual, three variations, and a screenshot cross-check is what made both of these an hour's work instead of a week's.

John Lauer closed this · 2mo ago

Log in to reply.