@unotest/mobile 0.8.3 → 0.9.1

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.
@@ -6,7 +6,7 @@ description: Write a new E2E test for the project under test using @unotest/mobi
6
6
  # Skill: write-e2e-test
7
7
 
8
8
  You are writing an end-to-end test using `@unotest/mobile` against an
9
- iOS React Native app. The test lives in `unotest/e2e/<name>.js` in the
9
+ iOS React Native app. The test lives in `unotest/e2e-mobile/<name>.js` in the
10
10
  project under test.
11
11
 
12
12
  **Default mode is verify-against-live-app.** A test that hasn't been
@@ -35,7 +35,7 @@ Five phases, in this order. Don't skip phases.
35
35
 
36
36
  ### Phase 0 — read context
37
37
 
38
- `unotest/e2e/_template/example.js` shows the exact DSL syntax (bare
38
+ `unotest/e2e-mobile/_template/example.js` shows the exact DSL syntax (bare
39
39
  top-level functions, no `import`/`export`/`async`/`await`/`const`/`let`/
40
40
  `var`/arrow-functions). Read it first. The DSL looks like JavaScript
41
41
  but is a frozen subset — when your default JS instincts disagree with
@@ -196,9 +196,12 @@ save_exploration_as_test { explorationId, scenarioName: "<name>" }
196
196
  with `// SKIPPED` comments.
197
197
  - File exists? Pass `overwrite: true`.
198
198
 
199
- The 3-line scenario header (`// id-...`, `// <title>`, `// #<color>`)
200
- is emitted automatically by the generator above the
201
- `function test_<name>()` line.
199
+ Every executable statement in a `test_*` body MUST live inside a
200
+ `step("human-readable intent", () => { ... })` block - the linter's
201
+ `validator:step-coverage` rule enforces it. The generator groups
202
+ recorded sections into `step(...)` blocks automatically; keep the
203
+ labels meaningful when editing (they're what an agent reads when
204
+ repairing a broken step).
202
205
 
203
206
  ### Phase 5 — reset state, then run_test
204
207
 
@@ -213,6 +216,10 @@ Via MCP — reinstall the app to wipe its sandbox AND auth tokens:
213
216
  app_install { path: "<APP_PATH from unotest/.env>", clean: true }
214
217
  ```
215
218
 
219
+ To just PREPARE devices (boot SIM_POOL + install APP_PATH, no wipe),
220
+ the viewer's App panel runs `npx unotest-mobile app-server` and holds
221
+ it; runs then find booted sims with the build installed.
222
+
216
223
  **Do NOT pass `erase: true`.** `erase: true` runs `simctl erase` which
217
224
  wipes the entire simulator including WebDriverAgent. `clean: true`
218
225
  alone is the right soft-reset — it now resets the keychain too, so
@@ -354,7 +361,7 @@ Time: `today()`, `daysFromNow(n)`, `nowMs()`.
354
361
 
355
362
  ## Helpers convention
356
363
 
357
- Reusable steps go in `unotest/e2e/_helpers/<group>.js`. Helper naming
364
+ Reusable steps go in `unotest/e2e-mobile/_helpers/<group>.js`. Helper naming
358
365
  is **snake_case** (`signin_as`, `seed_workout`). **Before recording an
359
366
  inline flow, check `_helpers/` for an existing helper** — `Glob` or
360
367
  `Read`. If `signin_as(email, password)` exists, the generated test
@@ -363,7 +370,7 @@ should call it instead of re-recording four steps. (After
363
370
  for the helper call.)
364
371
 
365
372
  ```
366
- unotest/e2e/_helpers/auth.js:
373
+ unotest/e2e-mobile/_helpers/auth.js:
367
374
  function signin_as(email, password) { ... }
368
375
  ```
369
376
 
@@ -394,13 +401,15 @@ npx unotest-mobile lint
394
401
  ```
395
402
 
396
403
  Lint codes to recognize:
397
- - E1/E2 — unknown function or unsupported statement (drift to plain JS).
398
- - E5wrong arg count for a built-in.
399
- - E6wrong arg TYPE / order, e.g. `swipe(getByTestId("x"), "up")` —
400
- argTypes are `("up"|..., selector)`.
401
- - E7missing 3-line header above `test_*`/`flow_*` (generator emits
402
- it; only fires if you hand-edited).
403
- - E8helper used but not defined in `_helpers/`.
404
+ - E2 — statement/expression outside the frozen subset (drift to plain JS).
405
+ - E3`setDevice()` with an unknown sim slot.
406
+ - E8duplicate helper name / shadowing a built-in.
407
+ - validator:unknown-function typo'd or undefined function name.
408
+ - validator:aritywrong arg count for a built-in.
409
+ - validator:arg-kind wrong arg TYPE / order, e.g.
410
+ `swipe(getByTestId("x"), "up")`argTypes are `("up"|..., selector)`.
411
+ - validator:step-coverage — a statement in a `test_*` body is not inside
412
+ `step("description", () => { ... })`. Wrap it.
404
413
 
405
414
  If lint flags `import`/`export`/`await`/`const` — re-read the DSL
406
415
  section above and rewrite.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,31 @@ All notable changes to `@unotest/mobile` will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [0.9.1] - 2026-06-14
8
+
9
+ ### Changed
10
+
11
+ - **`@unotest/studio` integration** — the mobile runner participates in the
12
+ desktop shell ("mobile in studio").
13
+ - Ecosystem version lockstep to `0.9.1`.
14
+
15
+ ## [0.9.0] - 2026-06-11
16
+
17
+ ### Added
18
+
19
+ - **Viewer integration.** The mobile runner now drives the localhost
20
+ `@unotest/viewer`: run artifacts (`steps.jsonl` / `runtime.json` / manifest),
21
+ live runtime state, the debug control plane (step / continue / abort / pause +
22
+ breakpoints), and snapshot / console / failure capture — so a mobile run is
23
+ inspectable in the same UI as web.
24
+
25
+ ### Changed
26
+
27
+ - **Workspace overlay + shared packages.** Moved into the `@unotest` pnpm
28
+ workspace overlay; adopts the shared `@unotest/core` (run / runtime writers,
29
+ debug-commands watcher) and the `@unotest/dsl` executor instead of forked
30
+ copies.
31
+
7
32
  ## [0.8.3] — 2026-05-18
8
33
 
9
34
  ### Added — README onboarding protocol for AI agents
package/README.md CHANGED
@@ -90,7 +90,7 @@ npx @unotest/mobile@latest e2e <test-name>
90
90
  ```
91
91
 
92
92
  That's it. You never edit DSL by hand — the agent writes the scenarios,
93
- you review the resulting `.js` files in `unotest/e2e/`.
93
+ you review the resulting `.js` files in `unotest/e2e-mobile/`.
94
94
 
95
95
  ## How it works
96
96
 
@@ -116,7 +116,7 @@ you review the resulting `.js` files in `unotest/e2e/`.
116
116
  returns a structured tree (roles, testID, text, bounds); we render it
117
117
  as a token-cheap text outline. Works on RN/Expo and on plain Swift
118
118
  apps too, as long as they expose accessibility.
119
- - **Tests are plain `.js` files in your repo.** `unotest/e2e/*.js`.
119
+ - **Tests are plain `.js` files in your repo.** `unotest/e2e-mobile/*.js`.
120
120
  Goes into git, into code review, into CI. No proprietary format, no
121
121
  binary blob.
122
122
  - **The DSL is a sandboxed JS subset.** Scenarios run in a vendored AST
@@ -150,8 +150,9 @@ agent already knows; you don't wire anything.
150
150
  | `npx @unotest/mobile@latest install <path>` | Install a `.app` on the configured sim; auto-detect bundle ID + permissions; `--update-env` persists. |
151
151
  | `npx @unotest/mobile@latest init` | Just bootstrap `unotest/` + `.mcp.json` without installing an app. |
152
152
  | `npx @unotest/mobile@latest doctor` | Re-check the environment (Xcode, sim, Node, WDA cache). |
153
- | `npx @unotest/mobile@latest e2e <name>` | Run `unotest/e2e/<name>.js`. |
153
+ | `npx @unotest/mobile@latest e2e <name>` | Run `unotest/e2e-mobile/<name>.js`. |
154
154
  | `npx @unotest/mobile@latest lint` | Static check of all scenarios + helpers. |
155
+ | `npx @unotest/mobile@latest viewer` | Start the local results viewer (HTTP+WS) with mobile as the active target; opens in your browser. |
155
156
  | `npx @unotest/mobile@latest` (no args) | Run as MCP stdio server (this is what Claude Code launches). |
156
157
 
157
158
  ## License
@@ -6,6 +6,7 @@
6
6
  // unotest-mobile doctor → re-run environment checks
7
7
  // unotest-mobile e2e <name> → run scenario unotest/e2e/<name>.js
8
8
  // unotest-mobile lint → lint scenarios + helpers
9
+ // unotest-mobile viewer → start the local results viewer (HTTP+WS)
9
10
  //
10
11
  // All entries are compiled by tsup to dist/ before publish; this dispatcher
11
12
  // just resolves the right one and execs it via plain node. No tsx at
@@ -41,6 +42,8 @@ if (sub === "--help" || sub === "-h") {
41
42
  ` install <path> Install a .app bundle on the configured simulator slot(s).\n` +
42
43
  ` e2e <name> Run scenario unotest/e2e/<name>.js.\n` +
43
44
  ` lint Static check of all scenarios + helpers.\n` +
45
+ ` viewer Start the local results viewer (HTTP+WS server).\n` +
46
+ ` app-server Boot SIM_POOL sims + install APP_PATH; hold until Ctrl-C.\n` +
44
47
  ` (no command) Run as MCP stdio server.\n\n` +
45
48
  `Flags:\n` +
46
49
  ` --version, -v Print package version.\n` +
@@ -70,6 +73,12 @@ function dispatch(sub) {
70
73
  return { entry: `${baseDir}/runner/cli${ext}`, forwardArgs: ["lint"] };
71
74
  case "e2e":
72
75
  return { entry: `${baseDir}/runner/cli${ext}`, forwardArgs: args.slice(1) };
76
+ case "viewer":
77
+ return { entry: `${baseDir}/runner/cli${ext}`, forwardArgs: ["viewer"] };
78
+ case "app-server":
79
+ // Device holder (M-16): boot SIM_POOL + install APP_PATH, hold
80
+ // until SIGTERM. Spawned by the viewer's App panel.
81
+ return { entry: `${baseDir}/runner/cli${ext}`, forwardArgs: ["app-server"] };
73
82
  default:
74
83
  // No-arg invocation → MCP server (current claude mcp add behavior).
75
84
  // Unknown subcommands also land here — print a hint to stderr so
@@ -0,0 +1,12 @@
1
+ import { IDslLanguageService, DslVocabEntry, DslValidateContext, DslDiagnostic } from '@unotest/protocol';
2
+
3
+ declare class MobileDslLanguageService implements IDslLanguageService {
4
+ private readonly vocab;
5
+ private readonly registry;
6
+ constructor();
7
+ getVocab(): DslVocabEntry[];
8
+ validate(source: string, ctx?: DslValidateContext): DslDiagnostic[];
9
+ }
10
+ declare const mobileDslLanguageService: MobileDslLanguageService;
11
+
12
+ export { MobileDslLanguageService, mobileDslLanguageService as default, mobileDslLanguageService };