@vostride/agent-qa-ios 0.2.2 → 0.2.4

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/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { PlatformAdapter, PlatformConfig, LiveStreamCapability, ObserveOptions, ActionsFor, ActionResult, ConsoleLogEntry } from '@vostride/agent-qa-runner';
1
+ import { PlatformAdapter, PlatformConfig, LiveStreamCapability, ActionsFor, ActionResult, ConsoleLogEntry } from '@vostride/agent-qa-runner';
2
2
  import { ScreenState } from '@vostride/agent-qa-schema';
3
3
 
4
4
  declare class IOSPlatformAdapter implements PlatformAdapter<'ios'> {
@@ -21,15 +21,8 @@ declare class IOSPlatformAdapter implements PlatformAdapter<'ios'> {
21
21
  private snapshotDepthWarned;
22
22
  setup(config: PlatformConfig): Promise<void>;
23
23
  cleanup(): Promise<void>;
24
- /**
25
- * 428 D-11: the labelled 1s snapshot preview, over `captureScreenshot()` — `takeScreenshot()`
26
- * and nothing else. Never `screenshot()`: that one switches the Appium context, and a tick
27
- * landing between a step's context switch and its command would run the command in the wrong
28
- * context. Declared before the driver exists (a capture then answers `undefined`), so the
29
- * composition's selection never falls through to `screenshot()`.
30
- */
31
24
  get liveStream(): LiveStreamCapability;
32
- observe(_options?: ObserveOptions): Promise<ScreenState>;
25
+ observe(): Promise<ScreenState>;
33
26
  execute(action: ActionsFor<'ios'>): Promise<ActionResult>;
34
27
  screenshot(): Promise<Buffer | undefined>;
35
28
  pollDeviceLogs(): Promise<void>;
@@ -66,60 +59,12 @@ declare class IOSPlatformAdapter implements PlatformAdapter<'ios'> {
66
59
  private readIOSPlaceholder;
67
60
  /** Read a focused element's `value` attribute; undefined when nothing is readable. */
68
61
  private readIOSValue;
69
- /**
70
- * EXEC-01 iOS fill compound (D-05..D-08). From the SAME focused-element handle: (append) capture
71
- * the prior value FIRST via getAttribute('value') then addValue, or (replace) setValue; RE-READ
72
- * via getAttribute('value') on that same handle; verdict via compareFillReread. Secure fields
73
- * (nativeType XCUIElementTypeSecureTextField) take the non-empty-only lane — the comparator yields
74
- * length sentinels so no raw value enters the result (T-359-02); bullet masking is tolerated
75
- * (A3/Pitfall 7 — never a length compare). On a passing re-read AND submit, a return/newline is
76
- * delivered AFTER the re-read (D-07). Single typing attempt; a mismatch is success:false + §7.1.
77
- *
78
- * Append (437 FA-1, owner 2026-10-08): the XCUITest `value` attribute often lags after addValue,
79
- * so a re-read equal to the prior cannot show the appended text. That fill is recorded unverified:
80
- * never exact, and never a mismatch. Every fill records the shared verdict as `fillVerdict`.
81
- * An empty field reads as its placeholder, so an append prior equal to it counts as empty.
82
- *
83
- * Return in a multi-line field adds a new line instead of submitting, so a multi-line submit is
84
- * never sent and is recorded as such.
85
- *
86
- * An unreadable re-read is unverified and does not stop a requested Return, as on Android (D-09,
87
- * D-10; owner 2026-10-09), but focus is confirmed once first and a Return is held as focus-lost
88
- * when the field no longer has it (444.1 D-10).
89
- */
90
62
  private fillIOSCompound;
91
- /**
92
- * EXEC-02 platform primitives injected into the shared runner scroll loops (D-02/D-03). The swipe
93
- * is a page-sized directional gesture (inverted to the finger direction) that reports unknown
94
- * movement; nativeSearchScroll drives
95
- * the XCUITest `mobile: scroll` predicateString rung over an injection-escaped target; readInterimTree
96
- * is a light page-source parse — NO refMap mint, NO screenshot, lastRefs untouched.
97
- */
98
63
  private buildIOSScrollPrimitives;
99
- /**
100
- * EXEC-03 openApp (D-10/D-12, Pitfall 5). Resolve a human name to a bundle id (or validate an
101
- * explicit bundleId) over the session-cached registry, then launch via activateApp. On real devices
102
- * `mobile: listApps` enumerates the registry; on simulators it throws, leaving the registry
103
- * unavailable — the appName lane then mints a structured §7.1 error directing to an explicit
104
- * bundleId, and the bundleId lane validates without the registry (isAppInstalled / activateApp
105
- * error conversion, A2). Never a best-guess launch (T-359-04); an invalid bundle never succeeds.
106
- */
107
64
  private openApp;
108
- /**
109
- * Explicit `{bundleId}` launch (D-12, A2). With a registry, validate membership; without one
110
- * (simulator) validate via `driver.isAppInstalled` when the driver exposes it, otherwise attempt
111
- * `activateApp` and convert a driver rejection into the structured §7.1 error. An invalid bundle
112
- * NEVER returns success:true.
113
- */
114
65
  private openByBundleId;
115
66
  /** Return the cached registry, enumerating once on first use. Failed enumeration returns null (uncached). */
116
67
  private ensureAppRegistry;
117
- /**
118
- * Enumerate installed apps through the driver only (ownership contract — webdriverio only, no
119
- * child_process/simctl). `mobile: listApps` maps bundle ids → AppRegistryEntry with CFBundleName
120
- * labels when present (D-10 — no per-app round-trips). It throws on simulators (Pitfall 5) ⇒ null,
121
- * leaving enumeration unavailable WITHOUT crashing.
122
- */
123
68
  private enumerateApps;
124
69
  /** Coerce a `mobile: listApps` result (bundle-id-keyed map, or an array of entries) into the registry. */
125
70
  private mapListApps;
@@ -132,11 +77,6 @@ interface IOSAdapterConfig extends PlatformConfig {
132
77
  udid?: string;
133
78
  }
134
79
 
135
- declare function createIOSSession(config: IOSAdapterConfig, farmSession?: {
136
- hostname: string;
137
- port: number;
138
- path: string;
139
- capabilities: Record<string, unknown>;
140
- }): Promise<WebdriverIO.Browser>;
80
+ declare function createIOSSession(config: IOSAdapterConfig): Promise<WebdriverIO.Browser>;
141
81
 
142
82
  export { type IOSAdapterConfig, IOSPlatformAdapter, createIOSSession };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { PlatformAdapter, PlatformConfig, LiveStreamCapability, ObserveOptions, ActionsFor, ActionResult, ConsoleLogEntry } from '@vostride/agent-qa-runner';
1
+ import { PlatformAdapter, PlatformConfig, LiveStreamCapability, ActionsFor, ActionResult, ConsoleLogEntry } from '@vostride/agent-qa-runner';
2
2
  import { ScreenState } from '@vostride/agent-qa-schema';
3
3
 
4
4
  declare class IOSPlatformAdapter implements PlatformAdapter<'ios'> {
@@ -21,15 +21,8 @@ declare class IOSPlatformAdapter implements PlatformAdapter<'ios'> {
21
21
  private snapshotDepthWarned;
22
22
  setup(config: PlatformConfig): Promise<void>;
23
23
  cleanup(): Promise<void>;
24
- /**
25
- * 428 D-11: the labelled 1s snapshot preview, over `captureScreenshot()` — `takeScreenshot()`
26
- * and nothing else. Never `screenshot()`: that one switches the Appium context, and a tick
27
- * landing between a step's context switch and its command would run the command in the wrong
28
- * context. Declared before the driver exists (a capture then answers `undefined`), so the
29
- * composition's selection never falls through to `screenshot()`.
30
- */
31
24
  get liveStream(): LiveStreamCapability;
32
- observe(_options?: ObserveOptions): Promise<ScreenState>;
25
+ observe(): Promise<ScreenState>;
33
26
  execute(action: ActionsFor<'ios'>): Promise<ActionResult>;
34
27
  screenshot(): Promise<Buffer | undefined>;
35
28
  pollDeviceLogs(): Promise<void>;
@@ -66,60 +59,12 @@ declare class IOSPlatformAdapter implements PlatformAdapter<'ios'> {
66
59
  private readIOSPlaceholder;
67
60
  /** Read a focused element's `value` attribute; undefined when nothing is readable. */
68
61
  private readIOSValue;
69
- /**
70
- * EXEC-01 iOS fill compound (D-05..D-08). From the SAME focused-element handle: (append) capture
71
- * the prior value FIRST via getAttribute('value') then addValue, or (replace) setValue; RE-READ
72
- * via getAttribute('value') on that same handle; verdict via compareFillReread. Secure fields
73
- * (nativeType XCUIElementTypeSecureTextField) take the non-empty-only lane — the comparator yields
74
- * length sentinels so no raw value enters the result (T-359-02); bullet masking is tolerated
75
- * (A3/Pitfall 7 — never a length compare). On a passing re-read AND submit, a return/newline is
76
- * delivered AFTER the re-read (D-07). Single typing attempt; a mismatch is success:false + §7.1.
77
- *
78
- * Append (437 FA-1, owner 2026-10-08): the XCUITest `value` attribute often lags after addValue,
79
- * so a re-read equal to the prior cannot show the appended text. That fill is recorded unverified:
80
- * never exact, and never a mismatch. Every fill records the shared verdict as `fillVerdict`.
81
- * An empty field reads as its placeholder, so an append prior equal to it counts as empty.
82
- *
83
- * Return in a multi-line field adds a new line instead of submitting, so a multi-line submit is
84
- * never sent and is recorded as such.
85
- *
86
- * An unreadable re-read is unverified and does not stop a requested Return, as on Android (D-09,
87
- * D-10; owner 2026-10-09), but focus is confirmed once first and a Return is held as focus-lost
88
- * when the field no longer has it (444.1 D-10).
89
- */
90
62
  private fillIOSCompound;
91
- /**
92
- * EXEC-02 platform primitives injected into the shared runner scroll loops (D-02/D-03). The swipe
93
- * is a page-sized directional gesture (inverted to the finger direction) that reports unknown
94
- * movement; nativeSearchScroll drives
95
- * the XCUITest `mobile: scroll` predicateString rung over an injection-escaped target; readInterimTree
96
- * is a light page-source parse — NO refMap mint, NO screenshot, lastRefs untouched.
97
- */
98
63
  private buildIOSScrollPrimitives;
99
- /**
100
- * EXEC-03 openApp (D-10/D-12, Pitfall 5). Resolve a human name to a bundle id (or validate an
101
- * explicit bundleId) over the session-cached registry, then launch via activateApp. On real devices
102
- * `mobile: listApps` enumerates the registry; on simulators it throws, leaving the registry
103
- * unavailable — the appName lane then mints a structured §7.1 error directing to an explicit
104
- * bundleId, and the bundleId lane validates without the registry (isAppInstalled / activateApp
105
- * error conversion, A2). Never a best-guess launch (T-359-04); an invalid bundle never succeeds.
106
- */
107
64
  private openApp;
108
- /**
109
- * Explicit `{bundleId}` launch (D-12, A2). With a registry, validate membership; without one
110
- * (simulator) validate via `driver.isAppInstalled` when the driver exposes it, otherwise attempt
111
- * `activateApp` and convert a driver rejection into the structured §7.1 error. An invalid bundle
112
- * NEVER returns success:true.
113
- */
114
65
  private openByBundleId;
115
66
  /** Return the cached registry, enumerating once on first use. Failed enumeration returns null (uncached). */
116
67
  private ensureAppRegistry;
117
- /**
118
- * Enumerate installed apps through the driver only (ownership contract — webdriverio only, no
119
- * child_process/simctl). `mobile: listApps` maps bundle ids → AppRegistryEntry with CFBundleName
120
- * labels when present (D-10 — no per-app round-trips). It throws on simulators (Pitfall 5) ⇒ null,
121
- * leaving enumeration unavailable WITHOUT crashing.
122
- */
123
68
  private enumerateApps;
124
69
  /** Coerce a `mobile: listApps` result (bundle-id-keyed map, or an array of entries) into the registry. */
125
70
  private mapListApps;
@@ -132,11 +77,6 @@ interface IOSAdapterConfig extends PlatformConfig {
132
77
  udid?: string;
133
78
  }
134
79
 
135
- declare function createIOSSession(config: IOSAdapterConfig, farmSession?: {
136
- hostname: string;
137
- port: number;
138
- path: string;
139
- capabilities: Record<string, unknown>;
140
- }): Promise<WebdriverIO.Browser>;
80
+ declare function createIOSSession(config: IOSAdapterConfig): Promise<WebdriverIO.Browser>;
141
81
 
142
82
  export { type IOSAdapterConfig, IOSPlatformAdapter, createIOSSession };
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  deriveMobileScroll,
21
21
  IOS_EDITABLE_TYPES,
22
22
  parseMobileSource,
23
+ printRunText,
23
24
  warnIfOutOfBounds
24
25
  } from "@vostride/agent-qa-core";
25
26
  import { alignToWindow } from "@vostride/agent-qa-runner-local";
@@ -36,10 +37,10 @@ function positiveInteger(value) {
36
37
  const parsed = Number(value.trim());
37
38
  return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
38
39
  }
39
- function resolveIOSSnapshotDepth(farmCapabilities) {
40
- return positiveInteger(farmCapabilities?.[IOS_SNAPSHOT_DEPTH_CAPABILITY]) ?? positiveInteger(process.env[IOS_SNAPSHOT_DEPTH_ENV]) ?? IOS_SNAPSHOT_MAX_DEPTH;
40
+ function resolveIOSSnapshotDepth() {
41
+ return positiveInteger(process.env[IOS_SNAPSHOT_DEPTH_ENV]) ?? IOS_SNAPSHOT_MAX_DEPTH;
41
42
  }
42
- async function createIOSSession(config, farmSession) {
43
+ async function createIOSSession(config) {
43
44
  const match = config.device?.match ?? {};
44
45
  const appiumUrl = process.env.AGENT_QA_APPIUM_URL ?? config.appiumUrl ?? "http://localhost:4723";
45
46
  const url = new URL(appiumUrl);
@@ -65,17 +66,6 @@ async function createIOSSession(config, farmSession) {
65
66
  delete capabilities[key];
66
67
  }
67
68
  }
68
- if (farmSession) {
69
- Object.assign(capabilities, farmSession.capabilities);
70
- return remote({
71
- protocol: "https",
72
- hostname: farmSession.hostname,
73
- port: farmSession.port,
74
- path: farmSession.path,
75
- logLevel: config.verbose ? "info" : "silent",
76
- capabilities
77
- });
78
- }
79
69
  return remote({
80
70
  hostname: url.hostname,
81
71
  port: parseInt(url.port || "4723", 10),
@@ -124,15 +114,6 @@ function firstErrorLine(err) {
124
114
  return (err instanceof Error ? err.message : String(err)).split("\n")[0];
125
115
  }
126
116
  var IOS_TAP_HOLD_MS = 100;
127
- async function safePerform(chain) {
128
- try {
129
- await chain.perform();
130
- } catch (err) {
131
- const msg = err instanceof Error ? err.message : String(err);
132
- if (msg.includes("actions") && msg.includes("DELETE")) return;
133
- throw err;
134
- }
135
- }
136
117
  var IOS_SYSLOG_REGEX = /^.*?\s+(\S+)\[(\d+)\].*?:\s+(.*)/;
137
118
  var IOS_APPIUM_LOG_REGEX = /^\S+\s+\S+\s+\[(\w+)\]\s+\[(\S+)\]\s+(.*)/;
138
119
  var IOS_LEVEL_MAP = {
@@ -278,8 +259,7 @@ var IOSPlatformAdapter = class {
278
259
  consoleDrainIndex = 0;
279
260
  captureConsole = true;
280
261
  bundleId = null;
281
- // EXEC-03 session-cached app registry (D-10/D-12): enumerated once per session. null = not yet
282
- // enumerated OR enumeration unavailable (iOS simulator — mobile: listApps throws). Reset in cleanup().
262
+ // `null` means not yet enumerated, or unavailable because simulators throw on `listApps`.
283
263
  appRegistry = null;
284
264
  sessionSnapshotDepth = IOS_SNAPSHOT_MAX_DEPTH;
285
265
  snapshotDepth = IOS_SNAPSHOT_MAX_DEPTH;
@@ -289,11 +269,11 @@ var IOSPlatformAdapter = class {
289
269
  this.config = config;
290
270
  this.captureConsole = config.logCapture?.console !== false;
291
271
  this.bundleId = this.config.bundleId ?? config.device?.match?.bundleId ?? null;
292
- this.sessionSnapshotDepth = resolveIOSSnapshotDepth(config.farmSession?.capabilities);
272
+ this.sessionSnapshotDepth = resolveIOSSnapshotDepth();
293
273
  this.snapshotDepth = this.sessionSnapshotDepth;
294
274
  this.readsAtLoweredDepth = 0;
295
275
  this.snapshotDepthWarned = false;
296
- this.driver = await createIOSSession(this.config, config.farmSession);
276
+ this.driver = await createIOSSession(this.config);
297
277
  const bundleId = this.bundleId;
298
278
  if (bundleId) {
299
279
  try {
@@ -336,18 +316,12 @@ var IOSPlatformAdapter = class {
336
316
  this.readsAtLoweredDepth = 0;
337
317
  this.snapshotDepthWarned = false;
338
318
  }
339
- /**
340
- * 428 D-11: the labelled 1s snapshot preview, over `captureScreenshot()` — `takeScreenshot()`
341
- * and nothing else. Never `screenshot()`: that one switches the Appium context, and a tick
342
- * landing between a step's context switch and its command would run the command in the wrong
343
- * context. Declared before the driver exists (a capture then answers `undefined`), so the
344
- * composition's selection never falls through to `screenshot()`.
345
- */
319
+ // `takeScreenshot()` only: `screenshot()` switches the Appium context mid-step.
346
320
  get liveStream() {
347
321
  this.liveStreamCapability ??= createSnapshotLiveStream({ capture: () => this.captureScreenshot() });
348
322
  return this.liveStreamCapability;
349
323
  }
350
- async observe(_options) {
324
+ async observe() {
351
325
  if (!this.driver) {
352
326
  throw new Error("IOSPlatformAdapter not initialized \u2014 call setup() first");
353
327
  }
@@ -511,9 +485,6 @@ var IOSPlatformAdapter = class {
511
485
  return void 0;
512
486
  }
513
487
  }
514
- // ---------------------------------------------------------------------------
515
- // Compound behaviors (EXEC-01/02/03)
516
- // ---------------------------------------------------------------------------
517
488
  async readIOSPageSource() {
518
489
  const driver = this.driver;
519
490
  if (this.snapshotDepth < this.sessionSnapshotDepth && this.readsAtLoweredDepth >= IOS_SNAPSHOT_PROBE_INTERVAL) {
@@ -541,8 +512,9 @@ var IOSPlatformAdapter = class {
541
512
  if (!this.snapshotDepthWarned) {
542
513
  this.snapshotDepthWarned = true;
543
514
  const detail = (readErr instanceof Error ? readErr.message : String(readErr)).split("\n")[0].slice(0, 160);
544
- console.warn(
545
- `[ios-adapter] page source failed at snapshotMaxDepth ${failedDepth}; later reads stay at ${IOS_SNAPSHOT_FALLBACK_DEPTH} and retry ${this.sessionSnapshotDepth} every ${IOS_SNAPSHOT_PROBE_INTERVAL} reads (${detail})`
515
+ printRunText(
516
+ `[ios-adapter] page source failed at snapshotMaxDepth ${failedDepth}; later reads stay at ${IOS_SNAPSHOT_FALLBACK_DEPTH} and retry ${this.sessionSnapshotDepth} every ${IOS_SNAPSHOT_PROBE_INTERVAL} reads (${detail})`,
517
+ (text) => console.warn(text)
546
518
  );
547
519
  }
548
520
  }
@@ -554,9 +526,7 @@ var IOSPlatformAdapter = class {
554
526
  return xml;
555
527
  }
556
528
  async tapAt(point) {
557
- await safePerform(
558
- this.driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x: Math.round(point.x), y: Math.round(point.y) }).down({ button: 0 }).pause(IOS_TAP_HOLD_MS).up({ button: 0 })
559
- );
529
+ await this.driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x: Math.round(point.x), y: Math.round(point.y) }).down({ button: 0 }).pause(IOS_TAP_HOLD_MS).up({ button: 0 }).perform();
560
530
  }
561
531
  async readRect(elementId) {
562
532
  try {
@@ -688,27 +658,7 @@ var IOSPlatformAdapter = class {
688
658
  return void 0;
689
659
  }
690
660
  }
691
- /**
692
- * EXEC-01 iOS fill compound (D-05..D-08). From the SAME focused-element handle: (append) capture
693
- * the prior value FIRST via getAttribute('value') then addValue, or (replace) setValue; RE-READ
694
- * via getAttribute('value') on that same handle; verdict via compareFillReread. Secure fields
695
- * (nativeType XCUIElementTypeSecureTextField) take the non-empty-only lane — the comparator yields
696
- * length sentinels so no raw value enters the result (T-359-02); bullet masking is tolerated
697
- * (A3/Pitfall 7 — never a length compare). On a passing re-read AND submit, a return/newline is
698
- * delivered AFTER the re-read (D-07). Single typing attempt; a mismatch is success:false + §7.1.
699
- *
700
- * Append (437 FA-1, owner 2026-10-08): the XCUITest `value` attribute often lags after addValue,
701
- * so a re-read equal to the prior cannot show the appended text. That fill is recorded unverified:
702
- * never exact, and never a mismatch. Every fill records the shared verdict as `fillVerdict`.
703
- * An empty field reads as its placeholder, so an append prior equal to it counts as empty.
704
- *
705
- * Return in a multi-line field adds a new line instead of submitting, so a multi-line submit is
706
- * never sent and is recorded as such.
707
- *
708
- * An unreadable re-read is unverified and does not stop a requested Return, as on Android (D-09,
709
- * D-10; owner 2026-10-09), but focus is confirmed once first and a Return is held as focus-lost
710
- * when the field no longer has it (444.1 D-10).
711
- */
661
+ // XCUITest `value` lags after `addValue`, so an append read back as the prior is unverified.
712
662
  async fillIOSCompound(el, action, box, secure, multiline, target) {
713
663
  const driver = this.driver;
714
664
  const isAppend = action.clearMode === "append";
@@ -746,13 +696,7 @@ var IOSPlatformAdapter = class {
746
696
  };
747
697
  return verdict.ok ? { success: true, ...box, ...notice, metadata } : { success: false, error: verdict.selfCorrectingError, ...box, ...notice, metadata };
748
698
  }
749
- /**
750
- * EXEC-02 platform primitives injected into the shared runner scroll loops (D-02/D-03). The swipe
751
- * is a page-sized directional gesture (inverted to the finger direction) that reports unknown
752
- * movement; nativeSearchScroll drives
753
- * the XCUITest `mobile: scroll` predicateString rung over an injection-escaped target; readInterimTree
754
- * is a light page-source parse — NO refMap mint, NO screenshot, lastRefs untouched.
755
- */
699
+ // iOS swipes report unknown movement; `readInterimTree` mints no refs and takes no screenshot.
756
700
  async buildIOSScrollPrimitives(scrollDirection, maxSwipes, factor = 1) {
757
701
  const driver = this.driver;
758
702
  const viewport = await driver.getWindowSize();
@@ -768,9 +712,7 @@ var IOSPlatformAdapter = class {
768
712
  sy = Math.round(center.y);
769
713
  }
770
714
  const pts = computeSwipe(sx, sy, fingerDirectionFor(direction), distance);
771
- await safePerform(
772
- driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x: pts.startX, y: pts.startY }).down({ button: 0 }).move({ x: pts.endX, y: pts.endY, duration: 300 }).up({ button: 0 })
773
- );
715
+ await driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x: pts.startX, y: pts.startY }).down({ button: 0 }).move({ x: pts.endX, y: pts.endY, duration: 300 }).up({ button: 0 }).perform();
774
716
  return "unknown";
775
717
  },
776
718
  nativeSearchScroll: async (target) => {
@@ -789,14 +731,7 @@ var IOSPlatformAdapter = class {
789
731
  }
790
732
  };
791
733
  }
792
- /**
793
- * EXEC-03 openApp (D-10/D-12, Pitfall 5). Resolve a human name to a bundle id (or validate an
794
- * explicit bundleId) over the session-cached registry, then launch via activateApp. On real devices
795
- * `mobile: listApps` enumerates the registry; on simulators it throws, leaving the registry
796
- * unavailable — the appName lane then mints a structured §7.1 error directing to an explicit
797
- * bundleId, and the bundleId lane validates without the registry (isAppInstalled / activateApp
798
- * error conversion, A2). Never a best-guess launch (T-359-04); an invalid bundle never succeeds.
799
- */
734
+ // Simulators throw on `listApps`, so `appName` needs a real device; `bundleId` works without it.
800
735
  async openApp(action) {
801
736
  const driver = this.driver;
802
737
  const registry = await this.ensureAppRegistry();
@@ -835,12 +770,7 @@ var IOSPlatformAdapter = class {
835
770
  }
836
771
  }
837
772
  }
838
- /**
839
- * Explicit `{bundleId}` launch (D-12, A2). With a registry, validate membership; without one
840
- * (simulator) validate via `driver.isAppInstalled` when the driver exposes it, otherwise attempt
841
- * `activateApp` and convert a driver rejection into the structured §7.1 error. An invalid bundle
842
- * NEVER returns success:true.
843
- */
773
+ // Without a registry, validate via `isAppInstalled` or convert an `activateApp` rejection.
844
774
  async openByBundleId(bundleId, registry) {
845
775
  const driver = this.driver;
846
776
  if (registry !== null) {
@@ -887,12 +817,7 @@ var IOSPlatformAdapter = class {
887
817
  if (enumerated !== null) this.appRegistry = enumerated;
888
818
  return enumerated;
889
819
  }
890
- /**
891
- * Enumerate installed apps through the driver only (ownership contract — webdriverio only, no
892
- * child_process/simctl). `mobile: listApps` maps bundle ids → AppRegistryEntry with CFBundleName
893
- * labels when present (D-10 — no per-app round-trips). It throws on simulators (Pitfall 5) ⇒ null,
894
- * leaving enumeration unavailable WITHOUT crashing.
895
- */
820
+ // Driver only, no simctl; `listApps` throws on simulators, which leaves enumeration unavailable.
896
821
  async enumerateApps() {
897
822
  const driver = this.driver;
898
823
  try {
@@ -1069,9 +994,7 @@ var IOSPlatformAdapter = class {
1069
994
  warnIfOutOfBounds({ x: pts.startX, y: pts.startY }, { width, height }, "swipe");
1070
995
  warnIfOutOfBounds({ x: pts.endX, y: pts.endY }, { width, height }, "swipe");
1071
996
  const swipeDuration = action.durationMs;
1072
- await safePerform(
1073
- driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x: pts.startX, y: pts.startY }).down({ button: 0 }).move({ x: pts.endX, y: pts.endY, duration: swipeDuration }).up({ button: 0 })
1074
- );
997
+ await driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x: pts.startX, y: pts.startY }).down({ button: 0 }).move({ x: pts.endX, y: pts.endY, duration: swipeDuration }).up({ button: 0 }).perform();
1075
998
  return {
1076
999
  success: true,
1077
1000
  startCoordinates: { x: pts.startX, y: pts.startY },
@@ -1122,9 +1045,7 @@ var IOSPlatformAdapter = class {
1122
1045
  }
1123
1046
  case "longpress": {
1124
1047
  const { center } = resolver.resolve(action.ref);
1125
- await safePerform(
1126
- driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x: Math.round(center.x), y: Math.round(center.y) }).down({ button: 0 }).pause(action.duration ?? 1e3).up({ button: 0 })
1127
- );
1048
+ await driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x: Math.round(center.x), y: Math.round(center.y) }).down({ button: 0 }).pause(action.duration ?? 1e3).up({ button: 0 }).perform();
1128
1049
  return { success: true };
1129
1050
  }
1130
1051
  case "waitFor": {
@@ -1312,25 +1233,19 @@ var IOSPlatformAdapter = class {
1312
1233
  success: false,
1313
1234
  error: 'assertValue {source:"network"} (HTTP log inspection) is not available on the iOS adapter. Use a web target for network assertions, or assert on visible UI text.'
1314
1235
  };
1315
- case "expr-result":
1316
- return { success: true, data: void 0 };
1317
1236
  }
1318
1237
  }
1319
1238
  case "drag": {
1320
1239
  const from = resolver.resolve(action.fromRef);
1321
1240
  const to = resolver.resolve(action.toRef);
1322
- await safePerform(
1323
- driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x: Math.round(from.center.x), y: Math.round(from.center.y) }).down({ button: 0 }).pause(300).move({ x: Math.round(to.center.x), y: Math.round(to.center.y), duration: 500 }).up({ button: 0 })
1324
- );
1241
+ await driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x: Math.round(from.center.x), y: Math.round(from.center.y) }).down({ button: 0 }).pause(300).move({ x: Math.round(to.center.x), y: Math.round(to.center.y), duration: 500 }).up({ button: 0 }).perform();
1325
1242
  return { success: true };
1326
1243
  }
1327
1244
  case "doubleTap": {
1328
1245
  const { center, bounds } = resolver.resolve(action.ref);
1329
1246
  const x = Math.round(center.x);
1330
1247
  const y = Math.round(center.y);
1331
- await safePerform(
1332
- driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x, y }).down({ button: 0 }).up({ button: 0 }).pause(50).down({ button: 0 }).up({ button: 0 })
1333
- );
1248
+ await driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x, y }).down({ button: 0 }).up({ button: 0 }).pause(50).down({ button: 0 }).up({ button: 0 }).perform();
1334
1249
  return { success: true, coordinates: center, boundingBox: bounds };
1335
1250
  }
1336
1251
  case "openApp":
@@ -1423,16 +1338,10 @@ var IOSPlatformAdapter = class {
1423
1338
  end = { x: midX, y: Math.round(height * 0.4) };
1424
1339
  break;
1425
1340
  }
1426
- await safePerform(
1427
- driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x: start.x, y: start.y }).down({ button: 0 }).move({ x: end.x, y: end.y, duration: 300 }).up({ button: 0 })
1428
- );
1341
+ await driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x: start.x, y: start.y }).down({ button: 0 }).move({ x: end.x, y: end.y, duration: 300 }).up({ button: 0 }).perform();
1429
1342
  return { success: true, startCoordinates: start, endCoordinates: end };
1430
1343
  }
1431
- // Runner-minted / author-channel verbs (D-01/D-15): reportValue, assertAI, extractAI, and
1432
- // setVariable are intercepted at the runner dispatch seam and never reach the adapter. A
1433
- // grouped §7.1 defensive case surfaces a dispatch-routing error if one arrives here;
1434
- // assertNever remains the backstop for a genuinely unknown schema verb. (uniqueValue is
1435
- // web-only and is not part of ActionsFor<'ios'>.)
1344
+ // Runner-channel verbs never reach the adapter; one arriving here is a routing error.
1436
1345
  case "reportValue":
1437
1346
  case "assertAI":
1438
1347
  case "extractAI":