@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/README.md +78 -0
- package/dist/index.cjs +24 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -63
- package/dist/index.d.ts +3 -63
- package/dist/index.js +25 -116
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/banner-dark.jpg">
|
|
3
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/banner-light.png">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/banner-light.png" alt="agent-qa by vostride — a self-improving QA agent for your software factory" width="100%">
|
|
5
|
+
</picture>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>English</strong> · <a href="https://github.com/vostride/agent-qa/blob/main/README.zh-CN.md">中文</a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/agent-qa"><img src="https://img.shields.io/npm/v/agent-qa?color=196872" alt="npm version"></a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/agent-qa"><img src="https://img.shields.io/npm/dm/agent-qa?color=196872" alt="npm downloads per month"></a>
|
|
14
|
+
<a href="https://github.com/vostride/agent-qa/stargazers"><img src="https://img.shields.io/github/stars/vostride/agent-qa?color=196872" alt="GitHub stars"></a>
|
|
15
|
+
<a href="https://github.com/vostride/agent-qa/actions/workflows/ci.yml"><img src="https://github.com/vostride/agent-qa/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
|
|
16
|
+
<a href="https://vostride.com/docs/agent-qa"><img src="https://img.shields.io/badge/docs-agent--qa-196872" alt="Documentation"></a>
|
|
17
|
+
<a href="https://github.com/vostride/agent-qa/blob/main/LICENSE.md"><img src="https://img.shields.io/badge/license-FSL--1.1--ALv2-196872" alt="License: FSL-1.1-ALv2"></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<a href="https://vostride.com/agent-qa">Website</a> · <a href="https://vostride.com/docs/agent-qa">Docs</a> · <a href="https://vostride.com/demo/agent-qa">Demo</a> · <a href="https://vostride.com/blog">Blog</a> · <a href="https://vostride.com/changelog">Changelog</a>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
**Your AI QA engineer, with memory.** Describe a user journey in plain language. agent-qa tests it on web, Android or iOS and keeps the evidence. Use the CLI, the dashboard, or your coding agent through MCP and skills.
|
|
25
|
+
|
|
26
|
+
- **memidex:** remembers facts and working procedures as reviewable files in your repository.
|
|
27
|
+
- **Self-improvement:** validates lessons from runs, retires stale knowledge, and uses your feedback to correct mistakes.
|
|
28
|
+
|
|
29
|
+
| Benchmark | Rank | Published result |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| [AndroidWorld](https://vostride.com/benchmark/android-world) | **#1** | **100% pass@1** (116/116 tasks) |
|
|
32
|
+
| [AppControlBench](https://vostride.com/benchmark/app-control-bench) | **#1** | **99.6% completion** (114.5/115 points) |
|
|
33
|
+
|
|
34
|
+
Read the full benchmark reports, with run evidence and methodology, on [our blog](https://vostride.com/blog). Both used gpt-6-sol with memory and self-improvement off; AppControlBench gives half credit for partial completion.
|
|
35
|
+
|
|
36
|
+
## See it in action
|
|
37
|
+
|
|
38
|
+
**Inspect a failure.** The demo catches a cart showing $29 instead of $53, with the failed step and its screenshot side by side.
|
|
39
|
+
|
|
40
|
+
<picture>
|
|
41
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/evidence-dark.png">
|
|
42
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/evidence-light.png">
|
|
43
|
+
<img src="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/evidence-light.png" alt="The dashboard shows the deliberate cart-total bug, its failed assertion, and recorded browser evidence" width="100%">
|
|
44
|
+
</picture>
|
|
45
|
+
|
|
46
|
+
**See what memory contributed.** Inspect the knowledge supplied to a run and trace it back to the steps that used it.
|
|
47
|
+
|
|
48
|
+
<picture>
|
|
49
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/knowledge-dark.png">
|
|
50
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/knowledge-light.png">
|
|
51
|
+
<img src="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/knowledge-light.png" alt="The Knowledge tab shows stored facts retrieved for the demo run" width="100%">
|
|
52
|
+
</picture>
|
|
53
|
+
|
|
54
|
+
**Run from your terminal.** Real output from the demo suite: checkout passes; the deliberate cart-total bug fails.
|
|
55
|
+
|
|
56
|
+
<picture>
|
|
57
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/cli-dark.png">
|
|
58
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/cli-light.png">
|
|
59
|
+
<img src="https://raw.githubusercontent.com/vostride/agent-qa/main/docs/assets/readme/cli-light.png" alt="CLI output excerpt showing one passed test, one failed test, and eight of nine steps passing" width="100%">
|
|
60
|
+
</picture>
|
|
61
|
+
|
|
62
|
+
## Get started
|
|
63
|
+
|
|
64
|
+
Requires Node.js 24+.
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
npm install -D agent-qa
|
|
68
|
+
npx agent-qa init
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Follow the [quickstart](https://vostride.com/docs/agent-qa/quickstart) to connect a model and run the demo, or [set up with your coding agent](https://vostride.com/docs/agent-qa/agent-quickstart).
|
|
72
|
+
|
|
73
|
+
> [!NOTE]
|
|
74
|
+
> Early stage. Expect bugs. [Report an issue](https://github.com/vostride/agent-qa/issues) when something breaks.
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
[Functional Source License (FSL-1.1-ALv2)](https://github.com/vostride/agent-qa/blob/main/LICENSE.md).
|
package/dist/index.cjs
CHANGED
|
@@ -42,10 +42,10 @@ function positiveInteger(value) {
|
|
|
42
42
|
const parsed = Number(value.trim());
|
|
43
43
|
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
44
44
|
}
|
|
45
|
-
function resolveIOSSnapshotDepth(
|
|
46
|
-
return positiveInteger(
|
|
45
|
+
function resolveIOSSnapshotDepth() {
|
|
46
|
+
return positiveInteger(process.env[IOS_SNAPSHOT_DEPTH_ENV]) ?? IOS_SNAPSHOT_MAX_DEPTH;
|
|
47
47
|
}
|
|
48
|
-
async function createIOSSession(config
|
|
48
|
+
async function createIOSSession(config) {
|
|
49
49
|
const match = config.device?.match ?? {};
|
|
50
50
|
const appiumUrl = process.env.AGENT_QA_APPIUM_URL ?? config.appiumUrl ?? "http://localhost:4723";
|
|
51
51
|
const url = new URL(appiumUrl);
|
|
@@ -71,17 +71,6 @@ async function createIOSSession(config, farmSession) {
|
|
|
71
71
|
delete capabilities[key];
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
if (farmSession) {
|
|
75
|
-
Object.assign(capabilities, farmSession.capabilities);
|
|
76
|
-
return (0, import_webdriverio.remote)({
|
|
77
|
-
protocol: "https",
|
|
78
|
-
hostname: farmSession.hostname,
|
|
79
|
-
port: farmSession.port,
|
|
80
|
-
path: farmSession.path,
|
|
81
|
-
logLevel: config.verbose ? "info" : "silent",
|
|
82
|
-
capabilities
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
74
|
return (0, import_webdriverio.remote)({
|
|
86
75
|
hostname: url.hostname,
|
|
87
76
|
port: parseInt(url.port || "4723", 10),
|
|
@@ -130,15 +119,6 @@ function firstErrorLine(err) {
|
|
|
130
119
|
return (err instanceof Error ? err.message : String(err)).split("\n")[0];
|
|
131
120
|
}
|
|
132
121
|
var IOS_TAP_HOLD_MS = 100;
|
|
133
|
-
async function safePerform(chain) {
|
|
134
|
-
try {
|
|
135
|
-
await chain.perform();
|
|
136
|
-
} catch (err) {
|
|
137
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
138
|
-
if (msg.includes("actions") && msg.includes("DELETE")) return;
|
|
139
|
-
throw err;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
122
|
var IOS_SYSLOG_REGEX = /^.*?\s+(\S+)\[(\d+)\].*?:\s+(.*)/;
|
|
143
123
|
var IOS_APPIUM_LOG_REGEX = /^\S+\s+\S+\s+\[(\w+)\]\s+\[(\S+)\]\s+(.*)/;
|
|
144
124
|
var IOS_LEVEL_MAP = {
|
|
@@ -284,8 +264,7 @@ var IOSPlatformAdapter = class {
|
|
|
284
264
|
consoleDrainIndex = 0;
|
|
285
265
|
captureConsole = true;
|
|
286
266
|
bundleId = null;
|
|
287
|
-
//
|
|
288
|
-
// enumerated OR enumeration unavailable (iOS simulator — mobile: listApps throws). Reset in cleanup().
|
|
267
|
+
// `null` means not yet enumerated, or unavailable because simulators throw on `listApps`.
|
|
289
268
|
appRegistry = null;
|
|
290
269
|
sessionSnapshotDepth = IOS_SNAPSHOT_MAX_DEPTH;
|
|
291
270
|
snapshotDepth = IOS_SNAPSHOT_MAX_DEPTH;
|
|
@@ -295,11 +274,11 @@ var IOSPlatformAdapter = class {
|
|
|
295
274
|
this.config = config;
|
|
296
275
|
this.captureConsole = config.logCapture?.console !== false;
|
|
297
276
|
this.bundleId = this.config.bundleId ?? config.device?.match?.bundleId ?? null;
|
|
298
|
-
this.sessionSnapshotDepth = resolveIOSSnapshotDepth(
|
|
277
|
+
this.sessionSnapshotDepth = resolveIOSSnapshotDepth();
|
|
299
278
|
this.snapshotDepth = this.sessionSnapshotDepth;
|
|
300
279
|
this.readsAtLoweredDepth = 0;
|
|
301
280
|
this.snapshotDepthWarned = false;
|
|
302
|
-
this.driver = await createIOSSession(this.config
|
|
281
|
+
this.driver = await createIOSSession(this.config);
|
|
303
282
|
const bundleId = this.bundleId;
|
|
304
283
|
if (bundleId) {
|
|
305
284
|
try {
|
|
@@ -342,18 +321,12 @@ var IOSPlatformAdapter = class {
|
|
|
342
321
|
this.readsAtLoweredDepth = 0;
|
|
343
322
|
this.snapshotDepthWarned = false;
|
|
344
323
|
}
|
|
345
|
-
|
|
346
|
-
* 428 D-11: the labelled 1s snapshot preview, over `captureScreenshot()` — `takeScreenshot()`
|
|
347
|
-
* and nothing else. Never `screenshot()`: that one switches the Appium context, and a tick
|
|
348
|
-
* landing between a step's context switch and its command would run the command in the wrong
|
|
349
|
-
* context. Declared before the driver exists (a capture then answers `undefined`), so the
|
|
350
|
-
* composition's selection never falls through to `screenshot()`.
|
|
351
|
-
*/
|
|
324
|
+
// `takeScreenshot()` only: `screenshot()` switches the Appium context mid-step.
|
|
352
325
|
get liveStream() {
|
|
353
326
|
this.liveStreamCapability ??= (0, import_agent_qa_runner.createSnapshotLiveStream)({ capture: () => this.captureScreenshot() });
|
|
354
327
|
return this.liveStreamCapability;
|
|
355
328
|
}
|
|
356
|
-
async observe(
|
|
329
|
+
async observe() {
|
|
357
330
|
if (!this.driver) {
|
|
358
331
|
throw new Error("IOSPlatformAdapter not initialized \u2014 call setup() first");
|
|
359
332
|
}
|
|
@@ -517,9 +490,6 @@ var IOSPlatformAdapter = class {
|
|
|
517
490
|
return void 0;
|
|
518
491
|
}
|
|
519
492
|
}
|
|
520
|
-
// ---------------------------------------------------------------------------
|
|
521
|
-
// Compound behaviors (EXEC-01/02/03)
|
|
522
|
-
// ---------------------------------------------------------------------------
|
|
523
493
|
async readIOSPageSource() {
|
|
524
494
|
const driver = this.driver;
|
|
525
495
|
if (this.snapshotDepth < this.sessionSnapshotDepth && this.readsAtLoweredDepth >= IOS_SNAPSHOT_PROBE_INTERVAL) {
|
|
@@ -547,8 +517,9 @@ var IOSPlatformAdapter = class {
|
|
|
547
517
|
if (!this.snapshotDepthWarned) {
|
|
548
518
|
this.snapshotDepthWarned = true;
|
|
549
519
|
const detail = (readErr instanceof Error ? readErr.message : String(readErr)).split("\n")[0].slice(0, 160);
|
|
550
|
-
|
|
551
|
-
`[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})
|
|
520
|
+
(0, import_agent_qa_core.printRunText)(
|
|
521
|
+
`[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})`,
|
|
522
|
+
(text) => console.warn(text)
|
|
552
523
|
);
|
|
553
524
|
}
|
|
554
525
|
}
|
|
@@ -560,9 +531,7 @@ var IOSPlatformAdapter = class {
|
|
|
560
531
|
return xml;
|
|
561
532
|
}
|
|
562
533
|
async tapAt(point) {
|
|
563
|
-
await
|
|
564
|
-
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 })
|
|
565
|
-
);
|
|
534
|
+
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();
|
|
566
535
|
}
|
|
567
536
|
async readRect(elementId) {
|
|
568
537
|
try {
|
|
@@ -694,27 +663,7 @@ var IOSPlatformAdapter = class {
|
|
|
694
663
|
return void 0;
|
|
695
664
|
}
|
|
696
665
|
}
|
|
697
|
-
|
|
698
|
-
* EXEC-01 iOS fill compound (D-05..D-08). From the SAME focused-element handle: (append) capture
|
|
699
|
-
* the prior value FIRST via getAttribute('value') then addValue, or (replace) setValue; RE-READ
|
|
700
|
-
* via getAttribute('value') on that same handle; verdict via compareFillReread. Secure fields
|
|
701
|
-
* (nativeType XCUIElementTypeSecureTextField) take the non-empty-only lane — the comparator yields
|
|
702
|
-
* length sentinels so no raw value enters the result (T-359-02); bullet masking is tolerated
|
|
703
|
-
* (A3/Pitfall 7 — never a length compare). On a passing re-read AND submit, a return/newline is
|
|
704
|
-
* delivered AFTER the re-read (D-07). Single typing attempt; a mismatch is success:false + §7.1.
|
|
705
|
-
*
|
|
706
|
-
* Append (437 FA-1, owner 2026-10-08): the XCUITest `value` attribute often lags after addValue,
|
|
707
|
-
* so a re-read equal to the prior cannot show the appended text. That fill is recorded unverified:
|
|
708
|
-
* never exact, and never a mismatch. Every fill records the shared verdict as `fillVerdict`.
|
|
709
|
-
* An empty field reads as its placeholder, so an append prior equal to it counts as empty.
|
|
710
|
-
*
|
|
711
|
-
* Return in a multi-line field adds a new line instead of submitting, so a multi-line submit is
|
|
712
|
-
* never sent and is recorded as such.
|
|
713
|
-
*
|
|
714
|
-
* An unreadable re-read is unverified and does not stop a requested Return, as on Android (D-09,
|
|
715
|
-
* D-10; owner 2026-10-09), but focus is confirmed once first and a Return is held as focus-lost
|
|
716
|
-
* when the field no longer has it (444.1 D-10).
|
|
717
|
-
*/
|
|
666
|
+
// XCUITest `value` lags after `addValue`, so an append read back as the prior is unverified.
|
|
718
667
|
async fillIOSCompound(el, action, box, secure, multiline, target) {
|
|
719
668
|
const driver = this.driver;
|
|
720
669
|
const isAppend = action.clearMode === "append";
|
|
@@ -752,13 +701,7 @@ var IOSPlatformAdapter = class {
|
|
|
752
701
|
};
|
|
753
702
|
return verdict.ok ? { success: true, ...box, ...notice, metadata } : { success: false, error: verdict.selfCorrectingError, ...box, ...notice, metadata };
|
|
754
703
|
}
|
|
755
|
-
|
|
756
|
-
* EXEC-02 platform primitives injected into the shared runner scroll loops (D-02/D-03). The swipe
|
|
757
|
-
* is a page-sized directional gesture (inverted to the finger direction) that reports unknown
|
|
758
|
-
* movement; nativeSearchScroll drives
|
|
759
|
-
* the XCUITest `mobile: scroll` predicateString rung over an injection-escaped target; readInterimTree
|
|
760
|
-
* is a light page-source parse — NO refMap mint, NO screenshot, lastRefs untouched.
|
|
761
|
-
*/
|
|
704
|
+
// iOS swipes report unknown movement; `readInterimTree` mints no refs and takes no screenshot.
|
|
762
705
|
async buildIOSScrollPrimitives(scrollDirection, maxSwipes, factor = 1) {
|
|
763
706
|
const driver = this.driver;
|
|
764
707
|
const viewport = await driver.getWindowSize();
|
|
@@ -774,9 +717,7 @@ var IOSPlatformAdapter = class {
|
|
|
774
717
|
sy = Math.round(center.y);
|
|
775
718
|
}
|
|
776
719
|
const pts = (0, import_agent_qa_core.computeSwipe)(sx, sy, fingerDirectionFor(direction), distance);
|
|
777
|
-
await
|
|
778
|
-
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 })
|
|
779
|
-
);
|
|
720
|
+
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();
|
|
780
721
|
return "unknown";
|
|
781
722
|
},
|
|
782
723
|
nativeSearchScroll: async (target) => {
|
|
@@ -795,14 +736,7 @@ var IOSPlatformAdapter = class {
|
|
|
795
736
|
}
|
|
796
737
|
};
|
|
797
738
|
}
|
|
798
|
-
|
|
799
|
-
* EXEC-03 openApp (D-10/D-12, Pitfall 5). Resolve a human name to a bundle id (or validate an
|
|
800
|
-
* explicit bundleId) over the session-cached registry, then launch via activateApp. On real devices
|
|
801
|
-
* `mobile: listApps` enumerates the registry; on simulators it throws, leaving the registry
|
|
802
|
-
* unavailable — the appName lane then mints a structured §7.1 error directing to an explicit
|
|
803
|
-
* bundleId, and the bundleId lane validates without the registry (isAppInstalled / activateApp
|
|
804
|
-
* error conversion, A2). Never a best-guess launch (T-359-04); an invalid bundle never succeeds.
|
|
805
|
-
*/
|
|
739
|
+
// Simulators throw on `listApps`, so `appName` needs a real device; `bundleId` works without it.
|
|
806
740
|
async openApp(action) {
|
|
807
741
|
const driver = this.driver;
|
|
808
742
|
const registry = await this.ensureAppRegistry();
|
|
@@ -841,12 +775,7 @@ var IOSPlatformAdapter = class {
|
|
|
841
775
|
}
|
|
842
776
|
}
|
|
843
777
|
}
|
|
844
|
-
|
|
845
|
-
* Explicit `{bundleId}` launch (D-12, A2). With a registry, validate membership; without one
|
|
846
|
-
* (simulator) validate via `driver.isAppInstalled` when the driver exposes it, otherwise attempt
|
|
847
|
-
* `activateApp` and convert a driver rejection into the structured §7.1 error. An invalid bundle
|
|
848
|
-
* NEVER returns success:true.
|
|
849
|
-
*/
|
|
778
|
+
// Without a registry, validate via `isAppInstalled` or convert an `activateApp` rejection.
|
|
850
779
|
async openByBundleId(bundleId, registry) {
|
|
851
780
|
const driver = this.driver;
|
|
852
781
|
if (registry !== null) {
|
|
@@ -893,12 +822,7 @@ var IOSPlatformAdapter = class {
|
|
|
893
822
|
if (enumerated !== null) this.appRegistry = enumerated;
|
|
894
823
|
return enumerated;
|
|
895
824
|
}
|
|
896
|
-
|
|
897
|
-
* Enumerate installed apps through the driver only (ownership contract — webdriverio only, no
|
|
898
|
-
* child_process/simctl). `mobile: listApps` maps bundle ids → AppRegistryEntry with CFBundleName
|
|
899
|
-
* labels when present (D-10 — no per-app round-trips). It throws on simulators (Pitfall 5) ⇒ null,
|
|
900
|
-
* leaving enumeration unavailable WITHOUT crashing.
|
|
901
|
-
*/
|
|
825
|
+
// Driver only, no simctl; `listApps` throws on simulators, which leaves enumeration unavailable.
|
|
902
826
|
async enumerateApps() {
|
|
903
827
|
const driver = this.driver;
|
|
904
828
|
try {
|
|
@@ -1075,9 +999,7 @@ var IOSPlatformAdapter = class {
|
|
|
1075
999
|
(0, import_agent_qa_core.warnIfOutOfBounds)({ x: pts.startX, y: pts.startY }, { width, height }, "swipe");
|
|
1076
1000
|
(0, import_agent_qa_core.warnIfOutOfBounds)({ x: pts.endX, y: pts.endY }, { width, height }, "swipe");
|
|
1077
1001
|
const swipeDuration = action.durationMs;
|
|
1078
|
-
await
|
|
1079
|
-
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 })
|
|
1080
|
-
);
|
|
1002
|
+
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();
|
|
1081
1003
|
return {
|
|
1082
1004
|
success: true,
|
|
1083
1005
|
startCoordinates: { x: pts.startX, y: pts.startY },
|
|
@@ -1128,9 +1050,7 @@ var IOSPlatformAdapter = class {
|
|
|
1128
1050
|
}
|
|
1129
1051
|
case "longpress": {
|
|
1130
1052
|
const { center } = resolver.resolve(action.ref);
|
|
1131
|
-
await
|
|
1132
|
-
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 })
|
|
1133
|
-
);
|
|
1053
|
+
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();
|
|
1134
1054
|
return { success: true };
|
|
1135
1055
|
}
|
|
1136
1056
|
case "waitFor": {
|
|
@@ -1318,25 +1238,19 @@ var IOSPlatformAdapter = class {
|
|
|
1318
1238
|
success: false,
|
|
1319
1239
|
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.'
|
|
1320
1240
|
};
|
|
1321
|
-
case "expr-result":
|
|
1322
|
-
return { success: true, data: void 0 };
|
|
1323
1241
|
}
|
|
1324
1242
|
}
|
|
1325
1243
|
case "drag": {
|
|
1326
1244
|
const from = resolver.resolve(action.fromRef);
|
|
1327
1245
|
const to = resolver.resolve(action.toRef);
|
|
1328
|
-
await
|
|
1329
|
-
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 })
|
|
1330
|
-
);
|
|
1246
|
+
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();
|
|
1331
1247
|
return { success: true };
|
|
1332
1248
|
}
|
|
1333
1249
|
case "doubleTap": {
|
|
1334
1250
|
const { center, bounds } = resolver.resolve(action.ref);
|
|
1335
1251
|
const x = Math.round(center.x);
|
|
1336
1252
|
const y = Math.round(center.y);
|
|
1337
|
-
await
|
|
1338
|
-
driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x, y }).down({ button: 0 }).up({ button: 0 }).pause(50).down({ button: 0 }).up({ button: 0 })
|
|
1339
|
-
);
|
|
1253
|
+
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();
|
|
1340
1254
|
return { success: true, coordinates: center, boundingBox: bounds };
|
|
1341
1255
|
}
|
|
1342
1256
|
case "openApp":
|
|
@@ -1429,16 +1343,10 @@ var IOSPlatformAdapter = class {
|
|
|
1429
1343
|
end = { x: midX, y: Math.round(height * 0.4) };
|
|
1430
1344
|
break;
|
|
1431
1345
|
}
|
|
1432
|
-
await
|
|
1433
|
-
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 })
|
|
1434
|
-
);
|
|
1346
|
+
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();
|
|
1435
1347
|
return { success: true, startCoordinates: start, endCoordinates: end };
|
|
1436
1348
|
}
|
|
1437
|
-
// Runner-
|
|
1438
|
-
// setVariable are intercepted at the runner dispatch seam and never reach the adapter. A
|
|
1439
|
-
// grouped §7.1 defensive case surfaces a dispatch-routing error if one arrives here;
|
|
1440
|
-
// assertNever remains the backstop for a genuinely unknown schema verb. (uniqueValue is
|
|
1441
|
-
// web-only and is not part of ActionsFor<'ios'>.)
|
|
1349
|
+
// Runner-channel verbs never reach the adapter; one arriving here is a routing error.
|
|
1442
1350
|
case "reportValue":
|
|
1443
1351
|
case "assertAI":
|
|
1444
1352
|
case "extractAI":
|