@mobilewright/test 0.0.46 → 0.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +177 -16
  2. package/package.json +4 -4
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Mobilewright
2
2
 
3
- [![npm](https://img.shields.io/npm/dw/mobilewright?style=flat-square&label=npm%20downloads)](https://www.npmjs.com/package/mobilewright)
3
+ [![npm](https://img.shields.io/npm/dm/mobilewright?style=flat-square&label=npm%20downloads)](https://www.npmjs.com/package/mobilewright)
4
4
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue?style=flat-square)](LICENSE)
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.4+-3178C6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
6
6
 
@@ -8,7 +8,7 @@ Framework for mobile device automation, inspired by Playwright's architecture an
8
8
 
9
9
  **Mobilewright** targets iOS and Android devices, simulators, and emulators through a clean, auto-waiting API built on top of [mobilecli](https://github.com/mobile-next/mobilecli).
10
10
 
11
- [Get Started](#quick-start) · [API Docs](#api-reference) · [Roadmap](ROADMAP.md) · [Cloud (mobile-use.com)](https://mobile-use.com)
11
+ [Get Started](#quick-start) · [API Docs](#api-reference) · [Roadmap](ROADMAP.md) · [Mobile Next Cloud](https://mobilenext.ai)
12
12
 
13
13
  ## Why Mobilewright?
14
14
 
@@ -21,7 +21,7 @@ If you've used Playwright, you already know Mobilewright.
21
21
  | Setup | `npm install mobilewright` | Server + drivers + caps | React Native only | Xcode/AS only |
22
22
  | Cross-platform | iOS + Android, one API | Yes, verbose | React Native only | Single platform |
23
23
  | AI agent support | First-class (accessibility tree) | Limited | No | No |
24
- | Real devices in the cloud | Via [mobile-use.com](https://mobile-use.com) | Yes (complex) | Simulators only | Yes |
24
+ | Real devices in the cloud | Via [Mobile Next Cloud](https://mobilenext.ai) | Yes (complex) | Simulators only | Yes |
25
25
  | Locators | Semantic roles + labels | XPath, CSS, ID | Test IDs | Native queries |
26
26
 
27
27
  ## Built for AI agents
@@ -93,10 +93,10 @@ It checks Xcode, Android SDK, simulators, ADB, and other dependencies — and te
93
93
  | `@mobilewright/test` | Test fixtures |
94
94
  | `@mobilewright/protocol` | TypeScript interfaces (`MobilewrightDriver`, `ViewNode`) |
95
95
  | `@mobilewright/driver-mobilecli` | WebSocket JSON-RPC client for mobilecli |
96
- | `@mobilewright/driver-mobile-use` | WebSocket JSON-RPC client for [mobile-use.com](https://mobile-use.com) cloud devices |
97
- | `@mobilewright/mobilewright-core` | `Device`, `Screen`, `Locator`, `expect` — the user-facing API |
96
+ | `@mobilewright/driver-mobilenext` | WebSocket JSON-RPC client for [Mobile Next Cloud](https://mobilenext.ai) cloud devices |
97
+ | `@mobilewright/core` | `Device`, `Screen`, `Locator`, `expect` — the user-facing API |
98
98
 
99
- Most users only need `mobilewright` (or `@mobilewright/test` for vitest integration).
99
+ Most users only need `mobilewright` (or `@mobilewright/test` for Playwright Test integration).
100
100
 
101
101
  ## API Reference
102
102
 
@@ -120,13 +120,13 @@ const device = await ios.launch({ deviceName: /My.*iPhone/ });
120
120
  const device = await ios.launch({ deviceId: '5A5FCFCA-...' });
121
121
 
122
122
  // List available devices
123
- const devices = ios.devices();
124
- const devices = android.devices();
123
+ const devices = await ios.devices();
124
+ const devices = await android.devices();
125
125
  ```
126
126
 
127
127
  `launch()` handles the full lifecycle:
128
128
  1. Checks if mobilecli is reachable (auto-starts it for local URLs if not running)
129
- 2. Discovers booted devices (prefers simulators over real devices)
129
+ 2. Discovers booted devices (picks the first device that matches your criteria)
130
130
  3. Connects and optionally launches the app
131
131
  4. On `device.close()`, kills the auto-started server
132
132
 
@@ -156,6 +156,9 @@ await screen.swipe('up')
156
156
  await screen.swipe('down', { distance: 300, duration: 500 })
157
157
  await screen.pressButton('HOME')
158
158
  await screen.tap(195, 400) // raw coordinate tap
159
+ await screen.doubleTap(195, 400) // raw coordinate double-tap
160
+ await screen.longPress(195, 400, 1000) // raw coordinate long-press (optional duration in ms)
161
+ const tree = await screen.viewTree() // raw accessibility tree (ViewNode[])
159
162
  ```
160
163
 
161
164
  ### Locator
@@ -168,14 +171,38 @@ Lazy, chainable element reference. No queries execute until you call an action o
168
171
  await locator.tap()
169
172
  await locator.doubleTap()
170
173
  await locator.longPress({ duration: 1000 })
171
- await locator.fill('hello@example.com') // tap to focus + type text
174
+ await locator.fill('hello@example.com') // tap to focus, clear, then type text
175
+ await locator.clear() // tap to focus + clear the field
172
176
  await locator.swipe({ direction: 'left' }) // swipe on a specific element
173
177
  await locator.scrollIntoViewIfNeeded() // scroll until element is visible
178
+ await locator.screenshot() // capture just this element (cropped PNG)
179
+ ```
180
+
181
+ **Narrowing & combining** — refine a locator that matches multiple elements:
182
+
183
+ ```typescript
184
+ locator.filter({ hasText: 'In stock' }) // keep matches whose subtree contains text
185
+ locator.filter({ hasNotText: /sold out/i }) // keep matches whose subtree does NOT contain text
186
+ locator.filter({ has: screen.getByRole('button') }) // keep matches containing a child locator
187
+ locator.filter({ hasNot: screen.getByText('Ad') }) // keep matches NOT containing a child locator
188
+ locator.and(screen.getByRole('button')) // match both this and another locator
189
+ locator.or(screen.getByText('Retry')) // match either this or another locator
190
+ ```
191
+
192
+ **Multiple matches** — work with locators that resolve to more than one element:
193
+
194
+ ```typescript
195
+ locator.first() // first match
196
+ locator.last() // last match
197
+ locator.nth(2) // match at index (negative counts from the end)
198
+ await locator.count() // number of matching elements
199
+ await locator.all() // array of Locators, one per match
174
200
  ```
175
201
 
176
202
  **Queries:**
177
203
 
178
204
  ```typescript
205
+ await locator.exists() // boolean — present in the hierarchy (no wait)
179
206
  await locator.isVisible() // boolean
180
207
  await locator.isEnabled() // boolean
181
208
  await locator.isSelected() // boolean
@@ -183,6 +210,7 @@ await locator.isFocused() // boolean
183
210
  await locator.isChecked() // boolean
184
211
  await locator.getText() // waits for visibility first
185
212
  await locator.getValue() // raw value (e.g. text field content)
213
+ await locator.boundingBox() // { x, y, width, height }
186
214
  ```
187
215
 
188
216
  **Explicit waiting:**
@@ -207,6 +235,30 @@ const title = await screen.getByType('NavigationBar').getByType('StaticText').ge
207
235
 
208
236
  When chaining, child lookups use bounds-based containment: any element whose bounds fit within the parent's bounds is considered a child. This works correctly with mobilecli's flat element lists.
209
237
 
238
+ ### WebView (hybrid apps)
239
+
240
+ Apps that embed web content — Cordova, Capacitor, Ionic, or a raw `WKWebView` / Android `WebView` — expose a real DOM behind the native view. `screen.getByWebView()` bridges into that DOM and hands you a **Playwright-compatible** `Page`, so web content is driven with the exact Playwright API you already know.
241
+
242
+ ```typescript
243
+ // Find the web view and resolve its page (Playwright-style)
244
+ const webview = screen.getByWebView(); // optionally { testId: 'checkout-web' }
245
+ const page = await webview.page();
246
+
247
+ // From here it's the standard Playwright Page / Locator API
248
+ await page.goto('https://example.com/login');
249
+ await page.getByLabel('Email').fill('user@example.com');
250
+ await page.getByRole('button', { name: 'Sign In' }).click();
251
+
252
+ await expect(page).toHaveURL(/dashboard/);
253
+ await expect(page.getByText('Welcome')).toBeVisible();
254
+ ```
255
+
256
+ **Page** — navigation and lifecycle: `goto`, `reload`, `goBack`, `goForward`, `url`, `title`, `content`, `waitForURL`, `waitForLoadState`, `close`. Locator factories: `locator`, `getByRole`, `getByText`, `getByLabel`, `getByPlaceholder`, `getByTestId`, `getByAltText`, `getByTitle`.
257
+
258
+ **Web Locator** — actions: `click`, `fill`, `type`, `press`, `focus`, `hover`, `scrollIntoViewIfNeeded`. Queries: `getText`, `getValue`, `textContent`, `innerText`, `innerHTML`, `inputValue`, `getAttribute`, `boundingBox`, `isVisible`, `isHidden`, `isEnabled`, `isDisabled`, `isChecked`. Plus `waitFor`, `first`/`last`/`nth`, `count`, and `all`. Assertions (`expect`) work the same as on native locators.
259
+
260
+ `MobileWebViewPage` and `MobileWebViewLocator` are exported from `mobilewright` for advanced use (`Page` / `WebLocator` remain as back-compat aliases).
261
+
210
262
  ### Device
211
263
 
212
264
  Manages the connection lifecycle and exposes device/app-level controls.
@@ -216,12 +268,15 @@ Manages the connection lifecycle and exposes device/app-level controls.
216
268
  await device.setOrientation('landscape');
217
269
  const orientation = await device.getOrientation();
218
270
 
271
+ // Screen dimensions and pixel density: { width, height, scale }
272
+ const size = await device.screenSize();
273
+
219
274
  // URLs / deep links (goto is a Playwright-style alias for openUrl)
220
275
  await device.goto('myapp://settings');
221
276
  await device.openUrl('https://example.com');
222
277
 
223
278
  // App lifecycle
224
- await device.launchApp('com.example.app', { locale: 'fr_FR' }); // waits until app is in foreground
279
+ await device.launchApp('com.example.app', { locales: ['fr-FR'] }); // waits until app is in foreground
225
280
  await device.launchApp('com.example.app', { noWaitAfter: true }); // skip foreground wait
226
281
  await device.terminateApp('com.example.app');
227
282
  const apps = await device.listApps();
@@ -242,13 +297,24 @@ import { expect } from 'mobilewright';
242
297
 
243
298
  await expect(locator).toBeVisible();
244
299
  await expect(locator).not.toBeVisible();
300
+ await expect(locator).toBeHidden();
245
301
 
246
302
  await expect(locator).toBeEnabled();
247
- await expect(locator).not.toBeEnabled();
303
+ await expect(locator).toBeDisabled();
304
+
305
+ await expect(locator).toBeSelected();
306
+ await expect(locator).toBeFocused();
307
+ await expect(locator).toBeChecked();
248
308
 
249
309
  await expect(locator).toHaveText('Welcome back!');
250
310
  await expect(locator).toHaveText(/welcome/i);
251
311
  await expect(locator).toContainText('back');
312
+ await expect(locator).toBeEmpty(); // element has no text
313
+
314
+ await expect(locator).toHaveValue('user@example.com'); // text field / input value
315
+ await expect(locator).toHaveValue(/@example\.com$/);
316
+
317
+ await expect(locator).toHaveCount(3); // number of matching elements
252
318
 
253
319
  await expect(locator).toBeVisible({ timeout: 10_000 });
254
320
  ```
@@ -299,13 +365,44 @@ All options:
299
365
  | `bundleId` | `string` | App bundle ID (optional) |
300
366
  | `deviceId` | `string` | Explicit device UDID (optional) |
301
367
  | `deviceName` | `RegExp` | RegExp to match device name (optional) |
302
- | `timeout` | `number` | Global locator timeout in ms (optional) |
368
+ | `installApps` | `string \| string[]` | App paths (APK/IPA) to install before launching (optional) |
369
+ | `autoAppLaunch` | `boolean` | Automatically launch the app after connecting. Default: `true` |
370
+ | `viewTree` | `'on-failure' \| 'off'` | Attach the accessibility tree as JSON to the report on failure. Default: `'off'` |
371
+ | `timeout` | `number` | Per-test timeout in ms (optional) |
372
+ | `globalTimeout` | `number` | Hard cap on the entire test suite run in ms (optional) |
303
373
  | `testDir` | `string` | Directory to search for test files (optional) |
304
374
  | `testMatch` | `string \| RegExp \| Array` | Glob patterns for test files (optional) |
375
+ | `testIgnore` | `string \| RegExp \| Array` | Glob patterns for files to skip during discovery (optional) |
376
+ | `outputDir` | `string` | Output directory for test artifacts. Default: `test-results` |
305
377
  | `reporter` | `'list' \| 'html' \| 'json' \| 'junit' \| Array` | Reporter to use (optional) |
306
378
  | `retries` | `number` | Maximum retry count for flaky tests (optional) |
379
+ | `workers` | `number \| string` | Number of concurrent workers (optional) |
380
+ | `fullyParallel` | `boolean` | Run all tests in parallel. Default: `false` |
381
+ | `use` | `MobilewrightUseOptions` | Per-action defaults applied to all tests (optional) |
382
+ | `expect` | `MobilewrightExpectConfig` | Default options for `expect()` assertions (optional) |
383
+ | `globalSetup` | `string \| string[]` | Setup file(s) run once before all tests (optional) |
384
+ | `globalTeardown` | `string \| string[]` | Teardown file(s) run once after all tests (optional) |
307
385
  | `projects` | `MobilewrightProjectConfig[]` | Multi-device / multi-platform project matrix (optional) |
308
386
 
387
+ The `use` object holds per-action defaults shared by every test:
388
+
389
+ | `use` option | Type | Description |
390
+ |---|---|---|
391
+ | `platform` | `'ios' \| 'android'` | Platform for the run (optional) |
392
+ | `deviceName` | `RegExp` | RegExp to match device name (optional) |
393
+ | `bundleId` | `string` | App bundle ID (optional) |
394
+ | `installApps` | `string \| string[]` | App paths to install — overrides top-level `installApps` (optional) |
395
+ | `animations` | `'on' \| 'off'` | Toggle system animations on the device; left unchanged if omitted |
396
+ | `actionTimeout` | `number` | Default timeout for locator actions (tap, fill, …) in ms. Default: `5000` |
397
+ | `appLaunchTimeout` | `number` | Timeout waiting for the app to reach foreground after launch, in ms. Default: `20000` |
398
+ | `installTimeout` | `number` | Timeout for app installation in ms (optional) |
399
+
400
+ The `expect` object sets assertion defaults:
401
+
402
+ | `expect` option | Type | Description |
403
+ |---|---|---|
404
+ | `timeout` | `number` | Default timeout for assertions (`toBeVisible`, `toHaveText`, …) in ms. Default: `5000` |
405
+
309
406
  Config values are used as defaults — `LaunchOptions` passed to `ios.launch()` always take precedence.
310
407
 
311
408
  Mobilewright will use the first device that matches your configured criteria.
@@ -335,6 +432,26 @@ test('can sign in', async ({ device, screen, bundleId }) => {
335
432
 
336
433
  The `device` fixture connects once per worker (reading from `mobilewright.config.ts`) and calls `device.close()` after all tests complete. The `screen` fixture provides `device.screen` to each test, with automatic screenshot-on-failure and optional video recording.
337
434
 
435
+ **`test.use()` options** — override config per file (or per `test.describe` block):
436
+
437
+ | Option | Type | Description |
438
+ |---|---|---|
439
+ | `bundleId` | `string` | App bundle ID for these tests |
440
+ | `platform` | `'ios' \| 'android'` | Target platform |
441
+ | `deviceName` | `RegExp` | RegExp to match the device name |
442
+ | `installApps` | `string \| string[]` | App paths (APK/IPA) to install before the tests run |
443
+ | `autoAppLaunch` | `boolean` | Launch the app automatically before each test. Default: `true` |
444
+ | `viewTree` | `'on-failure' \| 'off'` | Attach the accessibility tree as JSON when a test fails. Default: `'off'` |
445
+ | `video` | `'on' \| 'retain-on-failure' \| 'off'` | Record video — always, only on failure, or never. Default: `'off'` |
446
+
447
+ ```typescript
448
+ test.use({
449
+ bundleId: 'com.example.myapp',
450
+ video: 'retain-on-failure',
451
+ viewTree: 'on-failure',
452
+ });
453
+ ```
454
+
338
455
  ## CLI
339
456
 
340
457
  ### `mobilewright init`
@@ -365,6 +482,41 @@ ID Name Platform Type
365
482
  5A5FCFCA-27EC-4D1B-B412-BAE629154EE0 iPhone 17 Pro ios simulator booted
366
483
  ```
367
484
 
485
+ ### `mobilewright inspect`
486
+
487
+ Open the Mobilewright Inspector — a browser-based UI showing a live screenshot of your connected device alongside every element and its best locator.
488
+
489
+ ```bash
490
+ npx mobilewright inspect
491
+ npx mobilewright inspect --port 4621 # use a specific port (default: 4621)
492
+ ```
493
+
494
+ The Inspector opens automatically in your browser. Select a device from the picker at the top, then click **Refresh** or enable **Auto refresh**. Click any row in the element list to highlight its bounding box on the screenshot. Elements that share a locator with another element get a `dup` badge.
495
+
496
+ Locator priority matches what mobilewright uses: `getByTestId` > `getByRole` > `getByLabel` > `getByText`.
497
+
498
+ ### `mobilewright screenshot`
499
+
500
+ Capture a screenshot of a connected device. Auto-starts mobilecli if it isn't running.
501
+
502
+ ```bash
503
+ npx mobilewright screenshot # saves screenshot.png
504
+ npx mobilewright screenshot -o home.png # custom output path
505
+ npx mobilewright screenshot -d <device-id> # target a specific device
506
+ npx mobilewright screenshot --url ws://host:12000 # remote mobilecli server
507
+ ```
508
+
509
+ ### `mobilewright install`
510
+
511
+ Install the mobilecli agent on a connected device.
512
+
513
+ ```bash
514
+ npx mobilewright install # install on the first device
515
+ npx mobilewright install -d <device-id> # target a specific device
516
+ npx mobilewright install --force # force reinstall
517
+ npx mobilewright install --provisioning-profile <p> # iOS provisioning profile
518
+ ```
519
+
368
520
  ### `mobilewright test`
369
521
 
370
522
  Run your tests. Auto-discovers `mobilewright.config.ts` in the current directory.
@@ -388,11 +540,20 @@ npx mobilewright show-report
388
540
  npx mobilewright show-report mobilewright-report/
389
541
  ```
390
542
 
391
- ## Run on real devices with mobile-use.com
543
+ ### `mobilewright merge-reports`
544
+
545
+ Merge blob reports from sharded/parallel CI runs into a single report.
546
+
547
+ ```bash
548
+ npx mobilewright merge-reports ./blob-reports # merge into an HTML report (default)
549
+ npx mobilewright merge-reports ./blob-reports --reporter json
550
+ ```
551
+
552
+ ## Run on real devices with Mobile Next Cloud
392
553
 
393
- Need real phones in the cloud? [mobile-use.com](https://mobile-use.com) gives you API access to hundreds of real Android and iOS devices. Your Mobilewright scripts run with zero modification — point your config at the mobile-use.com endpoint and go.
554
+ Need real phones in the cloud? [Mobile Next Cloud](https://mobilenext.ai) gives you API access to hundreds of real Android and iOS devices. Your Mobilewright scripts run with zero modification — point your config at the Mobile Next Cloud endpoint and go.
394
555
 
395
- mobile-use.com is the only device cloud with native Mobilewright support.
556
+ Mobile Next Cloud is the only device cloud with native Mobilewright support.
396
557
 
397
558
  ## Telemetry
398
559
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mobilewright/test",
3
- "version": "0.0.46",
3
+ "version": "0.0.47",
4
4
  "description": "Test fixtures for Mobilewright",
5
5
  "homepage": "https://mobilewright.dev",
6
6
  "license": "Apache-2.0",
@@ -29,10 +29,10 @@
29
29
  "dist"
30
30
  ],
31
31
  "dependencies": {
32
- "@mobilewright/core": "^0.0.46",
33
- "@mobilewright/protocol": "^0.0.46",
32
+ "@mobilewright/core": "^0.0.47",
33
+ "@mobilewright/protocol": "^0.0.47",
34
34
  "@playwright/test": "1.58.2",
35
35
  "debug": "^4.4.3",
36
- "mobilewright": "^0.0.46"
36
+ "mobilewright": "^0.0.47"
37
37
  }
38
38
  }