@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.
- package/README.md +177 -16
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Mobilewright
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/mobilewright)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](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
|
|
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 [
|
|
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-
|
|
97
|
-
| `@mobilewright/
|
|
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
|
|
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 (
|
|
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
|
|
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', {
|
|
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).
|
|
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
|
-
| `
|
|
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
|
-
|
|
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? [
|
|
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
|
-
|
|
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.
|
|
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.
|
|
33
|
-
"@mobilewright/protocol": "^0.0.
|
|
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.
|
|
36
|
+
"mobilewright": "^0.0.47"
|
|
37
37
|
}
|
|
38
38
|
}
|