Skip to content

feat(dioxus): multi-window + deeplink (Phase 4-5)#277

Merged
goosewobbler merged 3 commits into
feat/dioxus-servicefrom
feat/dioxus-feature-complete
May 11, 2026
Merged

feat(dioxus): multi-window + deeplink (Phase 4-5)#277
goosewobbler merged 3 commits into
feat/dioxus-servicefrom
feat/dioxus-feature-complete

Conversation

@goosewobbler
Copy link
Copy Markdown
Contributor

Summary

PR3 first slice — ships Phase 4 (multi-window) and Phase 5 (deeplink) of the Dioxus service rollout. Stacks on feat/dioxus-service.

Phase 4 — Multi-window

Bridge (packages/dioxus-bridge/)

  • window_state.rs — process-global registry that auto-labels Dioxus windows in creation order: first is main, rest are window-1, window-2, .... Stores Weak<Window> so closed windows are filtered at query time. Labels are never reused.
  • install() chains Config::with_on_window to feed each new window into the registry and registers three invoke commands: __list_windows, __active_window, __window_states.
  • Doc note: install() must be the last builder before launch (Dioxus's Config.on_window is an Option<Box<_>> with no getter — a later user with_on_window would silently shadow the bridge hook).

Service (packages/dioxus-service/)

  • window.ts — per-sessionId label cache + getDefaultWindowLabel / getCurrentWindowLabel / setCurrentWindowLabel / clearWindowState / getActiveWindowLabel / listWindowLabels / switchWindowByLabel. The switch path validates against the bridge's live list, resolves label → WebDriver handle by walking getWindowHandles(), and restores the original handle on failure.
  • service.before() installs switchWindow + listWindows on browser.dioxus.
  • service.after() clears the window cache alongside mockStore.

Phase 5 — Deeplink

Service

  • commands/triggerDeeplink.ts — validate URL via @wdio/native-core, spawn the platform-native protocol handler:
    • Windows: rundll32.exe url.dll,FileProtocolHandler <url>
    • macOS: open <url>
    • Linux: gio open <url>
  • Rejects http:// / https:// / file://.

Bridge

  • deeplink.rs — optional reference helper register_handler(scheme, callback) that wraps Config::with_custom_protocol(scheme, ...). Pure convenience for app authors; not required for testing.

Types

  • DioxusServiceAPI restored: switchWindow, listWindows, triggerDeeplink.

Test plan

  • Bridge: cargo test — 18/18 (3 new in window_state)
  • Service: pnpm test:unit — 93/93 (8 new for triggerDeeplink, 14 new for window.ts)
  • Typecheck: pnpm typecheck green
  • CI green on feat/dioxus-feature-complete

Deferred

  • E2E pipelinefixtures/e2e-apps/dioxus/ doesn't exist yet; PR2 shipped without it. Window + deeplink E2E specs land in a dedicated PR that bootstraps the fixture app.
  • Phase 6 (embedded WebDriver provider + browser mode) — separate follow-up PR. Includes new wdio-dioxus-embedded-driver crate + platform executors + providers/embedded.ts + Vite dev-server branch.
  • Phase 7 (macOS support + visual regression) — depends on Phase 6.

🤖 Generated with Claude Code

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 11, 2026

Greptile Summary

This PR ships Phase 4 (multi-window label registry + switchWindow/listWindows service APIs) and Phase 5 (OS-native deeplink dispatch via triggerDeeplink) of the Dioxus service rollout, stacking cleanly on feat/dioxus-service. All three issues surfaced in the prior review pass — dead Weak<Window> accumulation in the registry Vec, the redundant switchToWindow call in switchWindowByLabel, and missing item-type validation in listWindowLabels — have been addressed in this revision.

  • Bridge (window_state.rs): monotonic counter drives label allocation so labels are never recycled; register_window now prunes dead entries before pushing new ones, keeping the backing Vec proportional to live windows; get_active_label / list_labels / get_window_states filter at query time via Weak::upgrade.
  • Service (window.ts): resolveLabelToHandle leaves the driver focused on the matching handle and returns immediately, eliminating the extra switchToWindow round-trip; listWindowLabels validates item types before casting to string[]; the failure path restores the original handle and re-throws the inner error unmodified to avoid the double-prefix message.
  • Service (triggerDeeplink.ts): thin wrapper around @wdio/native-core's validated URL + platform command helpers; 8 new unit tests cover all three OS paths, protocol rejection, and spawn failure propagation.

Confidence Score: 5/5

Safe to merge — the three issues from the prior review are all addressed, the new Rust registry is correctly bounded, and the TS window/deeplink helpers are well-covered by unit tests.

No functional defects found across the new Rust registry, the TS multi-window helpers, or the deeplink command. The label-to-handle resolution approach (OS focus after switchToWindow) is inherently correct for the msedgedriver external-provider path. The monotonic counter correctly decouples label allocation from the pruned entry count. Tests cover the failure and restore paths in sufficient depth.

No files require special attention.

Important Files Changed

Filename Overview
packages/dioxus-bridge/src/window_state.rs New process-global window registry with monotonic label counter, Weak liveness tracking, and GC pruning on each new registration; well-tested with 3 unit tests.
packages/dioxus-bridge/src/deeplink.rs New optional convenience helper that wraps Config::with_custom_protocol for in-app deeplink handling; cleanly documented, pure library code not required for testing.
packages/dioxus-bridge/src/lib.rs Adds window registry commands (__list_windows, __active_window, __window_states) and with_on_window hook; doc updated to reflect all shipped modules; stale forward-reference removed.
packages/dioxus-service/src/window.ts New multi-window helpers: per-session label cache, listWindowLabels with item-type validation, and switchWindowByLabel with handle resolution and original-handle restore on failure.
packages/dioxus-service/src/commands/triggerDeeplink.ts New Phase 5 command that validates the URL via @wdio/native-core, then delegates OS-native protocol dispatch to the same package; thin and well-tested.
packages/dioxus-service/src/service.ts Wires switchWindow, listWindows, and triggerDeeplink onto browser.dioxus in before(); clears window label cache alongside mockStore in after().
packages/native-types/src/dioxus.ts DioxusServiceAPI interface updated with switchWindow, listWindows, and triggerDeeplink; stale comment replaced.
packages/dioxus-service/test/window.spec.ts 14 new tests covering label cache, item-type validation, handle resolution, restore-on-failure, and session isolation; comprehensive coverage.
packages/dioxus-service/test/commands/triggerDeeplink.spec.ts 8 new tests covering all three platform commands, URL protocol rejection, malformed URLs, and spawn failure propagation.
packages/dioxus-service/test/service.spec.ts Extends existing service smoke test to assert switchWindow, listWindows, and triggerDeeplink are installed on browser.dioxus.

Sequence Diagram

sequenceDiagram
    participant Test as Test Script
    participant Svc as DioxusWorkerService
    participant Win as window.ts
    participant WD as WebDriver
    participant Bridge as wdio-dioxus-bridge

    Note over Test,Bridge: switchWindowByLabel('window-1')
    Test->>Svc: browser.dioxus.switchWindow('window-1')
    Svc->>Win: switchWindowByLabel(browser, 'window-1')
    Win->>WD: execute(__list_windows)
    WD->>Bridge: invoke('__list_windows')
    Bridge-->>WD: ['main','window-1']
    WD-->>Win: ['main','window-1']
    Win->>WD: getWindowHandle() → h-main (original)
    loop for each handle
        Win->>WD: switchToWindow(handle)
        Win->>WD: execute(__active_window)
        WD->>Bridge: invoke('__active_window')
        Bridge-->>WD: label
        WD-->>Win: label
        alt "label === targetLabel"
            Win-->>Win: return handle (driver already focused)
        end
    end
    Win->>Win: setCurrentWindowLabel(browser, 'window-1')
    Win-->>Test: void

    Note over Test,Bridge: triggerDeeplink('myapp://open')
    Test->>Svc: browser.dioxus.triggerDeeplink('myapp://open')
    Svc->>Svc: validateDeeplinkUrl('myapp://open')
    Svc->>Svc: getPlatformCommand(url, process.platform)
    Svc->>Svc: executeDeeplinkCommand(cmd, args)
    Note right of Svc: OS routes URL to registered protocol handler
    Svc-->>Test: void
Loading

Reviews (2): Last reviewed commit: "fix(dioxus): address greptile P2s on PR ..." | Re-trigger Greptile

Comment thread packages/dioxus-bridge/src/lib.rs Outdated
Comment thread packages/dioxus-bridge/src/window_state.rs
Comment thread packages/dioxus-service/src/window.ts
Comment thread packages/dioxus-service/src/window.ts
goosewobbler and others added 2 commits May 11, 2026 21:44
Bridge:
  - New window_state.rs: process-global registry that auto-labels windows
    in creation order (first is 'main', rest are 'window-N'). Stores
    Weak<Window> so closed windows are filtered out at query time.
  - install() now chains with_on_window to feed each created window into
    the registry, and registers three invoke commands: __list_windows,
    __active_window, __window_states.
  - Docs note: install() must be the last builder before launch because
    Config.on_window is an Option<Box<_>> with no getter — a later user
    with_on_window would overwrite the bridge hook.

Service:
  - New window.ts: per-sessionId label cache + getDefaultWindowLabel /
    getCurrentWindowLabel / setCurrentWindowLabel / clearWindowState /
    getActiveWindowLabel / listWindowLabels / switchWindowByLabel.
  - service.before() installs switchWindow + listWindows on browser.dioxus.
  - service.after() clears the window cache alongside mockStore.
  - Surface restored on DioxusServiceAPI in native-types.

Unit tests cover label cache, list/active happy + fallback paths, switch
validation, handle resolution, and original-handle restore on failure.
E2E fixture work + window.spec.ts follow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… (Phase 5)

Service:
  - New commands/triggerDeeplink.ts: validate URL via native-core then
    spawn the platform-native protocol handler (rundll32 / open / gio).
    Custom-protocol-only — http/https/file rejected.
  - service.before() installs triggerDeeplink on browser.dioxus.
  - DioxusServiceAPI gains triggerDeeplink in native-types.

Bridge:
  - New deeplink.rs reference helper: register_handler(scheme, callback)
    wraps Config::with_custom_protocol to give app authors a copy-paste
    pattern. Optional — apps with existing protocol handlers can ignore.

Tests: 8 new unit tests covering the three platform branches, protocol
rejection (http/https/file), malformed URLs, and spawn failure propagation.
93/93 dioxus-service unit tests pass; 18/18 bridge Rust tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@goosewobbler goosewobbler force-pushed the feat/dioxus-feature-complete branch from a26612e to e73cf22 Compare May 11, 2026 20:44
Four review comments, all legitimate:

1. lib.rs crate doc — said "subsequent milestones add the deeplink
   reference handler" but deeplink.rs ships in this PR. Doc rewritten
   to list every module the bridge now exposes.

2. window_state.rs — dead Weak<Window> entries accumulated in the Vec
   indefinitely. Split labelling state into:
     - entries: Mutex<Vec<Entry>> (live + freshly-pruned)
     - total_registered: Mutex<usize> (monotonic, drives labels)
   register_window() now retains-on-upgrade before push, so the Vec
   stays proportional to live windows. Labels remain stable across GC
   because they depend on the counter, not Vec length.

3. window.ts switchWindowByLabel — was double-switching: resolveLabel
   already calls switchToWindow on the matching handle, then we did
   it again. Dropped the redundant call. Also dropped the outer
   error-wrap: the inner resolver error already carried the
   @wdio/dioxus-service prefix, producing a double-prefixed message
   on the failure path. Test expectations updated.

4. window.ts listWindowLabels — only validated the response was an
   Array, not that items were strings. A bridge bug returning [null]
   or [42] would silently pass through. Added an every-string check
   at the boundary with a discoverable error message.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@goosewobbler goosewobbler merged commit f2951ef into feat/dioxus-service May 11, 2026
144 of 146 checks passed
@goosewobbler goosewobbler deleted the feat/dioxus-feature-complete branch May 11, 2026 21:44
@goosewobbler goosewobbler restored the feat/dioxus-feature-complete branch May 11, 2026 21:56
goosewobbler added a commit that referenced this pull request May 13, 2026
Tauri's logParser.ts has the same whole-line regex matching bug as
Dioxus's logParser — the level detector can match level words in the
message body rather than the level token position. Electron is not
affected (uses CDP events). PR #277 comments were all Dioxus-specific
and addressed; no new cross-service entries needed from that PR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
goosewobbler added a commit that referenced this pull request May 13, 2026
* fix(dioxus-bridge): serialise embedded tests + remove unused reset_for_tests

embedded.rs: the three async tests share a process-global OnceLock<Channel>.
Without serialisation, should_return_none_when_queue_is_empty's drain-all
loop races against should_round_trip_an_eval_request's push/poll_next pair,
causing the round-trip test to see an empty queue and panic on unwrap().

Fix: introduce a static tokio::sync::Mutex::const_new(()) test lock that
each test acquires before touching the channel. Each test also drains
leftovers at entry so earlier failures don't contaminate subsequent ones.

window_state.rs: reset_for_tests was pub(crate) but never called — the
window_state tests only exercise allocate_label (a pure function) and
cannot call register_window without a real Arc<Window>. Remove the dead
helper rather than silencing the warning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(dioxus): macOS support + full E2E infrastructure (Phase 7)

- Add darwin guard in launcher (macOS never supports 'external' provider)
- Add `install_with_commands` API to embedded driver lib for fixture apps
- Add `install_with_embedded_port_and_registry` to dioxus-bridge lib.rs
- Add dioxus fixture app (fixtures/e2e-apps/dioxus/) with bridge commands
  for get_platform_info, get_command_line_args, generate_test_logs
- Add WDIO E2E configs (wdio.dioxus.conf.ts, wdio.dioxus-embedded.conf.ts)
- Add 15 E2E spec files covering api, application, execute, mocking, logging,
  window, deeplink, visual, multiremote, and standalone test scenarios
- Extend envSchema.ts to accept FRAMEWORK=dioxus
- Extend utils.ts getE2EAppDirName for dioxus
- Add e2e package.json scripts for all dioxus test modes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus-e2e): resolve provider-aware log dir + multiremote port collision

- logging.spec.ts: read DRIVER_PROVIDER env var to derive the log dir
  dynamically (mirrors tauri/logging.spec.ts pattern) so the spec works
  under both wdio.dioxus.conf.ts (external/official) and
  wdio.dioxus-embedded.conf.ts (embedded) without targeting the wrong dir
- wdio.dioxus.conf.ts multiremote: remove port: 0 from both instances and
  add distinct dioxusDriverPort hints (9515 / 9517) so the launcher can
  allocate non-overlapping port pairs when external provider wiring lands;
  port: 0 would have been silently promoted to 4444 by WDIO detectBackend,
  causing both instances to share the same connection target

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus-e2e): add dioxusDriverPort to local DioxusCapability type

Excess-property check rejected dioxusDriverPort on wdio:dioxusServiceOptions
because the locally-defined type omitted it. Adds the optional field so the
multiremote per-instance port hints type-check correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus-e2e): set DRIVER_PROVIDER=embedded in embedded test scripts

Without this, process.env.DRIVER_PROVIDER is undefined inside logging.spec.ts,
so getLogDirName returns standard-dioxus while wdio.dioxus-embedded.conf.ts
writes output to embedded-standard-dioxus — readWdioLogs targets a
non-existent directory and both log assertions always fail.

Mirrors the pattern used by all tauri-basic-embedded scripts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus-e2e): set DRIVER_PROVIDER from conf file, not npm scripts

Each WDIO config already knows which provider it uses, so it sets
process.env.DRIVER_PROVIDER directly. Specs can then read it without
the caller needing to pass it externally. Reverts the cross-env
DRIVER_PROVIDER additions from the previous commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus): three E2E + test hygiene fixes

embedded.rs: rename should_start_inactive_before_init →
should_be_idempotent_on_repeated_init — the OnceLock<Channel> is
permanent so pre-init state can never be observed; the test only
verifies that calling init() twice does not panic.

wdio.dioxus.conf.ts: set process.env.DRIVER_PROVIDER = 'official' at
config load time, mirroring wdio.dioxus-embedded.conf.ts, so
logging.spec.ts derives the correct log directory regardless of whether
DRIVER_PROVIDER was set externally.

wdio.dioxus.conf.ts multiremote: add concrete port: 9515 / port: 9517
on the outer InstanceConfig objects. WDIO uses these top-level fields
to open WebDriver connections; without them WDIO defaults both to 4444
and the second instance fails. Values match the dioxusDriverPort hints
already set in each capability.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(e2e): add @wdio/dioxus-service workspace link to e2e dependencies

Without this, pnpm strict linking means Node.js cannot resolve
@wdio/dioxus-service when wdio.dioxus*.conf.ts loads, causing every
dioxus E2E run to fail with "Cannot find module".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus-e2e): decouple DRIVER_PROVIDER service value from log-dir sentinel

wdio.dioxus.conf.ts set DRIVER_PROVIDER='official' so that logging.spec.ts
would resolve the correct prefix-less log directory, but that caused the
service to receive 'official' instead of 'external', silently bypassing
the external driver path in the launcher.

Fix: set DRIVER_PROVIDER='external' (correct service value). In
logging.spec.ts, stop forwarding the raw env value to getLogDirName —
instead branch only on === 'embedded', since only the embedded provider
uses a prefixed log directory. Any other value (external, unset) maps
to the same prefix-less directory as 'official'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus): add session.ts for standalone mode; fix standalone spec

remote() only invokes worker-level hooks, so the embedded WebDriver server
was never started in standalone mode. Mirrors the Tauri pattern:

- native-utils log.ts: add 'session' to LogArea union so session.ts can
  use a distinct logger namespace rather than borrowing 'service'.

- packages/dioxus-service/src/session.ts: new module with init(), cleanup(),
  and createDioxusCapabilities(). init() manually calls launcher.onPrepare()
  to start the embedded driver and read back the assigned port before calling
  remote(), then calls service.before() to install browser.dioxus.*.

- packages/dioxus-service/src/index.ts: export startWdioSession,
  cleanupWdioSession, createDioxusCapabilities from session.ts.

- e2e/test/dioxus/standalone/api.spec.ts: replace the broken remote() +
  services[] call with startWdioSession/cleanupWdioSession/createDioxusCapabilities
  imported from @wdio/dioxus-service, matching the Tauri standalone spec pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: ignore local MCP config

* fix(dioxus): address Greptile PR review findings

- session.ts: call launcher.onComplete() on remote() failure to prevent
  subprocess leak when the embedded driver fails to connect
- embedded.rs: convert idempotent-init test to #[tokio::test] and acquire
  TEST_LOCK so it serializes correctly with other async tests
- wdio.dioxus-embedded.conf.ts: explicitly set maxInstances=1 in the
  standalone switch arm (was silently inheriting the default)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus-e2e): use dedicated port 4447 in standalone spec

Prevents an "address already in use" error if the spec is ever run inside
the WDIO-managed test runner (where the launcher already occupies the
default embedded port). Standalone specs are normally invoked via tsx
directly, but the explicit port eliminates any ambiguity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus-e2e): standalone consistency nits in external conf and api spec

- wdio.dioxus.conf.ts: explicitly set maxInstances=1 in the standalone
  switch arm (was silently inheriting the 5-instance default)
- standalone/api.spec.ts: restructure from a raw top-level script to a
  proper Mocha describe/it spec with before/after session lifecycle hooks,
  removing the non-standard process.exit() call

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(e2e): run Dioxus standalone spec via tsx, not wdio runner

Standalone mode tests the session API called directly from user code —
running it through wdio run + Mocha defeats that purpose because the
runner's own service lifecycle wraps the test. Mirror the Tauri/Electron
pattern: invoke the spec with tsx directly.

- Rewrite api.spec.ts as a top-level-await script (no describe/it)
- Change test:e2e:dioxus:standalone npm script to use tsx
- Drop the standalone switch-case from both dioxus WDIO configs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(e2e): fix bare expression string and remove orphaned standalone logging spec

- Add explicit 'return' to bare expression string in execute-advanced.spec.ts:
  WDIO wraps string scripts as a function body, so '1 + 2 + 3' returns undefined
  rather than 6 without an explicit return statement.

- Delete e2e/test/dioxus/standalone/logging.spec.ts: uses @wdio/globals so it
  cannot run via tsx (standalone mode), and is not included in any WDIO config
  spec glob.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus): use startTimeout for connectionRetryTimeout in standalone session

statusPollTimeout is the per-request alive-check interval (default 2 s), not
the overall startup budget. Using it produced connectionRetryTimeout = 8 s on
slow CI, causing spurious connection failures. Add startTimeout to
DioxusServiceOptions (mirrors TauriServiceOptions) and use it in session.ts
with a 60 s default, matching the Tauri service behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(e2e/dioxus): wrap standalone test body in try/finally to prevent process leak

If an assertion throws after startWdioSession() succeeds the embedded
binary stays running on port 4447, blocking subsequent invocations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus): store worker service and call service.after() on cleanup

init() was discarding the DioxusWorkerService after service.before(),
so cleanup() never called service.after(), leaving mockStore entries and
window state alive across sessions. Add activeServices WeakMap parallel
to activeLaunchers and drain it in cleanup().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add DEFERRED_ISSUES.md for post-dioxus follow-ups

Tracks bugs found during Dioxus work that also affect Tauri and Electron
but belong in separate PRs. First entry: service.after() never called in
standalone sessions (tauri-service, electron-service).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus): clean up browser session and launcher if service.before() throws

If remote() succeeds but service.before() subsequently throws, init()
propagated the error without closing the open WebDriver session or
stopping the embedded driver. Wrap service.before() in a try/catch
that calls browser.deleteSession() and launcher.onComplete() before
rethrowing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add two more deferred issues for tauri-service and electron-service

- remote() failure not cleaning up launcher (Tauri catch block is incomplete,
  Electron has no catch at all)
- service.before() throwing after remote() succeeds leaks browser session
  and launcher in both services

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add log-level detector deferred issue from PR #276 audit

Tauri's logParser.ts has the same whole-line regex matching bug as
Dioxus's logParser — the level detector can match level words in the
message body rather than the level token position. Electron is not
affected (uses CDP events). PR #277 comments were all Dioxus-specific
and addressed; no new cross-service entries needed from that PR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(e2e/dioxus): guard deleteSession() so cleanupWdioSession always runs

If the app crashed or the WebDriver connection timed out, deleteSession()
would reject and cleanupWdioSession() would never be called, leaving the
embedded binary running. Swallow the deleteSession error so cleanup always
proceeds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(e2e/dioxus): add Linux platform guard to external-provider config

On Linux the launcher throws SevereServiceError rather than exiting
cleanly, causing CI to see a hard failure instead of a skip. Exit 78
(same as the darwin guard) until the upstream Dioxus
Config::with_allow_automation PR lands.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(e2e): add 'external' to DRIVER_PROVIDER Zod enum in envSchema.ts

wdio.dioxus.conf.ts sets DRIVER_PROVIDER='external' but the schema only
accepted 'official'|'crabnebula'|'embedded', causing Zod to reject the
value in any code path that calls validateEnvironment() in the same
process. Also update the driverProvider getter return type to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus-service): guard launcher.onComplete() if service.after() throws in cleanup

If service.after() rejects (IPC teardown error, mock-store flush failure, etc.)
the await propagates and launcher.onComplete() is never reached, leaving the
embedded binary running indefinitely. Wrap in try/finally so the launcher is
always stopped. Also updated DEFERRED_ISSUES.md to include the same guard
pattern for when tauri-service / electron-service get their service.after() fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(dioxus-service): clean up launcher when port check fails in init()

If onPrepare() starts the embedded driver but capabilities.port is absent,
the previous code threw without calling launcher.onComplete(), leaving the
subprocess running. Call onComplete() (with .catch so it can't mask the
original error) before rethrowing, consistent with the remote() and
service.before() error paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant