fix(dioxus): preserve undefined in execute() via __wdio_value__ envelope#411
Conversation
The embedded polling loop coerced `result ?? null`, collapsing undefined
and null to null. Mirror @wdio/tauri-service's envelope pattern:
- guest-js: emit `{ __wdio_value__: result }` instead of `result ?? null`;
JSON.stringify omits undefined properties so undefined→{} (key absent)
while null→{"__wdio_value__":null} (key present), making them
distinguishable on receipt.
- dioxus-service execute.ts: unwrap the envelope in both `execute` and
`runInterceptorScript` via a shared `unwrapEmbeddedResult` helper.
- Rebuild and commit dist-js/index.js.
- Update tests to use envelope-wrapped mock return values.
Closes #409
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…spec.ts runInterceptorScript now unwraps the __wdio_value__ envelope, so unit test stubs that mock browser.execute returning call-data objects need to wrap those values in the envelope too. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
| Filename | Overview |
|---|---|
| packages/dioxus-service/src/commands/execute.ts | Adds embeddedBrowsers WeakSet gate, conditional envelope injection in function-form execute() and runInterceptorScript, and unwrapEmbeddedResult — all logic is correct and well-guarded |
| packages/dioxus-service/src/service.ts | Computes isEmbedded from capabilities in the constructor and calls markAsEmbedded per browser instance (including each multiremote instance) inside addDioxusApi |
| packages/dioxus-service/test/commands/execute.spec.ts | Adds embeddedBrowserStub helper and new tests covering the envelope unwrap for undefined, null, and plain-object results on both embedded and external driver paths |
| packages/dioxus-service/test/mock.spec.ts | Updates makeBrowser to mark the stub as embedded and wraps all interceptor-script mock return values in wdio_value envelopes to match the new embedded-driver contract |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Test as Test
participant Service as DioxusWorkerService
participant Execute as execute.ts
participant BrowserExecute as browser.execute
participant GuestJS as guest-js polling
Test->>Service: before()
Service->>Execute: markAsEmbedded(browser)
Test->>Execute: execute(browser, fn, args)
Note over Execute: isEmbedded=true, inject envelope wrapper
Execute->>BrowserExecute: wrapped script with resolve envelope
BrowserExecute->>GuestJS: run via embedded channel
Note over GuestJS: result = envelope object, not null
GuestJS-->>BrowserExecute: JSON response with envelope
BrowserExecute-->>Execute: raw object
Execute->>Execute: unwrapEmbeddedResult(raw)
Note over Execute: absent key = undefined, present key = value
Execute-->>Test: ReturnValue (undefined or actual value)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Test as Test
participant Service as DioxusWorkerService
participant Execute as execute.ts
participant BrowserExecute as browser.execute
participant GuestJS as guest-js polling
Test->>Service: before()
Service->>Execute: markAsEmbedded(browser)
Test->>Execute: execute(browser, fn, args)
Note over Execute: isEmbedded=true, inject envelope wrapper
Execute->>BrowserExecute: wrapped script with resolve envelope
BrowserExecute->>GuestJS: run via embedded channel
Note over GuestJS: result = envelope object, not null
GuestJS-->>BrowserExecute: JSON response with envelope
BrowserExecute-->>Execute: raw object
Execute->>Execute: unwrapEmbeddedResult(raw)
Note over Execute: absent key = undefined, present key = value
Execute-->>Test: ReturnValue (undefined or actual value)
Reviews (5): Last reviewed commit: "fix(dioxus): move __wdio_value__ envelop..." | Re-trigger Greptile
Only call unwrapEmbeddedResult when the browser is using the embedded driver. External WebDriver returns raw JS values — wrapping them unconditionally would corrupt any plain object result. Uses a module-level WeakSet<WebdriverIO.Browser> so the browser type is not changed. markAsEmbedded() is called from addDioxusApi() when driverProvider === 'embedded'. Tests: add embeddedBrowserStub() helper and an external-driver passthrough test; mark mock.spec.ts browsers as embedded so the interceptor call-data scripts unwrap correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Standing release PR: #400 · 11 packages queued · open 23h 30m · ✅ ready to merge Release Preview — 12 packages
These changes will be added to the release PR (#400) when merged: ChangelogProject-wide changesFixed
@wdio/dioxus-service N/A → 1.0.1Fixed
@wdio/electron-cdp-bridge wdio-electron-cdp-bridge@v10.0.0 → 10.0.1Changed
@wdio/electron-service wdio-electron-service@v10.0.0 → 10.0.1Changed
@wdio/flutter-service v1.0.0-next.0 → 1.0.1Changed
@wdio/native-cdp-bridge v0.1.0-next.0 → 0.1.1Changed
@wdio/native-mobile-core v1.0.0 → 1.0.1Changed
@wdio/native-spy wdio-native-spy@v1.1.0 → 1.1.1Changed
@wdio/native-types wdio-native-types@v2.3.1 → 2.3.2Changed
@wdio/native-utils wdio-native-utils@v2.4.0 → 2.4.1Changed
@wdio/react-native-service v1.0.0-next.0 → 1.0.1Changed
dioxus-package-test-app v0.1.0 → 0.1.1Changed
wdio-dioxus-embedded-driver N/A → 1.0.1Fixed
After merge — predicted release
Updated automatically by ReleaseKit |
…handlers The guest-js polling loop is shared by all embedded-driver eval paths: execute/sync, execute/async, and internal calls like getTitle/getUrl. Wrapping every result in the polling loop broke getTitle (returned an object instead of a string) and getUrl. Move the envelope injection into the execute_sync and execute_async Rust handlers instead, where it is applied only to user scripts. Internal eval calls continue to return raw values. The guest-js is reverted to its original `result ?? null` form. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nvelope
The previous attempt wrapped execute_sync results as:
return { __wdio_value__: (function() { <script> })() }
The TS execute.ts script always returns a Promise. That left an
unresolved Promise inside the envelope; JSON.stringify serialised it
as {} before it settled, corrupting the result.
Use Promise.resolve().then() instead so the inner Promise is fully
awaited before the settled value is wrapped in {__wdio_value__: r}.
This handles both synchronous returns (raw values) and async returns
(Promises from the TS function-script wrapper) uniformly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Rust execute_sync/execute_async handlers were wrapping every
/execute/sync and /execute/async response in the envelope. This broke
WDIO internal atoms (isDisplayed, scrollIntoView, etc.) that call
browser.execute() directly and have no TS-side unwrapper.
Correct approach: the Rust handlers stay as plain IIFEs. The envelope
is injected inside the TS-built function-form script's Promise .then()
resolver, only when the browser is marked as embedded. External driver
and string-form paths are unaffected.
runInterceptorScript: embedded path wraps synchronously as
`return { __wdio_value__: (${script})() }` — safe because interceptor
scripts are synchronous and need no Promise chain.
Tests updated: string-form embedded tests moved to function-form;
new test verifies the embedded wrapper in runInterceptorScript.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
browser.dioxus.execute(...)returningundefinedpreviously deliverednullto the test;nullandundefinedwere indistinguishableresult ?? null, collapsing both tonull@wdio/tauri-service's__wdio_value__envelope — wrap the result before posting through__embedded_result; unwrap inexecute.tsandrunInterceptorScriptJSON.stringifyomitsundefinedobject properties,{ __wdio_value__: undefined }serialises to{}(key absent) while{ __wdio_value__: null }serialises to{"__wdio_value__":null}(key present), making the two distinguishable on receiptdist-js/index.js(now tracked per fix(dioxus-bridge): commit guest-js bundle for git/crates.io consumers #407)Closes #409
Test plan
pnpm --filter @wdio/dioxus-service exec vitest run test/commands/execute.spec.ts— 12 tests passpnpm --filter @wdio/dioxus-bridge test— 10 tests passresetAllMocks(),execute(() => invoke('cmd'))returnsundefined, notnull🤖 Generated with Claude Code