Skip to content

Script-backed commands fail with "failed to parse script result: no result member" when issued during a navigation — deterministic, one failure per navigation #335

Description

@adrianrobinsondev

Summary

Any command that evaluates script against a browsing context fails if it lands in the instant that
context is being replaced by a navigation. The error is always:

com.vibium.errors.VibiumException: failed to parse script result: no result member
	at com.vibium.internal.BiDiClient.mapError(BiDiClient.java:240)
	at com.vibium.internal.BiDiClient.dispatchLine(BiDiClient.java:193)
	at com.vibium.internal.BiDiClient.receiveLoop(BiDiClient.java:163)

This is not specific to clicking, to a particular page, or to cross-origin navigation. It reproduces
on a plain same-origin page with no application framework involved.

Reproduction

Start a navigation, then issue page.url() as fast as the transport allows. No application code, no
click, no popup:

page.go(BASE + "/some/page");

for (int round = 0; round < 10; round++) {
    // start a same-origin navigation from inside the page
    page.evaluate("window.location = '" + BASE + "/some/page?r=" + round + "'");

    // then race it for 3 seconds
    final long deadline = System.currentTimeMillis() + 3000;
    while (System.currentTimeMillis() < deadline) {
        try {
            page.url();
        } catch (RuntimeException e) {
            // VibiumException: failed to parse script result: no result member
        }
    }
    page.waitForLoad();
}

Result:

rounds producing at least one failure 10 of 10
total page.url() calls 13,724
total failures 10 — exactly one per navigation

Exactly one failure per navigation is the shape of the defect: there is a single instant in which a
command can land, and a tight poll hits it exactly once.

Why page.url() is a script command

url() is not a protocol-level read — it goes through the same decoder as everything else:

vibium:page.url
  → Router.handlePageURL            (clicker/internal/api/handlers_navigation.go:99)
  → GetURL                          (handlers_navigation.go:304)
  → EvalSimpleScript(s, context, "() => window.location.href")
  → bidi.ParseScriptResponse        (clicker/internal/bidi/script.go:106)

GetTitle (:309) and GetContent (:314) are the same shape, and so is every actionability
check. So the exposure is not one API — it is anything that evaluates script while a navigation is
in flight.

Where this shows up in real use

Two independent cases in our suite, both now explained by the above:

  1. A cart page's checkout button fails ~67% of the time. Clicking
    #btn-secure-checkout-top and then calling page.waitForLoad(): 37 of 55 real-page runs
    failed (8/10 standalone, 20/35 on Grid, 9/10 local browser). The click starts a navigation and the
    commands that follow land in the window.
  2. Reading a PayPal popup out of about:blank. The popup opens blank and navigates to the
    provider a moment later; polling popup.url() to wait for that threw the same error in 4 runs
    of 4
    , with no click anywhere in the flow.

What never fails is equally informative:

  • page.go(url)0 failures in 36 — because the client waits the navigation out before issuing
    anything else.
  • A different button on the same application (#nextlinkdiv) that also replaces the document on
    click, exercised dozens of times. Verified it really does navigate, with a marker a navigation
    would destroy:
page.evaluate("window.__navProbe = 'set'");
click("#nextlinkdiv");
page.evaluate("typeof window.__navProbe");   // → "undefined": the document was replaced

So whether a given flow fails depends on when it issues its next command relative to the
navigation, not on the control being clicked.

Relationship to #336

#336 is why this is opaque rather than self-diagnosing. ParseScriptResponse has a result branch and
an exception branch but no error-envelope branch, so a BiDi error response — which we would
expect to say the context was destroyed or navigated — lands on the "no result member" path and the
real message is discarded. Every cause collapses to the same string, so a caller cannot tell a
recoverable navigation race from a genuinely malformed response. With #336 fixed, this issue should
name its own cause.

Relationship to #289 / #291 and PR #29

This looks like the same class of race that #289 / #291 fixed in the capture path, where a BiDi
command issued into an in-flight navigation goes unanswered. #291 measured the window — "the
navigation an action triggers is not reported until ~10ms after that action's command returns"
— and
fixed it with captureWithNavigationRetry. Our build contains that fix; the script path appears to
have no equivalent, and the reproduction above is that gap made deterministic.

PR #29 ("optional auto-waiting on navigation after action") would likely close the window for the
action case, but not for a bare read like url() issued by application code.

Environment

Note on this report's history

This issue has been rewritten three times now as the diagnosis improved, and the earlier framings are wrong:
it was first reported as "a click that navigates on a script-heavy page", then narrowed to "one
page's checkout button, while other navigating clicks are fine". Both were field observations
without a reproduction. Nine synthetic scenarios failed to reproduce it because each one clicked and
then waited — none issued a command into the navigation window, which turns out to be the only
thing that matters. A disableForm() call we had flagged as the distinguishing feature of the
failing control was a red herring; synthesising that shape did not reproduce it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions