@exodus/xqa 5.3.0 → 5.5.0
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 +4 -1
- package/dist/xqa.cjs +355 -94
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -180,7 +180,8 @@ Suite files live at `.xqa/suites/<name>.suite.json` and declare the work items p
|
|
|
180
180
|
"beforeEach": {
|
|
181
181
|
"script": "qa/prepare-sim.mjs",
|
|
182
182
|
"env": { "APP_PROFILE": "funded" },
|
|
183
|
-
"timeoutSeconds": 120
|
|
183
|
+
"timeoutSeconds": 120,
|
|
184
|
+
"retries": 3
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
187
|
}
|
|
@@ -206,6 +207,8 @@ Contract:
|
|
|
206
207
|
- Exit 0 → proceed with item.
|
|
207
208
|
- Non-zero exit → item marked failed, `executeItem` skipped, counts toward simulator-unhealthy threshold.
|
|
208
209
|
- Default 120s timeout, overridable via `hooks.beforeEach.timeoutSeconds`.
|
|
210
|
+
- Default 3 retries on failure (`HOOK_EXIT_NONZERO`, `HOOK_TIMEOUT`, `HOOK_SPAWN_FAILED`), overridable via `hooks.beforeEach.retries` (range `0..10`). Set to `0` to disable retries. Aborts (`HOOK_ABORTED`) are never retried.
|
|
211
|
+
- A `HOOK_RETRY` suite event is emitted before each retry attempt with `attempt`, `maxAttempts`, and `previousErrorType`.
|
|
209
212
|
- Honors the suite abort signal.
|
|
210
213
|
|
|
211
214
|
## Configuration
|
package/dist/xqa.cjs
CHANGED
|
@@ -161,7 +161,7 @@ var require_index_cjs = __commonJS({
|
|
|
161
161
|
}, reject);
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
|
-
var
|
|
164
|
+
var ResultAsync29 = class _ResultAsync {
|
|
165
165
|
constructor(res) {
|
|
166
166
|
this._promise = res;
|
|
167
167
|
}
|
|
@@ -262,8 +262,8 @@ var require_index_cjs = __commonJS({
|
|
|
262
262
|
return new Ok(res.value);
|
|
263
263
|
})));
|
|
264
264
|
}
|
|
265
|
-
match(
|
|
266
|
-
return this._promise.then((res) => res.match(
|
|
265
|
+
match(ok45, _err) {
|
|
266
|
+
return this._promise.then((res) => res.match(ok45, _err));
|
|
267
267
|
}
|
|
268
268
|
unwrapOr(t) {
|
|
269
269
|
return this._promise.then((res) => res.unwrapOr(t));
|
|
@@ -299,17 +299,17 @@ var require_index_cjs = __commonJS({
|
|
|
299
299
|
});
|
|
300
300
|
}
|
|
301
301
|
};
|
|
302
|
-
function
|
|
303
|
-
return new
|
|
302
|
+
function okAsync13(value) {
|
|
303
|
+
return new ResultAsync29(Promise.resolve(new Ok(value)));
|
|
304
304
|
}
|
|
305
305
|
function errAsync12(err45) {
|
|
306
|
-
return new
|
|
306
|
+
return new ResultAsync29(Promise.resolve(new Err(err45)));
|
|
307
307
|
}
|
|
308
|
-
var fromPromise =
|
|
309
|
-
var fromSafePromise2 =
|
|
310
|
-
var fromAsyncThrowable9 =
|
|
308
|
+
var fromPromise = ResultAsync29.fromPromise;
|
|
309
|
+
var fromSafePromise2 = ResultAsync29.fromSafePromise;
|
|
310
|
+
var fromAsyncThrowable9 = ResultAsync29.fromThrowable;
|
|
311
311
|
var combineResultList = (resultList) => {
|
|
312
|
-
let acc =
|
|
312
|
+
let acc = ok41([]);
|
|
313
313
|
for (const result of resultList) {
|
|
314
314
|
if (result.isErr()) {
|
|
315
315
|
acc = err41(result.error);
|
|
@@ -320,9 +320,9 @@ var require_index_cjs = __commonJS({
|
|
|
320
320
|
}
|
|
321
321
|
return acc;
|
|
322
322
|
};
|
|
323
|
-
var combineResultAsyncList = (asyncResultList) =>
|
|
323
|
+
var combineResultAsyncList = (asyncResultList) => ResultAsync29.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultList);
|
|
324
324
|
var combineResultListWithAllErrors = (resultList) => {
|
|
325
|
-
let acc =
|
|
325
|
+
let acc = ok41([]);
|
|
326
326
|
for (const result of resultList) {
|
|
327
327
|
if (result.isErr() && acc.isErr()) {
|
|
328
328
|
acc.error.push(result.error);
|
|
@@ -334,14 +334,14 @@ var require_index_cjs = __commonJS({
|
|
|
334
334
|
}
|
|
335
335
|
return acc;
|
|
336
336
|
};
|
|
337
|
-
var combineResultAsyncListWithAllErrors = (asyncResultList) =>
|
|
337
|
+
var combineResultAsyncListWithAllErrors = (asyncResultList) => ResultAsync29.fromSafePromise(Promise.all(asyncResultList)).andThen(combineResultListWithAllErrors);
|
|
338
338
|
exports2.Result = void 0;
|
|
339
339
|
(function(Result3) {
|
|
340
340
|
function fromThrowable21(fn, errorFn) {
|
|
341
341
|
return (...args) => {
|
|
342
342
|
try {
|
|
343
343
|
const result = fn(...args);
|
|
344
|
-
return
|
|
344
|
+
return ok41(result);
|
|
345
345
|
} catch (e3) {
|
|
346
346
|
return err41(errorFn ? errorFn(e3) : e3);
|
|
347
347
|
}
|
|
@@ -357,7 +357,7 @@ var require_index_cjs = __commonJS({
|
|
|
357
357
|
}
|
|
358
358
|
Result3.combineWithAllErrors = combineWithAllErrors;
|
|
359
359
|
})(exports2.Result || (exports2.Result = {}));
|
|
360
|
-
function
|
|
360
|
+
function ok41(value) {
|
|
361
361
|
return new Ok(value);
|
|
362
362
|
}
|
|
363
363
|
function err41(err45) {
|
|
@@ -366,7 +366,7 @@ var require_index_cjs = __commonJS({
|
|
|
366
366
|
function safeTry(body) {
|
|
367
367
|
const n3 = body().next();
|
|
368
368
|
if (n3 instanceof Promise) {
|
|
369
|
-
return new
|
|
369
|
+
return new ResultAsync29(n3.then((r3) => r3.value));
|
|
370
370
|
}
|
|
371
371
|
return n3.value;
|
|
372
372
|
}
|
|
@@ -381,11 +381,11 @@ var require_index_cjs = __commonJS({
|
|
|
381
381
|
return !this.isOk();
|
|
382
382
|
}
|
|
383
383
|
map(f6) {
|
|
384
|
-
return
|
|
384
|
+
return ok41(f6(this.value));
|
|
385
385
|
}
|
|
386
386
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
387
387
|
mapErr(_f) {
|
|
388
|
-
return
|
|
388
|
+
return ok41(this.value);
|
|
389
389
|
}
|
|
390
390
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
|
391
391
|
andThen(f6) {
|
|
@@ -400,14 +400,14 @@ var require_index_cjs = __commonJS({
|
|
|
400
400
|
f6(this.value);
|
|
401
401
|
} catch (e3) {
|
|
402
402
|
}
|
|
403
|
-
return
|
|
403
|
+
return ok41(this.value);
|
|
404
404
|
}
|
|
405
405
|
orTee(_f) {
|
|
406
|
-
return
|
|
406
|
+
return ok41(this.value);
|
|
407
407
|
}
|
|
408
408
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
|
409
409
|
orElse(_f) {
|
|
410
|
-
return
|
|
410
|
+
return ok41(this.value);
|
|
411
411
|
}
|
|
412
412
|
asyncAndThen(f6) {
|
|
413
413
|
return f6(this.value);
|
|
@@ -417,15 +417,15 @@ var require_index_cjs = __commonJS({
|
|
|
417
417
|
return f6(this.value).map(() => this.value);
|
|
418
418
|
}
|
|
419
419
|
asyncMap(f6) {
|
|
420
|
-
return
|
|
420
|
+
return ResultAsync29.fromSafePromise(f6(this.value));
|
|
421
421
|
}
|
|
422
422
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
423
423
|
unwrapOr(_v) {
|
|
424
424
|
return this.value;
|
|
425
425
|
}
|
|
426
426
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
427
|
-
match(
|
|
428
|
-
return
|
|
427
|
+
match(ok45, _err) {
|
|
428
|
+
return ok45(this.value);
|
|
429
429
|
}
|
|
430
430
|
safeUnwrap() {
|
|
431
431
|
const value = this.value;
|
|
@@ -521,15 +521,15 @@ var require_index_cjs = __commonJS({
|
|
|
521
521
|
var fromThrowable20 = exports2.Result.fromThrowable;
|
|
522
522
|
exports2.Err = Err;
|
|
523
523
|
exports2.Ok = Ok;
|
|
524
|
-
exports2.ResultAsync =
|
|
524
|
+
exports2.ResultAsync = ResultAsync29;
|
|
525
525
|
exports2.err = err41;
|
|
526
526
|
exports2.errAsync = errAsync12;
|
|
527
527
|
exports2.fromAsyncThrowable = fromAsyncThrowable9;
|
|
528
528
|
exports2.fromPromise = fromPromise;
|
|
529
529
|
exports2.fromSafePromise = fromSafePromise2;
|
|
530
530
|
exports2.fromThrowable = fromThrowable20;
|
|
531
|
-
exports2.ok =
|
|
532
|
-
exports2.okAsync =
|
|
531
|
+
exports2.ok = ok41;
|
|
532
|
+
exports2.okAsync = okAsync13;
|
|
533
533
|
exports2.safeTry = safeTry;
|
|
534
534
|
}
|
|
535
535
|
});
|
|
@@ -5321,8 +5321,8 @@ var require_resolve_flow_scalar = __commonJS({
|
|
|
5321
5321
|
};
|
|
5322
5322
|
function parseCharCode(source, offset, length, onError) {
|
|
5323
5323
|
const cc = source.substr(offset, length);
|
|
5324
|
-
const
|
|
5325
|
-
const code =
|
|
5324
|
+
const ok41 = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
|
|
5325
|
+
const code = ok41 ? parseInt(cc, 16) : NaN;
|
|
5326
5326
|
if (isNaN(code)) {
|
|
5327
5327
|
const raw = source.substr(offset - 2, length + 2);
|
|
5328
5328
|
onError(offset - 2, "BAD_DQ_ESCAPE", `Invalid escape sequence ${raw}`);
|
|
@@ -22262,10 +22262,10 @@ var require_array = __commonJS({
|
|
|
22262
22262
|
"use strict";
|
|
22263
22263
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
22264
22264
|
exports2.splitWhen = exports2.flatten = void 0;
|
|
22265
|
-
function
|
|
22265
|
+
function flatten2(items) {
|
|
22266
22266
|
return items.reduce((collection, item) => [].concat(collection, item), []);
|
|
22267
22267
|
}
|
|
22268
|
-
exports2.flatten =
|
|
22268
|
+
exports2.flatten = flatten2;
|
|
22269
22269
|
function splitWhen(items, predicate) {
|
|
22270
22270
|
const result = [[]];
|
|
22271
22271
|
let groupIndex = 0;
|
|
@@ -49496,9 +49496,9 @@ var tq = k((oq) => {
|
|
|
49496
49496
|
});
|
|
49497
49497
|
var sq = k((aq) => {
|
|
49498
49498
|
Object.defineProperty(aq, "__esModule", { value: true });
|
|
49499
|
-
var
|
|
49499
|
+
var ok41 = Q$(), tk = { keyword: "not", schemaType: ["object", "boolean"], trackErrors: true, code($) {
|
|
49500
49500
|
let { gen: X, schema: J, it: Q } = $;
|
|
49501
|
-
if ((0,
|
|
49501
|
+
if ((0, ok41.alwaysValidSchema)(Q, J)) {
|
|
49502
49502
|
$.fail();
|
|
49503
49503
|
return;
|
|
49504
49504
|
}
|
|
@@ -63484,6 +63484,104 @@ function collectElements(elements, screen) {
|
|
|
63484
63484
|
walk(elements);
|
|
63485
63485
|
return into;
|
|
63486
63486
|
}
|
|
63487
|
+
var OCCLUDED_BY_OVERLAP_TAG = "[occluded-by-overlap]";
|
|
63488
|
+
var FULL_BBOX_CONTAINMENT_RATIO = 0.85;
|
|
63489
|
+
function frameContainsPoint(frame, point) {
|
|
63490
|
+
return point.x >= frame.x && point.x < frame.x + frame.width && point.y >= frame.y && point.y < frame.y + frame.height;
|
|
63491
|
+
}
|
|
63492
|
+
function frameCenter(frame) {
|
|
63493
|
+
return { x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 };
|
|
63494
|
+
}
|
|
63495
|
+
function frameArea(frame) {
|
|
63496
|
+
return Math.max(0, frame.width) * Math.max(0, frame.height);
|
|
63497
|
+
}
|
|
63498
|
+
function intersectionArea(left, right) {
|
|
63499
|
+
const x1 = Math.max(left.x, right.x);
|
|
63500
|
+
const y12 = Math.max(left.y, right.y);
|
|
63501
|
+
const x22 = Math.min(left.x + left.width, right.x + right.width);
|
|
63502
|
+
const y22 = Math.min(left.y + left.height, right.y + right.height);
|
|
63503
|
+
if (x22 <= x1 || y22 <= y12) {
|
|
63504
|
+
return 0;
|
|
63505
|
+
}
|
|
63506
|
+
return (x22 - x1) * (y22 - y12);
|
|
63507
|
+
}
|
|
63508
|
+
function selfNode(element, input) {
|
|
63509
|
+
if (!element.frame || !isInViewport(element.frame, input.screen)) {
|
|
63510
|
+
return void 0;
|
|
63511
|
+
}
|
|
63512
|
+
return {
|
|
63513
|
+
element,
|
|
63514
|
+
frame: element.frame,
|
|
63515
|
+
ancestors: input.ancestors,
|
|
63516
|
+
treeOrder: input.startOrder
|
|
63517
|
+
};
|
|
63518
|
+
}
|
|
63519
|
+
function flattenList(list, input) {
|
|
63520
|
+
let nextOrder = input.startOrder;
|
|
63521
|
+
const collected = [];
|
|
63522
|
+
for (const element of list) {
|
|
63523
|
+
const subtree = flattenSubtree(element, { ...input, startOrder: nextOrder });
|
|
63524
|
+
collected.push(...subtree.nodes);
|
|
63525
|
+
nextOrder = subtree.nextOrder;
|
|
63526
|
+
}
|
|
63527
|
+
return { nodes: collected, nextOrder };
|
|
63528
|
+
}
|
|
63529
|
+
function flattenSubtree(element, input) {
|
|
63530
|
+
const self2 = selfNode(element, input);
|
|
63531
|
+
const selfNodes = self2 ? [self2] : [];
|
|
63532
|
+
const orderAfterSelf = self2 ? input.startOrder + 1 : input.startOrder;
|
|
63533
|
+
if (!element.children || element.children.length === 0) {
|
|
63534
|
+
return { nodes: selfNodes, nextOrder: orderAfterSelf };
|
|
63535
|
+
}
|
|
63536
|
+
const childAncestors = new Set(input.ancestors);
|
|
63537
|
+
childAncestors.add(element);
|
|
63538
|
+
const childOutput = flattenList(element.children, {
|
|
63539
|
+
ancestors: childAncestors,
|
|
63540
|
+
screen: input.screen,
|
|
63541
|
+
startOrder: orderAfterSelf
|
|
63542
|
+
});
|
|
63543
|
+
return {
|
|
63544
|
+
nodes: [...selfNodes, ...childOutput.nodes],
|
|
63545
|
+
nextOrder: childOutput.nextOrder
|
|
63546
|
+
};
|
|
63547
|
+
}
|
|
63548
|
+
function flatten(elements, screen) {
|
|
63549
|
+
return flattenList(elements, {
|
|
63550
|
+
ancestors: /* @__PURE__ */ new Set(),
|
|
63551
|
+
screen,
|
|
63552
|
+
startOrder: 0
|
|
63553
|
+
}).nodes;
|
|
63554
|
+
}
|
|
63555
|
+
function blocksTapPoint(target, candidate) {
|
|
63556
|
+
return frameContainsPoint(candidate.frame, frameCenter(target.frame));
|
|
63557
|
+
}
|
|
63558
|
+
function fullyCoversBoundingBox(target, candidate) {
|
|
63559
|
+
const targetArea = frameArea(target.frame);
|
|
63560
|
+
if (targetArea <= 0) {
|
|
63561
|
+
return false;
|
|
63562
|
+
}
|
|
63563
|
+
return intersectionArea(target.frame, candidate.frame) / targetArea >= FULL_BBOX_CONTAINMENT_RATIO;
|
|
63564
|
+
}
|
|
63565
|
+
function isOccluder(target, candidate) {
|
|
63566
|
+
if (candidate.treeOrder <= target.treeOrder) {
|
|
63567
|
+
return false;
|
|
63568
|
+
}
|
|
63569
|
+
if (candidate.ancestors.has(target.element)) {
|
|
63570
|
+
return false;
|
|
63571
|
+
}
|
|
63572
|
+
if (target.ancestors.has(candidate.element)) {
|
|
63573
|
+
return false;
|
|
63574
|
+
}
|
|
63575
|
+
return blocksTapPoint(target, candidate) || fullyCoversBoundingBox(target, candidate);
|
|
63576
|
+
}
|
|
63577
|
+
function isOccluded(target, nodes) {
|
|
63578
|
+
return nodes.some((candidate) => isOccluder(target, candidate));
|
|
63579
|
+
}
|
|
63580
|
+
function detectOccludedElements(elements, screen) {
|
|
63581
|
+
const nodes = flatten(elements, screen);
|
|
63582
|
+
const occludedElements = nodes.filter((target) => isOccluded(target, nodes)).map((node) => node.element);
|
|
63583
|
+
return new Set(occludedElements);
|
|
63584
|
+
}
|
|
63487
63585
|
function resolveLabel(element) {
|
|
63488
63586
|
return element.AXLabel ?? element.AXValue ?? "";
|
|
63489
63587
|
}
|
|
@@ -63509,15 +63607,27 @@ function resolveClippingTags(frame, screen) {
|
|
|
63509
63607
|
}
|
|
63510
63608
|
return tags.length > 0 ? ` ${tags.join(" ")}` : "";
|
|
63511
63609
|
}
|
|
63512
|
-
function formatElement(element,
|
|
63610
|
+
function formatElement(element, context) {
|
|
63513
63611
|
const type2 = resolveType(element);
|
|
63514
63612
|
const label = resolveLabel(element);
|
|
63515
63613
|
const frame = element.frame ?? { x: 0, y: 0, width: 0, height: 0 };
|
|
63516
63614
|
const cx = Math.round(frame.x + frame.width / 2);
|
|
63517
63615
|
const cy = Math.round(frame.y + frame.height / 2);
|
|
63518
63616
|
const state = element.enabled === false ? " [disabled]" : "";
|
|
63519
|
-
const clipping = resolveClippingTags(frame, screen);
|
|
63520
|
-
|
|
63617
|
+
const clipping = resolveClippingTags(frame, context.screen);
|
|
63618
|
+
const occluded = context.occluded.has(element) ? ` ${OCCLUDED_BY_OVERLAP_TAG}` : "";
|
|
63619
|
+
return `[${type2}] "${label}" at (${String(cx)}, ${String(cy)}) size ${String(Math.round(frame.width))}x${String(Math.round(frame.height))}${state}${clipping}${occluded}`;
|
|
63620
|
+
}
|
|
63621
|
+
function collectPrunedOccluded(list, query) {
|
|
63622
|
+
return list.flatMap((element) => {
|
|
63623
|
+
const inViewport = element.frame !== void 0 && isInViewport(element.frame, query.screen);
|
|
63624
|
+
const self2 = inViewport && query.occluded.has(element) && !query.visible.has(element) ? [element] : [];
|
|
63625
|
+
const children = element.children ? collectPrunedOccluded(element.children, query) : [];
|
|
63626
|
+
return [...self2, ...children];
|
|
63627
|
+
});
|
|
63628
|
+
}
|
|
63629
|
+
function findPrunedOccluded(query) {
|
|
63630
|
+
return collectPrunedOccluded(query.elements, query);
|
|
63521
63631
|
}
|
|
63522
63632
|
function formatAccessibilityElements(elements) {
|
|
63523
63633
|
const app = elements.find((element) => element.type === "Application");
|
|
@@ -63525,7 +63635,12 @@ function formatAccessibilityElements(elements) {
|
|
|
63525
63635
|
const screenHeight = app?.frame?.height ?? DEFAULT_SCREEN_HEIGHT;
|
|
63526
63636
|
const screen = { width: screenWidth, height: screenHeight };
|
|
63527
63637
|
const visible = collectElements(elements, screen);
|
|
63528
|
-
const
|
|
63638
|
+
const visibleSet = new Set(visible);
|
|
63639
|
+
const occluded = detectOccludedElements(elements, screen);
|
|
63640
|
+
const context = { screen, occluded };
|
|
63641
|
+
const prunedOccluded = findPrunedOccluded({ elements, visible: visibleSet, occluded, screen });
|
|
63642
|
+
const renderable = [...visible, ...prunedOccluded];
|
|
63643
|
+
const elementList = renderable.length === 0 ? "No elements found." : renderable.map((element) => formatElement(element, context)).join("\n");
|
|
63529
63644
|
const appName = app?.AXLabel;
|
|
63530
63645
|
return appName ? `Running app: ${appName}
|
|
63531
63646
|
|
|
@@ -63713,7 +63828,7 @@ function createListAppsTool(udid = "booted") {
|
|
|
63713
63828
|
}
|
|
63714
63829
|
var DEFAULT_LONG_PRESS_DURATION_MS = 500;
|
|
63715
63830
|
var MIN_PLAUSIBLE_LONG_PRESS_DURATION_MS = 100;
|
|
63716
|
-
var DEFAULT_SWIPE_DURATION_MS =
|
|
63831
|
+
var DEFAULT_SWIPE_DURATION_MS = 300;
|
|
63717
63832
|
var MIN_PLAUSIBLE_SWIPE_DURATION_MS = 50;
|
|
63718
63833
|
var MS_PER_SECOND = 1e3;
|
|
63719
63834
|
var ENTER_KEY_CODE = "0x28";
|
|
@@ -63836,13 +63951,13 @@ function createLongPressTool(udid = "booted") {
|
|
|
63836
63951
|
}
|
|
63837
63952
|
var DURATION_DESCRIPTION2 = `Gesture duration in milliseconds. Default ${String(
|
|
63838
63953
|
DEFAULT_SWIPE_DURATION_MS
|
|
63839
|
-
)}ms (flick) works for
|
|
63954
|
+
)}ms (controlled flick) works for most lists and avoids overshoot on medium-density content. Examples: duration 500 = 0.5 seconds, duration 1000 = 1 second. Velocity = distance / duration - raise duration at fixed distance to slow the gesture and reduce momentum. Raise to 500-800ms for slow controlled scrolling on long lists; lower to 100-150ms for a fast flick when long-distance scroll is desired. Values under ${String(
|
|
63840
63955
|
MIN_PLAUSIBLE_SWIPE_DURATION_MS
|
|
63841
63956
|
)}ms almost always indicate a unit mistake (seconds passed instead of milliseconds).`;
|
|
63842
63957
|
var DELTA_DESCRIPTION = "Pixel distance between interpolated touch points along the swipe path. Smaller values (e.g. 5) produce a denser event stream - smoother motion and more controllable stop-velocity, recommended when combining with a raised duration to tame long-list overshoot. Larger values produce coarser strokes. Omit to use idb defaults.";
|
|
63843
63958
|
var TOOL_DESCRIPTION2 = `Swipe on the screen from one point to another. Duration is in milliseconds (default ${String(
|
|
63844
63959
|
DEFAULT_SWIPE_DURATION_MS
|
|
63845
|
-
)}ms, a flick). Examples: duration 500 = 0.5 seconds, duration 1000 = 1 second.
|
|
63960
|
+
)}ms, a controlled flick). Examples: duration 500 = 0.5 seconds, duration 1000 = 1 second. The default duration suits most scrolling, sheet dismissal, and paging; shorten to 100-150ms when you need a long-distance fast flick; raise to 500+ for slow controlled drag (reorder, pan). For long lists where the default overshoots: shorten swipe distance AND raise duration; optionally lower delta for denser touch events and a more controllable stop. Do not pass seconds (e.g. 0.5) - that would swipe for less than a millisecond.`;
|
|
63846
63961
|
var SWIPE_SCHEMA = {
|
|
63847
63962
|
x_start: external_exports.number(),
|
|
63848
63963
|
y_start: external_exports.number(),
|
|
@@ -63887,7 +64002,7 @@ function buildSuccessText2(input) {
|
|
|
63887
64002
|
MIN_PLAUSIBLE_SWIPE_DURATION_MS
|
|
63888
64003
|
)}ms - this is almost certainly a unit mistake. The duration parameter is in milliseconds; use ${String(
|
|
63889
64004
|
DEFAULT_SWIPE_DURATION_MS
|
|
63890
|
-
)}ms for
|
|
64005
|
+
)}ms for the default controlled flick and 500-800ms for slow drag (e.g. duration 500 = 0.5 seconds).`;
|
|
63891
64006
|
}
|
|
63892
64007
|
return base;
|
|
63893
64008
|
}
|
|
@@ -65151,6 +65266,15 @@ function normalizeSimulatorUnhealthy(event) {
|
|
|
65151
65266
|
}
|
|
65152
65267
|
];
|
|
65153
65268
|
}
|
|
65269
|
+
function normalizeHookRetry(event) {
|
|
65270
|
+
return [
|
|
65271
|
+
{
|
|
65272
|
+
kind: "text",
|
|
65273
|
+
style: "default",
|
|
65274
|
+
text: `[${event.itemName}] HOOK_RETRY ${String(event.attempt)}/${String(event.maxAttempts)} after ${event.previousErrorType}`
|
|
65275
|
+
}
|
|
65276
|
+
];
|
|
65277
|
+
}
|
|
65154
65278
|
function normalizeSuiteCompleted(event) {
|
|
65155
65279
|
return [
|
|
65156
65280
|
{
|
|
@@ -65168,6 +65292,7 @@ var HANDLERS = {
|
|
|
65168
65292
|
ITEM_TIMEOUT: (event) => normalizeItemTimeout(event),
|
|
65169
65293
|
ITEM_ABORTED: (event) => normalizeItemAborted(event),
|
|
65170
65294
|
SIMULATOR_UNHEALTHY: (event) => normalizeSimulatorUnhealthy(event),
|
|
65295
|
+
HOOK_RETRY: (event) => normalizeHookRetry(event),
|
|
65171
65296
|
SUITE_COMPLETED: (event) => normalizeSuiteCompleted(event)
|
|
65172
65297
|
};
|
|
65173
65298
|
function normalizeSuiteEvent(event) {
|
|
@@ -65571,6 +65696,7 @@ var SUITE_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
65571
65696
|
"ITEM_TIMEOUT",
|
|
65572
65697
|
"ITEM_ABORTED",
|
|
65573
65698
|
"SIMULATOR_UNHEALTHY",
|
|
65699
|
+
"HOOK_RETRY",
|
|
65574
65700
|
"SUITE_COMPLETED"
|
|
65575
65701
|
]);
|
|
65576
65702
|
var UI_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -65681,6 +65807,14 @@ function reduceSimulatorUnhealthy(state, event) {
|
|
|
65681
65807
|
};
|
|
65682
65808
|
return { ...state, log: [...state.log, entry] };
|
|
65683
65809
|
}
|
|
65810
|
+
function reduceHookRetry(state, event) {
|
|
65811
|
+
const entry = {
|
|
65812
|
+
itemId: event.itemId,
|
|
65813
|
+
source: "annotation",
|
|
65814
|
+
lines: normalizeSuiteEvent(event)
|
|
65815
|
+
};
|
|
65816
|
+
return { ...state, log: [...state.log, entry] };
|
|
65817
|
+
}
|
|
65684
65818
|
function reduceSuiteEvent(state, event) {
|
|
65685
65819
|
switch (event.type) {
|
|
65686
65820
|
case "ITEM_QUEUED": {
|
|
@@ -65698,6 +65832,9 @@ function reduceSuiteEvent(state, event) {
|
|
|
65698
65832
|
case "SIMULATOR_UNHEALTHY": {
|
|
65699
65833
|
return reduceSimulatorUnhealthy(state, event);
|
|
65700
65834
|
}
|
|
65835
|
+
case "HOOK_RETRY": {
|
|
65836
|
+
return reduceHookRetry(state, event);
|
|
65837
|
+
}
|
|
65701
65838
|
case "SUITE_COMPLETED": {
|
|
65702
65839
|
return { ...state, suiteEnded: true };
|
|
65703
65840
|
}
|
|
@@ -74218,11 +74355,13 @@ async function runViewUiCapture(context, state) {
|
|
|
74218
74355
|
state
|
|
74219
74356
|
});
|
|
74220
74357
|
}
|
|
74221
|
-
var VIEW_UI_DESCRIPTION = `Capture current screen state:
|
|
74358
|
+
var VIEW_UI_DESCRIPTION = `Capture current screen state. This is your sole observation tool: returns a screenshot (your visual perception of the app) and an accessibility tree (interactability metadata and tap coordinates) in a single call. Use for all state observation, navigation decisions, element verification, and pre-interaction checks.
|
|
74359
|
+
|
|
74360
|
+
The screenshot is the ground truth for what screen you are on, what state the app is in, what content is visible, and what UX is happening. The a11y tree is authoritative for two questions only: "is this element interactable?" and "what tap coordinates should I use?" \u2014 never derive coordinates from the screenshot.
|
|
74222
74361
|
|
|
74223
74362
|
The result begins with a <screen_id> tag containing the current screen identifier. Use this to detect screen changes and track navigation history.
|
|
74224
74363
|
|
|
74225
|
-
|
|
74364
|
+
The \`screenshot\` tool is reserved exclusively for polling during a transient loading state to avoid incrementing the stuck-loop counter. Do not use \`screenshot\` for any other observation purpose.
|
|
74226
74365
|
|
|
74227
74366
|
IMPORTANT: Snapshot coordinates and screenshot pixels are in the same logical point space. Do not apply any scaling factor (no 2x retina adjustment).`;
|
|
74228
74367
|
var VIEW_UI_TOOL_NAME = "mcp__mobile-ios__view_ui";
|
|
@@ -74671,12 +74810,19 @@ function startAndRun(params) {
|
|
|
74671
74810
|
});
|
|
74672
74811
|
});
|
|
74673
74812
|
}
|
|
74813
|
+
var PERCEPTION_MODEL_SECTION = `## Perception Model
|
|
74814
|
+
|
|
74815
|
+
Every \`view_ui\` call returns two artifacts simultaneously:
|
|
74816
|
+
|
|
74817
|
+
- **Screenshot** \u2014 your visual perception of the app. This is the ground truth for what screen you are on, what state the app is in, what content is visible, and what UX is happening. Reason from the screenshot first when answering "what is the app showing me right now?"
|
|
74818
|
+
- **A11y tree** \u2014 metadata about that visual reality. It is authoritative for two questions only: "is this element interactable?" and "what tap coordinates should I use?" Never derive coordinates from the screenshot, even when the screenshot appears to show an element clearly.
|
|
74819
|
+
|
|
74820
|
+
Precedence: the screenshot governs comprehension of screen identity, state, and content. The a11y tree governs interactability and coordinates. These domains do not overlap \u2014 there is no scenario where the screenshot overrides a11y-sourced coordinates, and there is no scenario where the a11y tree overrides screenshot-sourced understanding of what the app is showing.`;
|
|
74674
74821
|
var TOOL_SELECTION_SECTION = `## Tool Selection
|
|
74675
74822
|
|
|
74676
|
-
- \`view_ui\` \u2014 returns
|
|
74677
|
-
- \`screenshot\` \u2014
|
|
74678
|
-
-
|
|
74679
|
-
- \`screenshot\` calls do not emit a \`<screen_id>\` and do not advance the stuck-loop counter; if screen identity tracking matters, use \`view_ui\``;
|
|
74823
|
+
- \`view_ui\` \u2014 your sole observation tool; returns a screenshot (visual ground truth) and an a11y tree (interactability metadata and tap coordinates) in one call; use for all state observation, navigation decisions, element verification, and pre-interaction checks
|
|
74824
|
+
- \`screenshot\` \u2014 loading polls only; use exclusively while waiting for a transient loading state to resolve, to avoid false stuck-loop counter increments; do not use \`screenshot\` for any other observation purpose \u2014 see LOADING_STATE_RULE
|
|
74825
|
+
- \`screenshot\` calls do not emit a \`<screen_id>\` and do not advance the stuck-loop counter`;
|
|
74680
74826
|
var DEV_ENVIRONMENT_SECTION = `## Environment
|
|
74681
74827
|
|
|
74682
74828
|
This is a development build. Debug overlays and internal messages are expected artifacts \u2014 do not report them as findings.`;
|
|
@@ -74689,9 +74835,8 @@ At every reasoning step, maintain a mental ledger:
|
|
|
74689
74835
|
|
|
74690
74836
|
Consult the ledger before every action. Always prefer navigating to a QUEUE screen over a VISITED one.`;
|
|
74691
74837
|
var SESSION_START_RULE = `Before taking any other action \u2014 including initializing the Working State ledger or emitting findings \u2014 call \`view_ui\` once to observe the starting screen`;
|
|
74692
|
-
var POST_ACTION_OBSERVE_RULE = `After any action, observe the screen before deciding next step
|
|
74693
|
-
var
|
|
74694
|
-
var BACK_NAV_RULE = `After navigating forward to any new screen: attempt to return to the expected parent in PATH \u2014 consult App Knowledge first for the correct exit gesture on this screen, then try in order: (1) any visible back/close button, (2) OS back gesture, (3) swipe up, (4) swipe down, (5) swipe left, (6) swipe right \u2014 confirm return via \`screenshot\` if the parent is visually unambiguous, \`view_ui\` otherwise \u2014 only after ALL attempts fail emit a \`back-nav-failure\` finding, then navigate forward again to continue`;
|
|
74838
|
+
var POST_ACTION_OBSERVE_RULE = `After any action, call \`view_ui\` to observe the resulting screen state before deciding the next step. Exception: if the screen is in a transient loading state, use \`screenshot\` to poll \u2014 see LOADING_STATE_RULE.`;
|
|
74839
|
+
var BACK_NAV_RULE = `After navigating forward to any new screen: attempt to return to the expected parent in PATH \u2014 consult App Knowledge first for the correct exit gesture on this screen, then try in order: (1) any visible back/close button, (2) OS back gesture, (3) swipe up, (4) swipe down, (5) swipe left, (6) swipe right \u2014 confirm return via \`view_ui\` \u2014 only after ALL attempts fail emit a \`back-nav-failure\` finding, then navigate forward again to continue`;
|
|
74695
74840
|
var QUEUE_FIRST_RULE = `Before selecting any action, prefer navigating to a QUEUE screen over re-exploring a VISITED one`;
|
|
74696
74841
|
var STUCK_LOOP_RULE = `Stuck loop \u2014 emit a \`stuck-loop\` finding when any of these signals occur:
|
|
74697
74842
|
(1) \`view_ui\` returns the same \`<screen_id>\` across 3 or more consecutive \`view_ui\` calls
|
|
@@ -74714,34 +74859,49 @@ Example: if tab bar positions \`Tokens(-31) ETH(65) ... Tron(352)\` are unchange
|
|
|
74714
74859
|
Notes:
|
|
74715
74860
|
- \`screenshot\`-only calls do not update the stuck-loop counter; only \`view_ui\` calls count
|
|
74716
74861
|
- Zero-delta scroll stall is not a separate finding type \u2014 report as \`stuck-loop\``;
|
|
74717
|
-
var LOADING_STATE_RULE = `Transient loading state: when the screen shows spinners, skeleton screens, progress bars, "Loading..." text, or placeholder content NOT described in spec or app context \u2014 use \`screenshot\` to poll for resolution (up to 3 retries);
|
|
74862
|
+
var LOADING_STATE_RULE = `Transient loading state: when the screen shows spinners, skeleton screens, progress bars, "Loading..." text, or placeholder content NOT described in spec or app context \u2014 use \`screenshot\` to poll for resolution (up to 3 retries); \`screenshot\` is used here specifically to avoid incrementing the stuck-loop counter during intentional wait cycles, not because it provides different visual information. Call \`view_ui\` on the final check or whenever you are ready to act. If loading persists after 3 retries, proceed with what is visible. If spec or app context explicitly describes a loading screen as a step, skip polling \u2014 call \`view_ui\` and assert normally.`;
|
|
74718
74863
|
var EXPECTED_CONTENT_MISSING_RULE = `Expected content missing: when \`view_ui\` shows no loading indicator yet omits an element named or strongly implied by spec or app context \u2014 and its absence is not semantically consistent with the current screen \u2014 call \`wait_seconds\` with 2\u20135 seconds and retry \`view_ui\` up to 2 times; if element remains absent, emit a \`missing-content\` finding stating what was expected and what was observed`;
|
|
74719
|
-
var CLIPPED_ELEMENT_RULE = `Never tap an element tagged \`[clipped-top]\`, \`[clipped-bottom]\`, \`[clipped-left]\`, or \`[clipped-right]\` \u2014 scroll to fully reveal it first, then re-call \`view_ui\` before tapping
|
|
74864
|
+
var CLIPPED_ELEMENT_RULE = `Never tap an element tagged \`[clipped-top]\`, \`[clipped-bottom]\`, \`[clipped-left]\`, or \`[clipped-right]\` \u2014 scroll to fully reveal it first, then re-call \`view_ui\` before tapping. Only the explicit \`[clipped-*]\` tag in the a11y tree triggers this rule. Do NOT infer clipping from coordinate proximity to viewport edges (a low y-coord does not imply \`[clipped-top]\`).`;
|
|
74720
74865
|
var SCROLL_FOLD_RULE = `Scrollable lists: elements outside the visible viewport are absent from the a11y tree by design \u2014 this applies to elements below the fold in vertical lists AND elements clipped off-left or off-right in horizontal lists \u2014 scroll or swipe in the appropriate axis to reveal before asserting presence or absence; never emit a finding solely because list items, rows, or tabs are missing from the tree on a scrollable screen; if swipe attempts yield no position change across 2+ cycles, apply the scroll-stall path in STUCK_LOOP_RULE.`;
|
|
74866
|
+
var COORDINATE_SOURCE_RULE = `Never derive tap coordinates from the screenshot. Coordinates are authoritative only from the a11y tree returned by \`view_ui\`. Snapshot coordinates and screenshot pixels are in the same logical point space \u2014 no scaling factor is required. If an element is visible in the screenshot but absent from the a11y tree, apply A11Y_FALLBACK_RULE \u2014 do not estimate its position from visual layout.`;
|
|
74867
|
+
var GHOST_A11Y_ELEMENT_RULE = `Ghost a11y element: an element is a ghost when EITHER of these holds:
|
|
74868
|
+
(1) it is tagged \`${OCCLUDED_BY_OVERLAP_TAG}\` in the a11y tree (deterministic detector flagged it as covered by a later-z-order non-ancestor element whose frame either contains the target's tap-point center or fully covers its bbox). The detector is conservative \u2014 absence of the tag does NOT prove the element is not occluded; criterion (2) still applies, OR
|
|
74869
|
+
(2) the a11y tree reports an element AND the screenshot at that element's coordinates shows visibly different UI (a different layer, a different screen, no visible element at all). This includes the case where the a11y tree contains elements from two contradictory layers (e.g. a "USDC on ETH Network" modal AND a "USDC on SOL Network" modal at the same time, when only one can be visually present).
|
|
74870
|
+
|
|
74871
|
+
CRITICAL \u2014 finding-emission is mandatory and must happen FIRST:
|
|
74872
|
+
- The instant you observe ANY a11y/screenshot mismatch (criterion 1 or 2), STOP planning gestures.
|
|
74873
|
+
- Emit a \`ghost-a11y-element\` finding via \`report_finding\` BEFORE attempting any recovery. The finding must state: (a) what you intended to tap, (b) the a11y element's reported coordinates and label, (c) whether the \`${OCCLUDED_BY_OVERLAP_TAG}\` tag was present, (d) what the screenshot shows at those coordinates instead.
|
|
74874
|
+
- Recovery attempts WITHOUT first emitting the finding are a rule violation. The mismatch IS the bug \u2014 silently working around it loses the signal.
|
|
74875
|
+
- Do NOT tap a ghost element's reported coordinates. Even if the a11y label matches your intent, tapping at those coordinates will hit the visible layer's element at the same point, not the ghost.
|
|
74876
|
+
|
|
74877
|
+
After the finding is emitted, attempt to surface the correct layer in this order: (1) any visible close/X button on the blocking layer, (2) swipe down (sheet dismiss), (3) OS back gesture, (4) swipe up. Call a fresh \`view_ui\` after each recovery attempt before retrying the original tap. If the same overlap recurs after all recovery attempts, emit a separate \`stuck-modal\` finding for the visible layer that is blocking access.`;
|
|
74721
74878
|
var A11Y_FALLBACK_RULE = `Missing a11y element \u2014 if you intend to tap or interact with a UI element and that element is absent from the most recent \`view_ui\` a11y tree, emit a \`missing-a11y-element\` finding immediately, then continue: in freestyle mode keep exploring other reachable screens; in spec mode advance to the next step.
|
|
74722
74879
|
|
|
74723
74880
|
The finding must state:
|
|
74724
|
-
(1) your intent (what you were trying to do)
|
|
74725
|
-
(2) the approximate visual region where the element appeared (
|
|
74726
|
-
(3) nearby labeled elements
|
|
74881
|
+
(1) your intent (what you were trying to do), in user-visible terms (e.g. "tap the Send button on the Portfolio screen")
|
|
74882
|
+
(2) the approximate visual region where the element appeared \u2014 name the screen and describe its location in words (e.g. "top-right of the Receive sheet"), not pixel coordinates; any coordinates referenced here are descriptive only and must NOT be used as a tap target (see COORDINATE_SOURCE_RULE)
|
|
74883
|
+
(3) nearby labeled elements that serve as landmarks \u2014 use their on-screen labels
|
|
74884
|
+
|
|
74885
|
+
When writing the \`description\` field, follow Description Style: never paste raw coordinates, hex addresses, screen IDs, or accessibility tree excerpts.
|
|
74727
74886
|
|
|
74728
74887
|
Rules:
|
|
74729
|
-
- Visible in the screenshot does NOT imply interactable; the a11y tree is authoritative
|
|
74730
|
-
- do NOT
|
|
74731
|
-
- do NOT attempt any pixel-based tap
|
|
74888
|
+
- Visible in the screenshot does NOT imply interactable; the a11y tree is authoritative for interactability and coordinates
|
|
74889
|
+
- COORDINATE_SOURCE_RULE applies; do NOT attempt any pixel-based tap
|
|
74732
74890
|
- do NOT retry at different coordinates
|
|
74733
74891
|
- do NOT long-press or swipe in the element's visual region as a fallback
|
|
74734
74892
|
- a failed pixel tap is never an \`interaction-regression\` \u2014 it is a \`missing-a11y-element\``;
|
|
74893
|
+
var FREESTYLE_ANTI_RATIONALIZATION_RULE = `Reframe-by-substitution check: triggers ONLY when you have stated an explicit prior intent \u2014 verbatim in your reasoning \u2014 to interact with element X to achieve goal Y, and the observed UI does NOT contain X performing Y. If in that situation you find yourself reasoning "the [different element] is functioning as the [intended Y]" or "this is just a different way of doing [intended action]" rather than observing the literal X performing Y, do NOT mark the goal achieved. Emit a \`spec-deviation\` finding stating: (a) your prior intent verbatim, (b) what the screenshot shows instead, (c) the reframing reasoning verbatim. This rule does NOT trigger on benign UI variation (button label "Continue" vs "Next" with the same effect) \u2014 only on substituting a different element/affordance for the one originally intended. Lighter than spec-mode ANTI_RATIONALIZATION_RULE because freestyle has no spec outcome text; the trigger is the agent's own prior intent statement.`;
|
|
74735
74894
|
var PLATFORM_FIRST_RUN_RULE = `OS permission and platform dialogs on fresh install are normal platform behavior, not app bugs \u2014 this includes: iOS notification permission ("Would Like to Send You Notifications"), iOS Face ID / Touch ID enrollment, iOS App Tracking Transparency, iOS "Allow Paste" prompts, Android runtime permission dialogs (camera, microphone, contacts, location, storage), and Android biometric prompts \u2014 when such a dialog appears while executing a step, dismiss it via the appropriate button (Allow, Don't Allow, OK, or OS back), then retry the action that triggered it; only emit \`spec-deviation\` if, after dismissing the dialog AND retrying the action, the expected screen or outcome still does not appear \u2014 do NOT emit any finding on the dialog itself.`;
|
|
74736
74895
|
var COMMON_RULE_BULLETS = [
|
|
74737
74896
|
SESSION_START_RULE,
|
|
74738
74897
|
POST_ACTION_OBSERVE_RULE,
|
|
74739
|
-
NO_REDUNDANT_CAPTURE_RULE,
|
|
74740
74898
|
BACK_NAV_RULE,
|
|
74741
74899
|
QUEUE_FIRST_RULE,
|
|
74742
74900
|
STUCK_LOOP_RULE,
|
|
74743
74901
|
LOADING_STATE_RULE,
|
|
74744
74902
|
EXPECTED_CONTENT_MISSING_RULE,
|
|
74903
|
+
COORDINATE_SOURCE_RULE,
|
|
74904
|
+
GHOST_A11Y_ELEMENT_RULE,
|
|
74745
74905
|
A11Y_FALLBACK_RULE,
|
|
74746
74906
|
CLIPPED_ELEMENT_RULE,
|
|
74747
74907
|
SCROLL_FOLD_RULE
|
|
@@ -74755,12 +74915,38 @@ Write the description (what you saw vs. expected, where, when) before committing
|
|
|
74755
74915
|
- LOW \u2014 speculative; only include when freestyle/low-confidence triggers require it`;
|
|
74756
74916
|
var FINDING_TAXONOMY_SECTION = `## Finding Types
|
|
74757
74917
|
|
|
74758
|
-
You may emit only these trigger types: \`back-nav-failure\`, \`dead-end\`, \`stuck-modal\`, \`stuck-loop\`, \`missing-a11y-element\`, \`missing-content\`, \`spec-deviation\`, \`destructive-only-exit\`. Do NOT emit \`design-system-violation\`, \`motion-regression\`, \`continuity-regression\`, \`interaction-regression\`, or \`loading-regression\` \u2014 those belong to other agents.
|
|
74918
|
+
You may emit only these trigger types: \`back-nav-failure\`, \`dead-end\`, \`stuck-modal\`, \`stuck-loop\`, \`missing-a11y-element\`, \`ghost-a11y-element\`, \`missing-content\`, \`spec-deviation\`, \`destructive-only-exit\`. Do NOT emit \`design-system-violation\`, \`motion-regression\`, \`continuity-regression\`, \`interaction-regression\`, or \`loading-regression\` \u2014 those belong to other agents.
|
|
74759
74919
|
|
|
74760
74920
|
${CONFIDENCE_RUBRIC_SECTION}`;
|
|
74921
|
+
var DESCRIPTION_STYLE_SECTION = `## Description Style
|
|
74922
|
+
|
|
74923
|
+
The \`description\` field is read by a QA tester who has not seen your reasoning. Write so they can reproduce and triage without internal context.
|
|
74924
|
+
|
|
74925
|
+
Required:
|
|
74926
|
+
- One short sentence first: the user action plus the observed problem, in product terms (use the on-screen labels of buttons, screens, and modals)
|
|
74927
|
+
- Add a second sentence if you can state the probable cause in product terms without implementation guessing (e.g. "modal stacked behind another modal", "address did not change after picking the network")
|
|
74928
|
+
- Past tense, declarative, plain English
|
|
74929
|
+
- Keep to 1\u20132 sentences
|
|
74930
|
+
|
|
74931
|
+
Forbidden:
|
|
74932
|
+
- Internal jargon: \`a11y tree\`, \`view_ui\`, \`screen_id\`, \`ghost element\`, \`occluded-by-overlap\`, \`clipped-*\`, \`stuck-loop\`, tool names
|
|
74933
|
+
- PATH notation (e.g. \`Home > Settings > Privacy\`), screen IDs, internal element tags
|
|
74934
|
+
- Pixel coordinates, element positions, hex offsets, raw addresses, technical IDs
|
|
74935
|
+
- First-person narration ("I tapped\u2026", "Let me try\u2026"), tool-call traces, reasoning steps
|
|
74936
|
+
- Speculation about implementation ("rendering issue", "z-index", "layering bug") \u2014 describe the user-visible effect instead
|
|
74937
|
+
|
|
74938
|
+
Example 1 \u2014 overlapping modals:
|
|
74939
|
+
- Bad: \`After selecting Solana from the network picker while viewing the ETH Receive screen, the a11y tree shows SOL Network confirmation elements ("SOL NETWORK" text at (201,319) ...) but the screenshot still visually displays the ETH Network Receive screen with address 0xb30...\`
|
|
74940
|
+
- Good: \`Picking SOL in the network selector did not change the receive address. The SOL confirmation modal appeared behind the network picker modal instead of replacing the ETH receive screen.\`
|
|
74941
|
+
|
|
74942
|
+
Example 2 \u2014 dead end:
|
|
74943
|
+
- Bad: \`view_ui shows no elements matching 'Back' or 'Close' on screen_id=privacy_settings_0; PATH is Home > Settings > Privacy; OS back gesture and swipe down/up/left/right all returned the same screen_id\`
|
|
74944
|
+
- Good: \`The Privacy Settings screen had no way to exit. Tapping the back area and swiping in every direction did not navigate away.\``;
|
|
74761
74945
|
var REPORTING_FINDINGS_BASE = `## Reporting Findings
|
|
74762
74946
|
|
|
74763
|
-
CRITICAL: When you observe a finding, call \`report_finding\` IMMEDIATELY \u2014 before taking any further actions. Do not batch findings. Do not wait until the end of the run. Each \`report_finding\` call atomically records one finding with the current screen attached; the server captures the screenshot. Do not pass screenshot paths or step indices. If you are uncertain whether something warrants a finding, do not report it \u2014 \`report_finding\` is for confirmed observations only
|
|
74947
|
+
CRITICAL: When you observe a finding, call \`report_finding\` IMMEDIATELY \u2014 before taking any further actions. Do not batch findings. Do not wait until the end of the run. Each \`report_finding\` call atomically records one finding with the current screen attached; the server captures the screenshot. Do not pass screenshot paths or step indices. If you are uncertain whether something warrants a finding, do not report it \u2014 \`report_finding\` is for confirmed observations only.
|
|
74948
|
+
|
|
74949
|
+
${DESCRIPTION_STYLE_SECTION}`;
|
|
74764
74950
|
function buildReportFindingSection(scenarioId) {
|
|
74765
74951
|
if (scenarioId === void 0) {
|
|
74766
74952
|
return REPORTING_FINDINGS_BASE;
|
|
@@ -74790,7 +74976,11 @@ function buildEnvSection(buildEnv3) {
|
|
|
74790
74976
|
|
|
74791
74977
|
${DEV_ENVIRONMENT_SECTION}` : "";
|
|
74792
74978
|
}
|
|
74793
|
-
var FREESTYLE_RULE_BULLETS = [
|
|
74979
|
+
var FREESTYLE_RULE_BULLETS = [
|
|
74980
|
+
...COMMON_RULE_BULLETS,
|
|
74981
|
+
PLATFORM_FIRST_RUN_RULE,
|
|
74982
|
+
FREESTYLE_ANTI_RATIONALIZATION_RULE
|
|
74983
|
+
];
|
|
74794
74984
|
var FREESTYLE_RULES_SECTION = buildRulesSection2(FREESTYLE_RULE_BULLETS);
|
|
74795
74985
|
var WHAT_TO_TEST_SECTION = `## What to Test
|
|
74796
74986
|
|
|
@@ -74834,6 +75024,8 @@ function buildFreestyleBody({
|
|
|
74834
75024
|
|
|
74835
75025
|
${contextBlock}
|
|
74836
75026
|
|
|
75027
|
+
${PERCEPTION_MODEL_SECTION}
|
|
75028
|
+
|
|
74837
75029
|
${TOOL_SELECTION_SECTION}
|
|
74838
75030
|
|
|
74839
75031
|
${FREESTYLE_RULES_SECTION}
|
|
@@ -74859,7 +75051,7 @@ var FREESTYLE_TEMPLATE = (options2) => {
|
|
|
74859
75051
|
const reportingSection = buildReportFindingSection(scenarioId);
|
|
74860
75052
|
return buildFreestyleBody({ contextBlock, environmentSection, reportingSection });
|
|
74861
75053
|
};
|
|
74862
|
-
var OUTCOME_LITERAL_RULE = `When verifying a step outcome or assertion, interpret all quantifiers literally and apply them exhaustively. Any keyword that imposes a universal or count-bound constraint \u2014 including but not limited to \`only\`, \`all\`, \`every\`, \`each\`, \`both\`, \`no\`, \`none\`, \`neither\`, \`exactly N\`, \`at least N\`, \`fewer than N\`, \`more than N\` \u2014 a single counter-example observed
|
|
75054
|
+
var OUTCOME_LITERAL_RULE = `When verifying a step outcome or assertion, interpret all quantifiers literally and apply them exhaustively. Any keyword that imposes a universal or count-bound constraint \u2014 including but not limited to \`only\`, \`all\`, \`every\`, \`each\`, \`both\`, \`no\`, \`none\`, \`neither\`, \`exactly N\`, \`at least N\`, \`fewer than N\`, \`more than N\` \u2014 a single counter-example observed via \`view_ui\` constitutes a failed constraint.
|
|
74863
75055
|
|
|
74864
75056
|
Scope:
|
|
74865
75057
|
- Applies only when the outcome text contains a universal or count-bound quantifier
|
|
@@ -74875,10 +75067,10 @@ On violation: if one item violates the constraint, emit \`spec-deviation\` immed
|
|
|
74875
75067
|
Precedence: when the counter-evidence is an element absent from the a11y tree, A11Y_FALLBACK_RULE determines the finding type (\`missing-a11y-element\`). OUTCOME_LITERAL_RULE applies only to observed-but-unwanted elements.`;
|
|
74876
75068
|
var ANTI_RATIONALIZATION_RULE = `During outcome verification, monitor your own reasoning for reconciliation hypotheses. A reconciliation hypothesis is any reasoning that re-frames, redefines, or reinterprets the observed counter-example or target class in order to produce agreement with the spec outcome \u2014 regardless of phrasing. Treat such reasoning as a deviation signal, not a resolution: stop, do NOT mark the step complete, and emit \`spec-deviation\` with: (a) the literal outcome text, (b) the specific observation that triggered the hypothesis, (c) the reconciliation reasoning itself verbatim.
|
|
74877
75069
|
|
|
74878
|
-
Attestation: before marking any
|
|
75070
|
+
Attestation: before marking any step complete where an explicit \`\u2192 outcome\` is present in the spec step, state explicitly in your reasoning: \`No reconciliation hypothesis generated. Counter-examples found: [list or none].\` If you cannot make that statement honestly, a hypothesis exists \u2014 emit \`spec-deviation\`.
|
|
74879
75071
|
|
|
74880
75072
|
Ambiguity: when outcome verification is ambiguous, first re-verify via a fresh \`view_ui\` and re-evaluate against the outcome text. If still ambiguous after re-verification, emit \`spec-deviation\` citing the ambiguity \u2014 silence is not a pass, and marking the step complete without explicit evaluation does not qualify.`;
|
|
74881
|
-
var SPEC_ASSERTION_RULE = `Each item in \`**Assertions**\` is a mandatory pass/fail check \u2014 verify using \`view_ui
|
|
75073
|
+
var SPEC_ASSERTION_RULE = `Each item in \`**Assertions**\` is a mandatory pass/fail check \u2014 verify using \`view_ui\`; the screenshot embedded in the response is your visual evidence and the a11y tree confirms element presence and attributes. If the result cannot confirm the assertion, emit a \`spec-deviation\` finding based on what is observable.`;
|
|
74882
75074
|
var SPEC_PASSIVE_BREAKAGE_RULE = `Flag crash dialogs, unexpected system errors, or navigation failures that occur as a direct result of executing a spec step; if you observe a visibly broken element in passing while navigating, note it without interacting with it`;
|
|
74883
75075
|
var SPEC_RULE_BULLETS = [
|
|
74884
75076
|
...COMMON_RULE_BULLETS,
|
|
@@ -74905,7 +75097,7 @@ Each step has this shape:
|
|
|
74905
75097
|
<intent> [\u2192 <outcome>] [hint: <advisory>]
|
|
74906
75098
|
|
|
74907
75099
|
- The intent phrase is your goal. Achieve it by any reasonable UI path.
|
|
74908
|
-
- If an outcome state is present, it is your verification target. After acting,
|
|
75100
|
+
- If an outcome state is present, it is your verification target. After acting, call \`view_ui\` to confirm the outcome \u2014 the embedded screenshot verifies visual transitions and the a11y tree verifies element state. If no outcome is given, proceed when the action succeeds.
|
|
74909
75101
|
- A hint is advisory only. Prefer an element matching the hint, but if no literal match exists, use intent and visual context to select the best candidate. Never fail a step solely because a hint label is absent.
|
|
74910
75102
|
- Infer element role (primary action, secondary action, dismissal) from visual hierarchy, position, and hint text. Authors do not specify role.
|
|
74911
75103
|
- If no element satisfies the intent after exhausting visible UI, emit a \`spec-deviation\` finding and halt that step.`;
|
|
@@ -74934,6 +75126,8 @@ function buildSpecModeBody({
|
|
|
74934
75126
|
|
|
74935
75127
|
${contextBlock}
|
|
74936
75128
|
|
|
75129
|
+
${PERCEPTION_MODEL_SECTION}
|
|
75130
|
+
|
|
74937
75131
|
${TOOL_SELECTION_SECTION}
|
|
74938
75132
|
|
|
74939
75133
|
${SPEC_RULES_SECTION}
|
|
@@ -79360,6 +79554,7 @@ function formatOptionalNumber(value) {
|
|
|
79360
79554
|
// src/shell/debug-agent-events.ts
|
|
79361
79555
|
var TOOL_INPUT_MAX = 120;
|
|
79362
79556
|
var TOOL_ERROR_MAX = 120;
|
|
79557
|
+
var THOUGHT_TEXT_MAX = 240;
|
|
79363
79558
|
var UNSERIALIZABLE = "[unserializable]";
|
|
79364
79559
|
function tryStringify(value) {
|
|
79365
79560
|
const result = JSON.stringify(value);
|
|
@@ -79443,6 +79638,13 @@ function handleVisualStep(context, event) {
|
|
|
79443
79638
|
function handleError(context, event) {
|
|
79444
79639
|
context.logger.log("ERROR", `${event.agent} ${event.message}`);
|
|
79445
79640
|
}
|
|
79641
|
+
function flattenThoughtText(text) {
|
|
79642
|
+
return text.replaceAll(/\s+/g, " ").trim();
|
|
79643
|
+
}
|
|
79644
|
+
function handleThought(context, event) {
|
|
79645
|
+
const text = truncate(flattenThoughtText(event.text), THOUGHT_TEXT_MAX);
|
|
79646
|
+
context.logger.log("THOUGHT", `${event.agent} ${text}`);
|
|
79647
|
+
}
|
|
79446
79648
|
var AGENT_EVENT_HANDLERS = {
|
|
79447
79649
|
STAGE_START: handleStageStart,
|
|
79448
79650
|
STAGE_END: handleStageEnd,
|
|
@@ -79455,7 +79657,8 @@ var AGENT_EVENT_HANDLERS = {
|
|
|
79455
79657
|
INSPECTOR_STEP_START: handleInspectorStepStart,
|
|
79456
79658
|
INSPECTOR_STEP: handleInspectorStep,
|
|
79457
79659
|
VISUAL_STEP: handleVisualStep,
|
|
79458
|
-
ERROR: handleError
|
|
79660
|
+
ERROR: handleError,
|
|
79661
|
+
THOUGHT: handleThought
|
|
79459
79662
|
};
|
|
79460
79663
|
function handleAgentEvent(context, event) {
|
|
79461
79664
|
const handler2 = AGENT_EVENT_HANDLERS[event.type];
|
|
@@ -79502,6 +79705,12 @@ function handleSimulatorUnhealthy(logger, event) {
|
|
|
79502
79705
|
`sim=${event.simulatorUdid} failures=${String(event.consecutiveFailures)}`
|
|
79503
79706
|
);
|
|
79504
79707
|
}
|
|
79708
|
+
function handleHookRetry(logger, event) {
|
|
79709
|
+
logger.log(
|
|
79710
|
+
"HOOK_RETRY",
|
|
79711
|
+
`id=${event.itemId} sim=${event.simulatorUdid} attempt=${String(event.attempt)}/${String(event.maxAttempts)} prev=${event.previousErrorType}`
|
|
79712
|
+
);
|
|
79713
|
+
}
|
|
79505
79714
|
function handleSuiteCompleted(logger, event) {
|
|
79506
79715
|
logger.log(
|
|
79507
79716
|
"SUITE_COMPLETED",
|
|
@@ -79516,6 +79725,7 @@ var SUITE_EVENT_HANDLERS = {
|
|
|
79516
79725
|
ITEM_TIMEOUT: handleItemTimeout,
|
|
79517
79726
|
ITEM_ABORTED: handleItemAborted,
|
|
79518
79727
|
SIMULATOR_UNHEALTHY: handleSimulatorUnhealthy,
|
|
79728
|
+
HOOK_RETRY: handleHookRetry,
|
|
79519
79729
|
SUITE_COMPLETED: handleSuiteCompleted
|
|
79520
79730
|
};
|
|
79521
79731
|
function handleSuiteEvent(logger, event) {
|
|
@@ -85283,10 +85493,12 @@ var freestyleSchema = external_exports.union([external_exports.number().int().po
|
|
|
85283
85493
|
}
|
|
85284
85494
|
return value;
|
|
85285
85495
|
});
|
|
85496
|
+
var MAX_HOOK_RETRIES = 10;
|
|
85286
85497
|
var hookConfigSchema = external_exports.object({
|
|
85287
85498
|
script: external_exports.string().min(1),
|
|
85288
85499
|
env: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
85289
|
-
timeoutSeconds: external_exports.number().int().positive().optional()
|
|
85500
|
+
timeoutSeconds: external_exports.number().int().positive().optional(),
|
|
85501
|
+
retries: external_exports.number().int().nonnegative().max(MAX_HOOK_RETRIES).optional()
|
|
85290
85502
|
}).superRefine((data, context) => {
|
|
85291
85503
|
if (data.env === void 0) {
|
|
85292
85504
|
return;
|
|
@@ -85318,18 +85530,20 @@ var safeJsonParse5 = (0, import_neverthrow75.fromThrowable)(
|
|
|
85318
85530
|
cause
|
|
85319
85531
|
})
|
|
85320
85532
|
);
|
|
85533
|
+
function pickDefined(object2) {
|
|
85534
|
+
return Object.fromEntries(
|
|
85535
|
+
Object.entries(object2).filter(([, value]) => value !== void 0)
|
|
85536
|
+
);
|
|
85537
|
+
}
|
|
85321
85538
|
function buildHookConfig(parsed) {
|
|
85322
|
-
|
|
85323
|
-
|
|
85324
|
-
|
|
85325
|
-
|
|
85326
|
-
|
|
85327
|
-
|
|
85328
|
-
|
|
85329
|
-
|
|
85330
|
-
return { script, timeoutSeconds };
|
|
85331
|
-
}
|
|
85332
|
-
return { script };
|
|
85539
|
+
return {
|
|
85540
|
+
script: parsed.script,
|
|
85541
|
+
...pickDefined({
|
|
85542
|
+
env: parsed.env,
|
|
85543
|
+
timeoutSeconds: parsed.timeoutSeconds,
|
|
85544
|
+
retries: parsed.retries
|
|
85545
|
+
})
|
|
85546
|
+
};
|
|
85333
85547
|
}
|
|
85334
85548
|
function normalizeHooks(hooks) {
|
|
85335
85549
|
if (hooks === void 0) {
|
|
@@ -85845,25 +86059,71 @@ function runHook(options2) {
|
|
|
85845
86059
|
|
|
85846
86060
|
// src/suite/shell/hook-invoker.ts
|
|
85847
86061
|
var DEFAULT_HOOK_TIMEOUT_SECONDS = 120;
|
|
86062
|
+
var DEFAULT_HOOK_RETRIES = 3;
|
|
85848
86063
|
var MS_PER_SECOND5 = 1e3;
|
|
85849
|
-
|
|
85850
|
-
|
|
85851
|
-
|
|
85852
|
-
|
|
86064
|
+
var RETRYABLE_HOOK_ERROR_TYPES = /* @__PURE__ */ new Set([
|
|
86065
|
+
"HOOK_EXIT_NONZERO",
|
|
86066
|
+
"HOOK_TIMEOUT",
|
|
86067
|
+
"HOOK_SPAWN_FAILED"
|
|
86068
|
+
]);
|
|
86069
|
+
async function runHookOnce(context) {
|
|
85853
86070
|
return runHook({
|
|
85854
|
-
script:
|
|
85855
|
-
cwd: hookCwd,
|
|
85856
|
-
env:
|
|
85857
|
-
baseEnv: hookBaseEnv,
|
|
85858
|
-
nodeExecPath: hookNodeExecPath,
|
|
85859
|
-
timeoutMs:
|
|
85860
|
-
signal
|
|
86071
|
+
script: context.input.hook.script,
|
|
86072
|
+
cwd: context.input.hookCwd,
|
|
86073
|
+
env: context.env,
|
|
86074
|
+
baseEnv: context.input.hookBaseEnv,
|
|
86075
|
+
nodeExecPath: context.input.hookNodeExecPath,
|
|
86076
|
+
timeoutMs: context.timeoutMs,
|
|
86077
|
+
signal: context.input.signal
|
|
85861
86078
|
});
|
|
85862
86079
|
}
|
|
85863
|
-
|
|
86080
|
+
function emitRetryEvent(context, event) {
|
|
86081
|
+
context.input.observer?.({
|
|
86082
|
+
type: "HOOK_RETRY",
|
|
86083
|
+
itemId: context.input.item.id,
|
|
86084
|
+
itemName: context.input.item.name,
|
|
86085
|
+
simulatorUdid: context.input.simulatorUdid,
|
|
86086
|
+
attempt: event.attempt,
|
|
86087
|
+
maxAttempts: context.maxAttempts,
|
|
86088
|
+
previousErrorType: event.previous.type
|
|
86089
|
+
});
|
|
86090
|
+
}
|
|
86091
|
+
async function runAllAttempts(context) {
|
|
86092
|
+
let lastResult = await runHookOnce(context);
|
|
86093
|
+
for (let attempt = 2; attempt <= context.maxAttempts; attempt += 1) {
|
|
86094
|
+
if (lastResult.isOk()) {
|
|
86095
|
+
return lastResult;
|
|
86096
|
+
}
|
|
86097
|
+
if (!isRetryableHookError(lastResult.error) || context.input.signal.aborted) {
|
|
86098
|
+
return lastResult;
|
|
86099
|
+
}
|
|
86100
|
+
emitRetryEvent(context, { attempt, previous: lastResult.error });
|
|
86101
|
+
lastResult = await runHookOnce(context);
|
|
86102
|
+
}
|
|
86103
|
+
return lastResult;
|
|
86104
|
+
}
|
|
86105
|
+
function buildAttemptContext(input) {
|
|
86106
|
+
const env3 = buildHookEnv({
|
|
86107
|
+
item: input.item,
|
|
86108
|
+
simulatorUdid: input.simulatorUdid,
|
|
86109
|
+
suiteName: input.suiteName,
|
|
86110
|
+
suiteEnv: input.hook.env
|
|
86111
|
+
});
|
|
86112
|
+
const timeoutMs = (input.hook.timeoutSeconds ?? DEFAULT_HOOK_TIMEOUT_SECONDS) * MS_PER_SECOND5;
|
|
86113
|
+
const maxAttempts = (input.hook.retries ?? DEFAULT_HOOK_RETRIES) + 1;
|
|
86114
|
+
return { input, env: env3, timeoutMs, maxAttempts };
|
|
86115
|
+
}
|
|
86116
|
+
function invokeHook(input) {
|
|
86117
|
+
const context = buildAttemptContext(input);
|
|
86118
|
+
return import_neverthrow79.ResultAsync.fromSafePromise(runAllAttempts(context)).andThen((result) => result);
|
|
86119
|
+
}
|
|
86120
|
+
function isRetryableHookError(error48) {
|
|
86121
|
+
return RETRYABLE_HOOK_ERROR_TYPES.has(error48.type);
|
|
86122
|
+
}
|
|
86123
|
+
function maybeInvokeHook(input) {
|
|
85864
86124
|
const { hook: hook2 } = input;
|
|
85865
86125
|
if (hook2 === void 0) {
|
|
85866
|
-
return (0, import_neverthrow79.
|
|
86126
|
+
return (0, import_neverthrow79.okAsync)();
|
|
85867
86127
|
}
|
|
85868
86128
|
return invokeHook({ ...input, hook: hook2 });
|
|
85869
86129
|
}
|
|
@@ -85889,7 +86149,8 @@ function buildHookInvokeInput(workerContext, item) {
|
|
|
85889
86149
|
hookCwd: config2.hookCwd,
|
|
85890
86150
|
hookBaseEnv: config2.hookBaseEnv,
|
|
85891
86151
|
hookNodeExecPath: config2.hookNodeExecPath,
|
|
85892
|
-
signal: config2.signal
|
|
86152
|
+
signal: config2.signal,
|
|
86153
|
+
observer: config2.observer
|
|
85893
86154
|
};
|
|
85894
86155
|
}
|
|
85895
86156
|
function emitItemStarted(state) {
|
|
@@ -94264,7 +94525,7 @@ function buildProgram(options2) {
|
|
|
94264
94525
|
|
|
94265
94526
|
// src/index.ts
|
|
94266
94527
|
process.title = "xqa";
|
|
94267
|
-
var version2 = `${"5.
|
|
94528
|
+
var version2 = `${"5.5.0"}${false ? ` (dev build +${"432b4b3"})` : ""}`;
|
|
94268
94529
|
var program2 = buildProgram({ version: version2 });
|
|
94269
94530
|
void program2.parseAsync(process.argv);
|
|
94270
94531
|
/*! Bundled license information:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/xqa",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22"
|
|
@@ -27,18 +27,18 @@
|
|
|
27
27
|
"vitest": "^3.2.1",
|
|
28
28
|
"zod": "^4.3.6",
|
|
29
29
|
"@qa-agents/analyser": "0.0.0",
|
|
30
|
+
"@qa-agents/config": "0.0.0",
|
|
30
31
|
"@qa-agents/consolidator": "0.0.0",
|
|
31
32
|
"@qa-agents/display": "0.0.0",
|
|
33
|
+
"@qa-agents/eslint-config": "0.0.0",
|
|
32
34
|
"@qa-agents/explorer": "0.0.0",
|
|
33
35
|
"@qa-agents/inspector": "0.0.0",
|
|
34
36
|
"@qa-agents/mobile-ios": "0.0.0",
|
|
35
37
|
"@qa-agents/pipeline": "0.0.0",
|
|
36
38
|
"@qa-agents/planner": "0.0.0",
|
|
37
|
-
"@qa-agents/shared": "0.0.0",
|
|
38
|
-
"@qa-agents/config": "0.0.0",
|
|
39
39
|
"@qa-agents/triager": "0.0.0",
|
|
40
|
-
"@qa-agents/
|
|
41
|
-
"@qa-agents/
|
|
40
|
+
"@qa-agents/shared": "0.0.0",
|
|
41
|
+
"@qa-agents/typescript-config": "0.0.0"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@octokit/rest": "^21.0.0",
|