@validate.qa/runner 1.0.67 → 1.0.69
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/cli.js +498 -260
- package/dist/cli.mjs +494 -256
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -983,7 +983,7 @@ var require_mobile_source_parser = __commonJS({
|
|
|
983
983
|
const value = nonEmpty3(attrs.value);
|
|
984
984
|
const label = name ?? labelAttr ?? value;
|
|
985
985
|
const accessibilityId = name;
|
|
986
|
-
const text =
|
|
986
|
+
const text = labelAttr ?? (isStateValueRole(role) ? void 0 : value);
|
|
987
987
|
const bounds = parseIOSBounds(attrs);
|
|
988
988
|
const enabled = attrBoolDefaultTrue(attrs.enabled);
|
|
989
989
|
const visible = attrBoolDefaultTrue(attrs.visible);
|
|
@@ -1130,6 +1130,42 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1130
1130
|
}
|
|
1131
1131
|
}
|
|
1132
1132
|
}
|
|
1133
|
+
function annotateIdentityUniqueness(elements) {
|
|
1134
|
+
const a11yCounts = /* @__PURE__ */ new Map();
|
|
1135
|
+
const ridCounts = /* @__PURE__ */ new Map();
|
|
1136
|
+
const textCounts = /* @__PURE__ */ new Map();
|
|
1137
|
+
const bump = (map2, value) => {
|
|
1138
|
+
const key = value?.trim();
|
|
1139
|
+
if (!key)
|
|
1140
|
+
return;
|
|
1141
|
+
map2.set(key, (map2.get(key) ?? 0) + 1);
|
|
1142
|
+
};
|
|
1143
|
+
for (const element of elements) {
|
|
1144
|
+
bump(a11yCounts, element.accessibilityId);
|
|
1145
|
+
bump(ridCounts, element.resourceId);
|
|
1146
|
+
const identityValues = new Set([element.text, element.accessibilityId].map((value) => value?.trim()).filter((value) => Boolean(value)));
|
|
1147
|
+
for (const value of identityValues)
|
|
1148
|
+
bump(textCounts, value);
|
|
1149
|
+
}
|
|
1150
|
+
const isUnique = (map2, value) => {
|
|
1151
|
+
const key = value?.trim();
|
|
1152
|
+
if (!key)
|
|
1153
|
+
return void 0;
|
|
1154
|
+
return (map2.get(key) ?? 0) <= 1;
|
|
1155
|
+
};
|
|
1156
|
+
for (const element of elements) {
|
|
1157
|
+
const accessibilityId = isUnique(a11yCounts, element.accessibilityId);
|
|
1158
|
+
const resourceId = isUnique(ridCounts, element.resourceId);
|
|
1159
|
+
const text = isUnique(textCounts, element.text);
|
|
1160
|
+
if (accessibilityId === void 0 && resourceId === void 0 && text === void 0)
|
|
1161
|
+
continue;
|
|
1162
|
+
element.identityUnique = {
|
|
1163
|
+
...accessibilityId !== void 0 ? { accessibilityId } : {},
|
|
1164
|
+
...resourceId !== void 0 ? { resourceId } : {},
|
|
1165
|
+
...text !== void 0 ? { text } : {}
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1133
1169
|
function flatten(root, platform3) {
|
|
1134
1170
|
const out = [];
|
|
1135
1171
|
const stack = [];
|
|
@@ -1175,6 +1211,7 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1175
1211
|
}
|
|
1176
1212
|
}
|
|
1177
1213
|
markRepeatedBottomBarNavigation(out);
|
|
1214
|
+
annotateIdentityUniqueness(out);
|
|
1178
1215
|
return out;
|
|
1179
1216
|
}
|
|
1180
1217
|
function fnv1a(input) {
|
|
@@ -1256,6 +1293,13 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1256
1293
|
function isEditableElement(el) {
|
|
1257
1294
|
return EDITABLE_CLASS_RE.test(el.role.trim());
|
|
1258
1295
|
}
|
|
1296
|
+
var STATE_VALUE_CLASS_RE = /(XCUIElementTypeSwitch|XCUIElementTypeToggle|XCUIElementTypeSlider|XCUIElementTypeStepper|XCUIElementTypePageIndicator|XCUIElementTypeProgressIndicator|XCUIElementTypeSegmentedControl|XCUIElementTypePicker|XCUIElementTypePickerWheel|ToggleButton|SeekBar|ProgressBar|RatingBar|NumberPicker)$/;
|
|
1297
|
+
function isStateValueRole(role) {
|
|
1298
|
+
return STATE_VALUE_CLASS_RE.test(role.trim());
|
|
1299
|
+
}
|
|
1300
|
+
function isStateValueElement(el) {
|
|
1301
|
+
return isStateValueRole(el.role);
|
|
1302
|
+
}
|
|
1259
1303
|
function xpathAttrLiteral(value) {
|
|
1260
1304
|
if (!value.includes("'"))
|
|
1261
1305
|
return `'${value}'`;
|
|
@@ -1266,17 +1310,18 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1266
1310
|
function semanticXPath(el, platform3) {
|
|
1267
1311
|
const cls = nonBlank(el.role) ?? "*";
|
|
1268
1312
|
const a11y = nonBlank(el.accessibilityId);
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1313
|
+
const rid = platform3 === "ANDROID" ? nonBlank(el.resourceId) : void 0;
|
|
1314
|
+
const text = isEditableElement(el) || isStateValueElement(el) ? void 0 : nonBlank(el.text);
|
|
1315
|
+
const a11yXpath = a11y ? platform3 === "ANDROID" ? `//${cls}[@content-desc=${xpathAttrLiteral(a11y)}]` : `//${cls}[@name=${xpathAttrLiteral(a11y)}]` : void 0;
|
|
1316
|
+
const ridXpath = rid ? `//${cls}[@resource-id=${xpathAttrLiteral(rid)}]` : void 0;
|
|
1317
|
+
const textXpath = text ? platform3 === "ANDROID" ? `//${cls}[@text=${xpathAttrLiteral(text)}]` : `//${cls}[@label=${xpathAttrLiteral(text)}]` : void 0;
|
|
1318
|
+
const candidates = [
|
|
1319
|
+
{ xpath: a11yXpath, unique: el.identityUnique?.accessibilityId },
|
|
1320
|
+
{ xpath: ridXpath, unique: el.identityUnique?.resourceId },
|
|
1321
|
+
{ xpath: textXpath, unique: el.identityUnique?.text }
|
|
1322
|
+
];
|
|
1323
|
+
const uniqueFirst = candidates.find((c) => c.xpath && c.unique !== false);
|
|
1324
|
+
return (uniqueFirst ?? candidates.find((c) => c.xpath))?.xpath;
|
|
1280
1325
|
}
|
|
1281
1326
|
function nonBlank(value) {
|
|
1282
1327
|
if (typeof value !== "string")
|
|
@@ -1285,30 +1330,40 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1285
1330
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
1286
1331
|
}
|
|
1287
1332
|
function buildLocatorBundle(el, platform3) {
|
|
1333
|
+
const durableText = el.text && !isEditableElement(el) && !isStateValueElement(el) ? el.text : void 0;
|
|
1288
1334
|
const bundle = {
|
|
1289
1335
|
// Attribute-anchored xpath when the element has identity; the absolute
|
|
1290
1336
|
// indexed path only for identity-less elements (nothing else can find them).
|
|
1291
1337
|
xpath: semanticXPath(el, platform3) ?? el.xpath,
|
|
1292
1338
|
className: el.role
|
|
1293
1339
|
};
|
|
1294
|
-
|
|
1340
|
+
const a11yUnique = el.identityUnique?.accessibilityId !== false;
|
|
1341
|
+
const ridUnique = el.identityUnique?.resourceId !== false;
|
|
1342
|
+
const textUnique = el.identityUnique?.text !== false;
|
|
1343
|
+
const hasUniqueAlternativeToA11y = Boolean(el.resourceId && platform3 === "ANDROID" && ridUnique || durableText && textUnique);
|
|
1344
|
+
const hasUniqueAlternativeToRid = Boolean(el.accessibilityId && a11yUnique || durableText && textUnique);
|
|
1345
|
+
const includeA11y = Boolean(el.accessibilityId && (a11yUnique || !hasUniqueAlternativeToA11y));
|
|
1346
|
+
const includeRid = Boolean(platform3 === "ANDROID" && el.resourceId && (ridUnique || !hasUniqueAlternativeToRid));
|
|
1347
|
+
if (includeA11y && el.accessibilityId)
|
|
1295
1348
|
bundle.accessibilityId = el.accessibilityId;
|
|
1296
|
-
if (
|
|
1349
|
+
if (includeRid && el.resourceId)
|
|
1297
1350
|
bundle.resourceId = el.resourceId;
|
|
1298
|
-
if (
|
|
1299
|
-
bundle.text =
|
|
1300
|
-
if (platform3 === "ANDROID" && el.accessibilityId)
|
|
1351
|
+
if (durableText)
|
|
1352
|
+
bundle.text = durableText;
|
|
1353
|
+
if (includeA11y && platform3 === "ANDROID" && el.accessibilityId)
|
|
1301
1354
|
bundle.contentDesc = el.accessibilityId;
|
|
1302
1355
|
return bundle;
|
|
1303
1356
|
}
|
|
1304
1357
|
function preferredLocator(el) {
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1358
|
+
const durableText = el.text && !isEditableElement(el) && !isStateValueElement(el) ? el.text : void 0;
|
|
1359
|
+
const candidates = [
|
|
1360
|
+
{ value: el.accessibilityId, unique: el.identityUnique?.accessibilityId },
|
|
1361
|
+
{ value: el.resourceId, unique: el.identityUnique?.resourceId },
|
|
1362
|
+
{ value: durableText, unique: el.identityUnique?.text }
|
|
1363
|
+
];
|
|
1364
|
+
const uniqueFirst = candidates.find((c) => c.value && c.unique !== false);
|
|
1365
|
+
const chosen = (uniqueFirst ?? candidates.find((c) => c.value))?.value;
|
|
1366
|
+
return chosen ?? el.xpath;
|
|
1312
1367
|
}
|
|
1313
1368
|
function buildScreenSignal(elements, root, platform3) {
|
|
1314
1369
|
const signal = {
|
|
@@ -1971,6 +2026,11 @@ function isEditableMobileClassName(className) {
|
|
|
1971
2026
|
return false;
|
|
1972
2027
|
return /(EditText|AutoCompleteTextView|XCUIElementTypeTextField|XCUIElementTypeSecureTextField|XCUIElementTypeSearchField|XCUIElementTypeTextView)$/.test(className.trim());
|
|
1973
2028
|
}
|
|
2029
|
+
function isStateValueMobileClassName(className) {
|
|
2030
|
+
if (!className)
|
|
2031
|
+
return false;
|
|
2032
|
+
return /(XCUIElementTypeSwitch|XCUIElementTypeToggle|XCUIElementTypeSlider|XCUIElementTypeStepper|XCUIElementTypePageIndicator|XCUIElementTypeProgressIndicator|XCUIElementTypeSegmentedControl|XCUIElementTypePicker|XCUIElementTypePickerWheel|ToggleButton|SeekBar|ProgressBar|RatingBar|NumberPicker)$/.test(className.trim());
|
|
2033
|
+
}
|
|
1974
2034
|
function mobileCredentialPlaceholder(key) {
|
|
1975
2035
|
return `{{${key}}}`;
|
|
1976
2036
|
}
|
|
@@ -2100,9 +2160,10 @@ function buildStepTarget(metadata, selector) {
|
|
|
2100
2160
|
locatorBundle.resourceId,
|
|
2101
2161
|
// For editable fields the text attribute IS the currently-typed value — a
|
|
2102
2162
|
// self-invalidating locator that only matches while the recorded text is
|
|
2103
|
-
// still in the field (and it can leak typed input into locators).
|
|
2104
|
-
//
|
|
2105
|
-
|
|
2163
|
+
// still in the field (and it can leak typed input into locators). State
|
|
2164
|
+
// controls' text is their current state ("1"/"ON") and collides with every
|
|
2165
|
+
// same-state sibling. Static elements keep their text fallback.
|
|
2166
|
+
isEditableMobileClassName(locatorBundle.className) || isStateValueMobileClassName(locatorBundle.className) ? void 0 : locatorBundle.text,
|
|
2106
2167
|
locatorBundle.contentDesc,
|
|
2107
2168
|
locatorBundle.xpath,
|
|
2108
2169
|
selector
|
|
@@ -2258,7 +2319,9 @@ function mobileLocatorBundleHasIdentity(bundle) {
|
|
|
2258
2319
|
return false;
|
|
2259
2320
|
if (nonEmpty(bundle.accessibilityId) || nonEmpty(bundle.resourceId))
|
|
2260
2321
|
return true;
|
|
2261
|
-
if (nonEmpty(bundle.text)
|
|
2322
|
+
if (nonEmpty(bundle.text) && !isStateValueMobileClassName(bundle.className))
|
|
2323
|
+
return true;
|
|
2324
|
+
if (nonEmpty(bundle.contentDesc))
|
|
2262
2325
|
return true;
|
|
2263
2326
|
const xpath = nonEmpty(bundle.xpath);
|
|
2264
2327
|
return !!xpath && isIdentityMobileXpath(xpath);
|
|
@@ -2279,23 +2342,27 @@ function selectorCandidatesForStep(step) {
|
|
|
2279
2342
|
candidates.push(`~${bundle.accessibilityId}`);
|
|
2280
2343
|
if (nonEmpty(bundle.resourceId))
|
|
2281
2344
|
candidates.push(`id=${bundle.resourceId}`);
|
|
2345
|
+
if (nonEmpty(bundle.xpath))
|
|
2346
|
+
candidates.push(bundle.xpath);
|
|
2282
2347
|
const isGateStep = step.action === "waitForElement" || step.action === "assertVisible" || step.action === "assertText";
|
|
2283
|
-
const textIdentity = isGateStep || !isEditableMobileClassName(bundle.className) ? bundle.text : void 0;
|
|
2348
|
+
const textIdentity = (isGateStep || !isEditableMobileClassName(bundle.className)) && !isStateValueMobileClassName(bundle.className) ? bundle.text : void 0;
|
|
2284
2349
|
for (const value of [textIdentity, bundle.contentDesc]) {
|
|
2285
2350
|
const trimmed = nonEmpty(value);
|
|
2286
2351
|
if (trimmed)
|
|
2287
2352
|
candidates.push(identityTextXpath(trimmed));
|
|
2288
2353
|
}
|
|
2289
|
-
if (nonEmpty(bundle.xpath))
|
|
2290
|
-
candidates.push(bundle.xpath);
|
|
2291
2354
|
}
|
|
2292
2355
|
const hasExplicitResourceId = !!nonEmpty(bundle?.resourceId);
|
|
2356
|
+
const bundleContributedIdentity = candidates.some((value) => !(value.startsWith("/") || value.startsWith("(//")) || isIdentityMobileXpath(value));
|
|
2293
2357
|
const pushValueCandidates = (value) => {
|
|
2294
2358
|
candidates.push(selectorForValue(value));
|
|
2295
2359
|
const looksLikeIdToken = /^[A-Za-z_][\w.:-]*$/.test(value);
|
|
2296
2360
|
if (!hasExplicitResourceId && looksLikeIdToken && !/^(~|id=|css=|\/|\()/.test(value) && !value.includes(":id/")) {
|
|
2297
2361
|
candidates.push(`id=${value}`);
|
|
2298
2362
|
}
|
|
2363
|
+
if (!bundleContributedIdentity && !/^(~|id=|css=|\/|\()/.test(value)) {
|
|
2364
|
+
candidates.push(identityTextXpath(value));
|
|
2365
|
+
}
|
|
2299
2366
|
};
|
|
2300
2367
|
if (nonEmpty(target.primary))
|
|
2301
2368
|
pushValueCandidates(target.primary);
|
|
@@ -2990,7 +3057,8 @@ function isWebViewStep(step) {
|
|
|
2990
3057
|
function hasCrossPlatformIdentity(bundle) {
|
|
2991
3058
|
if (!bundle)
|
|
2992
3059
|
return false;
|
|
2993
|
-
|
|
3060
|
+
const durableText = isStateValueMobileClassName(bundle.className) ? void 0 : bundle.text;
|
|
3061
|
+
return Boolean(durableText?.trim() || bundle.contentDesc?.trim() || bundle.accessibilityId?.trim());
|
|
2994
3062
|
}
|
|
2995
3063
|
function stepIsDivergentForPlatform(step, platform3) {
|
|
2996
3064
|
return Array.isArray(step.crossPlatformDivergent) && step.crossPlatformDivergent.includes(platform3);
|
|
@@ -3072,7 +3140,8 @@ function summarizeMobileReadiness(steps, authoringPlatform) {
|
|
|
3072
3140
|
function hasStableNativeIdentity(bundle) {
|
|
3073
3141
|
if (!bundle)
|
|
3074
3142
|
return false;
|
|
3075
|
-
|
|
3143
|
+
const durableText = isStateValueMobileClassName(bundle.className) ? void 0 : bundle.text;
|
|
3144
|
+
return Boolean(bundle.accessibilityId?.trim() || bundle.resourceId?.trim() || durableText?.trim() || bundle.contentDesc?.trim() || bundle.xpath?.trim() && XPATH_ATTRIBUTE_EQUALITY_RE.test(bundle.xpath) && !/(?:contains|starts-with)\s*\(/i.test(bundle.xpath));
|
|
3076
3145
|
}
|
|
3077
3146
|
function isPositionalXpath(xpath) {
|
|
3078
3147
|
const value = xpath?.trim();
|
|
@@ -3152,6 +3221,7 @@ var MOBILE_PLATFORMS, LOCATOR_REQUIRING_ACTIONS, XPATH_ATTRIBUTE_EQUALITY_RE;
|
|
|
3152
3221
|
var init_mobile_cross_platform = __esm({
|
|
3153
3222
|
"../shared/dist/mobile-cross-platform.js"() {
|
|
3154
3223
|
"use strict";
|
|
3224
|
+
init_mobile_test_generation();
|
|
3155
3225
|
MOBILE_PLATFORMS = ["IOS", "ANDROID"];
|
|
3156
3226
|
LOCATOR_REQUIRING_ACTIONS = /* @__PURE__ */ new Set([
|
|
3157
3227
|
"tap",
|
|
@@ -6154,6 +6224,7 @@ __export(dist_exports, {
|
|
|
6154
6224
|
isEditableMobileClassName: () => isEditableMobileClassName,
|
|
6155
6225
|
isEmailFromIdentity: () => isEmailFromIdentity,
|
|
6156
6226
|
isEnvironmentalError: () => isEnvironmentalError,
|
|
6227
|
+
isIdentityMobileXpath: () => isIdentityMobileXpath,
|
|
6157
6228
|
isMobilePlatform: () => isMobilePlatform,
|
|
6158
6229
|
isPricingPlan: () => isPricingPlan,
|
|
6159
6230
|
isRunnableStep: () => isRunnableStep,
|
|
@@ -6161,6 +6232,7 @@ __export(dist_exports, {
|
|
|
6161
6232
|
isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
|
|
6162
6233
|
isSensitiveQueryKey: () => isSensitiveQueryKey,
|
|
6163
6234
|
isSharedAuthInjectEnabled: () => isSharedAuthInjectEnabled,
|
|
6235
|
+
isStateValueMobileClassName: () => isStateValueMobileClassName,
|
|
6164
6236
|
isSttModelForProvider: () => isSttModelForProvider,
|
|
6165
6237
|
isTestReadyForPlatform: () => isTestReadyForPlatform,
|
|
6166
6238
|
isTrialExpired: () => isTrialExpired,
|
|
@@ -7068,6 +7140,7 @@ var require_mobile_observation = __commonJS({
|
|
|
7068
7140
|
exports.buildCollectedElementsForScreen = buildCollectedElementsForScreen;
|
|
7069
7141
|
exports.extractNativeMobileLandmarks = extractNativeMobileLandmarks;
|
|
7070
7142
|
exports.safeMobileElementLabel = safeMobileElementLabel;
|
|
7143
|
+
var mobile_source_parser_js_1 = require_mobile_source_parser();
|
|
7071
7144
|
var DEFAULT_MAX_ELEMENTS = 60;
|
|
7072
7145
|
var MAX_LABEL_LENGTH = 80;
|
|
7073
7146
|
var EVIDENCE_LABEL_LIMIT = 40;
|
|
@@ -7158,7 +7231,8 @@ var require_mobile_observation = __commonJS({
|
|
|
7158
7231
|
const locators = [];
|
|
7159
7232
|
if (stableId)
|
|
7160
7233
|
locators.push({ strategy: "getByTestId", value: stableId, confidence: 0.95 });
|
|
7161
|
-
const
|
|
7234
|
+
const platform3 = element.role.startsWith("XCUIElementType") ? "IOS" : "ANDROID";
|
|
7235
|
+
const xpath = (0, mobile_source_parser_js_1.buildLocatorBundle)(element, platform3).xpath?.trim();
|
|
7162
7236
|
if (xpath)
|
|
7163
7237
|
locators.push({ strategy: "css", value: xpath, confidence: 0.4 });
|
|
7164
7238
|
const collected = {
|
|
@@ -11224,7 +11298,7 @@ var require_thread_stream = __commonJS({
|
|
|
11224
11298
|
var { version } = require_package();
|
|
11225
11299
|
var { EventEmitter: EventEmitter2 } = __require("events");
|
|
11226
11300
|
var { Worker } = __require("worker_threads");
|
|
11227
|
-
var { join:
|
|
11301
|
+
var { join: join8 } = __require("path");
|
|
11228
11302
|
var { pathToFileURL } = __require("url");
|
|
11229
11303
|
var { wait } = require_wait();
|
|
11230
11304
|
var {
|
|
@@ -11275,7 +11349,7 @@ var require_thread_stream = __commonJS({
|
|
|
11275
11349
|
function createWorker(stream, opts) {
|
|
11276
11350
|
const { filename, workerData } = opts;
|
|
11277
11351
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
11278
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
11352
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join8(__dirname, "lib", "worker.js");
|
|
11279
11353
|
const worker = new Worker(toExecute, {
|
|
11280
11354
|
...opts.workerOpts,
|
|
11281
11355
|
name: opts.workerOpts?.name || "thread-stream",
|
|
@@ -11743,7 +11817,7 @@ var require_transport = __commonJS({
|
|
|
11743
11817
|
var { createRequire } = __require("module");
|
|
11744
11818
|
var { existsSync: existsSync3 } = __require("fs");
|
|
11745
11819
|
var getCallers = require_caller();
|
|
11746
|
-
var { join:
|
|
11820
|
+
var { join: join8, isAbsolute: isAbsolute2, sep } = __require("path");
|
|
11747
11821
|
var { fileURLToPath } = __require("url");
|
|
11748
11822
|
var sleep = require_atomic_sleep();
|
|
11749
11823
|
var onExit = require_on_exit_leak_free();
|
|
@@ -11815,7 +11889,7 @@ var require_transport = __commonJS({
|
|
|
11815
11889
|
return false;
|
|
11816
11890
|
}
|
|
11817
11891
|
}
|
|
11818
|
-
return
|
|
11892
|
+
return isAbsolute2(path) && !existsSync3(path);
|
|
11819
11893
|
}
|
|
11820
11894
|
function stripQuotes(value) {
|
|
11821
11895
|
const first = value[0];
|
|
@@ -11896,7 +11970,7 @@ var require_transport = __commonJS({
|
|
|
11896
11970
|
throw new Error("only one of target or targets can be specified");
|
|
11897
11971
|
}
|
|
11898
11972
|
if (targets) {
|
|
11899
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
11973
|
+
target = bundlerOverrides["pino-worker"] || join8(__dirname, "worker.js");
|
|
11900
11974
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
11901
11975
|
return {
|
|
11902
11976
|
...dest,
|
|
@@ -11914,7 +11988,7 @@ var require_transport = __commonJS({
|
|
|
11914
11988
|
});
|
|
11915
11989
|
});
|
|
11916
11990
|
} else if (pipeline) {
|
|
11917
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
11991
|
+
target = bundlerOverrides["pino-worker"] || join8(__dirname, "worker.js");
|
|
11918
11992
|
options.pipelines = [pipeline.map((dest) => {
|
|
11919
11993
|
return {
|
|
11920
11994
|
...dest,
|
|
@@ -11933,11 +12007,11 @@ var require_transport = __commonJS({
|
|
|
11933
12007
|
return buildStream(fixTarget(target), options, worker, sync, name);
|
|
11934
12008
|
function fixTarget(origin) {
|
|
11935
12009
|
origin = bundlerOverrides[origin] || origin;
|
|
11936
|
-
if (
|
|
12010
|
+
if (isAbsolute2(origin) || origin.indexOf("file://") === 0) {
|
|
11937
12011
|
return origin;
|
|
11938
12012
|
}
|
|
11939
12013
|
if (origin === "pino/file") {
|
|
11940
|
-
return
|
|
12014
|
+
return join8(__dirname, "..", "file.js");
|
|
11941
12015
|
}
|
|
11942
12016
|
let fixTarget2;
|
|
11943
12017
|
for (const filePath of callers) {
|
|
@@ -12918,7 +12992,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
12918
12992
|
return circularValue;
|
|
12919
12993
|
}
|
|
12920
12994
|
let res = "";
|
|
12921
|
-
let
|
|
12995
|
+
let join8 = ",";
|
|
12922
12996
|
const originalIndentation = indentation;
|
|
12923
12997
|
if (Array.isArray(value)) {
|
|
12924
12998
|
if (value.length === 0) {
|
|
@@ -12932,7 +13006,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
12932
13006
|
indentation += spacer;
|
|
12933
13007
|
res += `
|
|
12934
13008
|
${indentation}`;
|
|
12935
|
-
|
|
13009
|
+
join8 = `,
|
|
12936
13010
|
${indentation}`;
|
|
12937
13011
|
}
|
|
12938
13012
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -12940,13 +13014,13 @@ ${indentation}`;
|
|
|
12940
13014
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
12941
13015
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
12942
13016
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
12943
|
-
res +=
|
|
13017
|
+
res += join8;
|
|
12944
13018
|
}
|
|
12945
13019
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
12946
13020
|
res += tmp !== void 0 ? tmp : "null";
|
|
12947
13021
|
if (value.length - 1 > maximumBreadth) {
|
|
12948
13022
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
12949
|
-
res += `${
|
|
13023
|
+
res += `${join8}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
12950
13024
|
}
|
|
12951
13025
|
if (spacer !== "") {
|
|
12952
13026
|
res += `
|
|
@@ -12967,7 +13041,7 @@ ${originalIndentation}`;
|
|
|
12967
13041
|
let separator = "";
|
|
12968
13042
|
if (spacer !== "") {
|
|
12969
13043
|
indentation += spacer;
|
|
12970
|
-
|
|
13044
|
+
join8 = `,
|
|
12971
13045
|
${indentation}`;
|
|
12972
13046
|
whitespace = " ";
|
|
12973
13047
|
}
|
|
@@ -12981,13 +13055,13 @@ ${indentation}`;
|
|
|
12981
13055
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
12982
13056
|
if (tmp !== void 0) {
|
|
12983
13057
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
12984
|
-
separator =
|
|
13058
|
+
separator = join8;
|
|
12985
13059
|
}
|
|
12986
13060
|
}
|
|
12987
13061
|
if (keyLength > maximumBreadth) {
|
|
12988
13062
|
const removedKeys = keyLength - maximumBreadth;
|
|
12989
13063
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
12990
|
-
separator =
|
|
13064
|
+
separator = join8;
|
|
12991
13065
|
}
|
|
12992
13066
|
if (spacer !== "" && separator.length > 1) {
|
|
12993
13067
|
res = `
|
|
@@ -13028,7 +13102,7 @@ ${originalIndentation}`;
|
|
|
13028
13102
|
}
|
|
13029
13103
|
const originalIndentation = indentation;
|
|
13030
13104
|
let res = "";
|
|
13031
|
-
let
|
|
13105
|
+
let join8 = ",";
|
|
13032
13106
|
if (Array.isArray(value)) {
|
|
13033
13107
|
if (value.length === 0) {
|
|
13034
13108
|
return "[]";
|
|
@@ -13041,7 +13115,7 @@ ${originalIndentation}`;
|
|
|
13041
13115
|
indentation += spacer;
|
|
13042
13116
|
res += `
|
|
13043
13117
|
${indentation}`;
|
|
13044
|
-
|
|
13118
|
+
join8 = `,
|
|
13045
13119
|
${indentation}`;
|
|
13046
13120
|
}
|
|
13047
13121
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -13049,13 +13123,13 @@ ${indentation}`;
|
|
|
13049
13123
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
13050
13124
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
13051
13125
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
13052
|
-
res +=
|
|
13126
|
+
res += join8;
|
|
13053
13127
|
}
|
|
13054
13128
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
13055
13129
|
res += tmp !== void 0 ? tmp : "null";
|
|
13056
13130
|
if (value.length - 1 > maximumBreadth) {
|
|
13057
13131
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
13058
|
-
res += `${
|
|
13132
|
+
res += `${join8}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
13059
13133
|
}
|
|
13060
13134
|
if (spacer !== "") {
|
|
13061
13135
|
res += `
|
|
@@ -13068,7 +13142,7 @@ ${originalIndentation}`;
|
|
|
13068
13142
|
let whitespace = "";
|
|
13069
13143
|
if (spacer !== "") {
|
|
13070
13144
|
indentation += spacer;
|
|
13071
|
-
|
|
13145
|
+
join8 = `,
|
|
13072
13146
|
${indentation}`;
|
|
13073
13147
|
whitespace = " ";
|
|
13074
13148
|
}
|
|
@@ -13077,7 +13151,7 @@ ${indentation}`;
|
|
|
13077
13151
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
13078
13152
|
if (tmp !== void 0) {
|
|
13079
13153
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
13080
|
-
separator =
|
|
13154
|
+
separator = join8;
|
|
13081
13155
|
}
|
|
13082
13156
|
}
|
|
13083
13157
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -13135,20 +13209,20 @@ ${originalIndentation}`;
|
|
|
13135
13209
|
indentation += spacer;
|
|
13136
13210
|
let res2 = `
|
|
13137
13211
|
${indentation}`;
|
|
13138
|
-
const
|
|
13212
|
+
const join9 = `,
|
|
13139
13213
|
${indentation}`;
|
|
13140
13214
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
13141
13215
|
let i = 0;
|
|
13142
13216
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
13143
13217
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
13144
13218
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
13145
|
-
res2 +=
|
|
13219
|
+
res2 += join9;
|
|
13146
13220
|
}
|
|
13147
13221
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
13148
13222
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
13149
13223
|
if (value.length - 1 > maximumBreadth) {
|
|
13150
13224
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
13151
|
-
res2 += `${
|
|
13225
|
+
res2 += `${join9}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
13152
13226
|
}
|
|
13153
13227
|
res2 += `
|
|
13154
13228
|
${originalIndentation}`;
|
|
@@ -13164,16 +13238,16 @@ ${originalIndentation}`;
|
|
|
13164
13238
|
return '"[Object]"';
|
|
13165
13239
|
}
|
|
13166
13240
|
indentation += spacer;
|
|
13167
|
-
const
|
|
13241
|
+
const join8 = `,
|
|
13168
13242
|
${indentation}`;
|
|
13169
13243
|
let res = "";
|
|
13170
13244
|
let separator = "";
|
|
13171
13245
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
13172
13246
|
if (isTypedArrayWithEntries(value)) {
|
|
13173
|
-
res += stringifyTypedArray(value,
|
|
13247
|
+
res += stringifyTypedArray(value, join8, maximumBreadth);
|
|
13174
13248
|
keys = keys.slice(value.length);
|
|
13175
13249
|
maximumPropertiesToStringify -= value.length;
|
|
13176
|
-
separator =
|
|
13250
|
+
separator = join8;
|
|
13177
13251
|
}
|
|
13178
13252
|
if (deterministic) {
|
|
13179
13253
|
keys = sort(keys, comparator);
|
|
@@ -13184,13 +13258,13 @@ ${indentation}`;
|
|
|
13184
13258
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
13185
13259
|
if (tmp !== void 0) {
|
|
13186
13260
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
13187
|
-
separator =
|
|
13261
|
+
separator = join8;
|
|
13188
13262
|
}
|
|
13189
13263
|
}
|
|
13190
13264
|
if (keyLength > maximumBreadth) {
|
|
13191
13265
|
const removedKeys = keyLength - maximumBreadth;
|
|
13192
13266
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
13193
|
-
separator =
|
|
13267
|
+
separator = join8;
|
|
13194
13268
|
}
|
|
13195
13269
|
if (separator !== "") {
|
|
13196
13270
|
res = `
|
|
@@ -61866,11 +61940,11 @@ var require_core = __commonJS({
|
|
|
61866
61940
|
function mapOuter(r, f) {
|
|
61867
61941
|
return r.matched ? f(r) : r;
|
|
61868
61942
|
}
|
|
61869
|
-
function ab(pa, pb,
|
|
61870
|
-
return (data, i) => mapOuter(pa(data, i), (ma) => mapInner(pb(data, ma.position), (vb, j) =>
|
|
61943
|
+
function ab(pa, pb, join8) {
|
|
61944
|
+
return (data, i) => mapOuter(pa(data, i), (ma) => mapInner(pb(data, ma.position), (vb, j) => join8(ma.value, vb, data, i, j)));
|
|
61871
61945
|
}
|
|
61872
|
-
function abc(pa, pb, pc,
|
|
61873
|
-
return (data, i) => mapOuter(pa(data, i), (ma) => mapOuter(pb(data, ma.position), (mb) => mapInner(pc(data, mb.position), (vc, j) =>
|
|
61946
|
+
function abc(pa, pb, pc, join8) {
|
|
61947
|
+
return (data, i) => mapOuter(pa(data, i), (ma) => mapOuter(pb(data, ma.position), (mb) => mapInner(pc(data, mb.position), (vc, j) => join8(ma.value, mb.value, vc, data, i, j))));
|
|
61874
61948
|
}
|
|
61875
61949
|
function action(f) {
|
|
61876
61950
|
return (data, i) => {
|
|
@@ -78407,6 +78481,10 @@ var require_discover_ai_planner = __commonJS({
|
|
|
78407
78481
|
if (context.appBrief) {
|
|
78408
78482
|
sections.push(`## Application Brief
|
|
78409
78483
|
${context.appBrief.slice(0, 500)}`);
|
|
78484
|
+
}
|
|
78485
|
+
if (context.knowledgeContext && context.knowledgeContext.trim().length > 0) {
|
|
78486
|
+
sections.push(`## Known Facts
|
|
78487
|
+
${context.knowledgeContext.slice(0, 4e3)}`);
|
|
78410
78488
|
}
|
|
78411
78489
|
sections.push(`## Base URL
|
|
78412
78490
|
${compressed.baseUrl}`);
|
|
@@ -83451,48 +83529,29 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83451
83529
|
log2(" app data reset skipped by policy \u2014 scenario records after relaunch with preserved data.");
|
|
83452
83530
|
return applyScenarioEntryDeepLink(driver, target, { appDataCleared: false, coldResetUnavailable: false, refuseGenerate: false, reason: "preserve_policy" }, log2);
|
|
83453
83531
|
}
|
|
83454
|
-
|
|
83455
|
-
|
|
83456
|
-
|
|
83457
|
-
|
|
83458
|
-
|
|
83459
|
-
|
|
83460
|
-
return applyScenarioEntryDeepLink(driver, target, { appDataCleared: true, coldResetUnavailable: false, refuseGenerate: false }, log2);
|
|
83461
|
-
}
|
|
83462
|
-
await driver.relaunch();
|
|
83463
|
-
if (resetPolicy === "CLEAR_APP_DATA") {
|
|
83464
|
-
log2(" CLEAR_APP_DATA required but app-data reset was unavailable \u2014 refusing to generate from warm state.");
|
|
83465
|
-
return {
|
|
83466
|
-
appDataCleared: false,
|
|
83467
|
-
coldResetUnavailable: true,
|
|
83468
|
-
refuseGenerate: true,
|
|
83469
|
-
reason: "clear_app_data_unavailable"
|
|
83470
|
-
};
|
|
83471
|
-
}
|
|
83472
|
-
log2(" app-data reset unavailable \u2014 generating with @warm-state (relaunch only; may not match cold-boot replay).");
|
|
83473
|
-
return applyScenarioEntryDeepLink(driver, target, {
|
|
83474
|
-
appDataCleared: false,
|
|
83475
|
-
coldResetUnavailable: true,
|
|
83476
|
-
refuseGenerate: false,
|
|
83477
|
-
reason: platform3 === "ANDROID" ? "android_reset_unavailable" : "reset_unavailable"
|
|
83478
|
-
}, log2);
|
|
83532
|
+
const appDataCleared = await driver.resetAppState?.().catch((err) => {
|
|
83533
|
+
log2(` app-data reset failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
83534
|
+
return false;
|
|
83535
|
+
}) ?? false;
|
|
83536
|
+
if (appDataCleared) {
|
|
83537
|
+
return applyScenarioEntryDeepLink(driver, target, { appDataCleared: true, coldResetUnavailable: false, refuseGenerate: false }, log2);
|
|
83479
83538
|
}
|
|
83480
83539
|
await driver.relaunch();
|
|
83481
83540
|
if (resetPolicy === "CLEAR_APP_DATA") {
|
|
83482
|
-
log2(" CLEAR_APP_DATA required but iOS
|
|
83541
|
+
log2(platform3 === "IOS" ? " CLEAR_APP_DATA required but this iOS device cannot be cleared (simulator required) \u2014 refusing to generate from warm state." : " CLEAR_APP_DATA required but app-data reset was unavailable \u2014 refusing to generate from warm state.");
|
|
83483
83542
|
return {
|
|
83484
83543
|
appDataCleared: false,
|
|
83485
83544
|
coldResetUnavailable: true,
|
|
83486
83545
|
refuseGenerate: true,
|
|
83487
|
-
reason: "ios_clear_app_data_unavailable"
|
|
83546
|
+
reason: platform3 === "IOS" ? "ios_clear_app_data_unavailable" : "clear_app_data_unavailable"
|
|
83488
83547
|
};
|
|
83489
83548
|
}
|
|
83490
|
-
log2(" iOS
|
|
83549
|
+
log2(platform3 === "IOS" ? " iOS app-data reset unavailable (physical device or simctl failure) \u2014 generating with @warm-state (relaunch only; may not match cold-boot replay)." : " app-data reset unavailable \u2014 generating with @warm-state (relaunch only; may not match cold-boot replay).");
|
|
83491
83550
|
return applyScenarioEntryDeepLink(driver, target, {
|
|
83492
83551
|
appDataCleared: false,
|
|
83493
83552
|
coldResetUnavailable: true,
|
|
83494
83553
|
refuseGenerate: false,
|
|
83495
|
-
reason: "ios_no_clear_app"
|
|
83554
|
+
reason: platform3 === "IOS" ? "ios_no_clear_app" : platform3 === "ANDROID" ? "android_reset_unavailable" : "reset_unavailable"
|
|
83496
83555
|
}, log2);
|
|
83497
83556
|
}
|
|
83498
83557
|
async function runMobileGeneratePhase(args) {
|
|
@@ -88953,7 +89012,7 @@ var require_kill_tree = __commonJS({
|
|
|
88953
89012
|
var node_child_process_1 = __require("child_process");
|
|
88954
89013
|
var node_util_1 = __require("util");
|
|
88955
89014
|
var node_fs_1 = __require("fs");
|
|
88956
|
-
var
|
|
89015
|
+
var execFileAsync4 = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
88957
89016
|
var PS_TIMEOUT_MS = 2e3;
|
|
88958
89017
|
var PS_BUFFER = 1024 * 1024;
|
|
88959
89018
|
async function gracefulCloseWithTreeKill(options) {
|
|
@@ -89007,7 +89066,7 @@ var require_kill_tree = __commonJS({
|
|
|
89007
89066
|
timer.unref?.();
|
|
89008
89067
|
let stdout;
|
|
89009
89068
|
try {
|
|
89010
|
-
const result = await
|
|
89069
|
+
const result = await execFileAsync4("ps", ["-A", "-o", "pid=,ppid=,command="], {
|
|
89011
89070
|
maxBuffer: PS_BUFFER,
|
|
89012
89071
|
signal: controller.signal
|
|
89013
89072
|
});
|
|
@@ -89045,7 +89104,7 @@ var require_kill_tree = __commonJS({
|
|
|
89045
89104
|
timer.unref?.();
|
|
89046
89105
|
let stdout;
|
|
89047
89106
|
try {
|
|
89048
|
-
const result = await
|
|
89107
|
+
const result = await execFileAsync4("ps", ["-A", "-o", "pid=,ppid=,command="], {
|
|
89049
89108
|
maxBuffer: PS_BUFFER,
|
|
89050
89109
|
signal: controller.signal
|
|
89051
89110
|
});
|
|
@@ -89122,7 +89181,7 @@ var require_kill_tree = __commonJS({
|
|
|
89122
89181
|
timer.unref?.();
|
|
89123
89182
|
let stdout;
|
|
89124
89183
|
try {
|
|
89125
|
-
const result = await
|
|
89184
|
+
const result = await execFileAsync4("ps", ["-A", "-o", "pid=,ppid="], {
|
|
89126
89185
|
maxBuffer: PS_BUFFER,
|
|
89127
89186
|
signal: controller.signal
|
|
89128
89187
|
});
|
|
@@ -92288,6 +92347,15 @@ var require_agentic_discovery_prompts = __commonJS({
|
|
|
92288
92347
|
lines.push(input.appBrief.trim());
|
|
92289
92348
|
lines.push("");
|
|
92290
92349
|
}
|
|
92350
|
+
if (input.knownFactsBlock && input.knownFactsBlock.trim()) {
|
|
92351
|
+
lines.push("## Known facts about this feature (from project documentation)");
|
|
92352
|
+
lines.push("");
|
|
92353
|
+
lines.push("Advisory hints about routes/data/constraints \u2014 verify against the live");
|
|
92354
|
+
lines.push("app; the browser is the source of truth for locators.");
|
|
92355
|
+
lines.push("");
|
|
92356
|
+
lines.push(input.knownFactsBlock.trim());
|
|
92357
|
+
lines.push("");
|
|
92358
|
+
}
|
|
92291
92359
|
if (input.credentials && input.credentials.length > 0) {
|
|
92292
92360
|
lines.push("## Test credentials (throwaway accounts)");
|
|
92293
92361
|
lines.push("");
|
|
@@ -98663,8 +98731,8 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
|
|
|
98663
98731
|
onRetry: (attempt, reason, delayMs) => logs2.push(` \u26A0 scenario-gen AI call retry ${attempt} (${reason}) \u2014 waiting ${delayMs}ms`)
|
|
98664
98732
|
});
|
|
98665
98733
|
} catch (aiErr) {
|
|
98666
|
-
const
|
|
98667
|
-
logs2.push(` AI call failed at iteration ${iteration} (after retries): ${
|
|
98734
|
+
const errMsg3 = aiErr instanceof Error ? aiErr.message : String(aiErr);
|
|
98735
|
+
logs2.push(` AI call failed at iteration ${iteration} (after retries): ${errMsg3}`);
|
|
98668
98736
|
throw (0, ai_retry_js_1.markAIProviderError)(aiErr);
|
|
98669
98737
|
}
|
|
98670
98738
|
const iterDuration = Date.now() - iterStart;
|
|
@@ -98707,8 +98775,8 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
|
|
|
98707
98775
|
try {
|
|
98708
98776
|
toolArgs = JSON.parse(toolCall.function.arguments);
|
|
98709
98777
|
} catch (parseErr) {
|
|
98710
|
-
const
|
|
98711
|
-
toolResults.push({ role: "tool", tool_call_id: toolCall.id, content:
|
|
98778
|
+
const errMsg3 = `Invalid tool arguments: ${parseErr.message}`;
|
|
98779
|
+
toolResults.push({ role: "tool", tool_call_id: toolCall.id, content: errMsg3 });
|
|
98712
98780
|
continue;
|
|
98713
98781
|
}
|
|
98714
98782
|
if (toolName === "finish_generate") {
|
|
@@ -99926,7 +99994,7 @@ var require_util4 = __commonJS({
|
|
|
99926
99994
|
}
|
|
99927
99995
|
path = url.path;
|
|
99928
99996
|
}
|
|
99929
|
-
var
|
|
99997
|
+
var isAbsolute2 = exports.isAbsolute(path);
|
|
99930
99998
|
var parts = path.split(/\/+/);
|
|
99931
99999
|
for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
|
|
99932
100000
|
part = parts[i];
|
|
@@ -99946,7 +100014,7 @@ var require_util4 = __commonJS({
|
|
|
99946
100014
|
}
|
|
99947
100015
|
path = parts.join("/");
|
|
99948
100016
|
if (path === "") {
|
|
99949
|
-
path =
|
|
100017
|
+
path = isAbsolute2 ? "/" : ".";
|
|
99950
100018
|
}
|
|
99951
100019
|
if (url) {
|
|
99952
100020
|
url.path = path;
|
|
@@ -99955,7 +100023,7 @@ var require_util4 = __commonJS({
|
|
|
99955
100023
|
return path;
|
|
99956
100024
|
}
|
|
99957
100025
|
exports.normalize = normalize;
|
|
99958
|
-
function
|
|
100026
|
+
function join8(aRoot, aPath) {
|
|
99959
100027
|
if (aRoot === "") {
|
|
99960
100028
|
aRoot = ".";
|
|
99961
100029
|
}
|
|
@@ -99987,7 +100055,7 @@ var require_util4 = __commonJS({
|
|
|
99987
100055
|
}
|
|
99988
100056
|
return joined;
|
|
99989
100057
|
}
|
|
99990
|
-
exports.join =
|
|
100058
|
+
exports.join = join8;
|
|
99991
100059
|
exports.isAbsolute = function(aPath) {
|
|
99992
100060
|
return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
|
|
99993
100061
|
};
|
|
@@ -100160,7 +100228,7 @@ var require_util4 = __commonJS({
|
|
|
100160
100228
|
parsed.path = parsed.path.substring(0, index + 1);
|
|
100161
100229
|
}
|
|
100162
100230
|
}
|
|
100163
|
-
sourceURL =
|
|
100231
|
+
sourceURL = join8(urlGenerate(parsed), sourceURL);
|
|
100164
100232
|
}
|
|
100165
100233
|
return normalize(sourceURL);
|
|
100166
100234
|
}
|
|
@@ -313872,8 +313940,8 @@ Try a different fix approach. This is NOT your final script-driven attempt yet,
|
|
|
313872
313940
|
...(0, ai_provider_js_1.getJsonObjectResponseFormat)(agentModel)
|
|
313873
313941
|
})), { label: `script-heal-${attempt}` });
|
|
313874
313942
|
} catch (aiErr) {
|
|
313875
|
-
const
|
|
313876
|
-
logs2.push(`\u26A0 AI call failed: ${
|
|
313943
|
+
const errMsg3 = aiErr instanceof Error ? aiErr.message : String(aiErr);
|
|
313944
|
+
logs2.push(`\u26A0 AI call failed: ${errMsg3}`);
|
|
313877
313945
|
attemptHistory.push({
|
|
313878
313946
|
phase: "script",
|
|
313879
313947
|
attempt,
|
|
@@ -313881,13 +313949,13 @@ Try a different fix approach. This is NOT your final script-driven attempt yet,
|
|
|
313881
313949
|
passed: false,
|
|
313882
313950
|
changedCode: false,
|
|
313883
313951
|
summary: "AI call failed before returning a heal result",
|
|
313884
|
-
errorSummary: truncateForPrompt(
|
|
313952
|
+
errorSummary: truncateForPrompt(errMsg3),
|
|
313885
313953
|
failedStep: summarizeFailedStep(currentStepResults)
|
|
313886
313954
|
});
|
|
313887
313955
|
if (feedbackEnabled) {
|
|
313888
|
-
lastTransportFailure = truncateForPrompt(
|
|
313956
|
+
lastTransportFailure = truncateForPrompt(errMsg3, 200) ?? "the AI request failed";
|
|
313889
313957
|
} else {
|
|
313890
|
-
currentError = `AI call failed on attempt ${attempt}: ${
|
|
313958
|
+
currentError = `AI call failed on attempt ${attempt}: ${errMsg3}`;
|
|
313891
313959
|
}
|
|
313892
313960
|
markRecoverableTurn();
|
|
313893
313961
|
continue;
|
|
@@ -314137,8 +314205,8 @@ ${currentError.slice(0, 2e3)}`
|
|
|
314137
314205
|
// item 5b
|
|
314138
314206
|
});
|
|
314139
314207
|
} catch (runErr) {
|
|
314140
|
-
const
|
|
314141
|
-
logs2.push(`\u{1F4A5} Native execution threw: ${
|
|
314208
|
+
const errMsg3 = runErr instanceof Error ? runErr.message : String(runErr);
|
|
314209
|
+
logs2.push(`\u{1F4A5} Native execution threw: ${errMsg3}`);
|
|
314142
314210
|
attemptHistory.push({
|
|
314143
314211
|
phase: "script",
|
|
314144
314212
|
attempt,
|
|
@@ -314146,9 +314214,9 @@ ${currentError.slice(0, 2e3)}`
|
|
|
314146
314214
|
passed: false,
|
|
314147
314215
|
changedCode: true,
|
|
314148
314216
|
summary: truncateForPrompt(changeDesc) ?? "Applied code fix, but native execution threw before verification completed",
|
|
314149
|
-
errorSummary: truncateForPrompt(
|
|
314217
|
+
errorSummary: truncateForPrompt(errMsg3)
|
|
314150
314218
|
});
|
|
314151
|
-
currentError = `Native execution threw: ${
|
|
314219
|
+
currentError = `Native execution threw: ${errMsg3}`;
|
|
314152
314220
|
currentStepResults = [];
|
|
314153
314221
|
continue;
|
|
314154
314222
|
}
|
|
@@ -314904,9 +314972,9 @@ Last error (after heal attempts): ${lastErrorSnippet}` : `Heal aborted: same err
|
|
|
314904
314972
|
previousErrorSignature = errorSig;
|
|
314905
314973
|
}
|
|
314906
314974
|
} catch (runErr) {
|
|
314907
|
-
const
|
|
314908
|
-
logs2.push(`\u{1F4A5} Native run threw: ${
|
|
314909
|
-
currentError =
|
|
314975
|
+
const errMsg3 = runErr instanceof Error ? runErr.message : String(runErr);
|
|
314976
|
+
logs2.push(`\u{1F4A5} Native run threw: ${errMsg3}`);
|
|
314977
|
+
currentError = errMsg3;
|
|
314910
314978
|
currentStepResults = [];
|
|
314911
314979
|
}
|
|
314912
314980
|
} else {
|
|
@@ -315468,8 +315536,8 @@ When you call finish_fix, preserve locators only when they are stable structural
|
|
|
315468
315536
|
}
|
|
315469
315537
|
}
|
|
315470
315538
|
} catch (toolErr) {
|
|
315471
|
-
const
|
|
315472
|
-
let errorContent = `Error: ${
|
|
315539
|
+
const errMsg3 = toolErr instanceof Error ? toolErr.message : String(toolErr);
|
|
315540
|
+
let errorContent = `Error: ${errMsg3}`;
|
|
315473
315541
|
try {
|
|
315474
315542
|
const consoleResult = await mcpClient.callTool({ name: "browser_console_messages", arguments: {} });
|
|
315475
315543
|
const consoleText = Array.isArray(consoleResult.content) ? consoleResult.content.map((c) => c.type === "text" ? c.text ?? "" : "").join("\n") : String(consoleResult.content ?? "");
|
|
@@ -315490,8 +315558,8 @@ ${errorContent}`;
|
|
|
315490
315558
|
}
|
|
315491
315559
|
}
|
|
315492
315560
|
} catch (mcpErr) {
|
|
315493
|
-
const
|
|
315494
|
-
logs2.push(`\u{1F4A5} MCP exploration failed: ${
|
|
315561
|
+
const errMsg3 = mcpErr instanceof Error ? mcpErr.message : String(mcpErr);
|
|
315562
|
+
logs2.push(`\u{1F4A5} MCP exploration failed: ${errMsg3}`);
|
|
315495
315563
|
}
|
|
315496
315564
|
if (verdict === "app_bug") {
|
|
315497
315565
|
phase2AttemptHistory.push({
|
|
@@ -316054,9 +316122,9 @@ ${testBlocks}
|
|
|
316054
316122
|
}
|
|
316055
316123
|
emit(` \u2713 ${toolResultContent.slice(0, 200)}${toolResultContent.length > 200 ? "..." : ""}`);
|
|
316056
316124
|
} catch (err) {
|
|
316057
|
-
const
|
|
316058
|
-
toolResultContent = `ERROR: ${
|
|
316059
|
-
emit(` \u2717 ${
|
|
316125
|
+
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
316126
|
+
toolResultContent = `ERROR: ${errMsg3}`;
|
|
316127
|
+
emit(` \u2717 ${errMsg3}`);
|
|
316060
316128
|
}
|
|
316061
316129
|
toolResults.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
316062
316130
|
}
|
|
@@ -321911,8 +321979,8 @@ var require_capture_page_normalizer = __commonJS({
|
|
|
321911
321979
|
const snapItems = Array.isArray(snapResult.content) ? snapResult.content : [];
|
|
321912
321980
|
snapshotText = extractInlineSnapshotYaml(snapItems.find((c) => c.type === "text")?.text || null);
|
|
321913
321981
|
} catch (err) {
|
|
321914
|
-
const
|
|
321915
|
-
const isTimeout = /timeout|timed out|TimeoutError/i.test(
|
|
321982
|
+
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
321983
|
+
const isTimeout = /timeout|timed out|TimeoutError/i.test(errMsg3);
|
|
321916
321984
|
return {
|
|
321917
321985
|
snapshotText: null,
|
|
321918
321986
|
meta: {
|
|
@@ -325934,9 +326002,9 @@ ${currentSummary}`;
|
|
|
325934
326002
|
});
|
|
325935
326003
|
}
|
|
325936
326004
|
} catch (err) {
|
|
325937
|
-
const
|
|
325938
|
-
logs2.push(` Error persisting page: ${
|
|
325939
|
-
toolResults.push({ role: "tool", tool_call_id: toolCall.id, content: `ERROR persisting page: ${
|
|
326005
|
+
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
326006
|
+
logs2.push(` Error persisting page: ${errMsg3}`);
|
|
326007
|
+
toolResults.push({ role: "tool", tool_call_id: toolCall.id, content: `ERROR persisting page: ${errMsg3}` });
|
|
325940
326008
|
}
|
|
325941
326009
|
continue;
|
|
325942
326010
|
}
|
|
@@ -326138,12 +326206,12 @@ ${currentSummary}`;
|
|
|
326138
326206
|
})
|
|
326139
326207
|
});
|
|
326140
326208
|
} catch (err) {
|
|
326141
|
-
const
|
|
326142
|
-
logs2.push(` Error persisting page: ${
|
|
326209
|
+
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
326210
|
+
logs2.push(` Error persisting page: ${errMsg3}`);
|
|
326143
326211
|
toolResults.push({
|
|
326144
326212
|
role: "tool",
|
|
326145
326213
|
tool_call_id: toolCall.id,
|
|
326146
|
-
content: `ERROR persisting page: ${
|
|
326214
|
+
content: `ERROR persisting page: ${errMsg3}`
|
|
326147
326215
|
});
|
|
326148
326216
|
}
|
|
326149
326217
|
continue;
|
|
@@ -326178,12 +326246,12 @@ ${currentSummary}`;
|
|
|
326178
326246
|
content: JSON.stringify({ acknowledged: true })
|
|
326179
326247
|
});
|
|
326180
326248
|
} catch (err) {
|
|
326181
|
-
const
|
|
326182
|
-
logs2.push(` Error persisting transition: ${
|
|
326249
|
+
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
326250
|
+
logs2.push(` Error persisting transition: ${errMsg3}`);
|
|
326183
326251
|
toolResults.push({
|
|
326184
326252
|
role: "tool",
|
|
326185
326253
|
tool_call_id: toolCall.id,
|
|
326186
|
-
content: `ERROR persisting transition: ${
|
|
326254
|
+
content: `ERROR persisting transition: ${errMsg3}`
|
|
326187
326255
|
});
|
|
326188
326256
|
}
|
|
326189
326257
|
continue;
|
|
@@ -326838,15 +326906,15 @@ Exploration complete: ${summary}`);
|
|
|
326838
326906
|
toolResultContent = (0, snapshot_context_compactor_js_1.formatMCPToolResult)(toolResultContent, MAX_TOOL_RESULT_CHARS);
|
|
326839
326907
|
logs2.push(` OK: ${toolResultContent.slice(0, 200)}${toolResultContent.length > 200 ? "..." : ""}`);
|
|
326840
326908
|
} catch (err) {
|
|
326841
|
-
const
|
|
326842
|
-
toolResultContent = `ERROR: ${
|
|
326843
|
-
logs2.push(` Error: ${
|
|
326909
|
+
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
326910
|
+
toolResultContent = `ERROR: ${errMsg3}`;
|
|
326911
|
+
logs2.push(` Error: ${errMsg3}`);
|
|
326844
326912
|
if (useV2) {
|
|
326845
326913
|
(0, exploration_state_js_1.reduceEvent)(state2, {
|
|
326846
326914
|
type: "ACTION_FAILED",
|
|
326847
326915
|
route: state2.currentRoute,
|
|
326848
326916
|
action: `${toolName}(${JSON.stringify(toolArgs).slice(0, 100)})`,
|
|
326849
|
-
error:
|
|
326917
|
+
error: errMsg3
|
|
326850
326918
|
});
|
|
326851
326919
|
}
|
|
326852
326920
|
}
|
|
@@ -328955,6 +329023,15 @@ var require_agentic_survey_prompts = __commonJS({
|
|
|
328955
329023
|
lines.push(input.appBrief.trim());
|
|
328956
329024
|
lines.push("");
|
|
328957
329025
|
}
|
|
329026
|
+
if (input.knownFactsBlock && input.knownFactsBlock.trim().length > 0) {
|
|
329027
|
+
lines.push("## Known facts about this app (from project documentation)");
|
|
329028
|
+
lines.push("");
|
|
329029
|
+
lines.push("Use these as hints for what features/routes/constraints exist. They are");
|
|
329030
|
+
lines.push("advisory \u2014 verify against the live app; the browser is the source of truth.");
|
|
329031
|
+
lines.push("");
|
|
329032
|
+
lines.push(input.knownFactsBlock.trim());
|
|
329033
|
+
lines.push("");
|
|
329034
|
+
}
|
|
328958
329035
|
if (input.credentials && input.credentials.length > 0) {
|
|
328959
329036
|
lines.push("## Test credentials (throwaway accounts)");
|
|
328960
329037
|
lines.push("");
|
|
@@ -329582,6 +329659,7 @@ var require_grok_survey_runner = __commonJS({
|
|
|
329582
329659
|
const briefInput = {
|
|
329583
329660
|
baseUrl: ctx.baseUrl,
|
|
329584
329661
|
appBrief: ctx.appBrief,
|
|
329662
|
+
knownFactsBlock: ctx.knowledgeContext,
|
|
329585
329663
|
credentials: credentials.length > 0 ? credentials : void 0,
|
|
329586
329664
|
existingFeatureNames: ctx.existingFeatureNames ?? [],
|
|
329587
329665
|
focusAreas: ctx.focusAreas,
|
|
@@ -329682,10 +329760,10 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
|
|
|
329682
329760
|
onLog: options.onLog,
|
|
329683
329761
|
onProvenCommands: recordProvenCommands
|
|
329684
329762
|
});
|
|
329685
|
-
const
|
|
329763
|
+
const errMsg3 = firstError?.message ?? (grokError instanceof Error ? grokError.message : "unknown error");
|
|
329686
329764
|
const fixPrompt = [
|
|
329687
329765
|
"Your previous attempt did not produce a valid result file.",
|
|
329688
|
-
`Error: ${
|
|
329766
|
+
`Error: ${errMsg3}`,
|
|
329689
329767
|
`Re-read BRIEF.md and write a valid SurveyorOutput JSON to ${resultFilePath}.`,
|
|
329690
329768
|
"Single JSON object. No prose. Then exit."
|
|
329691
329769
|
].join("\n");
|
|
@@ -331277,6 +331355,7 @@ var require_grok_discovery_runner = __commonJS({
|
|
|
331277
331355
|
featureObservations: ctx.featureContext?.flowIntel,
|
|
331278
331356
|
notesForTestAuthor: ctx.featureContext?.priorInsight,
|
|
331279
331357
|
appBrief: ctx.appBrief,
|
|
331358
|
+
knownFactsBlock: ctx.knowledgeContext,
|
|
331280
331359
|
credentials: credentials.length > 0 ? credentials : void 0,
|
|
331281
331360
|
testVariableKeys: testVariableKeys.length > 0 ? testVariableKeys : void 0,
|
|
331282
331361
|
existingTestNames: ctx.existingTestNames ?? [],
|
|
@@ -331825,7 +331904,7 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
|
|
|
331825
331904
|
log2(`First-pass discovery-result invalid or missing: ${firstError.message}`);
|
|
331826
331905
|
}
|
|
331827
331906
|
if (!parsedResult) {
|
|
331828
|
-
const
|
|
331907
|
+
const errMsg3 = firstError?.message ?? (grokError instanceof Error ? grokError.message : "unknown error");
|
|
331829
331908
|
if (planDebounceTimerRef.current) {
|
|
331830
331909
|
clearTimeout(planDebounceTimerRef.current);
|
|
331831
331910
|
planDebounceTimerRef.current = void 0;
|
|
@@ -331850,7 +331929,7 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
|
|
|
331850
331929
|
""
|
|
331851
331930
|
].join("\n");
|
|
331852
331931
|
const fixPrompt = [
|
|
331853
|
-
`Your previous attempt did not produce a valid result file. Error: ${
|
|
331932
|
+
`Your previous attempt did not produce a valid result file. Error: ${errMsg3}`,
|
|
331854
331933
|
"Re-read BRIEF.md.",
|
|
331855
331934
|
phaseContext,
|
|
331856
331935
|
`Continue from where you left off \u2014 already verified tests are in ${testsDirPath}/`,
|
|
@@ -332214,6 +332293,7 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
|
|
|
332214
332293
|
featureObservations: ctx.featureContext?.flowIntel,
|
|
332215
332294
|
notesForTestAuthor: ctx.featureContext?.priorInsight,
|
|
332216
332295
|
appBrief: ctx.appBrief,
|
|
332296
|
+
knownFactsBlock: ctx.knowledgeContext,
|
|
332217
332297
|
credentials: credentials.length > 0 ? credentials : void 0,
|
|
332218
332298
|
testVariableKeys: testVariableKeys.length > 0 ? testVariableKeys : void 0,
|
|
332219
332299
|
existingTestNames: ctx.existingTestNames ?? [],
|
|
@@ -333305,6 +333385,7 @@ ${preview}`);
|
|
|
333305
333385
|
const briefInput = {
|
|
333306
333386
|
baseUrl: ctx.baseUrl,
|
|
333307
333387
|
appBrief: ctx.appBrief,
|
|
333388
|
+
knownFactsBlock: ctx.knowledgeContext,
|
|
333308
333389
|
credentials: credentials.length > 0 ? credentials : void 0,
|
|
333309
333390
|
existingFeatureNames: ctx.existingFeatureNames ?? [],
|
|
333310
333391
|
focusAreas: ctx.focusAreas,
|
|
@@ -333477,10 +333558,10 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
|
|
|
333477
333558
|
onLog: parserLog,
|
|
333478
333559
|
onProvenCommands: recordProvenCommands
|
|
333479
333560
|
});
|
|
333480
|
-
const
|
|
333561
|
+
const errMsg3 = firstError?.message ?? (claudeError instanceof Error ? claudeError.message : "unknown error");
|
|
333481
333562
|
const fixPrompt = [
|
|
333482
333563
|
"Your previous attempt did not leave a valid result file.",
|
|
333483
|
-
`Error: ${
|
|
333564
|
+
`Error: ${errMsg3}`,
|
|
333484
333565
|
`Writing a valid SurveyorOutput JSON object to ${resultFilePath} is your ONLY priority now.`,
|
|
333485
333566
|
"Procedure: navigate to at most 6-8 of the main pages, then IMMEDIATELY use",
|
|
333486
333567
|
`the Write tool to save the SurveyorOutput JSON to ${resultFilePath}. Do this`,
|
|
@@ -334164,6 +334245,7 @@ var require_claude_discovery_runner = __commonJS({
|
|
|
334164
334245
|
featureObservations: ctx.featureContext?.flowIntel,
|
|
334165
334246
|
notesForTestAuthor: ctx.featureContext?.priorInsight,
|
|
334166
334247
|
appBrief: ctx.appBrief,
|
|
334248
|
+
knownFactsBlock: ctx.knowledgeContext,
|
|
334167
334249
|
credentials: credentials.length > 0 ? credentials : void 0,
|
|
334168
334250
|
testVariableKeys: testVariableKeys.length > 0 ? testVariableKeys : void 0,
|
|
334169
334251
|
existingTestNames: ctx.existingTestNames ?? [],
|
|
@@ -336018,7 +336100,8 @@ Explore complete: ${exploreResult.pagesDiscovered} pages, ${exploreResult.elemen
|
|
|
336018
336100
|
// scenarioTouchesSignupAuthSurface) still isolate non-auth scenarios. The
|
|
336019
336101
|
// auth-feature scope IS applied at the DEEP call site where ctx.featureName is known.
|
|
336020
336102
|
emailVerifyArmed: mode !== "survey" && plannerEmailVerifyArmed(ctx),
|
|
336021
|
-
testBudget
|
|
336103
|
+
testBudget,
|
|
336104
|
+
knowledgeContext: ctx.knowledgeContext
|
|
336022
336105
|
}, {
|
|
336023
336106
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
336024
336107
|
gateModelOverride: ctx.planGateModel,
|
|
@@ -336130,6 +336213,7 @@ Health: ${surveyHealth.totalIssues} issue(s) - ${surveyHealth.consoleErrors.leng
|
|
|
336130
336213
|
interactionLog: exploreResult.interactionLog,
|
|
336131
336214
|
modelOverride: ctx.generateModel,
|
|
336132
336215
|
appBrief: ctx.appBrief,
|
|
336216
|
+
knowledgeContext: ctx.knowledgeContext,
|
|
336133
336217
|
onAICall,
|
|
336134
336218
|
onProgress,
|
|
336135
336219
|
mcpClient: mcpClient ?? void 0,
|
|
@@ -336609,7 +336693,8 @@ Plan (reused): ${resumeSavedScenarios.length} scenarios from the paused run.`);
|
|
|
336609
336693
|
// configured inbox), so the planner never promises what generation can't do.
|
|
336610
336694
|
// Scoped to AUTH features: a non-auth feature has no signup surface to test.
|
|
336611
336695
|
emailVerifyArmed: plannerEmailVerifyArmed(ctx) && featureLooksLikeAuth(ctx.featureName, augmentedFeatureFocusAreas),
|
|
336612
|
-
testBudget: featureTestBudget
|
|
336696
|
+
testBudget: featureTestBudget,
|
|
336697
|
+
knowledgeContext: ctx.knowledgeContext
|
|
336613
336698
|
}, {
|
|
336614
336699
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
336615
336700
|
gateModelOverride: ctx.planGateModel,
|
|
@@ -336655,6 +336740,7 @@ Plan: ${planResult.scenarios.length} scenarios (budget: ${featureTestBudget})`);
|
|
|
336655
336740
|
interactionLog: exploreResult.interactionLog,
|
|
336656
336741
|
modelOverride: ctx.generateModel,
|
|
336657
336742
|
appBrief: ctx.appBrief,
|
|
336743
|
+
knowledgeContext: ctx.knowledgeContext,
|
|
336658
336744
|
onAICall,
|
|
336659
336745
|
mcpClient: mcpClient ?? void 0,
|
|
336660
336746
|
// Pooled browser for parallel scenario generation (Fargate opt-in via
|
|
@@ -339853,8 +339939,8 @@ var require_adm_zip = __commonJS({
|
|
|
339853
339939
|
return null;
|
|
339854
339940
|
}
|
|
339855
339941
|
function fixPath(zipPath) {
|
|
339856
|
-
const { join:
|
|
339857
|
-
return
|
|
339942
|
+
const { join: join8, normalize, sep } = pth.posix;
|
|
339943
|
+
return join8(pth.isAbsolute(zipPath) ? "/" : ".", normalize(sep + zipPath.split("\\").join(sep) + sep));
|
|
339858
339944
|
}
|
|
339859
339945
|
function filenameFilter(filterfn) {
|
|
339860
339946
|
if (filterfn instanceof RegExp) {
|
|
@@ -342689,11 +342775,11 @@ var require_winchars = __commonJS({
|
|
|
342689
342775
|
var require_strip_absolute_path = __commonJS({
|
|
342690
342776
|
"../../node_modules/tar/lib/strip-absolute-path.js"(exports, module) {
|
|
342691
342777
|
"use strict";
|
|
342692
|
-
var { isAbsolute, parse } = __require("path").win32;
|
|
342778
|
+
var { isAbsolute: isAbsolute2, parse } = __require("path").win32;
|
|
342693
342779
|
module.exports = (path) => {
|
|
342694
342780
|
let r = "";
|
|
342695
342781
|
let parsed = parse(path);
|
|
342696
|
-
while (
|
|
342782
|
+
while (isAbsolute2(path) || parsed.root) {
|
|
342697
342783
|
const root = path.charAt(0) === "/" && path.slice(0, 4) !== "//?/" ? "/" : parsed.root;
|
|
342698
342784
|
path = path.slice(root.length);
|
|
342699
342785
|
r += root;
|
|
@@ -345205,7 +345291,7 @@ var require_update = __commonJS({
|
|
|
345205
345291
|
var require_opts_arg = __commonJS({
|
|
345206
345292
|
"../../node_modules/tar/node_modules/mkdirp/lib/opts-arg.js"(exports, module) {
|
|
345207
345293
|
"use strict";
|
|
345208
|
-
var { promisify:
|
|
345294
|
+
var { promisify: promisify4 } = __require("util");
|
|
345209
345295
|
var fs = __require("fs");
|
|
345210
345296
|
var optsArg = (opts) => {
|
|
345211
345297
|
if (!opts)
|
|
@@ -345219,9 +345305,9 @@ var require_opts_arg = __commonJS({
|
|
|
345219
345305
|
else
|
|
345220
345306
|
throw new TypeError("invalid options argument");
|
|
345221
345307
|
opts.mkdir = opts.mkdir || opts.fs.mkdir || fs.mkdir;
|
|
345222
|
-
opts.mkdirAsync =
|
|
345308
|
+
opts.mkdirAsync = promisify4(opts.mkdir);
|
|
345223
345309
|
opts.stat = opts.stat || opts.fs.stat || fs.stat;
|
|
345224
|
-
opts.statAsync =
|
|
345310
|
+
opts.statAsync = promisify4(opts.stat);
|
|
345225
345311
|
opts.statSync = opts.statSync || opts.fs.statSync || fs.statSync;
|
|
345226
345312
|
opts.mkdirSync = opts.mkdirSync || opts.fs.mkdirSync || fs.mkdirSync;
|
|
345227
345313
|
return opts;
|
|
@@ -345474,10 +345560,10 @@ var require_chownr = __commonJS({
|
|
|
345474
345560
|
}
|
|
345475
345561
|
} : (path2, uid, gid) => lchownSync(path2, uid, gid);
|
|
345476
345562
|
var nodeVersion = process.version;
|
|
345477
|
-
var
|
|
345563
|
+
var readdir3 = (path2, options, cb) => fs.readdir(path2, options, cb);
|
|
345478
345564
|
var readdirSync = (path2, options) => fs.readdirSync(path2, options);
|
|
345479
345565
|
if (/^v4\./.test(nodeVersion))
|
|
345480
|
-
|
|
345566
|
+
readdir3 = (path2, options, cb) => fs.readdir(path2, cb);
|
|
345481
345567
|
var chown = (cpath, uid, gid, cb) => {
|
|
345482
345568
|
fs[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, (er) => {
|
|
345483
345569
|
cb(er && er.code !== "ENOENT" ? er : null);
|
|
@@ -345504,7 +345590,7 @@ var require_chownr = __commonJS({
|
|
|
345504
345590
|
}
|
|
345505
345591
|
};
|
|
345506
345592
|
var chownr = (p, uid, gid, cb) => {
|
|
345507
|
-
|
|
345593
|
+
readdir3(p, { withFileTypes: true }, (er, children) => {
|
|
345508
345594
|
if (er) {
|
|
345509
345595
|
if (er.code === "ENOENT")
|
|
345510
345596
|
return cb();
|
|
@@ -345778,7 +345864,7 @@ var require_path_reservations = __commonJS({
|
|
|
345778
345864
|
var assert = __require("assert");
|
|
345779
345865
|
var normalize = require_normalize_unicode();
|
|
345780
345866
|
var stripSlashes = require_strip_trailing_slashes();
|
|
345781
|
-
var { join:
|
|
345867
|
+
var { join: join8 } = __require("path");
|
|
345782
345868
|
var platform3 = process.env.TESTING_TAR_FAKE_PLATFORM || process.platform;
|
|
345783
345869
|
var isWindows = platform3 === "win32";
|
|
345784
345870
|
module.exports = () => {
|
|
@@ -345787,7 +345873,7 @@ var require_path_reservations = __commonJS({
|
|
|
345787
345873
|
const getDirs = (path) => {
|
|
345788
345874
|
const dirs = path.split("/").slice(0, -1).reduce((set, path2) => {
|
|
345789
345875
|
if (set.length) {
|
|
345790
|
-
path2 =
|
|
345876
|
+
path2 = join8(set[set.length - 1], path2);
|
|
345791
345877
|
}
|
|
345792
345878
|
set.push(path2 || "/");
|
|
345793
345879
|
return set;
|
|
@@ -345855,7 +345941,7 @@ var require_path_reservations = __commonJS({
|
|
|
345855
345941
|
};
|
|
345856
345942
|
const reserve = (paths, fn) => {
|
|
345857
345943
|
paths = isWindows ? ["win32 parallelization disabled"] : paths.map((p) => {
|
|
345858
|
-
return stripSlashes(
|
|
345944
|
+
return stripSlashes(join8(normalize(p))).toLowerCase();
|
|
345859
345945
|
});
|
|
345860
345946
|
const dirs = new Set(
|
|
345861
345947
|
paths.map((path) => getDirs(path)).reduce((a, b) => a.concat(b))
|
|
@@ -347868,6 +347954,12 @@ Write valid JSON only (no markdown fences inside files). Be exhaustive.`;
|
|
|
347868
347954
|
"Use {{TEST_USER_EMAIL}}/{{TEST_USER_PASSWORD}} for credentials. Include",
|
|
347869
347955
|
"assertVisible or assertText unless tagging @needs-review. When an incremental",
|
|
347870
347956
|
"allow-list is present, extract ONLY from those files.",
|
|
347957
|
+
"",
|
|
347958
|
+
"Every tap/type/clear/waitForElement/assertVisible/assertText target MUST",
|
|
347959
|
+
"carry an IDENTITY locator: ~accessibility-id, id=resource-id, visible text,",
|
|
347960
|
+
"or an attribute-EQUALITY xpath (//android.widget.Button[@text='Submit']).",
|
|
347961
|
+
"Positional/index-only xpaths (/hierarchy/\u2026/View[3]) are rejected \u2014 imported",
|
|
347962
|
+
"steps have no recorded coordinates, so such a step can never replay.",
|
|
347871
347963
|
"Write strictly valid JSON."
|
|
347872
347964
|
].join("\n");
|
|
347873
347965
|
}
|
|
@@ -347885,6 +347977,32 @@ Write valid JSON only (no markdown fences inside files). Be exhaustive.`;
|
|
|
347885
347977
|
function isPlainObject2(value) {
|
|
347886
347978
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
347887
347979
|
}
|
|
347980
|
+
var LOCATOR_REQUIRING_IMPORT_ACTIONS = /* @__PURE__ */ new Set([
|
|
347981
|
+
"tap",
|
|
347982
|
+
"type",
|
|
347983
|
+
"clear",
|
|
347984
|
+
"waitForElement",
|
|
347985
|
+
"assertVisible",
|
|
347986
|
+
"assertText"
|
|
347987
|
+
]);
|
|
347988
|
+
function selectorValueHasIdentity(value) {
|
|
347989
|
+
const trimmed = value.trim();
|
|
347990
|
+
if (!trimmed)
|
|
347991
|
+
return false;
|
|
347992
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("(//"))
|
|
347993
|
+
return (0, shared_1.isIdentityMobileXpath)(trimmed);
|
|
347994
|
+
return true;
|
|
347995
|
+
}
|
|
347996
|
+
function stepLocatorLacksIdentity(step) {
|
|
347997
|
+
if (!LOCATOR_REQUIRING_IMPORT_ACTIONS.has(step.action))
|
|
347998
|
+
return false;
|
|
347999
|
+
const target = step.target;
|
|
348000
|
+
if (!target)
|
|
348001
|
+
return true;
|
|
348002
|
+
if ((0, shared_1.mobileLocatorBundleHasIdentity)(target.locatorBundle))
|
|
348003
|
+
return false;
|
|
348004
|
+
return ![target.primary, ...target.fallbacks ?? []].some((value) => typeof value === "string" && selectorValueHasIdentity(value));
|
|
348005
|
+
}
|
|
347888
348006
|
function isValidStep(value) {
|
|
347889
348007
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
347890
348008
|
return false;
|
|
@@ -347936,6 +348054,13 @@ Write valid JSON only (no markdown fences inside files). Be exhaustive.`;
|
|
|
347936
348054
|
if (!o.steps.every(isValidStep)) {
|
|
347937
348055
|
return { ok: false, reason: "steps: invalid step shape" };
|
|
347938
348056
|
}
|
|
348057
|
+
const identityless = o.steps.find(stepLocatorLacksIdentity);
|
|
348058
|
+
if (identityless) {
|
|
348059
|
+
return {
|
|
348060
|
+
ok: false,
|
|
348061
|
+
reason: `steps: step ${identityless.order} (${identityless.action}) has no identity locator \u2014 a positional/index xpath cannot replay (imported steps have no recorded coordinates). Use ~accessibility-id, id=resource-id, visible text, or an attribute-equality xpath like //android.widget.Button[@text='Submit'].`
|
|
348062
|
+
};
|
|
348063
|
+
}
|
|
347939
348064
|
const steps = o.steps.map((step, order) => ({
|
|
347940
348065
|
...step,
|
|
347941
348066
|
order,
|
|
@@ -350031,7 +350156,7 @@ var require_process_inventory = __commonJS({
|
|
|
350031
350156
|
var node_child_process_1 = __require("child_process");
|
|
350032
350157
|
var node_os_1 = __importDefault(__require("os"));
|
|
350033
350158
|
var node_util_1 = __require("util");
|
|
350034
|
-
var
|
|
350159
|
+
var execFileAsync4 = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
350035
350160
|
var DEFAULT_LIMIT = 60;
|
|
350036
350161
|
var DEFAULT_TIMEOUT_MS2 = 800;
|
|
350037
350162
|
var MAX_COMMAND_LENGTH = 320;
|
|
@@ -350093,7 +350218,7 @@ var require_process_inventory = __commonJS({
|
|
|
350093
350218
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
350094
350219
|
timer.unref?.();
|
|
350095
350220
|
try {
|
|
350096
|
-
const { stdout } = await
|
|
350221
|
+
const { stdout } = await execFileAsync4(command, args, {
|
|
350097
350222
|
maxBuffer: 1024 * 1024,
|
|
350098
350223
|
signal: controller.signal
|
|
350099
350224
|
});
|
|
@@ -351229,7 +351354,7 @@ var require_package4 = __commonJS({
|
|
|
351229
351354
|
"package.json"(exports, module) {
|
|
351230
351355
|
module.exports = {
|
|
351231
351356
|
name: "@validate.qa/runner",
|
|
351232
|
-
version: "1.0.
|
|
351357
|
+
version: "1.0.69",
|
|
351233
351358
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
351234
351359
|
bin: {
|
|
351235
351360
|
"validate-runner": "dist/cli.js"
|
|
@@ -351316,7 +351441,7 @@ function getConfigDir() {
|
|
|
351316
351441
|
}
|
|
351317
351442
|
|
|
351318
351443
|
// src/runner.ts
|
|
351319
|
-
import { join as
|
|
351444
|
+
import { join as join7 } from "path";
|
|
351320
351445
|
import { homedir as homedir3 } from "os";
|
|
351321
351446
|
import { createHash } from "crypto";
|
|
351322
351447
|
|
|
@@ -351911,6 +352036,66 @@ function resetServerState(port = APPIUM_PORT, restartCount = 0) {
|
|
|
351911
352036
|
};
|
|
351912
352037
|
}
|
|
351913
352038
|
|
|
352039
|
+
// src/services/mobile/ios-simulator-reset.ts
|
|
352040
|
+
import { execFile } from "child_process";
|
|
352041
|
+
import { promisify } from "util";
|
|
352042
|
+
import { readdir, rm } from "fs/promises";
|
|
352043
|
+
import { join as join3, isAbsolute } from "path";
|
|
352044
|
+
var execFileAsync = promisify(execFile);
|
|
352045
|
+
var SIMCTL_TIMEOUT_MS = 15e3;
|
|
352046
|
+
var SIMULATOR_UDID_RE = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
|
|
352047
|
+
function isIosSimulatorUdid(udid) {
|
|
352048
|
+
return typeof udid === "string" && SIMULATOR_UDID_RE.test(udid.trim());
|
|
352049
|
+
}
|
|
352050
|
+
function errMsg(err) {
|
|
352051
|
+
return err instanceof Error ? err.message : String(err);
|
|
352052
|
+
}
|
|
352053
|
+
async function simctl(args) {
|
|
352054
|
+
const { stdout } = await execFileAsync("xcrun", ["simctl", ...args], {
|
|
352055
|
+
timeout: SIMCTL_TIMEOUT_MS,
|
|
352056
|
+
encoding: "utf-8"
|
|
352057
|
+
});
|
|
352058
|
+
return stdout.trim();
|
|
352059
|
+
}
|
|
352060
|
+
async function clearIosSimulatorAppData(udid, bundleId, env = process.env) {
|
|
352061
|
+
if (!isIosSimulatorUdid(udid)) {
|
|
352062
|
+
return { ok: false, detail: `'${udid}' is not a simulator UDID \u2014 physical iOS devices cannot be cleared` };
|
|
352063
|
+
}
|
|
352064
|
+
let container;
|
|
352065
|
+
try {
|
|
352066
|
+
container = await simctl(["get_app_container", udid, bundleId, "data"]);
|
|
352067
|
+
} catch (err) {
|
|
352068
|
+
return { ok: false, detail: `simctl get_app_container failed (app not installed?): ${errMsg(err)}` };
|
|
352069
|
+
}
|
|
352070
|
+
if (!isAbsolute(container) || !container.includes("/Containers/Data/Application/")) {
|
|
352071
|
+
return { ok: false, detail: `unexpected app container path: ${container}` };
|
|
352072
|
+
}
|
|
352073
|
+
try {
|
|
352074
|
+
await simctl(["spawn", udid, "defaults", "delete", bundleId]);
|
|
352075
|
+
} catch {
|
|
352076
|
+
}
|
|
352077
|
+
try {
|
|
352078
|
+
const entries = await readdir(container);
|
|
352079
|
+
for (const entry of entries) {
|
|
352080
|
+
await rm(join3(container, entry), { recursive: true, force: true });
|
|
352081
|
+
}
|
|
352082
|
+
} catch (err) {
|
|
352083
|
+
return { ok: false, detail: `app container wipe failed: ${errMsg(err)}` };
|
|
352084
|
+
}
|
|
352085
|
+
const keychainPolicy = (env.VALIDATEQA_MOBILE_IOS_KEYCHAIN_RESET ?? "").trim().toLowerCase();
|
|
352086
|
+
if (keychainPolicy !== "never") {
|
|
352087
|
+
try {
|
|
352088
|
+
await simctl(["keychain", udid, "reset"]);
|
|
352089
|
+
} catch (err) {
|
|
352090
|
+
return {
|
|
352091
|
+
ok: false,
|
|
352092
|
+
detail: `simulator keychain reset failed (${errMsg(err)}) \u2014 app data was wiped but keychain items (e.g. auth tokens) may survive. Set VALIDATEQA_MOBILE_IOS_KEYCHAIN_RESET=never to skip the keychain step.`
|
|
352093
|
+
};
|
|
352094
|
+
}
|
|
352095
|
+
}
|
|
352096
|
+
return { ok: true };
|
|
352097
|
+
}
|
|
352098
|
+
|
|
351914
352099
|
// src/services/mobile/appium-actions.ts
|
|
351915
352100
|
var APPIUM_REQUEST_TIMEOUT_MS = 1e4;
|
|
351916
352101
|
var APPIUM_WEBVIEW_CONTEXT_TIMEOUT_MS = 12e4;
|
|
@@ -352093,9 +352278,12 @@ async function tapAt(sessionPath, bounds) {
|
|
|
352093
352278
|
}
|
|
352094
352279
|
}
|
|
352095
352280
|
function shouldResetMobileAppData(platform3, deviceId, env = process.env) {
|
|
352096
|
-
if (platform3 !== "ANDROID") return false;
|
|
352097
352281
|
const policy = (env.VALIDATEQA_MOBILE_APP_RESET ?? "").trim().toLowerCase();
|
|
352098
352282
|
if (policy === "never") return false;
|
|
352283
|
+
if (platform3 === "IOS") {
|
|
352284
|
+
return isIosSimulatorUdid(deviceId);
|
|
352285
|
+
}
|
|
352286
|
+
if (platform3 !== "ANDROID") return false;
|
|
352099
352287
|
if (policy === "always") return true;
|
|
352100
352288
|
return !!deviceId && deviceId.startsWith("emulator-");
|
|
352101
352289
|
}
|
|
@@ -352976,10 +353164,10 @@ async function pressHome(sessionPath, platform3) {
|
|
|
352976
353164
|
|
|
352977
353165
|
// src/services/appium-executor.ts
|
|
352978
353166
|
var import_runner_core8 = __toESM(require_dist2());
|
|
352979
|
-
import { execFile as
|
|
352980
|
-
import { promisify as
|
|
353167
|
+
import { execFile as execFile3 } from "child_process";
|
|
353168
|
+
import { promisify as promisify3 } from "util";
|
|
352981
353169
|
import { tmpdir as tmpdir3 } from "os";
|
|
352982
|
-
import { join as
|
|
353170
|
+
import { join as join5 } from "path";
|
|
352983
353171
|
|
|
352984
353172
|
// src/services/device-discovery.ts
|
|
352985
353173
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -353286,9 +353474,6 @@ function countReadyDevicesByKind(platform3, snapshot = getCachedDeviceSnapshot()
|
|
|
353286
353474
|
function selectFreeDevice(platform3, leased, snapshot = getCachedDeviceSnapshot(), requirements = {}) {
|
|
353287
353475
|
const ready = snapshot.filter((d) => d.platform === platform3 && d.isAvailable && d.state === "BOOTED");
|
|
353288
353476
|
if (ready.length === 0) return { kind: "busy", reason: `no booted ${platform3} device` };
|
|
353289
|
-
if (requirements.resetPolicy === "CLEAR_APP_DATA" && platform3 !== "ANDROID") {
|
|
353290
|
-
return { kind: "unsatisfiable", reason: "CLEAR_APP_DATA is unsupported on iOS" };
|
|
353291
|
-
}
|
|
353292
353477
|
let candidates = requirements.resetPolicy === "CLEAR_APP_DATA" ? ready.filter((d) => !d.isPhysical) : [...ready];
|
|
353293
353478
|
const appId = requirements.appId?.trim();
|
|
353294
353479
|
if (appId) {
|
|
@@ -353337,23 +353522,23 @@ function pickFreeDevice(platform3, leased, snapshot = getCachedDeviceSnapshot(),
|
|
|
353337
353522
|
|
|
353338
353523
|
// src/services/mobile/mobile-network-capture.ts
|
|
353339
353524
|
var import_runner_core7 = __toESM(require_dist2());
|
|
353340
|
-
import { execFile, execFileSync as execFileSync3 } from "child_process";
|
|
353525
|
+
import { execFile as execFile2, execFileSync as execFileSync3 } from "child_process";
|
|
353341
353526
|
import { randomUUID } from "crypto";
|
|
353342
|
-
import { mkdtemp, rm, writeFile, readFile, readdir, mkdir, unlink } from "fs/promises";
|
|
353527
|
+
import { mkdtemp, rm as rm2, writeFile, readFile, readdir as readdir2, mkdir, unlink } from "fs/promises";
|
|
353343
353528
|
import { tmpdir as tmpdir2, networkInterfaces } from "os";
|
|
353344
|
-
import { join as
|
|
353345
|
-
import { promisify } from "util";
|
|
353529
|
+
import { join as join4 } from "path";
|
|
353530
|
+
import { promisify as promisify2 } from "util";
|
|
353346
353531
|
import {
|
|
353347
353532
|
getLocal,
|
|
353348
353533
|
generateCACertificate
|
|
353349
353534
|
} from "mockttp";
|
|
353350
|
-
var
|
|
353535
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
353351
353536
|
var MAX_CALLS = 1e3;
|
|
353352
353537
|
var MAX_BODY_CAPTURE_BYTES = 64 * 1024;
|
|
353353
353538
|
var DEVICE_CMD_TIMEOUT_MS = 15e3;
|
|
353354
353539
|
var CA_KEY_BITS = 2048;
|
|
353355
353540
|
var ANDROID_EMULATOR_HOST_LOOPBACK = "10.0.2.2";
|
|
353356
|
-
var PENDING_CLEANUP_DIR =
|
|
353541
|
+
var PENDING_CLEANUP_DIR = join4(tmpdir2(), "validateqa-mobile-capture-pending");
|
|
353357
353542
|
var IOS_SIM_HOST_PROXY_ENV = "VALIDATEQA_MOBILE_IOS_SIM_HOST_PROXY";
|
|
353358
353543
|
var STALE_MARKER_RECONCILE_MS = 6 * 60 * 60 * 1e3;
|
|
353359
353544
|
function envFlag(name) {
|
|
@@ -353535,7 +353720,7 @@ var MitmProxy = class {
|
|
|
353535
353720
|
try {
|
|
353536
353721
|
await server3.stop();
|
|
353537
353722
|
} catch (err) {
|
|
353538
|
-
this.onLog?.(`[mobile-net] proxy stop error (ignored): ${
|
|
353723
|
+
this.onLog?.(`[mobile-net] proxy stop error (ignored): ${errMsg2(err)}`);
|
|
353539
353724
|
}
|
|
353540
353725
|
this.onLog?.(
|
|
353541
353726
|
`[mobile-net] proxy closed: ${this.calls.length} calls captured` + (this.dropped > 0 ? ` (${this.dropped} dropped over cap)` : "")
|
|
@@ -353560,7 +353745,7 @@ var MitmProxy = class {
|
|
|
353560
353745
|
isHttps
|
|
353561
353746
|
});
|
|
353562
353747
|
} catch (err) {
|
|
353563
|
-
this.onLog?.(`[mobile-net] request capture error (ignored): ${
|
|
353748
|
+
this.onLog?.(`[mobile-net] request capture error (ignored): ${errMsg2(err)}`);
|
|
353564
353749
|
}
|
|
353565
353750
|
}
|
|
353566
353751
|
// ── response → finalize the matched request ───────────────────────────────
|
|
@@ -353603,7 +353788,7 @@ var MitmProxy = class {
|
|
|
353603
353788
|
intercepted: true
|
|
353604
353789
|
});
|
|
353605
353790
|
} catch (err) {
|
|
353606
|
-
this.onLog?.(`[mobile-net] response capture error (ignored): ${
|
|
353791
|
+
this.onLog?.(`[mobile-net] response capture error (ignored): ${errMsg2(err)}`);
|
|
353607
353792
|
}
|
|
353608
353793
|
}
|
|
353609
353794
|
// ── tls-client-error → record WHY interception failed ─────────────────────
|
|
@@ -353680,17 +353865,17 @@ async function generateCaCert(dir, onLog) {
|
|
|
353680
353865
|
// rely on stop() to remove the cert from the device promptly after the run
|
|
353681
353866
|
// rather than on a short expiry.
|
|
353682
353867
|
});
|
|
353683
|
-
const caCertPath =
|
|
353868
|
+
const caCertPath = join4(dir, "validateqa-mobile-ca.pem");
|
|
353684
353869
|
await writeFile(caCertPath, cert, "utf-8");
|
|
353685
353870
|
return { caCertPath, caKeyPem: key, caCertPem: cert };
|
|
353686
353871
|
} catch (err) {
|
|
353687
|
-
onLog?.(`[mobile-net] CA generation failed; running plaintext-only: ${
|
|
353872
|
+
onLog?.(`[mobile-net] CA generation failed; running plaintext-only: ${errMsg2(err)}`);
|
|
353688
353873
|
return null;
|
|
353689
353874
|
}
|
|
353690
353875
|
}
|
|
353691
353876
|
async function androidReadProxy(deviceId) {
|
|
353692
353877
|
try {
|
|
353693
|
-
const { stdout } = await
|
|
353878
|
+
const { stdout } = await execFileAsync2(
|
|
353694
353879
|
"adb",
|
|
353695
353880
|
["-s", deviceId, "shell", "settings", "get", "global", "http_proxy"],
|
|
353696
353881
|
{ timeout: DEVICE_CMD_TIMEOUT_MS }
|
|
@@ -353703,33 +353888,33 @@ async function androidReadProxy(deviceId) {
|
|
|
353703
353888
|
}
|
|
353704
353889
|
async function androidSetProxy(deviceId, hostPort, onLog) {
|
|
353705
353890
|
try {
|
|
353706
|
-
await
|
|
353891
|
+
await execFileAsync2(
|
|
353707
353892
|
"adb",
|
|
353708
353893
|
["-s", deviceId, "shell", "settings", "put", "global", "http_proxy", hostPort],
|
|
353709
353894
|
{ timeout: DEVICE_CMD_TIMEOUT_MS }
|
|
353710
353895
|
);
|
|
353711
353896
|
return true;
|
|
353712
353897
|
} catch (err) {
|
|
353713
|
-
onLog?.(`[mobile-net] adb set http_proxy failed: ${
|
|
353898
|
+
onLog?.(`[mobile-net] adb set http_proxy failed: ${errMsg2(err)}`);
|
|
353714
353899
|
return false;
|
|
353715
353900
|
}
|
|
353716
353901
|
}
|
|
353717
353902
|
async function androidClearProxy(deviceId, originalProxy, onLog) {
|
|
353718
353903
|
const restoreValue = originalProxy && originalProxy.length > 0 ? originalProxy : ":0";
|
|
353719
353904
|
try {
|
|
353720
|
-
await
|
|
353905
|
+
await execFileAsync2(
|
|
353721
353906
|
"adb",
|
|
353722
353907
|
["-s", deviceId, "shell", "settings", "put", "global", "http_proxy", restoreValue],
|
|
353723
353908
|
{ timeout: DEVICE_CMD_TIMEOUT_MS }
|
|
353724
353909
|
);
|
|
353725
353910
|
} catch (err) {
|
|
353726
|
-
onLog?.(`[mobile-net] adb restore http_proxy failed: ${
|
|
353911
|
+
onLog?.(`[mobile-net] adb restore http_proxy failed: ${errMsg2(err)}`);
|
|
353727
353912
|
}
|
|
353728
353913
|
}
|
|
353729
353914
|
async function androidInstallCa(deviceId, caCertPath) {
|
|
353730
353915
|
const devicePath = "/sdcard/validateqa-mobile-ca.pem";
|
|
353731
353916
|
try {
|
|
353732
|
-
await
|
|
353917
|
+
await execFileAsync2("adb", ["-s", deviceId, "push", caCertPath, devicePath], {
|
|
353733
353918
|
timeout: DEVICE_CMD_TIMEOUT_MS
|
|
353734
353919
|
});
|
|
353735
353920
|
return {
|
|
@@ -353737,23 +353922,23 @@ async function androidInstallCa(deviceId, caCertPath) {
|
|
|
353737
353922
|
note: `CA copied from ${caCertPath} to ${devicePath} but NOT auto-trusted: Android 7+ apps must opt into user CAs (network_security_config), and the system store requires a rooted/writable image. HTTPS bodies decrypt only for apps that trust user CAs; pinned apps never decrypt.`
|
|
353738
353923
|
};
|
|
353739
353924
|
} catch (err) {
|
|
353740
|
-
return { installed: false, note: `CA push failed: ${
|
|
353925
|
+
return { installed: false, note: `CA push failed: ${errMsg2(err)}` };
|
|
353741
353926
|
}
|
|
353742
353927
|
}
|
|
353743
353928
|
async function androidRemoveCa(deviceId, onLog) {
|
|
353744
353929
|
try {
|
|
353745
|
-
await
|
|
353930
|
+
await execFileAsync2(
|
|
353746
353931
|
"adb",
|
|
353747
353932
|
["-s", deviceId, "shell", "rm", "-f", "/sdcard/validateqa-mobile-ca.pem"],
|
|
353748
353933
|
{ timeout: DEVICE_CMD_TIMEOUT_MS }
|
|
353749
353934
|
);
|
|
353750
353935
|
} catch (err) {
|
|
353751
|
-
onLog?.(`[mobile-net] adb remove CA failed: ${
|
|
353936
|
+
onLog?.(`[mobile-net] adb remove CA failed: ${errMsg2(err)}`);
|
|
353752
353937
|
}
|
|
353753
353938
|
}
|
|
353754
353939
|
async function iosSimTrustCa(deviceId, caCertPath) {
|
|
353755
353940
|
try {
|
|
353756
|
-
await
|
|
353941
|
+
await execFileAsync2("xcrun", ["simctl", "keychain", deviceId, "add-root-cert", caCertPath], {
|
|
353757
353942
|
timeout: DEVICE_CMD_TIMEOUT_MS
|
|
353758
353943
|
});
|
|
353759
353944
|
return {
|
|
@@ -353761,18 +353946,18 @@ async function iosSimTrustCa(deviceId, caCertPath) {
|
|
|
353761
353946
|
note: "CA added to simulator keychain (add-root-cert). NOTE: the simulator has no per-app HTTP proxy setting controllable via simctl \u2014 it shares the host network, so only traffic that actually routes through the proxy is intercepted; pinned apps still never decrypt."
|
|
353762
353947
|
};
|
|
353763
353948
|
} catch (err) {
|
|
353764
|
-
return { trusted: false, note: `simctl add-root-cert failed: ${
|
|
353949
|
+
return { trusted: false, note: `simctl add-root-cert failed: ${errMsg2(err)}` };
|
|
353765
353950
|
}
|
|
353766
353951
|
}
|
|
353767
353952
|
async function iosSimRemoveCa(deviceId, onLog) {
|
|
353768
353953
|
if (process.env.VALIDATEQA_MOBILE_SIM_KEYCHAIN_RESET === "1") {
|
|
353769
353954
|
try {
|
|
353770
|
-
await
|
|
353955
|
+
await execFileAsync2("xcrun", ["simctl", "keychain", deviceId, "reset"], {
|
|
353771
353956
|
timeout: DEVICE_CMD_TIMEOUT_MS
|
|
353772
353957
|
});
|
|
353773
353958
|
onLog?.(`[mobile-net] simctl keychain reset run for ${deviceId} (opt-in; cleared ALL sim certs).`);
|
|
353774
353959
|
} catch (err) {
|
|
353775
|
-
onLog?.(`[mobile-net] simctl keychain reset failed: ${
|
|
353960
|
+
onLog?.(`[mobile-net] simctl keychain reset failed: ${errMsg2(err)}`);
|
|
353776
353961
|
}
|
|
353777
353962
|
return;
|
|
353778
353963
|
}
|
|
@@ -353780,7 +353965,7 @@ async function iosSimRemoveCa(deviceId, onLog) {
|
|
|
353780
353965
|
`[mobile-net] NOTE: the validate.qa capture CA remains TRUSTED in simulator ${deviceId}. simctl has no scoped remove (only a full keychain reset that wipes ALL certs), so we leave the single CA in place. Use a throwaway simulator, or set VALIDATEQA_MOBILE_SIM_KEYCHAIN_RESET=1 to reset the whole sim keychain on teardown.`
|
|
353781
353966
|
);
|
|
353782
353967
|
}
|
|
353783
|
-
function
|
|
353968
|
+
function errMsg2(err) {
|
|
353784
353969
|
return err instanceof Error ? err.message : String(err);
|
|
353785
353970
|
}
|
|
353786
353971
|
function isProcessAlive(pid) {
|
|
@@ -353871,7 +354056,7 @@ async function iosSimConfigureHostProxy(host, port, onLog) {
|
|
|
353871
354056
|
let service;
|
|
353872
354057
|
let prior;
|
|
353873
354058
|
try {
|
|
353874
|
-
const { stdout: order } = await
|
|
354059
|
+
const { stdout: order } = await execFileAsync2("networksetup", ["-listnetworkserviceorder"], {
|
|
353875
354060
|
timeout: DEVICE_CMD_TIMEOUT_MS
|
|
353876
354061
|
});
|
|
353877
354062
|
const svc = parsePrimaryNetworkService(order);
|
|
@@ -353880,26 +354065,26 @@ async function iosSimConfigureHostProxy(host, port, onLog) {
|
|
|
353880
354065
|
}
|
|
353881
354066
|
service = svc;
|
|
353882
354067
|
const [{ stdout: webOut }, { stdout: secureOut }] = await Promise.all([
|
|
353883
|
-
|
|
353884
|
-
|
|
354068
|
+
execFileAsync2("networksetup", ["-getwebproxy", service], { timeout: DEVICE_CMD_TIMEOUT_MS }),
|
|
354069
|
+
execFileAsync2("networksetup", ["-getsecurewebproxy", service], { timeout: DEVICE_CMD_TIMEOUT_MS })
|
|
353885
354070
|
]);
|
|
353886
354071
|
prior = { web: parseWebProxyState(webOut), secure: parseWebProxyState(secureOut) };
|
|
353887
354072
|
for (const args of buildHostProxySetCommands(service, host, port)) {
|
|
353888
|
-
await
|
|
354073
|
+
await execFileAsync2("networksetup", args, { timeout: DEVICE_CMD_TIMEOUT_MS });
|
|
353889
354074
|
}
|
|
353890
354075
|
onLog?.(`[mobile-net] macOS host proxy set on "${service}" \u2192 ${host}:${port} (iOS simulator shares the host network).`);
|
|
353891
354076
|
return { configured: true, service, prior };
|
|
353892
354077
|
} catch (err) {
|
|
353893
354078
|
if (service && prior) {
|
|
353894
|
-
onLog?.(`[mobile-net] host proxy set failed mid-way \u2014 rolling back "${service}" to prior state: ${
|
|
354079
|
+
onLog?.(`[mobile-net] host proxy set failed mid-way \u2014 rolling back "${service}" to prior state: ${errMsg2(err)}`);
|
|
353895
354080
|
for (const args of buildHostProxyRestoreCommands(service, prior)) {
|
|
353896
354081
|
try {
|
|
353897
|
-
await
|
|
354082
|
+
await execFileAsync2("networksetup", args, { timeout: DEVICE_CMD_TIMEOUT_MS });
|
|
353898
354083
|
} catch {
|
|
353899
354084
|
}
|
|
353900
354085
|
}
|
|
353901
354086
|
}
|
|
353902
|
-
return { configured: false, reason: `iOS simulator host proxy could not be configured: ${
|
|
354087
|
+
return { configured: false, reason: `iOS simulator host proxy could not be configured: ${errMsg2(err)}` };
|
|
353903
354088
|
}
|
|
353904
354089
|
}
|
|
353905
354090
|
async function iosSimRestoreHostProxy(service, prior, onLog) {
|
|
@@ -353909,9 +354094,9 @@ async function iosSimRestoreHostProxy(service, prior, onLog) {
|
|
|
353909
354094
|
};
|
|
353910
354095
|
for (const args of buildHostProxyRestoreCommands(service, restore)) {
|
|
353911
354096
|
try {
|
|
353912
|
-
await
|
|
354097
|
+
await execFileAsync2("networksetup", args, { timeout: DEVICE_CMD_TIMEOUT_MS });
|
|
353913
354098
|
} catch (err) {
|
|
353914
|
-
onLog?.(`[mobile-net] networksetup restore (${args[0]}) failed: ${
|
|
354099
|
+
onLog?.(`[mobile-net] networksetup restore (${args[0]}) failed: ${errMsg2(err)}`);
|
|
353915
354100
|
}
|
|
353916
354101
|
}
|
|
353917
354102
|
onLog?.(`[mobile-net] macOS host proxy restored on "${service}".`);
|
|
@@ -353919,7 +354104,7 @@ async function iosSimRestoreHostProxy(service, prior, onLog) {
|
|
|
353919
354104
|
var activeCleanups = /* @__PURE__ */ new Map();
|
|
353920
354105
|
var exitHandlersInstalled = false;
|
|
353921
354106
|
function markerPath(sessionKey) {
|
|
353922
|
-
return
|
|
354107
|
+
return join4(PENDING_CLEANUP_DIR, `${sessionKey}.json`);
|
|
353923
354108
|
}
|
|
353924
354109
|
async function writePendingCleanup(sessionKey, marker) {
|
|
353925
354110
|
try {
|
|
@@ -353963,7 +354148,7 @@ function restoreDeviceSync(marker, onLog) {
|
|
|
353963
354148
|
async function reconcilePendingCleanups(onLog) {
|
|
353964
354149
|
let files;
|
|
353965
354150
|
try {
|
|
353966
|
-
files = await
|
|
354151
|
+
files = await readdir2(PENDING_CLEANUP_DIR);
|
|
353967
354152
|
} catch {
|
|
353968
354153
|
return;
|
|
353969
354154
|
}
|
|
@@ -353971,7 +354156,7 @@ async function reconcilePendingCleanups(onLog) {
|
|
|
353971
354156
|
if (!file.endsWith(".json")) continue;
|
|
353972
354157
|
const sessionKey = file.slice(0, -".json".length);
|
|
353973
354158
|
if (activeCleanups.has(sessionKey)) continue;
|
|
353974
|
-
const full =
|
|
354159
|
+
const full = join4(PENDING_CLEANUP_DIR, file);
|
|
353975
354160
|
try {
|
|
353976
354161
|
const raw = await readFile(full, "utf-8");
|
|
353977
354162
|
const marker = JSON.parse(raw);
|
|
@@ -354024,7 +354209,7 @@ async function startMobileNetworkCapture(options) {
|
|
|
354024
354209
|
let caKeyPem = null;
|
|
354025
354210
|
let caCertPem = null;
|
|
354026
354211
|
try {
|
|
354027
|
-
tmpDir = await mkdtemp(
|
|
354212
|
+
tmpDir = await mkdtemp(join4(tmpdir2(), "validateqa-mobile-ca-"));
|
|
354028
354213
|
const ca = await generateCaCert(tmpDir, onLog);
|
|
354029
354214
|
if (ca) {
|
|
354030
354215
|
caCertPath = ca.caCertPath;
|
|
@@ -354032,7 +354217,7 @@ async function startMobileNetworkCapture(options) {
|
|
|
354032
354217
|
caCertPem = ca.caCertPem;
|
|
354033
354218
|
}
|
|
354034
354219
|
} catch (err) {
|
|
354035
|
-
onLog?.(`[mobile-net] CA setup degraded: ${
|
|
354220
|
+
onLog?.(`[mobile-net] CA setup degraded: ${errMsg2(err)}`);
|
|
354036
354221
|
}
|
|
354037
354222
|
if (!caKeyPem || !caCertPem) {
|
|
354038
354223
|
try {
|
|
@@ -354040,9 +354225,9 @@ async function startMobileNetworkCapture(options) {
|
|
|
354040
354225
|
caKeyPem = fallback.key;
|
|
354041
354226
|
caCertPem = fallback.cert;
|
|
354042
354227
|
} catch (err) {
|
|
354043
|
-
onLog?.(`[mobile-net] fatal CA failure, cannot start proxy: ${
|
|
354228
|
+
onLog?.(`[mobile-net] fatal CA failure, cannot start proxy: ${errMsg2(err)}`);
|
|
354044
354229
|
if (tmpDir) {
|
|
354045
|
-
await
|
|
354230
|
+
await rm2(tmpDir, { recursive: true, force: true }).catch(() => void 0);
|
|
354046
354231
|
}
|
|
354047
354232
|
throw err instanceof Error ? err : new Error(String(err));
|
|
354048
354233
|
}
|
|
@@ -354097,7 +354282,7 @@ async function startMobileNetworkCapture(options) {
|
|
|
354097
354282
|
}
|
|
354098
354283
|
}
|
|
354099
354284
|
} catch (err) {
|
|
354100
|
-
wiringReason = `device wiring degraded: ${
|
|
354285
|
+
wiringReason = `device wiring degraded: ${errMsg2(err)}`;
|
|
354101
354286
|
onLog?.(`[mobile-net] ${wiringReason}`);
|
|
354102
354287
|
}
|
|
354103
354288
|
if (proxySet || caInstalledOnDevice || hostProxySet) {
|
|
@@ -354186,9 +354371,9 @@ async function startMobileNetworkCapture(options) {
|
|
|
354186
354371
|
await proxy.close();
|
|
354187
354372
|
if (capturedTmpDir) {
|
|
354188
354373
|
try {
|
|
354189
|
-
await
|
|
354374
|
+
await rm2(capturedTmpDir, { recursive: true, force: true });
|
|
354190
354375
|
} catch (err) {
|
|
354191
|
-
onLog?.(`[mobile-net] temp CA cleanup failed: ${
|
|
354376
|
+
onLog?.(`[mobile-net] temp CA cleanup failed: ${errMsg2(err)}`);
|
|
354192
354377
|
}
|
|
354193
354378
|
}
|
|
354194
354379
|
const apiCount = finalCalls.filter((c) => c.isApi).length;
|
|
@@ -354225,7 +354410,7 @@ var WD_SESSION_CREATE_TIMEOUT_MS = 12e4;
|
|
|
354225
354410
|
var WD_ACTION_TIMEOUT_MS = 3e4;
|
|
354226
354411
|
var APPIUM_DRIVER_DEAD_RE2 = /instrumentation process is not running|cannot be proxied to UiAutomator2 server|socket hang up|ECONNRESET/i;
|
|
354227
354412
|
var MAX_SESSION_CREATE_ATTEMPTS = 3;
|
|
354228
|
-
var
|
|
354413
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
354229
354414
|
var MOBILE_TEXT_ATTRIBUTE_NAMES = ["text", "content-desc", "contentDescription", "name", "label", "value"];
|
|
354230
354415
|
var WebDriverTransportError = class extends Error {
|
|
354231
354416
|
isTransport = true;
|
|
@@ -354354,11 +354539,11 @@ function mobileParallelSessionCapabilities(platform3, portOffset = 0, deviceId =
|
|
|
354354
354539
|
return { "appium:systemPort": readPort("APPIUM_SYSTEM_PORT", 8200) };
|
|
354355
354540
|
}
|
|
354356
354541
|
const safeDevice = deviceId.replace(/[^A-Za-z0-9._-]+/g, "-") || `device-${offset}`;
|
|
354357
|
-
const derivedDataRoot = env.APPIUM_DERIVED_DATA_PATH?.trim() ||
|
|
354542
|
+
const derivedDataRoot = env.APPIUM_DERIVED_DATA_PATH?.trim() || join5(tmpdir3(), "validateqa-wda");
|
|
354358
354543
|
return {
|
|
354359
354544
|
"appium:wdaLocalPort": readPort("APPIUM_WDA_LOCAL_PORT", 8100),
|
|
354360
354545
|
"appium:mjpegServerPort": readPort("APPIUM_MJPEG_SERVER_PORT", 9100),
|
|
354361
|
-
"appium:derivedDataPath":
|
|
354546
|
+
"appium:derivedDataPath": join5(derivedDataRoot, safeDevice)
|
|
354362
354547
|
};
|
|
354363
354548
|
}
|
|
354364
354549
|
function isAndroidSystemPortBusyError(error2) {
|
|
@@ -354442,7 +354627,7 @@ async function removeStaleAndroidSystemPortForward(target, error2) {
|
|
|
354442
354627
|
const args = androidSystemPortForwardRemovalArgs(target, error2);
|
|
354443
354628
|
if (!args) return false;
|
|
354444
354629
|
try {
|
|
354445
|
-
await
|
|
354630
|
+
await execFileAsync3("adb", args, { timeout: 1e4, windowsHide: true });
|
|
354446
354631
|
return true;
|
|
354447
354632
|
} catch {
|
|
354448
354633
|
return false;
|
|
@@ -354700,7 +354885,7 @@ function boundsForRun(step, platform3, isCrossPlatform) {
|
|
|
354700
354885
|
if (platform3 && hasPlatformLocator(step.target, platform3)) return null;
|
|
354701
354886
|
return boundsFromStep(step);
|
|
354702
354887
|
}
|
|
354703
|
-
function locatorStrategyForValue(value) {
|
|
354888
|
+
function locatorStrategyForValue(value, platform3) {
|
|
354704
354889
|
const trimmed = value?.trim();
|
|
354705
354890
|
if (!trimmed) {
|
|
354706
354891
|
return [];
|
|
@@ -354716,7 +354901,8 @@ function locatorStrategyForValue(value) {
|
|
|
354716
354901
|
}
|
|
354717
354902
|
return [
|
|
354718
354903
|
{ using: "accessibility id", value: trimmed },
|
|
354719
|
-
{ using: "id", value: trimmed }
|
|
354904
|
+
{ using: "id", value: trimmed },
|
|
354905
|
+
...textIdentityStrategies(trimmed, platform3)
|
|
354720
354906
|
];
|
|
354721
354907
|
}
|
|
354722
354908
|
function buildLocatorStrategies(target, platform3) {
|
|
@@ -354726,19 +354912,19 @@ function buildLocatorStrategies(target, platform3) {
|
|
|
354726
354912
|
if (bundle) {
|
|
354727
354913
|
if (bundle.accessibilityId) strategies.push({ using: "accessibility id", value: bundle.accessibilityId });
|
|
354728
354914
|
if (bundle.resourceId) strategies.push({ using: "id", value: bundle.resourceId });
|
|
354915
|
+
if (bundle.xpath && isIdentityXpath(bundle.xpath)) strategies.push({ using: "xpath", value: bundle.xpath });
|
|
354729
354916
|
for (const value of [
|
|
354730
|
-
isEditableMobileClassName(bundle.className) ? void 0 : bundle.text,
|
|
354917
|
+
isEditableMobileClassName(bundle.className) || isStateValueMobileClassName(bundle.className) ? void 0 : bundle.text,
|
|
354731
354918
|
bundle.contentDesc
|
|
354732
354919
|
]) {
|
|
354733
354920
|
const trimmed = value?.trim();
|
|
354734
|
-
if (trimmed) strategies.push(
|
|
354921
|
+
if (trimmed) strategies.push(...textIdentityStrategies(trimmed, platform3));
|
|
354735
354922
|
}
|
|
354736
|
-
if (bundle.xpath) strategies.push({ using: "xpath", value: bundle.xpath });
|
|
354737
354923
|
}
|
|
354738
354924
|
if (!(platform3 && hasPlatformLocator(target, platform3))) {
|
|
354739
|
-
strategies.push(...locatorStrategyForValue(target.primary));
|
|
354925
|
+
strategies.push(...locatorStrategyForValue(target.primary, platform3));
|
|
354740
354926
|
for (const fallback of target.fallbacks ?? []) {
|
|
354741
|
-
strategies.push(...locatorStrategyForValue(fallback));
|
|
354927
|
+
strategies.push(...locatorStrategyForValue(fallback, platform3));
|
|
354742
354928
|
}
|
|
354743
354929
|
}
|
|
354744
354930
|
return strategies.filter((strategy) => strategy.using !== "xpath" || isIdentityXpath(strategy.value)).filter(
|
|
@@ -354760,12 +354946,31 @@ function identityTextXpath2(value) {
|
|
|
354760
354946
|
const lit = xpathLiteral3(value);
|
|
354761
354947
|
return `//*[@text=${lit} or @content-desc=${lit} or @label=${lit} or @name=${lit} or @value=${lit}]`;
|
|
354762
354948
|
}
|
|
354949
|
+
function iosPredicateLiteral(value) {
|
|
354950
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
354951
|
+
}
|
|
354952
|
+
function iosTextIdentityPredicate(value) {
|
|
354953
|
+
const lit = iosPredicateLiteral(value);
|
|
354954
|
+
return `label == ${lit} OR name == ${lit} OR value == ${lit}`;
|
|
354955
|
+
}
|
|
354956
|
+
function textIdentityStrategies(value, platform3) {
|
|
354957
|
+
const strategies = [];
|
|
354958
|
+
if (platform3 === "IOS") {
|
|
354959
|
+
strategies.push({ using: "-ios predicate string", value: iosTextIdentityPredicate(value) });
|
|
354960
|
+
}
|
|
354961
|
+
strategies.push({ using: "xpath", value: identityTextXpath2(value) });
|
|
354962
|
+
return strategies;
|
|
354963
|
+
}
|
|
354964
|
+
function isIdentityIosPredicate(value) {
|
|
354965
|
+
return /(?:^|[\s(])(?:name|label|value)\s*==\s*"/.test(value) && !/\b(?:CONTAINS|BEGINSWITH|ENDSWITH|LIKE|MATCHES|IN)\b/i.test(value);
|
|
354966
|
+
}
|
|
354763
354967
|
function isIdentityXpath(value) {
|
|
354764
354968
|
return /@[A-Za-z_:][-A-Za-z0-9_:.]*\s*=/.test(value) && !/(?:contains|starts-with)\s*\(/i.test(value);
|
|
354765
354969
|
}
|
|
354766
354970
|
function isIdentityStrategy(strategy) {
|
|
354767
354971
|
if (strategy.using === "accessibility id" || strategy.using === "id") return true;
|
|
354768
354972
|
if (strategy.using === "xpath") return isIdentityXpath(strategy.value);
|
|
354973
|
+
if (strategy.using === "-ios predicate string") return isIdentityIosPredicate(strategy.value);
|
|
354769
354974
|
return false;
|
|
354770
354975
|
}
|
|
354771
354976
|
function buildIdentityLocatorStrategies(target, platform3) {
|
|
@@ -354775,15 +354980,18 @@ function buildIdentityLocatorStrategies(target, platform3) {
|
|
|
354775
354980
|
if (bundle) {
|
|
354776
354981
|
if (bundle.accessibilityId) strategies.push({ using: "accessibility id", value: bundle.accessibilityId });
|
|
354777
354982
|
if (bundle.resourceId) strategies.push({ using: "id", value: bundle.resourceId });
|
|
354778
|
-
|
|
354983
|
+
if (bundle.xpath && isIdentityXpath(bundle.xpath)) strategies.push({ using: "xpath", value: bundle.xpath });
|
|
354984
|
+
for (const value of [
|
|
354985
|
+
isStateValueMobileClassName(bundle.className) ? void 0 : bundle.text,
|
|
354986
|
+
bundle.contentDesc
|
|
354987
|
+
]) {
|
|
354779
354988
|
const trimmed = value?.trim();
|
|
354780
|
-
if (trimmed) strategies.push(
|
|
354989
|
+
if (trimmed) strategies.push(...textIdentityStrategies(trimmed, platform3));
|
|
354781
354990
|
}
|
|
354782
|
-
if (bundle.xpath && isIdentityXpath(bundle.xpath)) strategies.push({ using: "xpath", value: bundle.xpath });
|
|
354783
354991
|
}
|
|
354784
354992
|
if (!(platform3 && hasPlatformLocator(target, platform3))) {
|
|
354785
354993
|
for (const value of [target.primary, ...target.fallbacks ?? []]) {
|
|
354786
|
-
for (const strategy of locatorStrategyForValue(value)) {
|
|
354994
|
+
for (const strategy of locatorStrategyForValue(value, platform3)) {
|
|
354787
354995
|
if (isIdentityStrategy(strategy)) strategies.push(strategy);
|
|
354788
354996
|
}
|
|
354789
354997
|
}
|
|
@@ -354893,7 +355101,7 @@ async function prepareAndroidInputEnvironment(deviceId) {
|
|
|
354893
355101
|
];
|
|
354894
355102
|
for (const [namespace, key, value] of settingsToDisable) {
|
|
354895
355103
|
try {
|
|
354896
|
-
await
|
|
355104
|
+
await execFileAsync3("adb", ["-s", deviceId, "shell", "settings", "put", namespace, key, value], {
|
|
354897
355105
|
timeout: 8e3,
|
|
354898
355106
|
windowsHide: true
|
|
354899
355107
|
});
|
|
@@ -354905,14 +355113,14 @@ async function sendAndroidTextToFocusedInput(deviceId, value) {
|
|
|
354905
355113
|
if (!deviceId) {
|
|
354906
355114
|
throw new Error("Android keyboard fallback requires a device id");
|
|
354907
355115
|
}
|
|
354908
|
-
await
|
|
355116
|
+
await execFileAsync3("adb", ["-s", deviceId, "shell", "input", "text", encodeAndroidInputText(value)], {
|
|
354909
355117
|
timeout: 1e4,
|
|
354910
355118
|
windowsHide: true
|
|
354911
355119
|
});
|
|
354912
355120
|
}
|
|
354913
355121
|
async function pressAndroidKeyboardEnter(deviceId) {
|
|
354914
355122
|
if (!deviceId) throw new Error("Android keyboard Enter fallback requires a device id");
|
|
354915
|
-
await
|
|
355123
|
+
await execFileAsync3("adb", ["-s", deviceId, "shell", "input", "keyevent", "66"], {
|
|
354916
355124
|
timeout: 1e4,
|
|
354917
355125
|
windowsHide: true
|
|
354918
355126
|
});
|
|
@@ -355433,7 +355641,14 @@ async function executeMobileTest(options) {
|
|
|
355433
355641
|
const resetAllowed = shouldResetMobileAppData(options.target.platform, selectedDevice.id);
|
|
355434
355642
|
if (resetAllowed) {
|
|
355435
355643
|
try {
|
|
355436
|
-
|
|
355644
|
+
if (options.target.platform === "IOS") {
|
|
355645
|
+
await terminateTargetApp(sessionId, "IOS", options.target.appId).catch(() => {
|
|
355646
|
+
});
|
|
355647
|
+
const wiped = await clearIosSimulatorAppData(selectedDevice.id, options.target.appId);
|
|
355648
|
+
if (!wiped.ok) throw new Error(wiped.detail);
|
|
355649
|
+
} else {
|
|
355650
|
+
await executeMobileScript(sessionId, "mobile: clearApp", [{ appId: options.target.appId }]);
|
|
355651
|
+
}
|
|
355437
355652
|
clearedAppDataBeforeLaunch = true;
|
|
355438
355653
|
log2(resetPolicy === "CLEAR_APP_DATA" ? "App data cleared before launch (target resetPolicy=CLEAR_APP_DATA)." : "App data cleared before launch (test was recorded from a cold logged-out state).");
|
|
355439
355654
|
} catch (err) {
|
|
@@ -355778,8 +355993,7 @@ async function readElementAttribute(sessionId, elementId, name) {
|
|
|
355778
355993
|
function harvestFallbackXpath(platform3, attrs) {
|
|
355779
355994
|
if (platform3 === "IOS") {
|
|
355780
355995
|
if (attrs.name) return `//*[@name=${xpathLiteral3(attrs.name)}]`;
|
|
355781
|
-
|
|
355782
|
-
if (text) return identityTextXpath2(text);
|
|
355996
|
+
if (attrs.text) return identityTextXpath2(attrs.text);
|
|
355783
355997
|
return "//*";
|
|
355784
355998
|
}
|
|
355785
355999
|
if (attrs.resourceId) return `//*[@resource-id=${xpathLiteral3(attrs.resourceId)}]`;
|
|
@@ -355789,14 +356003,15 @@ function harvestFallbackXpath(platform3, attrs) {
|
|
|
355789
356003
|
}
|
|
355790
356004
|
async function readNativeElementBundle(sessionId, elementId, platform3) {
|
|
355791
356005
|
const className = await readElementAttribute(sessionId, elementId, platform3 === "IOS" ? "type" : "class") ?? "";
|
|
356006
|
+
const volatileText = isEditableMobileClassName(className) || isStateValueMobileClassName(className);
|
|
355792
356007
|
if (platform3 === "IOS") {
|
|
355793
356008
|
const name = await readElementAttribute(sessionId, elementId, "name");
|
|
355794
356009
|
const label = await readElementAttribute(sessionId, elementId, "label");
|
|
355795
356010
|
const value = await readElementAttribute(sessionId, elementId, "value");
|
|
355796
|
-
const text2 = label ?? value;
|
|
356011
|
+
const text2 = label ?? (volatileText ? void 0 : value);
|
|
355797
356012
|
if (!name && !text2) return null;
|
|
355798
356013
|
const bundle2 = {
|
|
355799
|
-
xpath: harvestFallbackXpath("IOS", { name,
|
|
356014
|
+
xpath: harvestFallbackXpath("IOS", { name, text: text2 }),
|
|
355800
356015
|
className
|
|
355801
356016
|
};
|
|
355802
356017
|
if (name) bundle2.accessibilityId = name;
|
|
@@ -355805,7 +356020,8 @@ async function readNativeElementBundle(sessionId, elementId, platform3) {
|
|
|
355805
356020
|
}
|
|
355806
356021
|
const resourceId = await readElementAttribute(sessionId, elementId, "resource-id");
|
|
355807
356022
|
const contentDesc = await readElementAttribute(sessionId, elementId, "content-desc") ?? await readElementAttribute(sessionId, elementId, "contentDescription");
|
|
355808
|
-
const
|
|
356023
|
+
const rawText = await readElementAttribute(sessionId, elementId, "text");
|
|
356024
|
+
const text = volatileText ? void 0 : rawText;
|
|
355809
356025
|
if (!resourceId && !contentDesc && !text) return null;
|
|
355810
356026
|
const bundle = {
|
|
355811
356027
|
xpath: harvestFallbackXpath("ANDROID", { text, contentDesc, resourceId }),
|
|
@@ -356093,7 +356309,7 @@ import { createServer } from "http";
|
|
|
356093
356309
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
356094
356310
|
import { randomBytes, timingSafeEqual } from "crypto";
|
|
356095
356311
|
import { homedir as homedir2 } from "os";
|
|
356096
|
-
import { join as
|
|
356312
|
+
import { join as join6, dirname } from "path";
|
|
356097
356313
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
356098
356314
|
init_dist();
|
|
356099
356315
|
var MOBILE_BRIDGE_PORT2 = 9515;
|
|
@@ -356200,7 +356416,7 @@ async function getWindowSize3() {
|
|
|
356200
356416
|
}
|
|
356201
356417
|
var BRIDGE_PROCESS_MARKER = "validateqa-mobile-bridge";
|
|
356202
356418
|
var BRIDGE_PORT = MOBILE_BRIDGE_PORT2;
|
|
356203
|
-
var BRIDGE_PID_FILE =
|
|
356419
|
+
var BRIDGE_PID_FILE = join6(homedir2(), ".validate.qa", "mobile-bridge.pid");
|
|
356204
356420
|
function readBridgePidFile() {
|
|
356205
356421
|
try {
|
|
356206
356422
|
const raw = readFileSync2(BRIDGE_PID_FILE, "utf-8").trim();
|
|
@@ -357338,6 +357554,7 @@ var MobileBridgeDriver = class {
|
|
|
357338
357554
|
sessionId;
|
|
357339
357555
|
platform;
|
|
357340
357556
|
appId;
|
|
357557
|
+
deviceId;
|
|
357341
357558
|
allowAppReset;
|
|
357342
357559
|
recoverSession;
|
|
357343
357560
|
maxSessionRecoveries;
|
|
@@ -357358,6 +357575,7 @@ var MobileBridgeDriver = class {
|
|
|
357358
357575
|
this.sessionId = config.sessionId;
|
|
357359
357576
|
this.platform = config.platform;
|
|
357360
357577
|
this.appId = config.appId;
|
|
357578
|
+
this.deviceId = config.deviceId;
|
|
357361
357579
|
this.allowAppReset = config.allowAppReset === true;
|
|
357362
357580
|
this.recoverSession = config.recoverSession;
|
|
357363
357581
|
this.maxSessionRecoveries = Math.max(0, Math.floor(config.maxSessionRecoveries ?? 1));
|
|
@@ -357655,17 +357873,34 @@ var MobileBridgeDriver = class {
|
|
|
357655
357873
|
});
|
|
357656
357874
|
}
|
|
357657
357875
|
/**
|
|
357658
|
-
* Deterministic state reset: clear app data + relaunch
|
|
357659
|
-
*
|
|
357660
|
-
*
|
|
357661
|
-
*
|
|
357662
|
-
*
|
|
357876
|
+
* Deterministic state reset: clear app data + relaunch, per the
|
|
357877
|
+
* runner-computed allowAppReset policy (Android emulators / iOS simulators
|
|
357878
|
+
* only — a personal physical device is never wiped). Android clears via
|
|
357879
|
+
* `mobile: clearApp`; iOS terminates the app and wipes its simulator data
|
|
357880
|
+
* container + keychain through `simctl` (see ios-simulator-reset.ts).
|
|
357881
|
+
* Returns false when unsupported/disallowed or when the clear fails —
|
|
357882
|
+
* callers fall back to relaunch(), preserving today's behavior. On success,
|
|
357883
|
+
* waits for cold-boot content so recording and replay agree on the true
|
|
357884
|
+
* entry screen.
|
|
357663
357885
|
*/
|
|
357664
357886
|
async resetAppState() {
|
|
357665
|
-
if (!this.allowAppReset
|
|
357887
|
+
if (!this.allowAppReset) return false;
|
|
357888
|
+
if (this.platform === "IOS" && !isIosSimulatorUdid(this.deviceId)) return false;
|
|
357666
357889
|
return this.withSessionRecovery("app data reset", async () => {
|
|
357667
357890
|
try {
|
|
357668
|
-
|
|
357891
|
+
if (this.platform === "IOS") {
|
|
357892
|
+
try {
|
|
357893
|
+
await this.executeScript("mobile: terminateApp", [{ ...this.appLifecycleArgs(), timeout: 5e3 }]);
|
|
357894
|
+
} catch (err) {
|
|
357895
|
+
if (isRecoverableAppiumSessionFailure(err)) throw err;
|
|
357896
|
+
}
|
|
357897
|
+
const wiped = await clearIosSimulatorAppData(this.deviceId ?? "", this.appId);
|
|
357898
|
+
if (!wiped.ok) {
|
|
357899
|
+
throw new Error(`iOS simulator app-data reset failed: ${wiped.detail}`);
|
|
357900
|
+
}
|
|
357901
|
+
} else {
|
|
357902
|
+
await this.executeScript("mobile: clearApp", [{ appId: this.appId }]);
|
|
357903
|
+
}
|
|
357669
357904
|
await this.executeScript("mobile: activateApp", [this.appLifecycleArgs()]);
|
|
357670
357905
|
try {
|
|
357671
357906
|
await waitForColdBootSettle(this.sessionPath);
|
|
@@ -358331,7 +358566,7 @@ var log = {
|
|
|
358331
358566
|
dim: (msg) => console.log(`${C2.gray}${msg}${C2.reset}`)
|
|
358332
358567
|
};
|
|
358333
358568
|
function getRunsDir() {
|
|
358334
|
-
const dir = process.env.RUNS_DIR ||
|
|
358569
|
+
const dir = process.env.RUNS_DIR || join7(homedir3(), ".validate.qa", "runs");
|
|
358335
358570
|
(0, import_runner_core12.ensurePrivateDirectory)(dir);
|
|
358336
358571
|
return dir;
|
|
358337
358572
|
}
|
|
@@ -360263,9 +360498,11 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
360263
360498
|
sessionId: session.sessionId,
|
|
360264
360499
|
platform: platform3,
|
|
360265
360500
|
appId: session.appId,
|
|
360266
|
-
|
|
360267
|
-
//
|
|
360268
|
-
//
|
|
360501
|
+
deviceId: session.deviceId,
|
|
360502
|
+
// Deterministic-state authorization: Android emulators / iOS
|
|
360503
|
+
// simulators only (VALIDATEQA_MOBILE_APP_RESET overrides the
|
|
360504
|
+
// Android default) — a personal physical device must never have
|
|
360505
|
+
// its app data wiped.
|
|
360269
360506
|
allowAppReset: mobileTarget.resetPolicy !== "PRESERVE_APP_DATA" && shouldResetMobileAppData(platform3, session.deviceId),
|
|
360270
360507
|
maxSessionRecoveries: 1,
|
|
360271
360508
|
recoverSession: async (cause) => {
|
|
@@ -360781,6 +361018,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
360781
361018
|
sessionId: opened.sessionId,
|
|
360782
361019
|
platform: target.platform,
|
|
360783
361020
|
appId: opened.appId,
|
|
361021
|
+
deviceId: opened.deviceId,
|
|
360784
361022
|
allowAppReset: target.resetPolicy !== "PRESERVE_APP_DATA" && shouldResetMobileAppData(target.platform, opened.deviceId)
|
|
360785
361023
|
});
|
|
360786
361024
|
};
|