Skip to content

execute/sync does not resolve W3C element references to DOM nodes #2

@FumblingBarbell

Description

@FumblingBarbell

Note: Thanks for putting together this crate. This is a much needed piece of functionality.
I ran into this issue trying to use it, and asked Claude to see if it could provide a solution. Here's what it came up with.
The text below was created by Claude Opus 4.6.


Summary

The execute_sync (and likely execute_async) handler in tauri-wd does not resolve W3C WebDriver element references back to DOM nodes before passing them to the injected script. This causes every WebdriverIO command that internally uses browser.execute(script, element) to fail on macOS WKWebView.

Error

WebDriverError: Argument 1 ('other') to Node.contains must be an instance of Node
when running "execute/sync" with method "POST"

Root Cause

When WebdriverIO calls browser.execute(script, element), it serializes the element per the W3C WebDriver spec as:

{"element-6066-11e4-a52e-4f735466cecf": "<uuid>"}

Per the spec, the WebDriver server must deserialize this JSON object back into a real DOM element before passing it to the JavaScript function.

The perform_actions handler in tauri-wd does this correctly — it walks action arguments, resolves the UUID to a (selector, index) pair, and the plugin uses document.querySelectorAll(selector)[index] to get the actual DOM node.

However, execute_sync does not do this. It directly serializes args to JSON and injects them into the script:

let args_json = serde_json::to_string(&body.args).unwrap();
let script = format!(
    "var __args={args_json};return (function(){{{}}}).apply(null,__args)",
    body.script
);

So the function receives a plain JavaScript object {"element-6066-11e4-a52e-4f735466cecf": "some-uuid"} instead of a DOM Node.

Impact

WebdriverIO v9 uses browser.execute() with element arguments for many fundamental operations:

  • isDisplayed() / waitForDisplayed() — runs the isElementDisplayed script which calls document.contains(element)
  • checkVisibility() — passes element to elem.checkVisibility()
  • getComputedStyle() — passes element to window.getComputedStyle(elem)

This means virtually every element interaction fails, making E2E tests unusable.

Suggested Fix

The execute_sync handler should walk the args array before forwarding to the plugin. For each argument that is a JSON object with the key "element-6066-11e4-a52e-4f735466cecf", look up the UUID in session.elements and replace it with inline JavaScript that resolves the element in the browser, e.g.:

// Replace W3C element reference with a resolution expression
let resolution_js = format!(
    "document.querySelectorAll('{}')[{}]",
    elem_ref.selector, elem_ref.index
);

This matches the pattern already used in perform_actions.

Environment

  • tauri-webdriver-automation v0.1.3
  • WebdriverIO v9.19.0
  • macOS 15 (Sequoia), WKWebView
  • Tauri v2.10.2

Reproduction

Any WebdriverIO test that interacts with elements will trigger this. Minimal example:

const el = await $("#some-element");
await expect(el).toBeDisplayed(); // fails with Node.contains error

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions