Skip to main content

App

Struct App 

Source
pub struct App { /* private fields */ }

Implementations§

Source§

impl App

Source

pub fn new(driver: SimctlDriver, simctl: SimctlClient) -> Self

Construct from a fully-wired driver + simctl client. Use this when you already manage Cell / UDID lifecycle externally.

Back-compat constructor: still accepts SimctlDriver (alias to IosDriver) and SimctlClient; internally wraps into Box<dyn Driver> and Box::new(IosDeviceControl::with_client(...)).

Source

pub fn new_with(driver: Box<dyn Driver>, device: Box<dyn DeviceControl>) -> Self

Generic constructor for cross-platform tests. Use this when constructing with non-iOS Driver / DeviceControl impls.

Source

pub fn http_runner_client(&self) -> Option<&HttpRunnerClient>

v1.0.3 — accessor for the underlying HTTP runner client, used by Session to drive the /session/* routes. Returns None when the driver is not backed by an HTTP runner (e.g. mock driver in tests).

Source

pub async fn open_session( self, bundle_id: &str, activate: bool, ) -> Result<Session, ExpectationFailure>

v1.0.3 — open a runner-side session bound to bundle_id. Subsequent requests via the returned Session send the Session-Id header and skip per-request activation entirely.

activate = true causes the runner to .activate() the target once as part of the open (idiomatic when the target may not be foregrounded yet). activate = false opens a passive binding suitable when the caller has already ensured foreground state.

This is the recommended surface for long-running gates. See the Session docs for the lifecycle contract.

Source

pub async fn connect_to_runner(port: u16) -> Result<Self, ExpectationFailure>

Convenience: connect to a runner on 127.0.0.1:{port} and probe GET /health once. Returns App ready for sense+act calls.

Source

pub async fn connect_to_runner_android( port: u16, ) -> Result<Self, ExpectationFailure>

Connect to an Android Kotlin runner on 127.0.0.1:{port} (the host-forwarded port that proxies to the device-side runner instrumentation). Returns App ready for cross-platform sense+act calls dispatched via AndroidDriver + AndroidDeviceControl.

Source

pub fn with_udid<S: Into<String>>(self, udid: S) -> Self

Bind a UDID for lifecycle operations (launch/terminate/install/etc.).

Source

pub fn with_bundle_id<S: Into<String>>(self, bundle: S) -> Self

Thread the target bundle id down to the driver, which forwards it to the runner via the App-Bundle-Id HTTP header. The iOS runner rebinds XCUIApplication(bundleIdentifier:) per request so calls stay pinned to the right app even when something else briefly claims foreground.

Source

pub fn with_auto_activate(self, activate: bool) -> Self

Enable auto-activate on every request. Runner side .activate()s the resolved target before operating. Costs one XCUITest activate call per request (~50-100ms); opt-in.

Source

pub fn with_force_key_events(self, force: bool) -> Self

Force key-event dispatch mode on text-input verbs. Sends Input-Dispatch-Mode: key-events header on every request. Covers the RN hidden-input pattern where a11y-focus lookup returns nothing. Also opt-in via smix run --force-key-events.

Source

pub fn udid(&self) -> Option<&str>

Source

pub fn driver(&self) -> &dyn Driver

Direct access to underlying Driver trait object. Use app.driver() for cross-platform calls; downcast to IosDriver only if iOS-specific behavior needed.

Source

pub fn simctl(&self) -> &SimctlClient

Back-compat &SimctlClient accessor. iOS-only. Panics on Android (use app.device() instead).

Source

pub fn device(&self) -> &dyn DeviceControl

Cross-platform sim/host control trait object.

Source

pub async fn clear_app_data(&self) -> Result<u64, ExpectationFailure>

v1.0.8 §D2 — clear the current session’s target app data IN PLACE via cooperative XCUIApplication.terminate() + host SimctlClient sandbox wipe + cooperative XCUIApplication.launch(). Preserves XCUITest binding and does NOT signal com.apple.ReportCrash — the fix for the “Insight quit unexpectedly” system dialog that both simctl uninstall + install and v1.0.4’s simctl terminate + simctl spawn rm still tripped.

Requires a Session-Id set on the driver (auto-populated by smix run and by App::open_session); errors otherwise. Requires an iOS driver + UDID (SimctlClient access for the host-side wipe). Android is not supported yet — Android’s UiAutomator lifecycle differs and needs its own charter.

Source

pub async fn clear_app_data_with_launch( &self, launch_args: &[String], launch_env: &BTreeMap<String, String>, ) -> Result<u64, ExpectationFailure>

v1.0.11 §D2 — same three-step orchestration as Self::clear_app_data, but applies caller-supplied launchArguments and launchEnvironment to the runner’s cooperative launch step. Unblocks scaffolding like the Expo SDK 57 dev-launcher server picker that clearAppData wipes from persisted state and can no longer restore via a URL scheme call. Insight-side yaml:

- clearAppData:
    launchArgs: ["-EXInternalMetroPort", "8081"]
    launchEnv:
      EX_DEV_CLIENT_METRO_URL: "http://localhost:8081"

Empty args + empty env is equivalent to Self::clear_app_data.

Source

pub async fn launch(&self, bundle_id: &str) -> Result<(), ExpectationFailure>

Source

pub async fn terminate(&self, bundle_id: &str) -> Result<(), ExpectationFailure>

Source

pub async fn install(&self, app_path: &str) -> Result<(), ExpectationFailure>

Source

pub async fn uninstall(&self, bundle_id: &str) -> Result<(), ExpectationFailure>

Source

pub async fn launch_fresh( &self, bundle_id: &str, clear_state: bool, clear_keychain: bool, app_path: Option<&str>, launch_arguments: &[String], ) -> Result<Vec<String>, ExpectationFailure>

Launch the app with optional state / keychain wipe before launch. See plan_launch_fresh_calls for the op sequence semantics. Returns the warnings produced by the planner (graceful fallback path is taken when clear_state=true but app_path is None). The caller should append these warnings to its own collector (e.g. RunReport::warnings in the maestro adapter). launch_arguments is process-level argv (simctl launch -- <args>). Empty &[] skips argv injection.

Source

pub async fn set_permission( &self, bundle_id: &str, permission: SimctlPermission, action: PermissionAction, ) -> Result<(), ExpectationFailure>

Apply a permission action to a bundle. Maps maestro yaml permissions: { camera: allow|deny|unset } to simctl privacy. §12.1 three-layer architecture: sense+act live in core; the adapter only translates maestro yaml strings to the PermissionAction enum.

Source

pub async fn launch_app_with_options( &self, opts: &LaunchAppOptions, ) -> Result<Vec<String>, ExpectationFailure>

Typed launch entry: apply permissions in declaration order, then dispatch to Self::launch_fresh (when clear_state / clear_keychain) or simctl terminate + launch_with_args (otherwise). Maps maestro yaml launchApp: { ... } in full (permissions / arguments / clearState / clearKeychain). Returns warnings emitted by launch_fresh (caller appends to its own collector).

Source

pub async fn clear_user_defaults( &self, bundle_id: &str, keys: &[String], ) -> Result<(), ExpectationFailure>

v1.0.27 — delete keys from the target app’s persisted user-defaults store (iOS: NSUserDefaults via simctl spawn defaults delete; Android: unsupported, explicit error). Contract is “ensure keys absent” — already-absent keys are success. Terminate the app first: a running process caches its defaults in-memory and may rewrite keys at exit.

Motivating case: expo-dev-launcher persists the most recent deep link and re-delivers it after every JS bundle load (insight round-5 Ask 12); deleting its storage key between terminate and relaunch neutralizes the replay at the source.

Source

pub async fn open_url(&self, url: &str) -> Result<(), ExpectationFailure>

Source

pub async fn send_push( &self, bundle_id: &str, apns_json_path: &str, ) -> Result<(), ExpectationFailure>

Deliver an APNS payload to bundle_id via simctl push. The payload file must contain a JSON dictionary with at least an aps key (per Apple’s spec). Mirrors maestro yaml sendPush: once that command lands upstream — there is no public maestro yaml sendPush today, so this is SDK-only surface.

Source

pub async fn screenshot(&self) -> Result<Vec<u8>, ExpectationFailure>

Source

pub fn mark_fixture_action(&self, action_id: &str)

Register a fixture-side action anchor in the SDK ledger so that the capsule_reconcile window can attribute the kAXFirstResponderChangedNotification (1018) the fixture’s UIKit modal present is about to emit. Use this immediately before triggering a fixture-owned present path (UIActivityViewController, UIDocumentPickerViewController, SpringBoard system popup) that smix-driver would otherwise leave unattributed — without it, the phantom focus change inflates unattributed_count.

Source

pub async fn tree(&self) -> Result<A11yNode, ExpectationFailure>

Source

pub async fn describe(&self) -> Result<ScreenDescription, ExpectationFailure>

Source

pub async fn find_one( &self, selector: &Selector, ) -> Result<Option<A11yNode>, ExpectationFailure>

Source

pub async fn find_all( &self, selector: &Selector, ) -> Result<Vec<A11yNode>, ExpectationFailure>

Source

pub async fn find( &self, selector: &Selector, ) -> Result<bool, ExpectationFailure>

Source

pub async fn system_popups( &self, ) -> Result<Vec<SystemPopup>, ExpectationFailure>

Source

pub async fn system_popup_action( &self, popup_id: &str, button_id: &str, ) -> Result<bool, ExpectationFailure>

Tap a button on a previously enumerated system popup. popup_id and button_id round-trip from system_popups() — the runner walks the same scan order so callers don’t need to manage an id map. Returns Ok(true) when matched and tapped, Ok(false) when the runner returned 404 not_found (popup or button id stale). Paired with system_popups() to close the sense/act loop on iOS system popups.

Source

pub async fn tap(&self, selector: &Selector) -> Result<(), ExpectationFailure>

Source

pub async fn tap_with_mode( &self, selector: &Selector, mode: TapMode, ) -> Result<(), ExpectationFailure>

Tap a selector via an explicit dispatch mode. Use TapMode::DaemonProxySynthesize for RN Pressable buttons that don’t fire onPress with the default tap() Apple-native-event -chain dispatch. All other selectors should use tap(selector) (no mode) — the default host-resolve plus tap_at_norm_coord path is faster and works for non-RN-Pressable elements.

Source

pub async fn fill( &self, selector: &Selector, text: &str, ) -> Result<(), ExpectationFailure>

Source

pub async fn clear(&self, selector: &Selector) -> Result<(), ExpectationFailure>

Source

pub async fn press_key(&self, key: KeyName) -> Result<(), ExpectationFailure>

Source

pub async fn scroll( &self, selector: &Selector, direction: SwipeDirection, ) -> Result<(), ExpectationFailure>

Source

pub async fn swipe_once( &self, direction: SwipeDirection, ) -> Result<(), ExpectationFailure>

Source

pub async fn hide_keyboard(&self) -> Result<(), ExpectationFailure>

Source

pub async fn go_back(&self) -> Result<(), ExpectationFailure>

Source

pub async fn tap_at_coord( &self, nx: f64, ny: f64, ) -> Result<(), ExpectationFailure>

Tap at normalized (nx, ny) coordinates — escape hatch for coord-based maestro yaml port and other no-a11y-semantic scenarios. (nx, ny) MUST be in [0, 1] (normalized to viewport).

§9 #3 lift (v3.16, escape hatch): the Selector surface still forbids xpath/coord — this method is NOT a Selector, it is the direct Apple-native-event-chain wire entry. Only tap is exposed; swipe_at_coord / fill_at_coord / anchor_at_coord are intentionally NOT provided and would require an independent CLAUDE.md §10 decision.

Prefer tap(&selector) for any path with a11y semantic. Use this only for yaml-port edge cases (e.g. maestro point: "X%,Y%").

Source

pub async fn tap_xcui(&self, id: &str) -> Result<(), ExpectationFailure>

Tap via XCUIElement.tap() over the XCTest gesture-recognizer chain instead of the default host-HID-at-coord path. The id selector is resolved runner-side via XCUIApplication.descendants(matching: .any) .matching(identifier:).firstMatch.tap().

Why this exists: SwiftUI .sheet / .alert / .confirmationDialog / .fullScreenCover dismiss buttons present in a separate modal window scene. The default tap(&selector) resolves the button frame and injects an IOKit event at that coord, but iOS routes the touch to the underlying scene’s hit-target, so SwiftUI’s onTap closure for the modal-window button never fires. XCUIElement.tap() operates on the resolved element handle and reaches the binding regardless of window topology.

Use tap(&selector) for everything else — the default path is faster and works on non-modal SwiftUI / UIKit hierarchies.

Source

pub async fn find_norm_coord( &self, selector: &Selector, ) -> Result<Option<(f64, f64)>, ExpectationFailure>

Apple Vision OCR find. Returns the matching text observation’s bounding box (UIKit normalized) or None. locales are BCP-47 language subtags; empty defaults to the SDK’s current locale (["en"] if unset). Covers “lib without testID but with visible text” scenarios.

Find a selector’s centroid as viewport-normalized (nx, ny). Used by adapter AnchorRelative dispatch. Returns None when the selector resolves no node / empty frame.

Source

pub async fn webview_eval(&self, js: &str) -> Result<Value, ExpectationFailure>

Eval JS against the app-side WKWebView bridge. Returns the JS result as a JSON Value. Bridge must be running in the target app.

Source

pub async fn find_by_text_ocr( &self, text: &str, locales: &[String], ) -> Result<Option<OcrFrame>, ExpectationFailure>

Source

pub async fn tap_by_text_ocr( &self, text: &str, locales: &[String], ) -> Result<(), ExpectationFailure>

Find by OCR + tap at frame center via IOHID synthesize. Convenience for the common OCR fallback path (OCR keyword → tap). Returns ElementNotFound when OCR finds no match.

Source

pub async fn swipe_at_coord( &self, from: (f64, f64), to: (f64, f64), ) -> Result<(), ExpectationFailure>

Swipe between two normalized coordinate points — escape hatch for coord-based maestro yaml port (swipe: { from: "X%,Y%", to: "X%,Y%" }). Both points MUST be in [0, 1].

§9 #3 lift (escape hatch): companion to Self::tap_at_coord. The Selector surface still forbids xpath/coord — this method is NOT a Selector, it is the direct Apple-native-event-chain wire entry. Only tap and swipe coord forms are exposed; fill_at_coord / anchor_at_coord / hover_at_coord are intentionally NOT provided and would each require an independent CLAUDE.md §10 decision.

Source

pub async fn scroll_screen( &self, direction: SwipeDirection, ) -> Result<(), ExpectationFailure>

Viewport scroll one swipe in the given direction — no selector required. Maps to maestro yaml scroll: (bare, no args, defaults to down).

Implementation: a single normalized-coord swipe from the viewport center to one edge (sense+act layer). Self::scroll is the scroll-until-visible composite and is orthogonal; scroll_screen is a pure act primitive.

Source

pub async fn wait_for_not_visible( &self, selector: &Selector, timeout: Duration, ) -> Result<(), ExpectationFailure>

Assert that the selector is NOT visible. Dual of Self::assert_visible. Maps to maestro yaml assertNotVisible:.

Assertion is a core sense+assertion primitive, not an adapter-only synthesis. Uses a single non-waiting Self::find probe; if the selector matches, raise AssertionFailed. Wait until the selector is NOT visible. Dual of Self::wait_for. Polls Self::find at 250ms intervals; returns Ok the first instant the element is absent. Returns AssertionFailed if the element is still visible after timeout elapses.

The assertion+sense composite is a core platform capability, not adapter-only synthesis. Maps to maestro yaml extendedWaitUntil: { notVisible: ... , timeout: N }.

Source

pub async fn assert_screenshot( &self, baseline_path: &Path, max_hamming: u32, ) -> Result<AssertScreenshotOutcome, ExpectationFailure>

Assert the current sim screenshot matches a recorded baseline PNG via 64-bit dhash perceptual diff. Maestro assertScreenshot: <baseline-path>.

Baseline lifecycle (same as maestro):

  • Baseline missing → write the captured PNG + return Recorded { path } (auto-record default).
  • SMIX_ASSERT_SCREENSHOT_NO_AUTORECORD=1 env → strict mode: missing baseline = DriverError.
  • Baseline present → dhash(baseline) vs dhash(current) → hamming distance; ≤ max_hamming = Matched { hamming }, otherwise AssertionFailed.

max_hamming typically ≤ 10 (adapter runtime arm pins 5).

Source

pub async fn assert_not_visible( &self, selector: &Selector, ) -> Result<(), ExpectationFailure>

Source

pub async fn set_clipboard(&self, text: &str) -> Result<(), ExpectationFailure>

Write text to the iOS Simulator device pasteboard via xcrun simctl pbcopy <udid>. Maps to maestro yaml setClipboard: "literal".

Clipboard set is a core act primitive. Uses the simctl host-side path (device-scoped, explicit UDID) rather than the swift sim-side UIPasteboard wire — SimctlClient::pasteboard_set already has a stable wire, no need to add a new swift route.

Source

pub async fn get_clipboard(&self) -> Result<String, ExpectationFailure>

Read the current iOS Simulator device pasteboard via xcrun simctl pbpaste <udid>. Returns the raw string (may be empty).

Source

pub async fn paste_text( &self, text: Option<&str>, ) -> Result<(), ExpectationFailure>

Paste text into the currently-focused input field. Maps maestro yaml pasteText: "literal" (text-bearing form) and bare - pasteText (None form, reads current clipboard first).

Both forms preserve the clipboard side-effect maestro yaml users implicitly rely on (literal form writes clipboard so the post-flow pasteboard mirrors the typed text — same as native “paste from clipboard” UX).

Source

pub async fn double_tap( &self, selector: &Selector, ) -> Result<(), ExpectationFailure>

Double-tap an element. Maps to maestro yaml doubleTapOn: <selector>. Backed by XCUIElement.doubleTap() on the swift sim side.

Source

pub async fn long_press( &self, selector: &Selector, duration: Duration, ) -> Result<(), ExpectationFailure>

Long-press an element for duration. Maps to maestro yaml longPressOn (optional duration: ms, default 500 on the adapter side). Backed by XCUIElement.press(forDuration:) on the swift sim side.

Source

pub async fn set_location( &self, latitude: f64, longitude: f64, ) -> Result<(), ExpectationFailure>

Set sim location. Maestro setLocation: { latitude, longitude }.

Source

pub async fn travel( &self, points: &[(f64, f64)], speed_mps: Option<f64>, ) -> Result<(), ExpectationFailure>

Interpolate sim location along waypoints. Maestro travel. Fire-and-return: simctl injects scenario and returns immediately; sim continues interpolation in background. Caller must explicitly waitForAnimationToEnd / sleep if downstream logic depends on playback completion.

Source

pub async fn add_media( &self, paths: &[String], ) -> Result<(), ExpectationFailure>

Add photos / videos / contacts to the sim library. Maestro addMedia: <path> (scalar) or addMedia: [paths] (array; adapter flattens to Vec).

Source

pub async fn start_recording( &self, path: &str, ) -> Result<(), ExpectationFailure>

Start recording the sim display to path. Maestro startRecording: <path>. Spawns xcrun simctl io recordVideo as a long-running child; returns immediately. Errors if a recording is already in progress (call stop_recording first — no silent no-op).

Source

pub async fn stop_recording(&self) -> Result<(), ExpectationFailure>

Stop the active recording (SIGINT-and-wait simctl child; flushes mp4 trailer). Maestro stopRecording. Errors if no recording is active — explicit DriverError + hint, not a silent no-op.

Source

pub async fn set_orientation( &self, orientation: MaestroOrientation, ) -> Result<(), ExpectationFailure>

Rotate sim. Maestro setOrientation: portrait | portraitUpsideDown | landscapeLeft | landscapeRight. Walks driver.set_orientation → POST /set-orientation → swift XCUIDevice.shared.orientation.

Source

pub async fn set_permissions( &self, bundle_id: &str, permissions: &[(SimctlPermission, PermissionAction)], ) -> Result<(), ExpectationFailure>

Batch permission setter. Maestro setPermissions: { camera: allow, location: deny, ... } (top-level command, distinct from launchApp.permissions). Reuses set_permission per entry (sequential apply; fail-fast on first error).

Source

pub async fn copy_text_from( &self, selector: &Selector, ) -> Result<(), ExpectationFailure>

Read text content from the matched element and write it to the device pasteboard. Maps to maestro yaml copyTextFrom: <selector>. Field priority follows the maestro iOS driver: value → text → label. All three empty raises AssertionFailed — no silent no-op.

Source

pub async fn start_capsule_recording(&self) -> Result<(), ExpectationFailure>

Start Capsule recording — triggers the UITest runner EventRecorder.installSwizzle registration path (if TEST_RUNNER_SMIX_RECORD_ENABLED=1 is set in the env) and clears the SDK-internal issued-action ledger. The runner must be started with TEST_RUNNER_SMIX_RECORD_ENABLED=1; otherwise swift-side recordEnabled is false, installSwizzle is skipped, and /record/start returns 404.

Source

pub async fn stop_capsule_recording_and_reconcile( &self, window_ms: Option<u64>, ) -> Result<CapsuleReconciliation, ExpectationFailure>

Stop recording and reconcile. window_ms = None uses DEFAULT_RECONCILE_WINDOW_MS (500 ms). Returned CapsuleReconciliation includes the full unattributed_events detail.

Source

pub async fn foreground( &self, bundle_id: &str, ) -> Result<(), ExpectationFailure>

Source

pub async fn wait_for( &self, selector: &Selector, timeout: Duration, ) -> Result<A11yNode, ExpectationFailure>

Source

pub async fn assert_visible( &self, selector: &Selector, ) -> Result<(), ExpectationFailure>

Assert that the selector matches a visible element. Re-uses wait_for semantics (5s default budget) with NotVisible failure code.

Source

pub async fn assert_enabled( &self, selector: &Selector, ) -> Result<(), ExpectationFailure>

Assert that the matched element has enabled = true.

Source

pub async fn assert_text(&self, literal: &str) -> Result<(), ExpectationFailure>

Assert that the screen contains at least one element whose text / label / 6-field OR scan matches the literal. Useful for “page rendered” smoke checks without crafting a full selector tree.

Auto Trait Implementations§

§

impl !RefUnwindSafe for App

§

impl !UnwindSafe for App

§

impl Freeze for App

§

impl Send for App

§

impl Sync for App

§

impl Unpin for App

§

impl UnsafeUnpin for App

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more