@vostride/agent-qa-android 0.2.3 → 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.cjs +30 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -92
- package/dist/index.d.ts +7 -92
- package/dist/index.js +30 -119
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
// src/session.ts
|
|
32
32
|
import { remote } from "webdriverio";
|
|
33
33
|
import { MobileSetupError } from "@vostride/agent-qa-core";
|
|
34
|
-
async function createAndroidSession(config
|
|
34
|
+
async function createAndroidSession(config) {
|
|
35
35
|
const match = config.device?.match ?? {};
|
|
36
36
|
const appiumUrl = process.env.AGENT_QA_APPIUM_URL ?? config.appiumUrl ?? "http://localhost:4723";
|
|
37
37
|
const url = new URL(appiumUrl);
|
|
@@ -74,17 +74,6 @@ async function createAndroidSession(config, farmSession) {
|
|
|
74
74
|
delete capabilities[key];
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
if (farmSession) {
|
|
78
|
-
Object.assign(capabilities, farmSession.capabilities);
|
|
79
|
-
return remote({
|
|
80
|
-
protocol: "https",
|
|
81
|
-
hostname: farmSession.hostname,
|
|
82
|
-
port: farmSession.port,
|
|
83
|
-
path: farmSession.path,
|
|
84
|
-
logLevel: config.verbose ? "info" : "silent",
|
|
85
|
-
capabilities
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
77
|
return remote({
|
|
89
78
|
hostname: url.hostname,
|
|
90
79
|
port: parseInt(url.port || "4723", 10),
|
|
@@ -264,8 +253,7 @@ return (function() {
|
|
|
264
253
|
return node ? (node.textContent || '').trim() : '';
|
|
265
254
|
}
|
|
266
255
|
|
|
267
|
-
//
|
|
268
|
-
// becomes a name. A wrapping label's text drops the field's own text (a select's options).
|
|
256
|
+
// A field is named by its label, not its value; a wrapping label drops the field's own text.
|
|
269
257
|
function labelText(el, label) {
|
|
270
258
|
var text = label.textContent || '';
|
|
271
259
|
var own = el.textContent || '';
|
|
@@ -289,8 +277,7 @@ return (function() {
|
|
|
289
277
|
return '';
|
|
290
278
|
}
|
|
291
279
|
|
|
292
|
-
//
|
|
293
|
-
// away, matching the mobile/web walkers' Pitfall #7 handling).
|
|
280
|
+
// The nearest included ancestor: structural and skipped nodes collapse, as on mobile and web.
|
|
294
281
|
function nearestAncestorIndex(el) {
|
|
295
282
|
var p = el.parentElement;
|
|
296
283
|
while (p) {
|
|
@@ -310,8 +297,7 @@ return (function() {
|
|
|
310
297
|
if (style.display === 'none' || style.visibility === 'hidden') continue;
|
|
311
298
|
|
|
312
299
|
var tag = el.tagName;
|
|
313
|
-
//
|
|
314
|
-
// its descendants are ordinary nodes.
|
|
300
|
+
// Only the outermost content-editable element is an editor; its descendants are ordinary nodes.
|
|
315
301
|
var host = el.isContentEditable === true
|
|
316
302
|
&& !(el.parentElement && el.parentElement.isContentEditable === true);
|
|
317
303
|
var ariaRole = el.getAttribute('role');
|
|
@@ -349,7 +335,7 @@ return (function() {
|
|
|
349
335
|
height: Math.round(rect.height)
|
|
350
336
|
};
|
|
351
337
|
|
|
352
|
-
// Real DOM state signals only
|
|
338
|
+
// Real DOM state signals only; the builder derives every default.
|
|
353
339
|
var state = {};
|
|
354
340
|
var disabled = el.disabled === true
|
|
355
341
|
|| el.hasAttribute('disabled')
|
|
@@ -375,7 +361,7 @@ return (function() {
|
|
|
375
361
|
|
|
376
362
|
var parentIndex = nearestAncestorIndex(el);
|
|
377
363
|
|
|
378
|
-
//
|
|
364
|
+
// What a field or an editor holds is its value, never its name; a password has no value here.
|
|
379
365
|
var observedValue = undefined;
|
|
380
366
|
var textEntry = tag === 'TEXTAREA' || (tag === 'INPUT' && !VALUELESS_TYPES[String(el.type).toLowerCase()]);
|
|
381
367
|
if (textEntry && el.value) {
|
|
@@ -534,14 +520,13 @@ var AndroidPlatformAdapter = class {
|
|
|
534
520
|
captureConsole = true;
|
|
535
521
|
appPackage = null;
|
|
536
522
|
appPid = null;
|
|
537
|
-
//
|
|
538
|
-
// enumerated (or enumeration failed and may be retried). Reset in cleanup().
|
|
523
|
+
// Enumerated once per session; `null` is not yet enumerated or retryable; reset in `cleanup()`.
|
|
539
524
|
appRegistry = null;
|
|
540
525
|
async setup(config) {
|
|
541
526
|
this.config = config;
|
|
542
527
|
this.captureConsole = config.logCapture?.console !== false;
|
|
543
528
|
this.appPackage = this.config.appPackage ?? config.device?.match?.appPackage ?? null;
|
|
544
|
-
this.driver = await createAndroidSession(this.config
|
|
529
|
+
this.driver = await createAndroidSession(this.config);
|
|
545
530
|
const appPackage = this.appPackage;
|
|
546
531
|
if (appPackage && !this.config.browserName && !this.config.device?.match?.browserName) {
|
|
547
532
|
let state;
|
|
@@ -610,7 +595,7 @@ var AndroidPlatformAdapter = class {
|
|
|
610
595
|
// Once true, we know a WebView/browser exists and should retry rather than
|
|
611
596
|
// falling back to native (which returns an opaque WebView and can crash UiAutomator2).
|
|
612
597
|
hasSeenWebContext = false;
|
|
613
|
-
async observe(
|
|
598
|
+
async observe() {
|
|
614
599
|
if (!this.driver) {
|
|
615
600
|
throw new Error("AndroidPlatformAdapter not initialized \u2014 call setup() first");
|
|
616
601
|
}
|
|
@@ -685,7 +670,7 @@ var AndroidPlatformAdapter = class {
|
|
|
685
670
|
const dims = await getImageDimensions(buf);
|
|
686
671
|
const { width } = await this.driver.getWindowSize();
|
|
687
672
|
if (dims && Math.abs(dims.width - width) > 1) {
|
|
688
|
-
console.warn(`[android-adapter] screenshot width ${dims.width} != window width ${width} (coord-space divergence, handled by scaleFactor
|
|
673
|
+
console.warn(`[android-adapter] screenshot width ${dims.width} != window width ${width} (coord-space divergence, handled by scaleFactor)`);
|
|
689
674
|
}
|
|
690
675
|
} catch {
|
|
691
676
|
}
|
|
@@ -717,13 +702,7 @@ var AndroidPlatformAdapter = class {
|
|
|
717
702
|
if (!level) return newLogs;
|
|
718
703
|
return newLogs.filter((e) => e.level === level);
|
|
719
704
|
}
|
|
720
|
-
|
|
721
|
-
* 428 D-11: the labelled 1s snapshot preview, over `captureScreenshot()` — `takeScreenshot()`
|
|
722
|
-
* and nothing else. Never `screenshot()`: that one switches the Appium context, and a tick
|
|
723
|
-
* landing between a step's context switch and its command would run the command in the wrong
|
|
724
|
-
* context. Declared before the driver exists (a capture then answers `undefined`), so the
|
|
725
|
-
* composition's selection never falls through to `screenshot()`.
|
|
726
|
-
*/
|
|
705
|
+
// `takeScreenshot()` only: `screenshot()` switches the Appium context mid-step.
|
|
727
706
|
get liveStream() {
|
|
728
707
|
this.liveStreamCapability ??= createSnapshotLiveStream({ capture: () => this.captureScreenshot() });
|
|
729
708
|
return this.liveStreamCapability;
|
|
@@ -899,9 +878,6 @@ var AndroidPlatformAdapter = class {
|
|
|
899
878
|
return void 0;
|
|
900
879
|
}
|
|
901
880
|
}
|
|
902
|
-
// ---------------------------------------------------------------------------
|
|
903
|
-
// Compound behaviors (EXEC-01/02/03)
|
|
904
|
-
// ---------------------------------------------------------------------------
|
|
905
881
|
/** Read the live text of a focused element handle; undefined when nothing is readable. */
|
|
906
882
|
async readElementText(el) {
|
|
907
883
|
try {
|
|
@@ -918,15 +894,12 @@ var AndroidPlatformAdapter = class {
|
|
|
918
894
|
return false;
|
|
919
895
|
}
|
|
920
896
|
}
|
|
921
|
-
/** An empty field's getText() returns its hint
|
|
897
|
+
/** An empty field's getText() returns its hint, so a showing hint reads ''. */
|
|
922
898
|
async readFocusedValue(el) {
|
|
923
899
|
if (await this.focusedMatches(FOCUSED_HINT_XPATH)) return "";
|
|
924
900
|
return await this.readElementText(el);
|
|
925
901
|
}
|
|
926
|
-
|
|
927
|
-
* The `mobile: shell` route of keyevent and openQuickSettings (437 D-18). A refusal by secure
|
|
928
|
-
* Appium is recorded as unavailable and any other driver error as failed; neither is a pass.
|
|
929
|
-
*/
|
|
902
|
+
// A secure-Appium refusal is recorded as unavailable and any other driver error as failed.
|
|
930
903
|
async runShell(verb, args) {
|
|
931
904
|
try {
|
|
932
905
|
await this.driver.execute("mobile: shell", args);
|
|
@@ -949,7 +922,7 @@ var AndroidPlatformAdapter = class {
|
|
|
949
922
|
return { success: false, error: `${verb} failed: ${detail}`, metadata: { shellAction } };
|
|
950
923
|
}
|
|
951
924
|
}
|
|
952
|
-
/** The one submit mechanism on every Android path
|
|
925
|
+
/** The one submit mechanism on every Android path; pressed once, never retried. */
|
|
953
926
|
async pressEnter() {
|
|
954
927
|
try {
|
|
955
928
|
await this.driver.execute("mobile: pressKey", { keycode: ENTER_KEYCODE });
|
|
@@ -958,7 +931,7 @@ var AndroidPlatformAdapter = class {
|
|
|
958
931
|
return { sent: false, outcome: "failed", detail: firstErrorLine(err) };
|
|
959
932
|
}
|
|
960
933
|
}
|
|
961
|
-
|
|
934
|
+
// Line breaks into a single-line field are refused before typing.
|
|
962
935
|
refuseLineBreaks(action, box) {
|
|
963
936
|
const fillRefusal = {
|
|
964
937
|
verb: "fill",
|
|
@@ -972,14 +945,7 @@ var AndroidPlatformAdapter = class {
|
|
|
972
945
|
metadata: { fillRefusal, ...action.submit ? { fillSubmit: { ...INPUT_FAILED } } : {} }
|
|
973
946
|
};
|
|
974
947
|
}
|
|
975
|
-
|
|
976
|
-
* Native fill from the focused handle (437 D-08..D-10, D-12, D-13, D-15, D-17). Delivers once
|
|
977
|
-
* (addValue for append, setValue for replace), reads back from the same handle and records the
|
|
978
|
-
* shared verdict. An unknown append prior or an unreadable read-back is unverified, which never
|
|
979
|
-
* stops a requested Enter; a mismatch or a multi-line field holds it. A driver error during
|
|
980
|
-
* delivery ends the fill: nothing is typed again and no Enter follows. A secure field yields
|
|
981
|
-
* length sentinels only.
|
|
982
|
-
*/
|
|
948
|
+
// Delivers once and reads back; an unverified result still presses Enter, a mismatch holds it.
|
|
983
949
|
async fillNativeCompound(el, action, box) {
|
|
984
950
|
const isAppend = action.clearMode === "append";
|
|
985
951
|
if (LINE_BREAK_RE.test(action.text) && await this.focusedMatches(FOCUSED_SINGLE_LINE_XPATH)) {
|
|
@@ -1015,11 +981,7 @@ var AndroidPlatformAdapter = class {
|
|
|
1015
981
|
return void 0;
|
|
1016
982
|
}
|
|
1017
983
|
}
|
|
1018
|
-
|
|
1019
|
-
* 437 D-11 (WR-02): where the focused node is relative to the ref, judged by its read bounds.
|
|
1020
|
-
* An unreadable or unparseable value is no evidence, so the native path stays (D-04, D-15).
|
|
1021
|
-
* 444.1 D-07: 'elsewhere' needs positive evidence, the bounds of another observed field.
|
|
1022
|
-
*/
|
|
984
|
+
// `elsewhere` needs positive evidence: the read bounds of another observed field.
|
|
1023
985
|
async focusIsElsewhere(handle, ref, bounds) {
|
|
1024
986
|
let raw;
|
|
1025
987
|
try {
|
|
@@ -1036,18 +998,12 @@ var AndroidPlatformAdapter = class {
|
|
|
1036
998
|
}
|
|
1037
999
|
return "moved";
|
|
1038
1000
|
}
|
|
1039
|
-
/** Ctrl+A then DEL as key input; clears only the view holding input focus
|
|
1001
|
+
/** Ctrl+A then DEL as key input; clears only the view holding input focus. */
|
|
1040
1002
|
async clearByKeys() {
|
|
1041
1003
|
await this.driver.execute("mobile: pressKey", CTRL_A);
|
|
1042
1004
|
await this.driver.execute("mobile: pressKey", { keycode: DEL_KEYCODE });
|
|
1043
1005
|
}
|
|
1044
|
-
|
|
1045
|
-
* The last-resort keyboard path (437 D-14): the tap left no focused handle. Replace clears by
|
|
1046
|
-
* keys, the text goes through one `mobile: type` (Appium's Unicode IME, exact for every D-08
|
|
1047
|
-
* text), and the value is read back only when a focused handle can be found afterwards. An
|
|
1048
|
-
* append's prior value is unknown, so it is never better than unverified. The field's kind comes
|
|
1049
|
-
* from the observed input-type (D-15): only positive evidence refuses line breaks or holds Enter.
|
|
1050
|
-
*/
|
|
1006
|
+
// Last resort when the tap left no handle; an append's prior is unknown, so at best unverified.
|
|
1051
1007
|
async fillKeyboardPath(action, box, refData) {
|
|
1052
1008
|
const isAppend = action.clearMode === "append";
|
|
1053
1009
|
const kind = fieldKindOf(refData);
|
|
@@ -1075,11 +1031,7 @@ var AndroidPlatformAdapter = class {
|
|
|
1075
1031
|
enter: () => this.pressEnter()
|
|
1076
1032
|
});
|
|
1077
1033
|
}
|
|
1078
|
-
|
|
1079
|
-
* Native and keyboard clearText (437 D-16): clearValue on the focused handle, or Ctrl+A then DEL
|
|
1080
|
-
* when the tap left none, then the hint-aware read-back judged against the empty string. The
|
|
1081
|
-
* caller has already checked that a focused handle is the referenced field (WR-02).
|
|
1082
|
-
*/
|
|
1034
|
+
// The caller has already checked that a focused handle is the referenced field.
|
|
1083
1035
|
async clearNativeOrKeyboard(box, handle) {
|
|
1084
1036
|
const path = handle ? "native" : "keyboard";
|
|
1085
1037
|
const before = handle ? await this.isSecure(handle) : false;
|
|
@@ -1139,11 +1091,7 @@ var AndroidPlatformAdapter = class {
|
|
|
1139
1091
|
metadata: { fillVerdict, ...action.submit ? { fillSubmit: { ...INPUT_FAILED } } : {} }
|
|
1140
1092
|
};
|
|
1141
1093
|
}
|
|
1142
|
-
|
|
1143
|
-
* The shared end of every Android fill (437 D-09, D-10, D-13, D-15, D-17): records the verdict
|
|
1144
|
-
* and, when a submit was requested, holds Enter on a mismatch or a multi-line field and otherwise
|
|
1145
|
-
* presses it once through `enter`.
|
|
1146
|
-
*/
|
|
1094
|
+
// Holds Enter on a mismatch or a multi-line field; otherwise presses it once.
|
|
1147
1095
|
async settleFill(action, box, settle) {
|
|
1148
1096
|
const { path, verdict, multiline, enter } = settle;
|
|
1149
1097
|
const fillVerdict = {
|
|
@@ -1179,7 +1127,7 @@ var AndroidPlatformAdapter = class {
|
|
|
1179
1127
|
}
|
|
1180
1128
|
return { success: true, ...box, ...notice, metadata };
|
|
1181
1129
|
}
|
|
1182
|
-
/** A real W3C touch tap in the WebView context at the ref's CSS-pixel centre
|
|
1130
|
+
/** A real W3C touch tap in the WebView context at the ref's CSS-pixel centre. */
|
|
1183
1131
|
async trustedWebTap(center) {
|
|
1184
1132
|
await safePerform(
|
|
1185
1133
|
this.driver.action("pointer", { parameters: { pointerType: "touch" } }).move({ x: Math.round(center.x), y: Math.round(center.y), origin: "viewport" }).down({ button: 0 }).up({ button: 0 })
|
|
@@ -1232,7 +1180,7 @@ var AndroidPlatformAdapter = class {
|
|
|
1232
1180
|
if (this.activeWebContext) await this.ensureContext(this.activeWebContext);
|
|
1233
1181
|
}
|
|
1234
1182
|
}
|
|
1235
|
-
|
|
1183
|
+
// A tap that leaves no editable field, or another field, focused writes nothing.
|
|
1236
1184
|
refuseFocus(verb, ref, focus, submit, box) {
|
|
1237
1185
|
const nothing = verb === "fill" ? "nothing was typed" : "nothing was cleared";
|
|
1238
1186
|
let fillRefusal;
|
|
@@ -1254,10 +1202,7 @@ var AndroidPlatformAdapter = class {
|
|
|
1254
1202
|
metadata: { fillRefusal, ...submit ? { fillSubmit: { ...INPUT_FAILED } } : {} }
|
|
1255
1203
|
};
|
|
1256
1204
|
}
|
|
1257
|
-
|
|
1258
|
-
* WebView clearText (437 D-11, D-16): the same tap and focus probe as the fill, W3C Ctrl+A and
|
|
1259
|
-
* Backspace, then a read-back judged against the empty string. Never success without that check.
|
|
1260
|
-
*/
|
|
1205
|
+
// Never success without the read-back judged against the empty string.
|
|
1261
1206
|
async clearWebViewField(ref, center, box) {
|
|
1262
1207
|
const driver = this.driver;
|
|
1263
1208
|
await this.trustedWebTap(center);
|
|
@@ -1273,11 +1218,7 @@ var AndroidPlatformAdapter = class {
|
|
|
1273
1218
|
}
|
|
1274
1219
|
return this.settleClear("webview", await this.readWebFieldValue(), focus.secure, box);
|
|
1275
1220
|
}
|
|
1276
|
-
|
|
1277
|
-
* WebView fill as trusted input (437 D-11): a W3C tap on the ref, a read-only focus probe,
|
|
1278
|
-
* W3C Ctrl+A and Backspace for replace, `mobile: type` from NATIVE_APP, a read-only read-back,
|
|
1279
|
-
* and one `mobile: pressKey 66` from NATIVE_APP. No script writes the field or fires an event.
|
|
1280
|
-
*/
|
|
1221
|
+
// Trusted input only: no page script writes the field or fires an event.
|
|
1281
1222
|
async fillWebViewCompound(action, box, center) {
|
|
1282
1223
|
const driver = this.driver;
|
|
1283
1224
|
const isAppend = action.clearMode === "append";
|
|
@@ -1314,15 +1255,7 @@ var AndroidPlatformAdapter = class {
|
|
|
1314
1255
|
enter: () => this.withNativeContext(() => this.pressEnter())
|
|
1315
1256
|
});
|
|
1316
1257
|
}
|
|
1317
|
-
|
|
1318
|
-
* EXEC-02 platform primitives injected into the shared runner scroll loops (D-02/D-03; 436-02
|
|
1319
|
-
* D-25). A native swipe is `mobile: scrollGesture` over the container (the ref's bounds, else the
|
|
1320
|
-
* screen), whose boolean is the driver's own boundary signal; in a WebView the touch swipe is
|
|
1321
|
-
* measured over every WebView scroller before and after (436-18), paired by identity (436-23).
|
|
1322
|
-
* nativeSearchScroll drives the UiAutomator
|
|
1323
|
-
* `UiScrollable.scrollIntoView` rung over an injection-escaped selector; readInterimTree is a
|
|
1324
|
-
* light page-source parse — NO refMap mint, NO screenshot, lastRefs untouched.
|
|
1325
|
-
*/
|
|
1258
|
+
// Native swipes trust `scrollGesture`'s boolean; WebView swipes compare every scroller by id.
|
|
1326
1259
|
async buildAndroidScrollPrimitives(scrollDirection, maxSwipes, factor = 1) {
|
|
1327
1260
|
const driver = this.driver;
|
|
1328
1261
|
const viewport = await driver.getWindowSize();
|
|
@@ -1381,13 +1314,7 @@ var AndroidPlatformAdapter = class {
|
|
|
1381
1314
|
}
|
|
1382
1315
|
};
|
|
1383
1316
|
}
|
|
1384
|
-
|
|
1385
|
-
* The WebView scrollers a touch swipe may have acted on (436-18 · D-25), read by the shared
|
|
1386
|
-
* WEBVIEW_SCROLLERS_SCRIPT. The touch point cannot be mapped into the WebView's CSS coordinates,
|
|
1387
|
-
* so the swipe is judged over all of them.
|
|
1388
|
-
* 2026-10-08 (436-23): a frame in view reads unknown; members carry an identity and are paired
|
|
1389
|
-
* by it; open shadow roots are walked; only on-screen element scrollers are members.
|
|
1390
|
-
*/
|
|
1317
|
+
// The touch point cannot map into CSS coordinates, so the swipe is judged over every scroller.
|
|
1391
1318
|
async readWebViewScrollers() {
|
|
1392
1319
|
try {
|
|
1393
1320
|
return parseWebViewScrollers(await this.driver.execute(WEBVIEW_SCROLLERS_SCRIPT));
|
|
@@ -1395,12 +1322,7 @@ var AndroidPlatformAdapter = class {
|
|
|
1395
1322
|
return void 0;
|
|
1396
1323
|
}
|
|
1397
1324
|
}
|
|
1398
|
-
|
|
1399
|
-
* EXEC-03 openApp (D-10/D-11/D-12). Resolve a human name to a package id (or validate an
|
|
1400
|
-
* explicit bundleId) over the session-cached registry, then launch via activateApp. On a
|
|
1401
|
-
* resolution MISS the registry is re-enumerated exactly once (catches mid-run installs) before
|
|
1402
|
-
* erroring with the resolver's candidate ids. Never a best-guess launch (T-359-04).
|
|
1403
|
-
*/
|
|
1325
|
+
// On a resolution miss the registry is re-enumerated once before erroring; never a best guess.
|
|
1404
1326
|
async openApp(action) {
|
|
1405
1327
|
const driver = this.driver;
|
|
1406
1328
|
let registry = await this.ensureAppRegistry();
|
|
@@ -1452,12 +1374,7 @@ var AndroidPlatformAdapter = class {
|
|
|
1452
1374
|
if (enumerated !== null) this.appRegistry = enumerated;
|
|
1453
1375
|
return enumerated;
|
|
1454
1376
|
}
|
|
1455
|
-
|
|
1456
|
-
* Enumerate installed apps through the driver only (ownership contract — no adb/child_process).
|
|
1457
|
-
* `mobile: listApps` maps package ids → AppRegistryEntry (labels only when the same call carries
|
|
1458
|
-
* them, D-10 — no per-package round-trips). On failure, fall back to `mobile: shell` pm-list ONLY
|
|
1459
|
-
* when the AGENT_QA_ANDROID_USE_MOBILE_SHELL gate is '1' (Pitfall 4). Null = enumeration unavailable.
|
|
1460
|
-
*/
|
|
1377
|
+
// Driver only, no adb; the `mobile: shell` fallback needs `AGENT_QA_ANDROID_USE_MOBILE_SHELL=1`.
|
|
1461
1378
|
async enumerateApps() {
|
|
1462
1379
|
const driver = this.driver;
|
|
1463
1380
|
try {
|
|
@@ -1893,8 +1810,6 @@ var AndroidPlatformAdapter = class {
|
|
|
1893
1810
|
success: false,
|
|
1894
1811
|
error: 'assertValue {source:"network"} (HTTP log inspection) is not available on the Android adapter. Use a web target for network assertions, or assert on visible UI text.'
|
|
1895
1812
|
};
|
|
1896
|
-
case "expr-result":
|
|
1897
|
-
return { success: true, data: void 0 };
|
|
1898
1813
|
}
|
|
1899
1814
|
}
|
|
1900
1815
|
case "drag": {
|
|
@@ -1948,11 +1863,7 @@ var AndroidPlatformAdapter = class {
|
|
|
1948
1863
|
}
|
|
1949
1864
|
return { success: true, coordinates: { x, y } };
|
|
1950
1865
|
}
|
|
1951
|
-
// Runner-
|
|
1952
|
-
// setVariable are intercepted at the runner dispatch seam and never reach the adapter. A
|
|
1953
|
-
// grouped §7.1 defensive case surfaces a dispatch-routing error if one arrives here;
|
|
1954
|
-
// assertNever remains the backstop for a genuinely unknown schema verb. (uniqueValue is
|
|
1955
|
-
// web-only and is not part of ActionsFor<'android'>.)
|
|
1866
|
+
// Runner-channel verbs never reach the adapter; one arriving here is a routing error.
|
|
1956
1867
|
case "reportValue":
|
|
1957
1868
|
case "assertAI":
|
|
1958
1869
|
case "extractAI":
|