Skip to content

feat(dioxus): implement W3C Actions API in the embedded driver#488

Merged
goosewobbler merged 13 commits into
mainfrom
feat/dioxus-embedded-actions
Jun 30, 2026
Merged

feat(dioxus): implement W3C Actions API in the embedded driver#488
goosewobbler merged 13 commits into
mainfrom
feat/dioxus-embedded-actions

Conversation

@goosewobbler

Copy link
Copy Markdown
Contributor

Summary

Implements the W3C Actions endpoint (POST / DELETE /session/{id}/actions) in @wdio/dioxus-embedded-driver, which was previously wired to the not_implemented stub. Any WDIO command routed through the Actions API failed with unsupported operation ("not implemented for Dioxus embedded driver").

Closes #427.

Affected commands now unlocked

element.click(options) (.click({ button: 'right' }), .click({ x, y })), element.doubleClick(), element.moveTo(), drag-and-drop, and browser.action(...) / browser.actions(...) chains (pointer, key, wheel, pause). (Bare .click() already worked via the elementClick endpoint.)

Handler design

The Dioxus embedded driver has no native input path — unlike the Tauri driver (which dispatches real OS-level events through a native platform executor), every Dioxus command runs as JavaScript inside the webview through the bridge eval channel. So pointer/key/wheel actions are synthesized as DOM events dispatched on the element under the resolved coordinates, matching what a real browser produces for input:

  • pointer pointerDown / pointerUp / pointerMoveMouseEvent (mousedown / mouseup / mousemove) dispatched on document.elementFromPoint(x, y). A primary press + release at the same spot synthesizes a click; a secondary (right) release synthesizes contextmenu — the events the browser would emit for real input, so element handlers fire.
  • key keyDown / keyUpKeyboardEvent on document.activeElement.
  • wheel scrollWheelEvent carrying deltaX / deltaY.
  • pausetokio::time::sleep.

Pointer position and pressed keys/buttons persist on the session's new ActionState so origin: "pointer" resolves relative to the last position and the DELETE release emits the matching keyUp / pointerUp events.

origin resolution (does NOT reproduce #423)

#427 is the Dioxus sibling of #423 (Tauri), where PointerMove was missing the origin field, so .click(options) silently clicked viewport (0, 0) and missed the element. origin is parsed and resolved correctly here from the start, mirroring the fixed Tauri handler:

  • "viewport" (or no origin) → x/y are absolute viewport coordinates
  • "pointer" → x/y are relative to the current pointer position
  • element reference { "element-6066-…": "<id>" } → x/y are offsets from the element's in-view center, computed via getBoundingClientRect() (reusing the same rect lookup behind the element rect endpoint)

So element.click(options) — which WDIO sends as a pointerMove with an element origin and x/y defaulting to 0 — lands on the element's center, not viewport (0, 0). An unrecognised named origin is rejected with invalid argument rather than silently treated as viewport.

Verification

Run from packages/dioxus-embedded-driver:

Command Result
cargo build ✅ Finished
cargo test ✅ 17 passed (8 new actions tests), 0 failed
cargo clippy --all-targets ✅ no warnings

origin resolution is covered by unit tests on the pure JS-builder + deserialization logic: the click-options shape (element origin, x/y = 0) deserializes to Origin::Element with the right element ref; the element-center JS adds half-width/half-height to the rect origin (the #423 regression guard); named "viewport"/"pointer" origins parse; and key/wheel/pause sequences round-trip. Cargo.lock is unchanged (no new dependencies — reuses serde, serde_json, tokio, uuid, axum, and the bridge).

Note on cargo fmt: this crate (and the whole repo) uses 2-space indentation per AGENTS.md, with no committed rustfmt.toml. Default cargo fmt --check reports diffs against every pre-existing file (it wants 4-space), so the new file matches the surrounding 2-space crate style rather than rustfmt defaults — running cargo fmt --write would reformat the entire crate.

Limitations

  • Events are synthesized DOM events, not real OS input (this is the only mechanism the JS-only Dioxus driver has). Native browser default behaviors that only the engine triggers for trusted events (e.g. native text-selection drag, native context menu) won't fire; the dispatched events do drive application-level listeners.
  • No deferred/stubbed action types — pointer (down/up/move/pause), key (down/up/pause), wheel (scroll/pause), and the null source (pause) are all handled. Pointer-move duration is honored as a pause before the final move (no intermediate-tick interpolation, matching the Tauri handler).

🤖 Generated with Claude Code

Wire POST/DELETE /session/{id}/actions to a real handler instead of the
not_implemented stub. Unlocks element.click(options), doubleClick, moveTo,
drag-and-drop, and browser.action(...)/actions(...) chains (pointer, key,
wheel, pause).

The Dioxus driver has no native input path — everything runs as JavaScript
inside the webview through the bridge eval channel — so pointer/key/wheel
actions are synthesized as DOM events (MouseEvent / KeyboardEvent /
WheelEvent) dispatched on the element under the resolved coordinates.

Pointer origin is resolved up front (viewport / pointer / element-center via
getBoundingClientRect, mirroring the element rect endpoint) so a
.click(options) on an element hits that element's center rather than viewport
(0,0). This is the bug #423 hit on the Tauri side; it is landed correctly here
from the start.

Closes #427

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR implements the W3C Actions API (POST/DELETE /session/{id}/actions) for the Dioxus embedded driver, synthesizing MouseEvent, KeyboardEvent, and WheelEvent via the JS bridge — unlocking element.click(options), element.doubleClick(), drag-and-drop, and browser.action(...) chains that previously returned unsupported operation. All four issues raised in the prior review round (buttons bitmask, tick-by-tick ordering, stale-element error type, button_to_mask overflow) are fully addressed in this revision.

  • New actions.rs handler: tick-by-tick action dispatch across sources, correct buttons cumulative mask, origin resolution (viewport / pointer / element-center via getBoundingClientRect), and cross-call ActionState persistence for pointer position and held keys/buttons.
  • ActionState in session.rs: minimal new struct carrying pointer position, pressed keys, and pressed buttons per action-source ID, initialised to defaults on session creation.
  • Test stub fix in launcher.browser.integration.spec.ts: adds a fetch stub/unstub pair to prevent the new dev-server preflight probe from breaking unrelated handshake tests.

Confidence Score: 5/5

Safe to merge; all four blocking issues from the prior review round are fully addressed and the new handler behaves correctly for the primary use cases (.click(), .doubleClick(), right-click, drag-and-drop, key/wheel sequences).

The buttons bitmask, tick-by-tick ordering, stale-element error type, and button_to_mask overflow are all fixed. The two remaining observations (contextmenu on right-button drag, missing auxclick for middle-button click) are minor DOM-spec gaps that don't affect any WDIO test command described in the PR and were entirely absent from the previous not-implemented stub.

packages/dioxus-embedded-driver/src/server/handlers/actions.rs — the contextmenu position guard and auxclick synthesis are the two items worth addressing in a follow-up.

Important Files Changed

Filename Overview
packages/dioxus-embedded-driver/src/server/handlers/actions.rs New 877-line file implementing W3C Actions API via JS event synthesis; all four previously-flagged issues (buttons bitmask, tick ordering, stale-element error, button_to_mask overflow) are resolved, but contextmenu fires on any right-button release without a position guard, and auxclick is not synthesized for non-primary buttons.
packages/dioxus-embedded-driver/src/webdriver/session.rs Adds ActionState struct (pointer_position, pressed_keys, pressed_buttons) and initialises it in Session::new(); clean, minimal change.
packages/dioxus-embedded-driver/src/server/router.rs Wires the two new handlers (perform / release) to the /session/{id}/actions route, replacing the not_implemented stub.
packages/dioxus-embedded-driver/src/server/handlers/mod.rs Exports the new actions module; single-line change, no issues.
packages/dioxus-service/test/integration/launcher.browser.integration.spec.ts Adds a fetch stub in beforeEach and unstubAllGlobals in afterEach to prevent the dev-server preflight probe from breaking existing happy-path handshake tests; unrelated to the actions implementation.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant WDIO as WebdriverIO
    participant Router as Axum Router
    participant Handler as actions::perform
    participant Session as SessionManager
    participant Bridge as Bridge Eval Channel
    participant DOM as Webview DOM

    WDIO->>Router: "POST /session/{id}/actions"
    Router->>Handler: Json(ActionsRequest)
    Handler->>Session: read() — get timeouts + ActionState

    loop For each tick (0..max_actions)
        loop For each ActionSequence source
            alt Key source
                Handler->>Session: write() — update pressed_keys, recompute Modifiers
                Handler->>Bridge: eval(key_event_js)
                Bridge->>DOM: dispatchEvent(KeyboardEvent)
            else Pointer PointerDown
                Handler->>Session: write() — record pressed button
                Handler->>Bridge: eval(pointer_event_js mousedown)
                Bridge->>DOM: dispatchEvent(MouseEvent mousedown)
            else Pointer PointerMove
                Handler->>Bridge: eval(element_center_js) → [cx, cy]
                Handler->>Bridge: eval(pointer_event_js mousemove)
                Bridge->>DOM: dispatchEvent(MouseEvent mousemove)
            else Pointer PointerUp
                Handler->>Bridge: eval(pointer_event_js mouseup)
                Bridge->>DOM: dispatchEvent(MouseEvent mouseup)
                opt primary + same position
                    Handler->>Bridge: eval(pointer_event_js click)
                    Bridge->>DOM: dispatchEvent(MouseEvent click)
                end
                opt second click at same spot
                    Handler->>Bridge: eval(pointer_event_js dblclick)
                    Bridge->>DOM: dispatchEvent(MouseEvent dblclick)
                end
                opt right button
                    Handler->>Bridge: eval(pointer_event_js contextmenu)
                    Bridge->>DOM: dispatchEvent(MouseEvent contextmenu)
                end
            else Wheel scroll
                Handler->>Bridge: eval(wheel_event_js)
                Bridge->>DOM: dispatchEvent(WheelEvent)
            else Pause
                Handler->>Handler: tokio::time::sleep(duration)
            end
        end
    end

    Handler->>Session: write() — persist pointer_position
    Handler-->>WDIO: 200 OK

    WDIO->>Router: "DELETE /session/{id}/actions"
    Router->>Handler: actions::release
    Handler->>Session: write() — drain pressed_keys + pressed_buttons
    loop each held key (sorted)
        Handler->>Bridge: eval(key_event_js keyup)
        Bridge->>DOM: dispatchEvent(KeyboardEvent keyup)
    end
    loop each held button (sorted)
        Handler->>Bridge: eval(pointer_event_js mouseup)
        Bridge->>DOM: dispatchEvent(MouseEvent mouseup)
    end
    Handler-->>WDIO: 200 OK
Loading
%%{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 WDIO as WebdriverIO
    participant Router as Axum Router
    participant Handler as actions::perform
    participant Session as SessionManager
    participant Bridge as Bridge Eval Channel
    participant DOM as Webview DOM

    WDIO->>Router: "POST /session/{id}/actions"
    Router->>Handler: Json(ActionsRequest)
    Handler->>Session: read() — get timeouts + ActionState

    loop For each tick (0..max_actions)
        loop For each ActionSequence source
            alt Key source
                Handler->>Session: write() — update pressed_keys, recompute Modifiers
                Handler->>Bridge: eval(key_event_js)
                Bridge->>DOM: dispatchEvent(KeyboardEvent)
            else Pointer PointerDown
                Handler->>Session: write() — record pressed button
                Handler->>Bridge: eval(pointer_event_js mousedown)
                Bridge->>DOM: dispatchEvent(MouseEvent mousedown)
            else Pointer PointerMove
                Handler->>Bridge: eval(element_center_js) → [cx, cy]
                Handler->>Bridge: eval(pointer_event_js mousemove)
                Bridge->>DOM: dispatchEvent(MouseEvent mousemove)
            else Pointer PointerUp
                Handler->>Bridge: eval(pointer_event_js mouseup)
                Bridge->>DOM: dispatchEvent(MouseEvent mouseup)
                opt primary + same position
                    Handler->>Bridge: eval(pointer_event_js click)
                    Bridge->>DOM: dispatchEvent(MouseEvent click)
                end
                opt second click at same spot
                    Handler->>Bridge: eval(pointer_event_js dblclick)
                    Bridge->>DOM: dispatchEvent(MouseEvent dblclick)
                end
                opt right button
                    Handler->>Bridge: eval(pointer_event_js contextmenu)
                    Bridge->>DOM: dispatchEvent(MouseEvent contextmenu)
                end
            else Wheel scroll
                Handler->>Bridge: eval(wheel_event_js)
                Bridge->>DOM: dispatchEvent(WheelEvent)
            else Pause
                Handler->>Handler: tokio::time::sleep(duration)
            end
        end
    end

    Handler->>Session: write() — persist pointer_position
    Handler-->>WDIO: 200 OK

    WDIO->>Router: "DELETE /session/{id}/actions"
    Router->>Handler: actions::release
    Handler->>Session: write() — drain pressed_keys + pressed_buttons
    loop each held key (sorted)
        Handler->>Bridge: eval(key_event_js keyup)
        Bridge->>DOM: dispatchEvent(KeyboardEvent keyup)
    end
    loop each held button (sorted)
        Handler->>Bridge: eval(pointer_event_js mouseup)
        Bridge->>DOM: dispatchEvent(MouseEvent mouseup)
    end
    Handler-->>WDIO: 200 OK
Loading

Reviews (11): Last reviewed commit: "fix(dioxus): deterministic multi-button ..." | Re-trigger Greptile

Comment thread packages/dioxus-embedded-driver/src/server/handlers/actions.rs Outdated
Comment thread packages/dioxus-embedded-driver/src/server/handlers/actions.rs Outdated
Comment thread packages/dioxus-embedded-driver/src/server/handlers/actions.rs
@github-actions

github-actions Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Standing release PR: #456 · 9 packages queued · open 158h 5m · ✅ ready to merge

Release Preview — 12 packages

Note: Labels on this PR are advisory in standing-pr mode. Bumps come from conventional commits in the standing PR; override by editing labels on the standing PR itself. Add release:immediate to bypass the standing PR and release this PR directly.

These changes will be added to the release PR (#456) when merged:

Changelog

@wdio/dioxus-bridge 1.0.0-next.3 → 1.1.0

Changed

  • Update version to 1.1.0
@wdio/dioxus-service 1.0.0-next.3 → 1.1.0

Fixed

wdio-dioxus-driver 1.0.0-next.3 → 1.1.0

Changed

  • Update version to 1.1.0
wdio-dioxus-embedded-driver 1.0.0-next.3 → 1.1.0

Added

Fixed

  • deterministic multi-button release + pointerCancel click-chain reset (dioxus)
  • set KeyboardEvent.code and deterministic keyup order (dioxus)
  • guard button_to_mask against out-of-range button indices (dioxus)
  • right-variant modifier keys + decreasing modifiers on release (dioxus)
  • carry keyboard modifier flags on synthesized action events (dioxus)
  • translate special-key codepoints and reset dblclick tracking on move (dioxus)
  • synthesize dblclick for element.doubleClick() (dioxus)
  • address review on the Actions handler (dioxus)
@wdio/tauri-service 1.2.0 → 1.3.0

Changed

  • Update version to 1.3.0
@wdio/flutter-service 1.0.0-next.1 → 1.1.0

Changed

  • Update version to 1.1.0
wdio_flutter N/A → 1.1.0

Changed

  • Update version to 1.1.0
@wdio/electron-service 10.1.0 → 10.2.0

Changed

  • Update version to 10.2.0
@wdio/native-core 1.0.0 → 1.1.0

Changed

  • Update version to 1.1.0
@wdio/native-mobile-core 1.0.0 → 1.1.0

Changed

  • Update version to 1.1.0
@wdio/native-spy 1.1.0 → 1.2.0

Changed

  • Update version to 1.2.0
@wdio/native-utils 2.4.0 → 2.5.0

Changed

  • Update version to 2.5.0
@wdio/react-native-service 1.0.0-next.0 → 1.1.0

Changed

  • Update version to 1.1.0

After merge — predicted release

Approximate. The standing PR rebuilds against main at merge time; if other commits land first, the prediction may shift.

Package Standing PR This PR After merge
@wdio/electron-service 10.2.0 10.2.0 10.2.0
@wdio/flutter-service 1.0.1 1.1.0 1.1.0 ⚠ escalated from 1.0.1
@wdio/native-core 1.1.0 1.1.0 1.1.0
@wdio/native-mobile-core 1.1.0 1.1.0 1.1.0
@wdio/native-spy 1.2.0 1.2.0 1.2.0
@wdio/native-utils 2.5.0 2.5.0 2.5.0
@wdio/react-native-service 1.1.0 1.1.0 1.1.0
@wdio/tauri-service 1.2.1 1.3.0 1.3.0 ⚠ escalated from 1.2.1
wdio_flutter 1.0.1 1.1.0 1.1.0 ⚠ escalated from 1.0.1

Updated automatically by ReleaseKit

goosewobbler and others added 2 commits June 27, 2026 10:29
- buttons bitmask (P1): MouseEvent.buttons now reflects the cumulative held
  buttons at fire time, not just the triggering button. A left mouseup reports
  buttons:0, click/contextmenu report buttons:0, and mousemove reports the
  currently-held mask. perform() and release() track held_mask through
  press/release; pointer_event_js takes an explicit `buttons` arg.
- stale element (P2): eval_point returns stale_element_reference (404) when the
  element-center eval yields JS null (element gone), instead of unknown_error.
- tick ordering (P2): documented that sources are dispatched serially (matching
  the Tauri handler); cross-source tick interleaving tracked in #491.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A second primary click on the same spot within one performActions now emits a
`dblclick` MouseEvent after the `click`, matching what a real browser produces
for element.doubleClick()'s two press/release pairs (tracked via last_click_pos,
local to the request so separate clicks don't merge into a double-click).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
goosewobbler and others added 4 commits June 29, 2026 22:45
A pointerCancel is a valid W3C pointer action, but without the enum variant
serde rejected it and 400'd the entire performActions request. Accept it and
release the source's held buttons (a cancel is not a click, so no MouseEvent
is synthesized). Addresses a gap flagged in the Greptile review of #488.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…king on move

Two defects in the embedded driver's Actions handler flagged by Greptile:

- Special keys arrive as W3C Private-Use-Area codepoints (Key.Enter = \u{E007},
  arrows, F-keys, modifiers). They were forwarded verbatim as KeyboardEvent.key,
  so Key.* constants produced a garbage `key`. Add normalize_key (mirroring the
  tauri-plugin-wdio-webdriver table) and translate before synthesizing the event.
- last_click_pos was never cleared on pointerMove, so click → move away → click
  back at the same coords fired a spurious dblclick. Reset it on every move;
  doubleClick() never moves between its press/release pairs, so real dblclicks
  are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greptile flagged that pointer_event_js/key_event_js never read the session's
pressed_keys, so a held Control/Shift/Alt/Meta produced MouseEvents/KeyboardEvents
with ctrlKey/shiftKey/etc. all false — Ctrl+click handlers, multi-select, and
shortcut handlers wouldn't observe the modifier.

Add a Modifiers value derived from the held modifier keys (left + right PUA
codepoints), kept in sync as key actions press/release and seeded from modifiers
still held from a prior performActions call, and stamp ctrlKey/shiftKey/altKey/
metaKey onto every synthesized pointer and key event.

Interleaving a modifier with a pointer *within a single* performActions (e.g.
Ctrl+click in one call) additionally needs tick-by-tick processing — still
tracked separately in #491.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lease

- normalize_key now maps the right-hand modifier codepoints (\u{E050}-\u{E053})
  to Shift/Control/Alt/Meta, so a right modifier dispatches the correct DOM
  KeyboardEvent.key instead of the raw PUA codepoint. (from_keys already set the
  flags for these; only the key name was missing.)
- release() emits each keyup with the modifiers still held after that key is
  released (computed from the remaining held keys), instead of a flat
  Modifiers::default() — so releasing Shift while Control is down reports
  shiftKey:false, ctrlKey:true. The mouseups run after all keys are up, so their
  empty modifiers stay correct.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread packages/dioxus-embedded-driver/src/server/handlers/actions.rs
goosewobbler and others added 2 commits June 30, 2026 00:13
`1 << n` overflows for button index >= 32 — a panic in debug/test builds, a
silent wrap in release (button 32 aliasing button 0). A malformed request could
crash the driver. Out-of-range indices now contribute no bit (W3C defines 0–4).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dispatch the action at index N from every source before index N+1 from any
source, per the W3C Actions tick model, instead of draining each source serially.
Cross-source chains now interleave correctly — e.g. a key source + a pointer
source in one performActions (Ctrl+click) holds the modifier down when the click
lands. Per-action behaviour is unchanged; only the iteration order changes.

Implements the Dioxus half of #491 (Tauri half tracked separately).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
goosewobbler and others added 4 commits June 30, 2026 02:05
Addresses two Greptile observations on the W3C Actions handler:

- Synthesized KeyboardEvents now carry `code` (the physical key), so apps
  reading `event.code` for keyboard shortcuts receive it. Derived for the
  special keys, ASCII letters (`KeyA`) and digits (`Digit1`) shortcuts use;
  left/right modifier variants map to distinct `*Left`/`*Right` codes.
  Layout-dependent symbols fall back to `""` (the spec default).
- The release handler drains held keys from a HashSet (unordered); sort
  before dispatching keyups so the order is deterministic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on tests (#493)

* chore: release @wdio/flutter-service@1.0.0-next.1 [skip ci]

* ci(release): use releasekit refresh-after-release for the post-release reconcile (#486)

releasekit 0.34.0 shipped `refresh-after-release` (goosewobbler/releasekit#438) — the
purpose-built post-release command that reconciles the standing PR (closes it if empty,
else rebuilds the release branch against the new HEAD) and refreshes feeder-PR previews.
Our two custom reconcile-after-* jobs duplicated the reconcile half via
standing-pr-update --reconcile.

- Add a `Refresh standing PR after release` step to the release job; it runs after a
  scoped, non-dry release and pushes over the checkout's SSH deploy key so the standing
  PR's CI re-runs (a GITHUB_TOKEN push wouldn't).
- Drop the `reconcile-after-auto` / `reconcile-after-manual` jobs and the `reconcile_after`
  dispatch input from release.yml.
- Drop the now-unused `reconcile` input from _standing-pr-update.reusable.yml (only the
  routine push-triggered update uses it now, without reconcile).

Behaviour change: manual releases now always reconcile (the `reconcile_after` opt-in is
gone) — the native command is the post-release default. The standing-pr-publish path is
unchanged (the step is gated to the scoped-release path). Feeder-PR preview refresh stays
a no-op until ci.prPreview.refreshAfterRelease is enabled (follow-up).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(release): bump releasekit to v0.35.0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(release): declare service packages as releasekit primaryPackages

Render the standing-PR checklist as release units (one checkbox per
top-level service, coupled members and prerequisites nested beneath),
using the releasekit 0.35.0 primaryPackages feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(release): add dioxus linked group; skip native-mobile-core github release

Group the dioxus service + bridge npm package and the standalone
embedded-driver/driver crates in lockstep. The colocated wdio-dioxus-bridge
crate is driven by its sibling npm package, so it is not listed separately.
Exclude @wdio/native-mobile-core from GitHub releases like the other shared
internal packages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(flutter): promote wdio_flutter to top-level packages/flutter-bridge

Move the cooperative Dart contract out of the flutter-service package up to a
top-level dir, matching how every other independently-published unit (the
dioxus crates, the tauri plugins) sits as a packages/* sibling. The published
pub.dev name stays wdio_flutter — only the directory changes.

Repointed: releasekit pub.paths, the e2e app's path: dep, the detect-changes
spec, the pubspec homepage, flutter-service README links, the ROADMAP link,
the release-workflow comment, and the AGENTS.md structure tree. flutter-bridge
rides the existing `flutter-` rule in detect-changes (no code change). Added a
package .gitignore for .dart_tool/ + pubspec.lock (libraries don't commit the
lockfile), closing the gap that was leaking them as untracked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(release): bump releasekit to v0.36.0 and drop version.skip

0.36.0 excludes private/unpublishable packages from the release flow by
default (npm `private: true`, Cargo `publish = false`, pub `publish_to: none`),
so the hand-maintained version.skip list is redundant — removed it.

To make privacy actually drive the skip, fixed the manifests it was masking:
- e2e/package.json: `"private": "true"` (string) → `true` (boolean); releasekit
  has a strict `private !== true` check that the string slipped past.
- mark the publishable fixture Cargo crates `publish = false`: tauri-e2e-app,
  tauri-app-example (hybrid: private npm root + a publishable src-tauri crate
  the name-based skip was hiding), and dioxus-package-test-app (a pre-existing
  leak — never in version.skip, so it was being versioned/tagged on every
  dioxus release).

Verified via `version --dry-run`: none of the 19 formerly-skipped packages
nor any fixture crate enters the release set; real packages still bump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(dioxus): browser-mode test parity (integration + E2E + fixture) (#489)

* test(dioxus): browser-mode test parity (integration + E2E + fixture)

Brings Dioxus browser-mode test coverage up to the Electron/Tauri template
(closes #472). Dioxus has no native event bus, so the events tier is omitted
by design (matching the Tauri/Electron docs' explicit-omission convention).

- Integration: add browser-mode.integration.spec.ts + launcher.browser.integration.spec.ts
  (+ helpers.ts) under packages/dioxus-service/test/integration/. Covers the
  launcher→worker handshake (capability→chrome transform, dioxus:options
  removal, devServerUrl validation/propagation, no embedded driver spawned)
  and the worker browser-mode machinery (initial navigation + IPC injection,
  url() override re-injecting after navigation, single shared mock store,
  mock register/update/restore lifecycle). 21 tests, all green.
- E2E: add e2e/wdio.dioxus-browser.conf.ts (real Chrome, self-hosted static
  dev server, ps-based no-Dioxus-driver assertion) + e2e/test/dioxus-browser/mock.spec.ts
  (value-returning invoke + invoke-with-arguments via browser.dioxus.mock).
  New fixtures/e2e-apps/dioxus-browser/ app drives window.__WDIO_DIOXUS__.invoke.
- Package fixture: extend fixtures/package-tests/dioxus-app to actually exercise
  browser.dioxus.mock('get_info') + mock.update() (button + invoke in browser/index.html).
- Docs: note in packages/dioxus-service/docs/browser-mode.md that events are
  unsupported (no native event bus; see packages/native-types/src/dioxus.ts).
- CI: wire pnpm e2e:dioxus-browser into _ci-browser-mode.reusable.yml + the
  e2e/root package.json scripts; route e2e/test/<svc>-*/ test dirs to their
  service in detect-changes (so dioxus-browser specs trigger only dioxus) with
  regression guards in detect-changes.spec.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(dioxus): address review — fixture try/catch, drop unused helpers

- package-test browser fixture: wrap the fetch-info invoke in try/catch (matching
  the e2e fixture) so a pre-injection click surfaces as visible output instead of
  an unhandled rejection.
- integration helpers.ts: remove the unused defer/createFakeMock/flushMicrotasks
  exports (and their types + the DioxusMock import) — Dioxus's specs only use
  createFakeBrowser, so the rest was dead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(browser-mode): clear errors for non-chrome browserName and unreachable dev server (#490)

* fix(browser-mode): clear errors for non-chrome browserName and unreachable dev server

Two browser-mode (#260) misconfigurations failed in ways that didn't match
the acceptance criterion (a misconfiguration should surface a clear,
actionable error). Both are fixed across the Tauri, Electron, and Dioxus
launchers via a shared helper in @wdio/native-core.

Fix 1 — non-chrome browserName was silently overwritten to 'chrome'. The
launcher now throws SevereServiceError when browserName is set to a value the
service can't drive in browser mode. Each service allows its own framework
display/detection name plus chrome (Electron: electron, Tauri: tauri/wry,
Dioxus: dioxus) so legitimate native-mode capabilities aren't rejected; only
genuinely foreign browsers (e.g. firefox) error.

Fix 2 — an unreachable dev server failed late inside browser.url(). The
launcher now preflights devServerUrl in onPrepare with a HEAD probe (fetch +
AbortSignal.timeout) and throws SevereServiceError("Dev server not reachable
at <url> — is it running?") before any session is created.

warn-vs-throw: throw, matching the existing fatal-misconfig pattern already
used for the missing/invalid devServerUrl cases (SevereServiceError stops the
runner with a clear message rather than warning and proceeding).

DRY: the three launchers hand-rolled the browser-mode block with no shared
helper, so the new logic lives once in @wdio/native-core (browserMode.ts:
nonChromeBrowserNameError + probeDevServerReachable, returning Result) and
each launcher throws its own SevereServiceError, consistent with the existing
inline devServerUrl handling.

Unit coverage added for both branches per service plus the shared helper.

Closes #416

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(browser-mode): address review — chrome-guided error, electron two-pass probe

- nonChromeBrowserNameError: the message now consistently guides to 'chrome'
  (the allow-list also carries tolerated native names like 'electron'/'wry' that
  shouldn't be advertised as a value to set).
- electron launcher: split the browser-mode block into validate-all-browserNames
  → probe-each-distinct-url-once → mutate, matching the Tauri/Dioxus two-pass.
  A non-chrome cap now fails fast before any dev-server probe, and a shared
  devServerUrl is probed once instead of per-cap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(browser-mode): narrow the caught probe error instead of casting

Use `error instanceof Error ? error.message : String(error)` rather than an
unchecked `(error as Error)` cast on the caught unknown.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(dioxus): stub dev-server probe in browser-mode launcher integration tests

#490 added a fetch HEAD dev-server probe to the dioxus launcher's onPrepare; #489's
launcher.browser.integration.spec.ts calls the real onPrepare on the happy path and
predates the probe, so it now hits an absent server and fails with
"Dev server not reachable" (main went red when both landed). Stub fetch reachable in
beforeEach (matching the sibling tauri/electron specs #490 already stubbed); error-path
tests throw before the probe and are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…actions

# Conflicts:
#	packages/dioxus-service/test/integration/launcher.browser.integration.spec.ts
…-chain reset

Addresses the two remaining Greptile edge-case observations on the W3C
Actions handler (both unusual sequences, off the common WDIO paths):

- releaseActions drains held buttons from a HashMap<_, HashSet> (both
  unordered), so a multi-button release dispatched its mouseups in a
  non-deterministic order. Collect and sort first — same fix class as the
  keyup ordering. Each mouseup still reports the buttons held after it.
- pointerCancel aborts the gesture but left `last_click_pos` set, so a later
  same-spot click could be misread as a dblclick. Reset it on cancel,
  mirroring the pointerMove break in the click chain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@goosewobbler goosewobbler merged commit 9ccb9cf into main Jun 30, 2026
93 checks passed
@goosewobbler goosewobbler deleted the feat/dioxus-embedded-actions branch June 30, 2026 02:01
@goosewobbler

Copy link
Copy Markdown
Contributor Author

Greptile's two latest DOM-completeness observations — contextmenu not position-guarded against a right-button drag, and missing auxclick for non-primary clicks — are tracked as follow-up #494. Neither affects any WDIO command this PR unlocks (.click({ button }), .doubleClick(), moveTo, drag-drop), and both were absent from the prior not_implemented stub, so they're deferred rather than blocking. Added an in-code tracking ref at the synthesis site.

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.

Dioxus embedded driver: implement W3C Actions API (/actions) — click(options)/drag/hover/key actions

1 participant