Mobilewright

repository·main·Indexed 42 days ago

https://github.com/mobile-next/mobilewright

A mobile device automation framework inspired by Playwright for iOS and Android. It provides a clean, auto-waiting API and leverages the device's accessibility tree to optimize interactions for both developers and AI agents. Features include semantic locators, a browser-based inspector, support for hybrid app WebViews, and integration with Playwright Test via @mobilewright/test.

Tokens
77.9K
Snippets
299
Records
348
Agent score
57%

What's inside Mobilewright

  1. Available features in Mobilewright today

    main

    Mobilewright currently supports end-to-end mobile testing with the following capabilities:

    • Platform Support: Android and iOS via configuration.
    • Test Framework: Support for test lifecycle hooks (test.beforeEach(), test.describe.serial()), skipping tests (test.skip()), and grouping actions (test.step()).
    • Locators & Assertions: A robust Locator API (getByText(), getByRole(), etc.) with corresponding assertions (toBeVisible(), toHaveText(), etc.) and value assertions (toBe(), toEqual()).
    • Interactions: Touch interactions (tap(), longPress(), fill()), screen actions (swipe(), pressButton(), screenshot()), and WebView support.
    • App & Device Control: App lifecycle management (launchApp(), installApp()), device controls (setOrientation(), setGeolocation()), and video recording attached to HTML reports.
    • Execution & Reporting: Parallel workers, sharding, and multiple reporter types (list, html, json, blob).
    • Tooling: A CLI for testing, inspecting, and managing devices, plus an Inspector for browsing the live view hierarchy.
  2. How locator actions and screen actions differ

    main

    Mobilewright provides two ways to drive an app, and choosing the right one is critical for test stability:

    1. Locator Actions: Performed on an element found via a locator (e.g., screen.getByText('Submit').tap()). These are the preferred method because they auto-wait until the element is actionable and target the element's center. Use these for almost all UI interactions.
    2. Screen Actions: Performed directly on the screen object using coordinates or device commands (e.g., screen.tap(200, 400)). These do not auto-wait. Use these only when there is no specific element to target (like a game canvas), for hardware buttons, or for complex custom gestures.

    Rule of thumb: Always prefer locator actions to ensure tests are resilient to UI loading and animations.

  3. How auto-waiting works in Mobilewright

    main

    Mobilewright eliminates the need for manual sleeps or waits by performing 'actionability checks' before executing actions. When an action is called, Mobilewright retries the check every 100ms (with a 50ms interval for stability checks) until all conditions are met or the timeout is reached. If checks fail after the timeout, a LocatorError is thrown.

    For interaction actions like tap(), Mobilewright ensures the element:

    1. Exists in the view hierarchy.
    2. Is visible on screen.
    3. Is enabled for interaction.
    4. Has stable bounds (not animating or moving).

    Methods that only read data (like getText()) only wait for visibility.

  4. Auto-waiting with assertions

    main

    Mobilewright's expect() assertions include built-in auto-waiting. Assertions will automatically retry until the condition is met or the timeout expires, reducing the need to call waitFor() manually before checking a state.

    // This will keep checking until the text is visible (up to 5s)
    await expect(screen.getByText('Success')).toBeVisible();
    
    // Wait for text content to match
    await expect(screen.getByRole('text')).toHaveText('Done');
  5. How locator chaining works

    main

    You can scope queries within a parent element's bounds by chaining locators. When chaining, child lookups use bounds-based containment: any element whose bounds fit within the parent's bounds is considered a child. This allows you to target specific elements within a specific UI component (like a row in a list).

    // Tap the delete button inside the first row
    const row = screen.getByType('Cell');
    await row.getByRole('button', { name: 'Delete' }).tap();
    
    // Get text from a navigation bar
    const title = await screen.getByType('NavigationBar').getByType('StaticText').getText();
  6. Understand Locator priority and duplication in the Inspector

    main

    The Inspector annotates each element with the single best locator based on the Mobilewright query engine's priority order:

    getByTestId > getByRole > getByLabel > getByText

    Handling Duplicates: If a locator resolves to more than one element, the Inspector will display a dup badge next to that locator. This indicates the locator is not unique. To fix this, you should add a testID to the element or use a more specific query before using it in your automation scripts.

  7. Use Cloud Providers to scale Mobilewright tests

    main

    Mobilewright allows you to run the same test suite on local devices or real devices in the cloud by simply changing the driver configuration in mobilewright.config.ts. This enables scaling test execution by increasing the workers count, where each worker is allocated a unique device by the cloud provider. This approach eliminates the need for maintaining physical device labs and prevents CI dependencies on local hardware.

    // Example concept: switching drivers in mobilewright.config.ts
    // (Note: Actual implementation details depend on the specific provider documentation)
    export default {
      driver: process.env.CLOUD_PROVIDER ? 'cloud-driver-name' : 'local-driver-name',
      workers: process.env.CI ? 10 : 1,
    };
  8. Use `getByRole()` for cross-platform element targeting

    main

    The getByRole() method allows you to target the same element on both Android and iOS using a single semantic name, even though the underlying native classes differ. Mobilewright normalizes native types by stripping platform-specific prefixes (like android.widget. or XCUIElementType) and mapping the remaining name to a semantic role.

    When to use getByRole() vs other locators:

    • Use getByRole('role_name') for standard UI elements (buttons, text fields, etc.) to keep tests cross-platform.
    • Use getByType('raw.native.Class') if the element has no role mapping or if you need to target a specific raw class.
    • Use getByLabel() or getByTestId() for elements that lack a clear semantic role.
    // Example: Targeting a text field on both platforms
    await screen.getByRole('textfield');
    
    // Example: Targeting a specific raw class when no role exists
    await screen.getByType('android.widget.Spinner');
  9. How Mobilewright supports AI agents

    main

    Mobilewright is designed for AI agents by exposing the device's accessibility tree rather than relying on visual models or fragile XPaths. This provides a deterministic, token-efficient way for agents (like Claude, Cursor, or mobile-mcp) to interact with mobile interfaces using semantic actions.

    // An AI agent can control a real phone with readable, semantic actions
    await screen.getByRole('button', { name: 'Sign In' }).tap();
    await screen.getByLabel('Email').fill('user@example.com');
    await expect(screen.getByText('Welcome')).toBeVisible();
  10. Mobilewright System Requirements

    main

    Mobilewright requires a working mobile toolchain. It does not start simulators or emulators for you; they must be booted and available.

    Common Requirements

    • Node.js: 22.12 or newer.
    • Devices: A booted simulator, emulator, or a USB-connected device.

    Platform Specifics

    iOS

    • OS: macOS 13 or newer.
    • Tools: Xcode and Xcode Command Line Tools.

    Android

    • OS: macOS or Windows 11 (Linux requires Docker).
    • Java: JDK 11 or newer.
    • Android SDK: ANDROID_HOME must be set and adb must be in your PATH.
  11. Manage devices and sessions

    main

    Selecting a Device

    If only one device is online, the CLI selects it automatically. To target a specific device, use the --device <id> flag or set the MOBILEWRIGHT_DEVICE environment variable. Once a device is specified, it is remembered for the duration of the session.

    Using Sessions

    Sessions allow you to drive multiple devices side-by-side. Use the -s <name> flag to create and switch between sessions. Session files are stored in the system temp directory or the directory specified by MOBILEWRIGHT_CLI_DIR.

    Example of driving an iOS and Android device simultaneously:

    mobilewright-cli -s phone --device <ios-id> launch com.example.app
    mobilewright-cli -s tablet --device <android-id> launch com.example.app
    mobilewright-cli --device Pixel_9_Pro launch com.example.app
  12. How Mobilewright CLI snapshots and references work

    main

    The CLI provides a token-efficient way for agents to interact with mobile screens. Instead of screenshots or large JSON hierarchies, it prints a compact text tree of the current screen.

    The Snapshot Format

    Each element in a snapshot is represented by a line following the pattern: - <role> "<name>" [ref=<ref>] followed by attributes like testid, placeholder, value, hidden, disabled, checked, selected, or focused.

    Example snapshot output:

    - textfield [ref=e7] [testid="email"] [placeholder="Email"]
    - button "Sign in" [ref=e9]

    Using References (Refs)

    Every element is assigned a stable reference (e.g., e7, e9). When performing actions like tap or fill, you target these references directly. This ensures the agent never has to guess coordinates.

    Mapping to Test Code

    After every action, the CLI prints the exact Mobilewright JavaScript code required to perform that action in a test environment. You can collect these lines to build automated E2E tests.

    mobilewright-cli snapshot