@unotest/mobile 0.1.1 → 0.8.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.
@@ -155,4 +155,3 @@ ${warnings} warning(s). Tests may still work, but review them.`);
155
155
  }
156
156
  __name(main, "main");
157
157
  process.exit(main());
158
- //# sourceMappingURL=doctor.js.map
@@ -1,7 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
- // src/runner/init.ts
4
+ // src/runner/init/run.ts
5
5
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
6
6
  import { dirname, join, relative, resolve } from "path";
7
7
  import { fileURLToPath } from "url";
@@ -137,6 +137,37 @@ function test_smoke_welcome() {
137
137
  setDevice("A");
138
138
  appLaunch(true);
139
139
  }
140
+ `,
141
+ // Canonical syntax reference for AI agents writing new scenarios. The
142
+ // sentinel banner up top is the strongest single-line signal against the
143
+ // default "this is modern JS" assumption that drives agents to write
144
+ // import/export/async/await. Skill references this file by path; eval
145
+ // harness seeds the same content into temp workdirs.
146
+ e2eTemplateExample: `// unotest-mobile JS-DSL \u2014 NOT Node.js.
147
+ //
148
+ // Bare top-level functions only. NO \`import\`/\`export\`/\`async\`/\`await\`/
149
+ // \`const\`/\`let\`/\`var\`/arrow-functions. Assignments are bare-name: \`x = expr;\`.
150
+ // Member access (\`obj.field\`) and object literals (\`{key: value}\`) are
151
+ // forbidden \u2014 payloads are passed as JSON strings.
152
+ //
153
+ // After editing any scenario in this directory:
154
+ // npx unotest-mobile lint
155
+ //
156
+ // Below: minimal valid example. Copy its shape when writing new scenarios.
157
+
158
+ // id-example-001
159
+ // Example sign-in flow
160
+ // #4a90e2
161
+ function test_example() {
162
+ setDevice("A");
163
+ appLaunch(true);
164
+
165
+ type(getByTestId("email-input"), "user@example.com");
166
+ type(getByTestId("password-input"), "secret123");
167
+ tap(getByTestId("signin-button"));
168
+
169
+ waitFor(getByTestId("home-screen"), 15000);
170
+ }
140
171
  `,
141
172
  scenarioTemplate: `// id-<your-scenario-id>
142
173
  // <one-line description>
@@ -189,54 +220,68 @@ Inside Claude Code, prefer the MCP \`run_test\` tool with
189
220
  \`pauseOnFailure: true\` \u2014 it pauses on the failed step so you can
190
221
  \`inspect_runtime\`, fix the scenario, and \`resume\`.
191
222
  `,
192
- envExample: `# unotest-mobile \u2014 copied to unotest/.env by \`init\`. Fill in below.
193
-
194
- # --- App under test --------------------------------------------------------
195
- APP_BUNDLE_ID=com.example.myapp
196
- APP_URL_SCHEME=myapp
197
- INVITE_DEEPLINK_PREFIX=myapp://invite/
198
-
199
- # --- Backend ---------------------------------------------------------------
200
- API_BASE_URL=http://localhost:3000/api
201
- # Optional. Absolute path used as default cwd for the \`shell(...)\` DSL
202
- # primitive. Set if your scenarios shell out to project-local CLIs
203
- # (pnpm cli, rails runner, etc.) that must run from the project root.
204
- PROJECT_ROOT=
223
+ envExample: `# unotest-mobile \u2014 copied to unotest/.env by \`init\`. Fill in only what
224
+ # your scenarios actually use. Most variables are optional.
205
225
 
206
- # --- Database --------------------------------------------------------------
207
- # Pick the driver matching your backend. Install peer-deps only as needed:
208
- # npm i -D pg @types/pg # for postgresql://
209
- # npm i -D mysql2 # for mysql://
210
- # (SQLite via better-sqlite3 is bundled \u2014 no separate install.)
226
+ # --- Simulators (required for any UI scenario) -----------------------------
227
+ # Names must match \`xcrun simctl list devices\`. SIM_POOL controls which
228
+ # slots are active; each slot in the pool needs the matching SIM_<slot>_NAME.
211
229
  #
212
- # Docker-compose Postgres/MySQL \u2014 ensure host port is exposed
213
- # (\`ports: ["5432:5432"]\`). Native clients connect from the host, not from
214
- # inside the compose network.
215
- DATABASE_URL=postgresql://postgres:postgres@localhost:5432/myapp_test
230
+ # If you have multiple sims with the same name across iOS versions,
231
+ # disambiguate via "<name> @ <runtime>" \u2014 runtime is a substring match,
232
+ # so "iOS 17" or "iOS 17.5" both work.
233
+ # SIM_A_NAME= # pick interactively on first \`install\`
234
+ # SIM_A_NAME=iPhone 16 @ iOS 17.5 # pin to a specific runtime
235
+ # SIM_B_NAME= # only needed if SIM_POOL includes "B"
236
+ # SIM_POOL=A,B # default \u2014 drop "B" if you only need one sim
237
+
238
+ # --- App under test (required if scenarios call appLaunch / openDeeplink) --
239
+ APP_BUNDLE_ID=com.example.myapp
240
+ # APP_URL_SCHEME=myapp # only used by Expo dev-client recovery flow
241
+ # Optional. Path to a built .app bundle. When set, \`unotest-mobile install\`
242
+ # (and the \`app_install\` MCP tool) can be invoked without an explicit path.
243
+ # Useful for repeated installs after each app rebuild.
244
+ # APP_PATH=./build-sim/Build/Products/Release-iphonesimulator/MyApp.app
245
+ # Comma-separated \`simctl privacy\` services to auto-grant on \`install --clean\`.
246
+ # Populated by \`unotest-mobile install --update-env\` from your app's
247
+ # Info.plist NS*UsageDescription keys. Pre-empts the iOS permission dialog
248
+ # on first launch \u2014 those dialogs live in SpringBoard, not in the app's
249
+ # a11y tree, and would otherwise block your scenarios.
250
+ # APP_PERMISSIONS=location,motion
216
251
 
217
- # --- Simulators ------------------------------------------------------------
218
- # Names must match \`xcrun simctl list devices\`.
219
- SIM_A_NAME=iPhone 15
220
- SIM_B_NAME=iPhone 15 Plus
221
- SIM_POOL=A,B
252
+ # --- Backend (optional \u2014 only required if scenarios use apiCall / db) ------
253
+ # API_BASE_URL=http://localhost:3000/api
254
+ # DATABASE_URL=postgresql://postgres:postgres@localhost:5432/myapp_test
255
+ # Drivers (install peer-deps as needed):
256
+ # npm i -D pg @types/pg # for postgresql://
257
+ # npm i -D mysql2 # for mysql://
258
+ # SQLite via better-sqlite3 is bundled \u2014 no separate install.
259
+ # Docker-compose Postgres/MySQL: ensure host port is exposed
260
+ # (\`ports: ["5432:5432"]\`). Native clients connect from the host, not
261
+ # from inside the compose network.
222
262
 
223
- # --- Dev tooling -----------------------------------------------------------
224
- METRO_URL=http://localhost:8081
225
- # Expo dev-client: auto-open dev-client deep link after a clean launch.
226
- EXPO_DEV_CLIENT=false
227
- SESSION_LOG_PATH=sessions/current.jsonl
228
- ARTIFACTS_DIR=artifacts
263
+ # --- Optional: shell() default cwd -----------------------------------------
264
+ # Absolute path used as default cwd for the \`shell(...)\` DSL primitive.
265
+ # When unset, shell commands run from process.cwd(). Set to your project
266
+ # root if your scenarios shell out to project-local CLIs that must run
267
+ # from there.
268
+ # PROJECT_ROOT=
229
269
 
230
270
  # --- WebDriverAgent --------------------------------------------------------
231
271
  # Per-slot WDA ports. Each slot in SIM_POOL needs one. Format: "slot=port".
232
272
  WDA_PORTS=A=8100,B=8101
233
- WDA_DEFAULT_ACTION_WAIT_MS=2000
234
- WDA_DEFAULT_WAITFOR_TIMEOUT_MS=10000
273
+ # WDA_DEFAULT_ACTION_WAIT_MS=2000
274
+ # WDA_DEFAULT_WAITFOR_TIMEOUT_MS=10000
275
+
276
+ # --- Artifacts / sessions / paused-runtime TTL -----------------------------
277
+ # Defaults are sensible \u2014 uncomment only to override.
278
+ # SESSION_LOG_PATH=unotest/sessions/current.jsonl
279
+ # ARTIFACTS_DIR=unotest/artifacts
280
+ # PAUSED_RUNTIME_TTL_MS=1800000 # 30 min before paused-on-failure auto-abort
235
281
 
236
- # --- D-17 paused-runtime TTL ----------------------------------------------
237
- # How long a paused-on-failure runtime stays alive before auto-abort (ms).
238
- # Default 30 minutes. Inspecting the runtime resets the timer.
239
- PAUSED_RUNTIME_TTL_MS=1800000
282
+ # --- Expo dev-client (reserved for future use) -----------------------------
283
+ # METRO_URL=http://localhost:8081
284
+ # EXPO_DEV_CLIENT=false
240
285
  `,
241
286
  gitignoreLines: [
242
287
  "",
@@ -245,10 +290,20 @@ PAUSED_RUNTIME_TTL_MS=1800000
245
290
  "unotest/artifacts/",
246
291
  "unotest/sessions/"
247
292
  ],
248
- mcpServerEntry: {
293
+ /**
294
+ * Build the `.mcp.json` entry for this package, pinned to the supplied
295
+ * version. Caller (`runInit`) reads its own `package.json:version` and
296
+ * passes it in, so the entry written into the consumer's project
297
+ * always matches the `init`-running copy. Pinning side-steps the
298
+ * `npx` stale-global / stale-cache ambush: a bare `@unotest/mobile`
299
+ * arg lets `npx` resolve to whatever globally-installed (often very
300
+ * old) copy a developer happens to have, which then crashes at
301
+ * startup against the current env schema.
302
+ */
303
+ mcpServerEntry: /* @__PURE__ */ __name((version) => ({
249
304
  command: "npx",
250
- args: ["-y", "@unotest/mobile"]
251
- }
305
+ args: ["-y", `@unotest/mobile@${version}`]
306
+ }), "mcpServerEntry")
252
307
  };
253
308
 
254
309
  // src/runner/init/mcp-config-merger.ts
@@ -314,16 +369,16 @@ function appendUniqueLines(existingContent, linesToAdd) {
314
369
  }
315
370
  __name(appendUniqueLines, "appendUniqueLines");
316
371
 
317
- // src/runner/init.ts
372
+ // src/runner/init/run.ts
318
373
  var here = dirname(fileURLToPath(import.meta.url));
319
374
  var packageRoot = resolve(here, "..", "..");
320
- function parseArgs(argv) {
375
+ function parseInitArgs(argv) {
321
376
  return {
322
377
  force: argv.includes("--force"),
323
378
  allowNonMacos: argv.includes("--allow-non-macos")
324
379
  };
325
380
  }
326
- __name(parseArgs, "parseArgs");
381
+ __name(parseInitArgs, "parseInitArgs");
327
382
  function symbol(severity) {
328
383
  return severity === "ok" ? "\u2713" : severity === "warning" ? "\u26A0" : "\u2717";
329
384
  }
@@ -346,8 +401,22 @@ function readPackageFile(relativePath) {
346
401
  return readFileSync(abs, "utf8");
347
402
  }
348
403
  __name(readPackageFile, "readPackageFile");
349
- function main() {
350
- const opts = parseArgs(process.argv.slice(2));
404
+ function readOwnVersion() {
405
+ const pkgRaw = readPackageFile("package.json");
406
+ if (pkgRaw === null) {
407
+ throw new Error(
408
+ `package.json missing at ${packageRoot}. This is a packaging bug \u2014 reinstall \`@unotest/mobile\` or report the issue.`
409
+ );
410
+ }
411
+ const parsed = JSON.parse(pkgRaw);
412
+ if (typeof parsed.version !== "string" || parsed.version.length === 0) {
413
+ throw new Error(`package.json:version is not a non-empty string`);
414
+ }
415
+ return parsed.version;
416
+ }
417
+ __name(readOwnVersion, "readOwnVersion");
418
+ function runInit(argv = process.argv.slice(2)) {
419
+ const opts = parseInitArgs(argv);
351
420
  const target = process.cwd();
352
421
  console.log("unotest-mobile init \u2014 bootstrapping project\n");
353
422
  console.log("Environment:");
@@ -365,6 +434,7 @@ function main() {
365
434
  console.log("\nFiles:");
366
435
  const summary = [];
367
436
  ensureDir(join(target, "unotest/e2e/_helpers"));
437
+ ensureDir(join(target, "unotest/e2e/_template"));
368
438
  summary.push({
369
439
  path: "unotest/e2e/smoke-welcome.js",
370
440
  status: writeIfNeeded(join(target, "unotest/e2e/smoke-welcome.js"), templates.smokeWelcome, opts.force)
@@ -373,6 +443,14 @@ function main() {
373
443
  path: "unotest/e2e/_template.js",
374
444
  status: writeIfNeeded(join(target, "unotest/e2e/_template.js"), templates.scenarioTemplate, opts.force)
375
445
  });
446
+ summary.push({
447
+ path: "unotest/e2e/_template/example.js",
448
+ status: writeIfNeeded(
449
+ join(target, "unotest/e2e/_template/example.js"),
450
+ templates.e2eTemplateExample,
451
+ opts.force
452
+ )
453
+ });
376
454
  summary.push({
377
455
  path: "unotest/AGENTS.md",
378
456
  status: writeIfNeeded(join(target, "unotest/AGENTS.md"), templates.agentsMd, opts.force)
@@ -392,10 +470,7 @@ function main() {
392
470
  const merge = mergeMcpConfig(
393
471
  existing,
394
472
  "unotest-mobile",
395
- {
396
- command: templates.mcpServerEntry.command,
397
- args: [...templates.mcpServerEntry.args]
398
- },
473
+ templates.mcpServerEntry(readOwnVersion()),
399
474
  { force: opts.force }
400
475
  );
401
476
  if (merge.action !== "already-present") {
@@ -448,6 +523,7 @@ Re-check environment anytime: \`npx @unotest/mobile doctor\`
448
523
  `);
449
524
  return 0;
450
525
  }
451
- __name(main, "main");
452
- process.exit(main());
453
- //# sourceMappingURL=init.js.map
526
+ __name(runInit, "runInit");
527
+
528
+ // src/runner/init.ts
529
+ process.exit(runInit());