@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.js
CHANGED
|
@@ -978,7 +978,7 @@ var require_mobile_source_parser = __commonJS({
|
|
|
978
978
|
const value = nonEmpty3(attrs.value);
|
|
979
979
|
const label = name ?? labelAttr ?? value;
|
|
980
980
|
const accessibilityId = name;
|
|
981
|
-
const text =
|
|
981
|
+
const text = labelAttr ?? (isStateValueRole(role) ? void 0 : value);
|
|
982
982
|
const bounds = parseIOSBounds(attrs);
|
|
983
983
|
const enabled = attrBoolDefaultTrue(attrs.enabled);
|
|
984
984
|
const visible = attrBoolDefaultTrue(attrs.visible);
|
|
@@ -1125,6 +1125,42 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1125
1125
|
}
|
|
1126
1126
|
}
|
|
1127
1127
|
}
|
|
1128
|
+
function annotateIdentityUniqueness(elements) {
|
|
1129
|
+
const a11yCounts = /* @__PURE__ */ new Map();
|
|
1130
|
+
const ridCounts = /* @__PURE__ */ new Map();
|
|
1131
|
+
const textCounts = /* @__PURE__ */ new Map();
|
|
1132
|
+
const bump = (map2, value) => {
|
|
1133
|
+
const key = value?.trim();
|
|
1134
|
+
if (!key)
|
|
1135
|
+
return;
|
|
1136
|
+
map2.set(key, (map2.get(key) ?? 0) + 1);
|
|
1137
|
+
};
|
|
1138
|
+
for (const element of elements) {
|
|
1139
|
+
bump(a11yCounts, element.accessibilityId);
|
|
1140
|
+
bump(ridCounts, element.resourceId);
|
|
1141
|
+
const identityValues = new Set([element.text, element.accessibilityId].map((value) => value?.trim()).filter((value) => Boolean(value)));
|
|
1142
|
+
for (const value of identityValues)
|
|
1143
|
+
bump(textCounts, value);
|
|
1144
|
+
}
|
|
1145
|
+
const isUnique = (map2, value) => {
|
|
1146
|
+
const key = value?.trim();
|
|
1147
|
+
if (!key)
|
|
1148
|
+
return void 0;
|
|
1149
|
+
return (map2.get(key) ?? 0) <= 1;
|
|
1150
|
+
};
|
|
1151
|
+
for (const element of elements) {
|
|
1152
|
+
const accessibilityId = isUnique(a11yCounts, element.accessibilityId);
|
|
1153
|
+
const resourceId = isUnique(ridCounts, element.resourceId);
|
|
1154
|
+
const text = isUnique(textCounts, element.text);
|
|
1155
|
+
if (accessibilityId === void 0 && resourceId === void 0 && text === void 0)
|
|
1156
|
+
continue;
|
|
1157
|
+
element.identityUnique = {
|
|
1158
|
+
...accessibilityId !== void 0 ? { accessibilityId } : {},
|
|
1159
|
+
...resourceId !== void 0 ? { resourceId } : {},
|
|
1160
|
+
...text !== void 0 ? { text } : {}
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1128
1164
|
function flatten(root, platform3) {
|
|
1129
1165
|
const out = [];
|
|
1130
1166
|
const stack = [];
|
|
@@ -1170,6 +1206,7 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1170
1206
|
}
|
|
1171
1207
|
}
|
|
1172
1208
|
markRepeatedBottomBarNavigation(out);
|
|
1209
|
+
annotateIdentityUniqueness(out);
|
|
1173
1210
|
return out;
|
|
1174
1211
|
}
|
|
1175
1212
|
function fnv1a(input) {
|
|
@@ -1251,6 +1288,13 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1251
1288
|
function isEditableElement(el) {
|
|
1252
1289
|
return EDITABLE_CLASS_RE.test(el.role.trim());
|
|
1253
1290
|
}
|
|
1291
|
+
var STATE_VALUE_CLASS_RE = /(XCUIElementTypeSwitch|XCUIElementTypeToggle|XCUIElementTypeSlider|XCUIElementTypeStepper|XCUIElementTypePageIndicator|XCUIElementTypeProgressIndicator|XCUIElementTypeSegmentedControl|XCUIElementTypePicker|XCUIElementTypePickerWheel|ToggleButton|SeekBar|ProgressBar|RatingBar|NumberPicker)$/;
|
|
1292
|
+
function isStateValueRole(role) {
|
|
1293
|
+
return STATE_VALUE_CLASS_RE.test(role.trim());
|
|
1294
|
+
}
|
|
1295
|
+
function isStateValueElement(el) {
|
|
1296
|
+
return isStateValueRole(el.role);
|
|
1297
|
+
}
|
|
1254
1298
|
function xpathAttrLiteral(value) {
|
|
1255
1299
|
if (!value.includes("'"))
|
|
1256
1300
|
return `'${value}'`;
|
|
@@ -1261,17 +1305,18 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1261
1305
|
function semanticXPath(el, platform3) {
|
|
1262
1306
|
const cls = nonBlank(el.role) ?? "*";
|
|
1263
1307
|
const a11y = nonBlank(el.accessibilityId);
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
}
|
|
1267
|
-
const
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1308
|
+
const rid = platform3 === "ANDROID" ? nonBlank(el.resourceId) : void 0;
|
|
1309
|
+
const text = isEditableElement(el) || isStateValueElement(el) ? void 0 : nonBlank(el.text);
|
|
1310
|
+
const a11yXpath = a11y ? platform3 === "ANDROID" ? `//${cls}[@content-desc=${xpathAttrLiteral(a11y)}]` : `//${cls}[@name=${xpathAttrLiteral(a11y)}]` : void 0;
|
|
1311
|
+
const ridXpath = rid ? `//${cls}[@resource-id=${xpathAttrLiteral(rid)}]` : void 0;
|
|
1312
|
+
const textXpath = text ? platform3 === "ANDROID" ? `//${cls}[@text=${xpathAttrLiteral(text)}]` : `//${cls}[@label=${xpathAttrLiteral(text)}]` : void 0;
|
|
1313
|
+
const candidates = [
|
|
1314
|
+
{ xpath: a11yXpath, unique: el.identityUnique?.accessibilityId },
|
|
1315
|
+
{ xpath: ridXpath, unique: el.identityUnique?.resourceId },
|
|
1316
|
+
{ xpath: textXpath, unique: el.identityUnique?.text }
|
|
1317
|
+
];
|
|
1318
|
+
const uniqueFirst = candidates.find((c) => c.xpath && c.unique !== false);
|
|
1319
|
+
return (uniqueFirst ?? candidates.find((c) => c.xpath))?.xpath;
|
|
1275
1320
|
}
|
|
1276
1321
|
function nonBlank(value) {
|
|
1277
1322
|
if (typeof value !== "string")
|
|
@@ -1280,30 +1325,40 @@ var require_mobile_source_parser = __commonJS({
|
|
|
1280
1325
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
1281
1326
|
}
|
|
1282
1327
|
function buildLocatorBundle(el, platform3) {
|
|
1328
|
+
const durableText = el.text && !isEditableElement(el) && !isStateValueElement(el) ? el.text : void 0;
|
|
1283
1329
|
const bundle = {
|
|
1284
1330
|
// Attribute-anchored xpath when the element has identity; the absolute
|
|
1285
1331
|
// indexed path only for identity-less elements (nothing else can find them).
|
|
1286
1332
|
xpath: semanticXPath(el, platform3) ?? el.xpath,
|
|
1287
1333
|
className: el.role
|
|
1288
1334
|
};
|
|
1289
|
-
|
|
1335
|
+
const a11yUnique = el.identityUnique?.accessibilityId !== false;
|
|
1336
|
+
const ridUnique = el.identityUnique?.resourceId !== false;
|
|
1337
|
+
const textUnique = el.identityUnique?.text !== false;
|
|
1338
|
+
const hasUniqueAlternativeToA11y = Boolean(el.resourceId && platform3 === "ANDROID" && ridUnique || durableText && textUnique);
|
|
1339
|
+
const hasUniqueAlternativeToRid = Boolean(el.accessibilityId && a11yUnique || durableText && textUnique);
|
|
1340
|
+
const includeA11y = Boolean(el.accessibilityId && (a11yUnique || !hasUniqueAlternativeToA11y));
|
|
1341
|
+
const includeRid = Boolean(platform3 === "ANDROID" && el.resourceId && (ridUnique || !hasUniqueAlternativeToRid));
|
|
1342
|
+
if (includeA11y && el.accessibilityId)
|
|
1290
1343
|
bundle.accessibilityId = el.accessibilityId;
|
|
1291
|
-
if (
|
|
1344
|
+
if (includeRid && el.resourceId)
|
|
1292
1345
|
bundle.resourceId = el.resourceId;
|
|
1293
|
-
if (
|
|
1294
|
-
bundle.text =
|
|
1295
|
-
if (platform3 === "ANDROID" && el.accessibilityId)
|
|
1346
|
+
if (durableText)
|
|
1347
|
+
bundle.text = durableText;
|
|
1348
|
+
if (includeA11y && platform3 === "ANDROID" && el.accessibilityId)
|
|
1296
1349
|
bundle.contentDesc = el.accessibilityId;
|
|
1297
1350
|
return bundle;
|
|
1298
1351
|
}
|
|
1299
1352
|
function preferredLocator(el) {
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1353
|
+
const durableText = el.text && !isEditableElement(el) && !isStateValueElement(el) ? el.text : void 0;
|
|
1354
|
+
const candidates = [
|
|
1355
|
+
{ value: el.accessibilityId, unique: el.identityUnique?.accessibilityId },
|
|
1356
|
+
{ value: el.resourceId, unique: el.identityUnique?.resourceId },
|
|
1357
|
+
{ value: durableText, unique: el.identityUnique?.text }
|
|
1358
|
+
];
|
|
1359
|
+
const uniqueFirst = candidates.find((c) => c.value && c.unique !== false);
|
|
1360
|
+
const chosen = (uniqueFirst ?? candidates.find((c) => c.value))?.value;
|
|
1361
|
+
return chosen ?? el.xpath;
|
|
1307
1362
|
}
|
|
1308
1363
|
function buildScreenSignal(elements, root, platform3) {
|
|
1309
1364
|
const signal = {
|
|
@@ -1966,6 +2021,11 @@ function isEditableMobileClassName(className) {
|
|
|
1966
2021
|
return false;
|
|
1967
2022
|
return /(EditText|AutoCompleteTextView|XCUIElementTypeTextField|XCUIElementTypeSecureTextField|XCUIElementTypeSearchField|XCUIElementTypeTextView)$/.test(className.trim());
|
|
1968
2023
|
}
|
|
2024
|
+
function isStateValueMobileClassName(className) {
|
|
2025
|
+
if (!className)
|
|
2026
|
+
return false;
|
|
2027
|
+
return /(XCUIElementTypeSwitch|XCUIElementTypeToggle|XCUIElementTypeSlider|XCUIElementTypeStepper|XCUIElementTypePageIndicator|XCUIElementTypeProgressIndicator|XCUIElementTypeSegmentedControl|XCUIElementTypePicker|XCUIElementTypePickerWheel|ToggleButton|SeekBar|ProgressBar|RatingBar|NumberPicker)$/.test(className.trim());
|
|
2028
|
+
}
|
|
1969
2029
|
function mobileCredentialPlaceholder(key) {
|
|
1970
2030
|
return `{{${key}}}`;
|
|
1971
2031
|
}
|
|
@@ -2095,9 +2155,10 @@ function buildStepTarget(metadata, selector) {
|
|
|
2095
2155
|
locatorBundle.resourceId,
|
|
2096
2156
|
// For editable fields the text attribute IS the currently-typed value — a
|
|
2097
2157
|
// self-invalidating locator that only matches while the recorded text is
|
|
2098
|
-
// still in the field (and it can leak typed input into locators).
|
|
2099
|
-
//
|
|
2100
|
-
|
|
2158
|
+
// still in the field (and it can leak typed input into locators). State
|
|
2159
|
+
// controls' text is their current state ("1"/"ON") and collides with every
|
|
2160
|
+
// same-state sibling. Static elements keep their text fallback.
|
|
2161
|
+
isEditableMobileClassName(locatorBundle.className) || isStateValueMobileClassName(locatorBundle.className) ? void 0 : locatorBundle.text,
|
|
2101
2162
|
locatorBundle.contentDesc,
|
|
2102
2163
|
locatorBundle.xpath,
|
|
2103
2164
|
selector
|
|
@@ -2253,7 +2314,9 @@ function mobileLocatorBundleHasIdentity(bundle) {
|
|
|
2253
2314
|
return false;
|
|
2254
2315
|
if (nonEmpty(bundle.accessibilityId) || nonEmpty(bundle.resourceId))
|
|
2255
2316
|
return true;
|
|
2256
|
-
if (nonEmpty(bundle.text)
|
|
2317
|
+
if (nonEmpty(bundle.text) && !isStateValueMobileClassName(bundle.className))
|
|
2318
|
+
return true;
|
|
2319
|
+
if (nonEmpty(bundle.contentDesc))
|
|
2257
2320
|
return true;
|
|
2258
2321
|
const xpath = nonEmpty(bundle.xpath);
|
|
2259
2322
|
return !!xpath && isIdentityMobileXpath(xpath);
|
|
@@ -2274,23 +2337,27 @@ function selectorCandidatesForStep(step) {
|
|
|
2274
2337
|
candidates.push(`~${bundle.accessibilityId}`);
|
|
2275
2338
|
if (nonEmpty(bundle.resourceId))
|
|
2276
2339
|
candidates.push(`id=${bundle.resourceId}`);
|
|
2340
|
+
if (nonEmpty(bundle.xpath))
|
|
2341
|
+
candidates.push(bundle.xpath);
|
|
2277
2342
|
const isGateStep = step.action === "waitForElement" || step.action === "assertVisible" || step.action === "assertText";
|
|
2278
|
-
const textIdentity = isGateStep || !isEditableMobileClassName(bundle.className) ? bundle.text : void 0;
|
|
2343
|
+
const textIdentity = (isGateStep || !isEditableMobileClassName(bundle.className)) && !isStateValueMobileClassName(bundle.className) ? bundle.text : void 0;
|
|
2279
2344
|
for (const value of [textIdentity, bundle.contentDesc]) {
|
|
2280
2345
|
const trimmed = nonEmpty(value);
|
|
2281
2346
|
if (trimmed)
|
|
2282
2347
|
candidates.push(identityTextXpath(trimmed));
|
|
2283
2348
|
}
|
|
2284
|
-
if (nonEmpty(bundle.xpath))
|
|
2285
|
-
candidates.push(bundle.xpath);
|
|
2286
2349
|
}
|
|
2287
2350
|
const hasExplicitResourceId = !!nonEmpty(bundle?.resourceId);
|
|
2351
|
+
const bundleContributedIdentity = candidates.some((value) => !(value.startsWith("/") || value.startsWith("(//")) || isIdentityMobileXpath(value));
|
|
2288
2352
|
const pushValueCandidates = (value) => {
|
|
2289
2353
|
candidates.push(selectorForValue(value));
|
|
2290
2354
|
const looksLikeIdToken = /^[A-Za-z_][\w.:-]*$/.test(value);
|
|
2291
2355
|
if (!hasExplicitResourceId && looksLikeIdToken && !/^(~|id=|css=|\/|\()/.test(value) && !value.includes(":id/")) {
|
|
2292
2356
|
candidates.push(`id=${value}`);
|
|
2293
2357
|
}
|
|
2358
|
+
if (!bundleContributedIdentity && !/^(~|id=|css=|\/|\()/.test(value)) {
|
|
2359
|
+
candidates.push(identityTextXpath(value));
|
|
2360
|
+
}
|
|
2294
2361
|
};
|
|
2295
2362
|
if (nonEmpty(target.primary))
|
|
2296
2363
|
pushValueCandidates(target.primary);
|
|
@@ -2985,7 +3052,8 @@ function isWebViewStep(step) {
|
|
|
2985
3052
|
function hasCrossPlatformIdentity(bundle) {
|
|
2986
3053
|
if (!bundle)
|
|
2987
3054
|
return false;
|
|
2988
|
-
|
|
3055
|
+
const durableText = isStateValueMobileClassName(bundle.className) ? void 0 : bundle.text;
|
|
3056
|
+
return Boolean(durableText?.trim() || bundle.contentDesc?.trim() || bundle.accessibilityId?.trim());
|
|
2989
3057
|
}
|
|
2990
3058
|
function stepIsDivergentForPlatform(step, platform3) {
|
|
2991
3059
|
return Array.isArray(step.crossPlatformDivergent) && step.crossPlatformDivergent.includes(platform3);
|
|
@@ -3067,7 +3135,8 @@ function summarizeMobileReadiness(steps, authoringPlatform) {
|
|
|
3067
3135
|
function hasStableNativeIdentity(bundle) {
|
|
3068
3136
|
if (!bundle)
|
|
3069
3137
|
return false;
|
|
3070
|
-
|
|
3138
|
+
const durableText = isStateValueMobileClassName(bundle.className) ? void 0 : bundle.text;
|
|
3139
|
+
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));
|
|
3071
3140
|
}
|
|
3072
3141
|
function isPositionalXpath(xpath) {
|
|
3073
3142
|
const value = xpath?.trim();
|
|
@@ -3147,6 +3216,7 @@ var MOBILE_PLATFORMS, LOCATOR_REQUIRING_ACTIONS, XPATH_ATTRIBUTE_EQUALITY_RE;
|
|
|
3147
3216
|
var init_mobile_cross_platform = __esm({
|
|
3148
3217
|
"../shared/dist/mobile-cross-platform.js"() {
|
|
3149
3218
|
"use strict";
|
|
3219
|
+
init_mobile_test_generation();
|
|
3150
3220
|
MOBILE_PLATFORMS = ["IOS", "ANDROID"];
|
|
3151
3221
|
LOCATOR_REQUIRING_ACTIONS = /* @__PURE__ */ new Set([
|
|
3152
3222
|
"tap",
|
|
@@ -6149,6 +6219,7 @@ __export(dist_exports, {
|
|
|
6149
6219
|
isEditableMobileClassName: () => isEditableMobileClassName,
|
|
6150
6220
|
isEmailFromIdentity: () => isEmailFromIdentity,
|
|
6151
6221
|
isEnvironmentalError: () => isEnvironmentalError,
|
|
6222
|
+
isIdentityMobileXpath: () => isIdentityMobileXpath,
|
|
6152
6223
|
isMobilePlatform: () => isMobilePlatform,
|
|
6153
6224
|
isPricingPlan: () => isPricingPlan,
|
|
6154
6225
|
isRunnableStep: () => isRunnableStep,
|
|
@@ -6156,6 +6227,7 @@ __export(dist_exports, {
|
|
|
6156
6227
|
isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
|
|
6157
6228
|
isSensitiveQueryKey: () => isSensitiveQueryKey,
|
|
6158
6229
|
isSharedAuthInjectEnabled: () => isSharedAuthInjectEnabled,
|
|
6230
|
+
isStateValueMobileClassName: () => isStateValueMobileClassName,
|
|
6159
6231
|
isSttModelForProvider: () => isSttModelForProvider,
|
|
6160
6232
|
isTestReadyForPlatform: () => isTestReadyForPlatform,
|
|
6161
6233
|
isTrialExpired: () => isTrialExpired,
|
|
@@ -7063,6 +7135,7 @@ var require_mobile_observation = __commonJS({
|
|
|
7063
7135
|
exports2.buildCollectedElementsForScreen = buildCollectedElementsForScreen;
|
|
7064
7136
|
exports2.extractNativeMobileLandmarks = extractNativeMobileLandmarks;
|
|
7065
7137
|
exports2.safeMobileElementLabel = safeMobileElementLabel;
|
|
7138
|
+
var mobile_source_parser_js_1 = require_mobile_source_parser();
|
|
7066
7139
|
var DEFAULT_MAX_ELEMENTS = 60;
|
|
7067
7140
|
var MAX_LABEL_LENGTH = 80;
|
|
7068
7141
|
var EVIDENCE_LABEL_LIMIT = 40;
|
|
@@ -7153,7 +7226,8 @@ var require_mobile_observation = __commonJS({
|
|
|
7153
7226
|
const locators = [];
|
|
7154
7227
|
if (stableId)
|
|
7155
7228
|
locators.push({ strategy: "getByTestId", value: stableId, confidence: 0.95 });
|
|
7156
|
-
const
|
|
7229
|
+
const platform3 = element.role.startsWith("XCUIElementType") ? "IOS" : "ANDROID";
|
|
7230
|
+
const xpath = (0, mobile_source_parser_js_1.buildLocatorBundle)(element, platform3).xpath?.trim();
|
|
7157
7231
|
if (xpath)
|
|
7158
7232
|
locators.push({ strategy: "css", value: xpath, confidence: 0.4 });
|
|
7159
7233
|
const collected = {
|
|
@@ -11219,7 +11293,7 @@ var require_thread_stream = __commonJS({
|
|
|
11219
11293
|
var { version } = require_package();
|
|
11220
11294
|
var { EventEmitter: EventEmitter2 } = require("events");
|
|
11221
11295
|
var { Worker } = require("worker_threads");
|
|
11222
|
-
var { join:
|
|
11296
|
+
var { join: join8 } = require("path");
|
|
11223
11297
|
var { pathToFileURL } = require("url");
|
|
11224
11298
|
var { wait } = require_wait();
|
|
11225
11299
|
var {
|
|
@@ -11270,7 +11344,7 @@ var require_thread_stream = __commonJS({
|
|
|
11270
11344
|
function createWorker(stream, opts) {
|
|
11271
11345
|
const { filename, workerData } = opts;
|
|
11272
11346
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
11273
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
11347
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join8(__dirname, "lib", "worker.js");
|
|
11274
11348
|
const worker = new Worker(toExecute, {
|
|
11275
11349
|
...opts.workerOpts,
|
|
11276
11350
|
name: opts.workerOpts?.name || "thread-stream",
|
|
@@ -11738,7 +11812,7 @@ var require_transport = __commonJS({
|
|
|
11738
11812
|
var { createRequire } = require("module");
|
|
11739
11813
|
var { existsSync: existsSync3 } = require("fs");
|
|
11740
11814
|
var getCallers = require_caller();
|
|
11741
|
-
var { join:
|
|
11815
|
+
var { join: join8, isAbsolute: isAbsolute2, sep } = require("path");
|
|
11742
11816
|
var { fileURLToPath } = require("url");
|
|
11743
11817
|
var sleep = require_atomic_sleep();
|
|
11744
11818
|
var onExit = require_on_exit_leak_free();
|
|
@@ -11810,7 +11884,7 @@ var require_transport = __commonJS({
|
|
|
11810
11884
|
return false;
|
|
11811
11885
|
}
|
|
11812
11886
|
}
|
|
11813
|
-
return
|
|
11887
|
+
return isAbsolute2(path) && !existsSync3(path);
|
|
11814
11888
|
}
|
|
11815
11889
|
function stripQuotes(value) {
|
|
11816
11890
|
const first = value[0];
|
|
@@ -11891,7 +11965,7 @@ var require_transport = __commonJS({
|
|
|
11891
11965
|
throw new Error("only one of target or targets can be specified");
|
|
11892
11966
|
}
|
|
11893
11967
|
if (targets) {
|
|
11894
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
11968
|
+
target = bundlerOverrides["pino-worker"] || join8(__dirname, "worker.js");
|
|
11895
11969
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
11896
11970
|
return {
|
|
11897
11971
|
...dest,
|
|
@@ -11909,7 +11983,7 @@ var require_transport = __commonJS({
|
|
|
11909
11983
|
});
|
|
11910
11984
|
});
|
|
11911
11985
|
} else if (pipeline) {
|
|
11912
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
11986
|
+
target = bundlerOverrides["pino-worker"] || join8(__dirname, "worker.js");
|
|
11913
11987
|
options.pipelines = [pipeline.map((dest) => {
|
|
11914
11988
|
return {
|
|
11915
11989
|
...dest,
|
|
@@ -11928,11 +12002,11 @@ var require_transport = __commonJS({
|
|
|
11928
12002
|
return buildStream(fixTarget(target), options, worker, sync, name);
|
|
11929
12003
|
function fixTarget(origin) {
|
|
11930
12004
|
origin = bundlerOverrides[origin] || origin;
|
|
11931
|
-
if (
|
|
12005
|
+
if (isAbsolute2(origin) || origin.indexOf("file://") === 0) {
|
|
11932
12006
|
return origin;
|
|
11933
12007
|
}
|
|
11934
12008
|
if (origin === "pino/file") {
|
|
11935
|
-
return
|
|
12009
|
+
return join8(__dirname, "..", "file.js");
|
|
11936
12010
|
}
|
|
11937
12011
|
let fixTarget2;
|
|
11938
12012
|
for (const filePath of callers) {
|
|
@@ -12913,7 +12987,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
12913
12987
|
return circularValue;
|
|
12914
12988
|
}
|
|
12915
12989
|
let res = "";
|
|
12916
|
-
let
|
|
12990
|
+
let join8 = ",";
|
|
12917
12991
|
const originalIndentation = indentation;
|
|
12918
12992
|
if (Array.isArray(value)) {
|
|
12919
12993
|
if (value.length === 0) {
|
|
@@ -12927,7 +13001,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
12927
13001
|
indentation += spacer;
|
|
12928
13002
|
res += `
|
|
12929
13003
|
${indentation}`;
|
|
12930
|
-
|
|
13004
|
+
join8 = `,
|
|
12931
13005
|
${indentation}`;
|
|
12932
13006
|
}
|
|
12933
13007
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -12935,13 +13009,13 @@ ${indentation}`;
|
|
|
12935
13009
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
12936
13010
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
12937
13011
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
12938
|
-
res +=
|
|
13012
|
+
res += join8;
|
|
12939
13013
|
}
|
|
12940
13014
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
12941
13015
|
res += tmp !== void 0 ? tmp : "null";
|
|
12942
13016
|
if (value.length - 1 > maximumBreadth) {
|
|
12943
13017
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
12944
|
-
res += `${
|
|
13018
|
+
res += `${join8}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
12945
13019
|
}
|
|
12946
13020
|
if (spacer !== "") {
|
|
12947
13021
|
res += `
|
|
@@ -12962,7 +13036,7 @@ ${originalIndentation}`;
|
|
|
12962
13036
|
let separator = "";
|
|
12963
13037
|
if (spacer !== "") {
|
|
12964
13038
|
indentation += spacer;
|
|
12965
|
-
|
|
13039
|
+
join8 = `,
|
|
12966
13040
|
${indentation}`;
|
|
12967
13041
|
whitespace = " ";
|
|
12968
13042
|
}
|
|
@@ -12976,13 +13050,13 @@ ${indentation}`;
|
|
|
12976
13050
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
12977
13051
|
if (tmp !== void 0) {
|
|
12978
13052
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
12979
|
-
separator =
|
|
13053
|
+
separator = join8;
|
|
12980
13054
|
}
|
|
12981
13055
|
}
|
|
12982
13056
|
if (keyLength > maximumBreadth) {
|
|
12983
13057
|
const removedKeys = keyLength - maximumBreadth;
|
|
12984
13058
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
12985
|
-
separator =
|
|
13059
|
+
separator = join8;
|
|
12986
13060
|
}
|
|
12987
13061
|
if (spacer !== "" && separator.length > 1) {
|
|
12988
13062
|
res = `
|
|
@@ -13023,7 +13097,7 @@ ${originalIndentation}`;
|
|
|
13023
13097
|
}
|
|
13024
13098
|
const originalIndentation = indentation;
|
|
13025
13099
|
let res = "";
|
|
13026
|
-
let
|
|
13100
|
+
let join8 = ",";
|
|
13027
13101
|
if (Array.isArray(value)) {
|
|
13028
13102
|
if (value.length === 0) {
|
|
13029
13103
|
return "[]";
|
|
@@ -13036,7 +13110,7 @@ ${originalIndentation}`;
|
|
|
13036
13110
|
indentation += spacer;
|
|
13037
13111
|
res += `
|
|
13038
13112
|
${indentation}`;
|
|
13039
|
-
|
|
13113
|
+
join8 = `,
|
|
13040
13114
|
${indentation}`;
|
|
13041
13115
|
}
|
|
13042
13116
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -13044,13 +13118,13 @@ ${indentation}`;
|
|
|
13044
13118
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
13045
13119
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
13046
13120
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
13047
|
-
res +=
|
|
13121
|
+
res += join8;
|
|
13048
13122
|
}
|
|
13049
13123
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
13050
13124
|
res += tmp !== void 0 ? tmp : "null";
|
|
13051
13125
|
if (value.length - 1 > maximumBreadth) {
|
|
13052
13126
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
13053
|
-
res += `${
|
|
13127
|
+
res += `${join8}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
13054
13128
|
}
|
|
13055
13129
|
if (spacer !== "") {
|
|
13056
13130
|
res += `
|
|
@@ -13063,7 +13137,7 @@ ${originalIndentation}`;
|
|
|
13063
13137
|
let whitespace = "";
|
|
13064
13138
|
if (spacer !== "") {
|
|
13065
13139
|
indentation += spacer;
|
|
13066
|
-
|
|
13140
|
+
join8 = `,
|
|
13067
13141
|
${indentation}`;
|
|
13068
13142
|
whitespace = " ";
|
|
13069
13143
|
}
|
|
@@ -13072,7 +13146,7 @@ ${indentation}`;
|
|
|
13072
13146
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
13073
13147
|
if (tmp !== void 0) {
|
|
13074
13148
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
13075
|
-
separator =
|
|
13149
|
+
separator = join8;
|
|
13076
13150
|
}
|
|
13077
13151
|
}
|
|
13078
13152
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -13130,20 +13204,20 @@ ${originalIndentation}`;
|
|
|
13130
13204
|
indentation += spacer;
|
|
13131
13205
|
let res2 = `
|
|
13132
13206
|
${indentation}`;
|
|
13133
|
-
const
|
|
13207
|
+
const join9 = `,
|
|
13134
13208
|
${indentation}`;
|
|
13135
13209
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
13136
13210
|
let i = 0;
|
|
13137
13211
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
13138
13212
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
13139
13213
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
13140
|
-
res2 +=
|
|
13214
|
+
res2 += join9;
|
|
13141
13215
|
}
|
|
13142
13216
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
13143
13217
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
13144
13218
|
if (value.length - 1 > maximumBreadth) {
|
|
13145
13219
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
13146
|
-
res2 += `${
|
|
13220
|
+
res2 += `${join9}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
13147
13221
|
}
|
|
13148
13222
|
res2 += `
|
|
13149
13223
|
${originalIndentation}`;
|
|
@@ -13159,16 +13233,16 @@ ${originalIndentation}`;
|
|
|
13159
13233
|
return '"[Object]"';
|
|
13160
13234
|
}
|
|
13161
13235
|
indentation += spacer;
|
|
13162
|
-
const
|
|
13236
|
+
const join8 = `,
|
|
13163
13237
|
${indentation}`;
|
|
13164
13238
|
let res = "";
|
|
13165
13239
|
let separator = "";
|
|
13166
13240
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
13167
13241
|
if (isTypedArrayWithEntries(value)) {
|
|
13168
|
-
res += stringifyTypedArray(value,
|
|
13242
|
+
res += stringifyTypedArray(value, join8, maximumBreadth);
|
|
13169
13243
|
keys = keys.slice(value.length);
|
|
13170
13244
|
maximumPropertiesToStringify -= value.length;
|
|
13171
|
-
separator =
|
|
13245
|
+
separator = join8;
|
|
13172
13246
|
}
|
|
13173
13247
|
if (deterministic) {
|
|
13174
13248
|
keys = sort(keys, comparator);
|
|
@@ -13179,13 +13253,13 @@ ${indentation}`;
|
|
|
13179
13253
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
13180
13254
|
if (tmp !== void 0) {
|
|
13181
13255
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
13182
|
-
separator =
|
|
13256
|
+
separator = join8;
|
|
13183
13257
|
}
|
|
13184
13258
|
}
|
|
13185
13259
|
if (keyLength > maximumBreadth) {
|
|
13186
13260
|
const removedKeys = keyLength - maximumBreadth;
|
|
13187
13261
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
13188
|
-
separator =
|
|
13262
|
+
separator = join8;
|
|
13189
13263
|
}
|
|
13190
13264
|
if (separator !== "") {
|
|
13191
13265
|
res = `
|
|
@@ -61861,11 +61935,11 @@ var require_core = __commonJS({
|
|
|
61861
61935
|
function mapOuter(r, f) {
|
|
61862
61936
|
return r.matched ? f(r) : r;
|
|
61863
61937
|
}
|
|
61864
|
-
function ab(pa, pb,
|
|
61865
|
-
return (data, i) => mapOuter(pa(data, i), (ma) => mapInner(pb(data, ma.position), (vb, j) =>
|
|
61938
|
+
function ab(pa, pb, join8) {
|
|
61939
|
+
return (data, i) => mapOuter(pa(data, i), (ma) => mapInner(pb(data, ma.position), (vb, j) => join8(ma.value, vb, data, i, j)));
|
|
61866
61940
|
}
|
|
61867
|
-
function abc(pa, pb, pc,
|
|
61868
|
-
return (data, i) => mapOuter(pa(data, i), (ma) => mapOuter(pb(data, ma.position), (mb) => mapInner(pc(data, mb.position), (vc, j) =>
|
|
61941
|
+
function abc(pa, pb, pc, join8) {
|
|
61942
|
+
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))));
|
|
61869
61943
|
}
|
|
61870
61944
|
function action(f) {
|
|
61871
61945
|
return (data, i) => {
|
|
@@ -78402,6 +78476,10 @@ var require_discover_ai_planner = __commonJS({
|
|
|
78402
78476
|
if (context.appBrief) {
|
|
78403
78477
|
sections.push(`## Application Brief
|
|
78404
78478
|
${context.appBrief.slice(0, 500)}`);
|
|
78479
|
+
}
|
|
78480
|
+
if (context.knowledgeContext && context.knowledgeContext.trim().length > 0) {
|
|
78481
|
+
sections.push(`## Known Facts
|
|
78482
|
+
${context.knowledgeContext.slice(0, 4e3)}`);
|
|
78405
78483
|
}
|
|
78406
78484
|
sections.push(`## Base URL
|
|
78407
78485
|
${compressed.baseUrl}`);
|
|
@@ -83446,48 +83524,29 @@ ${statePacket}`, redactionCredentials) },
|
|
|
83446
83524
|
log2(" app data reset skipped by policy \u2014 scenario records after relaunch with preserved data.");
|
|
83447
83525
|
return applyScenarioEntryDeepLink(driver, target, { appDataCleared: false, coldResetUnavailable: false, refuseGenerate: false, reason: "preserve_policy" }, log2);
|
|
83448
83526
|
}
|
|
83449
|
-
|
|
83450
|
-
|
|
83451
|
-
|
|
83452
|
-
|
|
83453
|
-
|
|
83454
|
-
|
|
83455
|
-
return applyScenarioEntryDeepLink(driver, target, { appDataCleared: true, coldResetUnavailable: false, refuseGenerate: false }, log2);
|
|
83456
|
-
}
|
|
83457
|
-
await driver.relaunch();
|
|
83458
|
-
if (resetPolicy === "CLEAR_APP_DATA") {
|
|
83459
|
-
log2(" CLEAR_APP_DATA required but app-data reset was unavailable \u2014 refusing to generate from warm state.");
|
|
83460
|
-
return {
|
|
83461
|
-
appDataCleared: false,
|
|
83462
|
-
coldResetUnavailable: true,
|
|
83463
|
-
refuseGenerate: true,
|
|
83464
|
-
reason: "clear_app_data_unavailable"
|
|
83465
|
-
};
|
|
83466
|
-
}
|
|
83467
|
-
log2(" app-data reset unavailable \u2014 generating with @warm-state (relaunch only; may not match cold-boot replay).");
|
|
83468
|
-
return applyScenarioEntryDeepLink(driver, target, {
|
|
83469
|
-
appDataCleared: false,
|
|
83470
|
-
coldResetUnavailable: true,
|
|
83471
|
-
refuseGenerate: false,
|
|
83472
|
-
reason: platform3 === "ANDROID" ? "android_reset_unavailable" : "reset_unavailable"
|
|
83473
|
-
}, log2);
|
|
83527
|
+
const appDataCleared = await driver.resetAppState?.().catch((err) => {
|
|
83528
|
+
log2(` app-data reset failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
83529
|
+
return false;
|
|
83530
|
+
}) ?? false;
|
|
83531
|
+
if (appDataCleared) {
|
|
83532
|
+
return applyScenarioEntryDeepLink(driver, target, { appDataCleared: true, coldResetUnavailable: false, refuseGenerate: false }, log2);
|
|
83474
83533
|
}
|
|
83475
83534
|
await driver.relaunch();
|
|
83476
83535
|
if (resetPolicy === "CLEAR_APP_DATA") {
|
|
83477
|
-
log2(" CLEAR_APP_DATA required but iOS
|
|
83536
|
+
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.");
|
|
83478
83537
|
return {
|
|
83479
83538
|
appDataCleared: false,
|
|
83480
83539
|
coldResetUnavailable: true,
|
|
83481
83540
|
refuseGenerate: true,
|
|
83482
|
-
reason: "ios_clear_app_data_unavailable"
|
|
83541
|
+
reason: platform3 === "IOS" ? "ios_clear_app_data_unavailable" : "clear_app_data_unavailable"
|
|
83483
83542
|
};
|
|
83484
83543
|
}
|
|
83485
|
-
log2(" iOS
|
|
83544
|
+
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).");
|
|
83486
83545
|
return applyScenarioEntryDeepLink(driver, target, {
|
|
83487
83546
|
appDataCleared: false,
|
|
83488
83547
|
coldResetUnavailable: true,
|
|
83489
83548
|
refuseGenerate: false,
|
|
83490
|
-
reason: "ios_no_clear_app"
|
|
83549
|
+
reason: platform3 === "IOS" ? "ios_no_clear_app" : platform3 === "ANDROID" ? "android_reset_unavailable" : "reset_unavailable"
|
|
83491
83550
|
}, log2);
|
|
83492
83551
|
}
|
|
83493
83552
|
async function runMobileGeneratePhase(args) {
|
|
@@ -88948,7 +89007,7 @@ var require_kill_tree = __commonJS({
|
|
|
88948
89007
|
var node_child_process_1 = require("child_process");
|
|
88949
89008
|
var node_util_1 = require("util");
|
|
88950
89009
|
var node_fs_1 = require("fs");
|
|
88951
|
-
var
|
|
89010
|
+
var execFileAsync4 = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
88952
89011
|
var PS_TIMEOUT_MS = 2e3;
|
|
88953
89012
|
var PS_BUFFER = 1024 * 1024;
|
|
88954
89013
|
async function gracefulCloseWithTreeKill(options) {
|
|
@@ -89002,7 +89061,7 @@ var require_kill_tree = __commonJS({
|
|
|
89002
89061
|
timer.unref?.();
|
|
89003
89062
|
let stdout;
|
|
89004
89063
|
try {
|
|
89005
|
-
const result = await
|
|
89064
|
+
const result = await execFileAsync4("ps", ["-A", "-o", "pid=,ppid=,command="], {
|
|
89006
89065
|
maxBuffer: PS_BUFFER,
|
|
89007
89066
|
signal: controller.signal
|
|
89008
89067
|
});
|
|
@@ -89040,7 +89099,7 @@ var require_kill_tree = __commonJS({
|
|
|
89040
89099
|
timer.unref?.();
|
|
89041
89100
|
let stdout;
|
|
89042
89101
|
try {
|
|
89043
|
-
const result = await
|
|
89102
|
+
const result = await execFileAsync4("ps", ["-A", "-o", "pid=,ppid=,command="], {
|
|
89044
89103
|
maxBuffer: PS_BUFFER,
|
|
89045
89104
|
signal: controller.signal
|
|
89046
89105
|
});
|
|
@@ -89117,7 +89176,7 @@ var require_kill_tree = __commonJS({
|
|
|
89117
89176
|
timer.unref?.();
|
|
89118
89177
|
let stdout;
|
|
89119
89178
|
try {
|
|
89120
|
-
const result = await
|
|
89179
|
+
const result = await execFileAsync4("ps", ["-A", "-o", "pid=,ppid="], {
|
|
89121
89180
|
maxBuffer: PS_BUFFER,
|
|
89122
89181
|
signal: controller.signal
|
|
89123
89182
|
});
|
|
@@ -92283,6 +92342,15 @@ var require_agentic_discovery_prompts = __commonJS({
|
|
|
92283
92342
|
lines.push(input.appBrief.trim());
|
|
92284
92343
|
lines.push("");
|
|
92285
92344
|
}
|
|
92345
|
+
if (input.knownFactsBlock && input.knownFactsBlock.trim()) {
|
|
92346
|
+
lines.push("## Known facts about this feature (from project documentation)");
|
|
92347
|
+
lines.push("");
|
|
92348
|
+
lines.push("Advisory hints about routes/data/constraints \u2014 verify against the live");
|
|
92349
|
+
lines.push("app; the browser is the source of truth for locators.");
|
|
92350
|
+
lines.push("");
|
|
92351
|
+
lines.push(input.knownFactsBlock.trim());
|
|
92352
|
+
lines.push("");
|
|
92353
|
+
}
|
|
92286
92354
|
if (input.credentials && input.credentials.length > 0) {
|
|
92287
92355
|
lines.push("## Test credentials (throwaway accounts)");
|
|
92288
92356
|
lines.push("");
|
|
@@ -98658,8 +98726,8 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
|
|
|
98658
98726
|
onRetry: (attempt, reason, delayMs) => logs2.push(` \u26A0 scenario-gen AI call retry ${attempt} (${reason}) \u2014 waiting ${delayMs}ms`)
|
|
98659
98727
|
});
|
|
98660
98728
|
} catch (aiErr) {
|
|
98661
|
-
const
|
|
98662
|
-
logs2.push(` AI call failed at iteration ${iteration} (after retries): ${
|
|
98729
|
+
const errMsg3 = aiErr instanceof Error ? aiErr.message : String(aiErr);
|
|
98730
|
+
logs2.push(` AI call failed at iteration ${iteration} (after retries): ${errMsg3}`);
|
|
98663
98731
|
throw (0, ai_retry_js_1.markAIProviderError)(aiErr);
|
|
98664
98732
|
}
|
|
98665
98733
|
const iterDuration = Date.now() - iterStart;
|
|
@@ -98702,8 +98770,8 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
|
|
|
98702
98770
|
try {
|
|
98703
98771
|
toolArgs = JSON.parse(toolCall.function.arguments);
|
|
98704
98772
|
} catch (parseErr) {
|
|
98705
|
-
const
|
|
98706
|
-
toolResults.push({ role: "tool", tool_call_id: toolCall.id, content:
|
|
98773
|
+
const errMsg3 = `Invalid tool arguments: ${parseErr.message}`;
|
|
98774
|
+
toolResults.push({ role: "tool", tool_call_id: toolCall.id, content: errMsg3 });
|
|
98707
98775
|
continue;
|
|
98708
98776
|
}
|
|
98709
98777
|
if (toolName === "finish_generate") {
|
|
@@ -99921,7 +99989,7 @@ var require_util4 = __commonJS({
|
|
|
99921
99989
|
}
|
|
99922
99990
|
path = url.path;
|
|
99923
99991
|
}
|
|
99924
|
-
var
|
|
99992
|
+
var isAbsolute2 = exports2.isAbsolute(path);
|
|
99925
99993
|
var parts = path.split(/\/+/);
|
|
99926
99994
|
for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
|
|
99927
99995
|
part = parts[i];
|
|
@@ -99941,7 +100009,7 @@ var require_util4 = __commonJS({
|
|
|
99941
100009
|
}
|
|
99942
100010
|
path = parts.join("/");
|
|
99943
100011
|
if (path === "") {
|
|
99944
|
-
path =
|
|
100012
|
+
path = isAbsolute2 ? "/" : ".";
|
|
99945
100013
|
}
|
|
99946
100014
|
if (url) {
|
|
99947
100015
|
url.path = path;
|
|
@@ -99950,7 +100018,7 @@ var require_util4 = __commonJS({
|
|
|
99950
100018
|
return path;
|
|
99951
100019
|
}
|
|
99952
100020
|
exports2.normalize = normalize;
|
|
99953
|
-
function
|
|
100021
|
+
function join8(aRoot, aPath) {
|
|
99954
100022
|
if (aRoot === "") {
|
|
99955
100023
|
aRoot = ".";
|
|
99956
100024
|
}
|
|
@@ -99982,7 +100050,7 @@ var require_util4 = __commonJS({
|
|
|
99982
100050
|
}
|
|
99983
100051
|
return joined;
|
|
99984
100052
|
}
|
|
99985
|
-
exports2.join =
|
|
100053
|
+
exports2.join = join8;
|
|
99986
100054
|
exports2.isAbsolute = function(aPath) {
|
|
99987
100055
|
return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
|
|
99988
100056
|
};
|
|
@@ -100155,7 +100223,7 @@ var require_util4 = __commonJS({
|
|
|
100155
100223
|
parsed.path = parsed.path.substring(0, index + 1);
|
|
100156
100224
|
}
|
|
100157
100225
|
}
|
|
100158
|
-
sourceURL =
|
|
100226
|
+
sourceURL = join8(urlGenerate(parsed), sourceURL);
|
|
100159
100227
|
}
|
|
100160
100228
|
return normalize(sourceURL);
|
|
100161
100229
|
}
|
|
@@ -313867,8 +313935,8 @@ Try a different fix approach. This is NOT your final script-driven attempt yet,
|
|
|
313867
313935
|
...(0, ai_provider_js_1.getJsonObjectResponseFormat)(agentModel)
|
|
313868
313936
|
})), { label: `script-heal-${attempt}` });
|
|
313869
313937
|
} catch (aiErr) {
|
|
313870
|
-
const
|
|
313871
|
-
logs2.push(`\u26A0 AI call failed: ${
|
|
313938
|
+
const errMsg3 = aiErr instanceof Error ? aiErr.message : String(aiErr);
|
|
313939
|
+
logs2.push(`\u26A0 AI call failed: ${errMsg3}`);
|
|
313872
313940
|
attemptHistory.push({
|
|
313873
313941
|
phase: "script",
|
|
313874
313942
|
attempt,
|
|
@@ -313876,13 +313944,13 @@ Try a different fix approach. This is NOT your final script-driven attempt yet,
|
|
|
313876
313944
|
passed: false,
|
|
313877
313945
|
changedCode: false,
|
|
313878
313946
|
summary: "AI call failed before returning a heal result",
|
|
313879
|
-
errorSummary: truncateForPrompt(
|
|
313947
|
+
errorSummary: truncateForPrompt(errMsg3),
|
|
313880
313948
|
failedStep: summarizeFailedStep(currentStepResults)
|
|
313881
313949
|
});
|
|
313882
313950
|
if (feedbackEnabled) {
|
|
313883
|
-
lastTransportFailure = truncateForPrompt(
|
|
313951
|
+
lastTransportFailure = truncateForPrompt(errMsg3, 200) ?? "the AI request failed";
|
|
313884
313952
|
} else {
|
|
313885
|
-
currentError = `AI call failed on attempt ${attempt}: ${
|
|
313953
|
+
currentError = `AI call failed on attempt ${attempt}: ${errMsg3}`;
|
|
313886
313954
|
}
|
|
313887
313955
|
markRecoverableTurn();
|
|
313888
313956
|
continue;
|
|
@@ -314132,8 +314200,8 @@ ${currentError.slice(0, 2e3)}`
|
|
|
314132
314200
|
// item 5b
|
|
314133
314201
|
});
|
|
314134
314202
|
} catch (runErr) {
|
|
314135
|
-
const
|
|
314136
|
-
logs2.push(`\u{1F4A5} Native execution threw: ${
|
|
314203
|
+
const errMsg3 = runErr instanceof Error ? runErr.message : String(runErr);
|
|
314204
|
+
logs2.push(`\u{1F4A5} Native execution threw: ${errMsg3}`);
|
|
314137
314205
|
attemptHistory.push({
|
|
314138
314206
|
phase: "script",
|
|
314139
314207
|
attempt,
|
|
@@ -314141,9 +314209,9 @@ ${currentError.slice(0, 2e3)}`
|
|
|
314141
314209
|
passed: false,
|
|
314142
314210
|
changedCode: true,
|
|
314143
314211
|
summary: truncateForPrompt(changeDesc) ?? "Applied code fix, but native execution threw before verification completed",
|
|
314144
|
-
errorSummary: truncateForPrompt(
|
|
314212
|
+
errorSummary: truncateForPrompt(errMsg3)
|
|
314145
314213
|
});
|
|
314146
|
-
currentError = `Native execution threw: ${
|
|
314214
|
+
currentError = `Native execution threw: ${errMsg3}`;
|
|
314147
314215
|
currentStepResults = [];
|
|
314148
314216
|
continue;
|
|
314149
314217
|
}
|
|
@@ -314899,9 +314967,9 @@ Last error (after heal attempts): ${lastErrorSnippet}` : `Heal aborted: same err
|
|
|
314899
314967
|
previousErrorSignature = errorSig;
|
|
314900
314968
|
}
|
|
314901
314969
|
} catch (runErr) {
|
|
314902
|
-
const
|
|
314903
|
-
logs2.push(`\u{1F4A5} Native run threw: ${
|
|
314904
|
-
currentError =
|
|
314970
|
+
const errMsg3 = runErr instanceof Error ? runErr.message : String(runErr);
|
|
314971
|
+
logs2.push(`\u{1F4A5} Native run threw: ${errMsg3}`);
|
|
314972
|
+
currentError = errMsg3;
|
|
314905
314973
|
currentStepResults = [];
|
|
314906
314974
|
}
|
|
314907
314975
|
} else {
|
|
@@ -315463,8 +315531,8 @@ When you call finish_fix, preserve locators only when they are stable structural
|
|
|
315463
315531
|
}
|
|
315464
315532
|
}
|
|
315465
315533
|
} catch (toolErr) {
|
|
315466
|
-
const
|
|
315467
|
-
let errorContent = `Error: ${
|
|
315534
|
+
const errMsg3 = toolErr instanceof Error ? toolErr.message : String(toolErr);
|
|
315535
|
+
let errorContent = `Error: ${errMsg3}`;
|
|
315468
315536
|
try {
|
|
315469
315537
|
const consoleResult = await mcpClient.callTool({ name: "browser_console_messages", arguments: {} });
|
|
315470
315538
|
const consoleText = Array.isArray(consoleResult.content) ? consoleResult.content.map((c) => c.type === "text" ? c.text ?? "" : "").join("\n") : String(consoleResult.content ?? "");
|
|
@@ -315485,8 +315553,8 @@ ${errorContent}`;
|
|
|
315485
315553
|
}
|
|
315486
315554
|
}
|
|
315487
315555
|
} catch (mcpErr) {
|
|
315488
|
-
const
|
|
315489
|
-
logs2.push(`\u{1F4A5} MCP exploration failed: ${
|
|
315556
|
+
const errMsg3 = mcpErr instanceof Error ? mcpErr.message : String(mcpErr);
|
|
315557
|
+
logs2.push(`\u{1F4A5} MCP exploration failed: ${errMsg3}`);
|
|
315490
315558
|
}
|
|
315491
315559
|
if (verdict === "app_bug") {
|
|
315492
315560
|
phase2AttemptHistory.push({
|
|
@@ -316049,9 +316117,9 @@ ${testBlocks}
|
|
|
316049
316117
|
}
|
|
316050
316118
|
emit(` \u2713 ${toolResultContent.slice(0, 200)}${toolResultContent.length > 200 ? "..." : ""}`);
|
|
316051
316119
|
} catch (err) {
|
|
316052
|
-
const
|
|
316053
|
-
toolResultContent = `ERROR: ${
|
|
316054
|
-
emit(` \u2717 ${
|
|
316120
|
+
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
316121
|
+
toolResultContent = `ERROR: ${errMsg3}`;
|
|
316122
|
+
emit(` \u2717 ${errMsg3}`);
|
|
316055
316123
|
}
|
|
316056
316124
|
toolResults.push({ role: "tool", tool_call_id: toolCall.id, content: toolResultContent });
|
|
316057
316125
|
}
|
|
@@ -321906,8 +321974,8 @@ var require_capture_page_normalizer = __commonJS({
|
|
|
321906
321974
|
const snapItems = Array.isArray(snapResult.content) ? snapResult.content : [];
|
|
321907
321975
|
snapshotText = extractInlineSnapshotYaml(snapItems.find((c) => c.type === "text")?.text || null);
|
|
321908
321976
|
} catch (err) {
|
|
321909
|
-
const
|
|
321910
|
-
const isTimeout = /timeout|timed out|TimeoutError/i.test(
|
|
321977
|
+
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
321978
|
+
const isTimeout = /timeout|timed out|TimeoutError/i.test(errMsg3);
|
|
321911
321979
|
return {
|
|
321912
321980
|
snapshotText: null,
|
|
321913
321981
|
meta: {
|
|
@@ -325929,9 +325997,9 @@ ${currentSummary}`;
|
|
|
325929
325997
|
});
|
|
325930
325998
|
}
|
|
325931
325999
|
} catch (err) {
|
|
325932
|
-
const
|
|
325933
|
-
logs2.push(` Error persisting page: ${
|
|
325934
|
-
toolResults.push({ role: "tool", tool_call_id: toolCall.id, content: `ERROR persisting page: ${
|
|
326000
|
+
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
326001
|
+
logs2.push(` Error persisting page: ${errMsg3}`);
|
|
326002
|
+
toolResults.push({ role: "tool", tool_call_id: toolCall.id, content: `ERROR persisting page: ${errMsg3}` });
|
|
325935
326003
|
}
|
|
325936
326004
|
continue;
|
|
325937
326005
|
}
|
|
@@ -326133,12 +326201,12 @@ ${currentSummary}`;
|
|
|
326133
326201
|
})
|
|
326134
326202
|
});
|
|
326135
326203
|
} catch (err) {
|
|
326136
|
-
const
|
|
326137
|
-
logs2.push(` Error persisting page: ${
|
|
326204
|
+
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
326205
|
+
logs2.push(` Error persisting page: ${errMsg3}`);
|
|
326138
326206
|
toolResults.push({
|
|
326139
326207
|
role: "tool",
|
|
326140
326208
|
tool_call_id: toolCall.id,
|
|
326141
|
-
content: `ERROR persisting page: ${
|
|
326209
|
+
content: `ERROR persisting page: ${errMsg3}`
|
|
326142
326210
|
});
|
|
326143
326211
|
}
|
|
326144
326212
|
continue;
|
|
@@ -326173,12 +326241,12 @@ ${currentSummary}`;
|
|
|
326173
326241
|
content: JSON.stringify({ acknowledged: true })
|
|
326174
326242
|
});
|
|
326175
326243
|
} catch (err) {
|
|
326176
|
-
const
|
|
326177
|
-
logs2.push(` Error persisting transition: ${
|
|
326244
|
+
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
326245
|
+
logs2.push(` Error persisting transition: ${errMsg3}`);
|
|
326178
326246
|
toolResults.push({
|
|
326179
326247
|
role: "tool",
|
|
326180
326248
|
tool_call_id: toolCall.id,
|
|
326181
|
-
content: `ERROR persisting transition: ${
|
|
326249
|
+
content: `ERROR persisting transition: ${errMsg3}`
|
|
326182
326250
|
});
|
|
326183
326251
|
}
|
|
326184
326252
|
continue;
|
|
@@ -326833,15 +326901,15 @@ Exploration complete: ${summary}`);
|
|
|
326833
326901
|
toolResultContent = (0, snapshot_context_compactor_js_1.formatMCPToolResult)(toolResultContent, MAX_TOOL_RESULT_CHARS);
|
|
326834
326902
|
logs2.push(` OK: ${toolResultContent.slice(0, 200)}${toolResultContent.length > 200 ? "..." : ""}`);
|
|
326835
326903
|
} catch (err) {
|
|
326836
|
-
const
|
|
326837
|
-
toolResultContent = `ERROR: ${
|
|
326838
|
-
logs2.push(` Error: ${
|
|
326904
|
+
const errMsg3 = err instanceof Error ? err.message : String(err);
|
|
326905
|
+
toolResultContent = `ERROR: ${errMsg3}`;
|
|
326906
|
+
logs2.push(` Error: ${errMsg3}`);
|
|
326839
326907
|
if (useV2) {
|
|
326840
326908
|
(0, exploration_state_js_1.reduceEvent)(state2, {
|
|
326841
326909
|
type: "ACTION_FAILED",
|
|
326842
326910
|
route: state2.currentRoute,
|
|
326843
326911
|
action: `${toolName}(${JSON.stringify(toolArgs).slice(0, 100)})`,
|
|
326844
|
-
error:
|
|
326912
|
+
error: errMsg3
|
|
326845
326913
|
});
|
|
326846
326914
|
}
|
|
326847
326915
|
}
|
|
@@ -328950,6 +329018,15 @@ var require_agentic_survey_prompts = __commonJS({
|
|
|
328950
329018
|
lines.push(input.appBrief.trim());
|
|
328951
329019
|
lines.push("");
|
|
328952
329020
|
}
|
|
329021
|
+
if (input.knownFactsBlock && input.knownFactsBlock.trim().length > 0) {
|
|
329022
|
+
lines.push("## Known facts about this app (from project documentation)");
|
|
329023
|
+
lines.push("");
|
|
329024
|
+
lines.push("Use these as hints for what features/routes/constraints exist. They are");
|
|
329025
|
+
lines.push("advisory \u2014 verify against the live app; the browser is the source of truth.");
|
|
329026
|
+
lines.push("");
|
|
329027
|
+
lines.push(input.knownFactsBlock.trim());
|
|
329028
|
+
lines.push("");
|
|
329029
|
+
}
|
|
328953
329030
|
if (input.credentials && input.credentials.length > 0) {
|
|
328954
329031
|
lines.push("## Test credentials (throwaway accounts)");
|
|
328955
329032
|
lines.push("");
|
|
@@ -329577,6 +329654,7 @@ var require_grok_survey_runner = __commonJS({
|
|
|
329577
329654
|
const briefInput = {
|
|
329578
329655
|
baseUrl: ctx.baseUrl,
|
|
329579
329656
|
appBrief: ctx.appBrief,
|
|
329657
|
+
knownFactsBlock: ctx.knowledgeContext,
|
|
329580
329658
|
credentials: credentials.length > 0 ? credentials : void 0,
|
|
329581
329659
|
existingFeatureNames: ctx.existingFeatureNames ?? [],
|
|
329582
329660
|
focusAreas: ctx.focusAreas,
|
|
@@ -329677,10 +329755,10 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
|
|
|
329677
329755
|
onLog: options.onLog,
|
|
329678
329756
|
onProvenCommands: recordProvenCommands
|
|
329679
329757
|
});
|
|
329680
|
-
const
|
|
329758
|
+
const errMsg3 = firstError?.message ?? (grokError instanceof Error ? grokError.message : "unknown error");
|
|
329681
329759
|
const fixPrompt = [
|
|
329682
329760
|
"Your previous attempt did not produce a valid result file.",
|
|
329683
|
-
`Error: ${
|
|
329761
|
+
`Error: ${errMsg3}`,
|
|
329684
329762
|
`Re-read BRIEF.md and write a valid SurveyorOutput JSON to ${resultFilePath}.`,
|
|
329685
329763
|
"Single JSON object. No prose. Then exit."
|
|
329686
329764
|
].join("\n");
|
|
@@ -331272,6 +331350,7 @@ var require_grok_discovery_runner = __commonJS({
|
|
|
331272
331350
|
featureObservations: ctx.featureContext?.flowIntel,
|
|
331273
331351
|
notesForTestAuthor: ctx.featureContext?.priorInsight,
|
|
331274
331352
|
appBrief: ctx.appBrief,
|
|
331353
|
+
knownFactsBlock: ctx.knowledgeContext,
|
|
331275
331354
|
credentials: credentials.length > 0 ? credentials : void 0,
|
|
331276
331355
|
testVariableKeys: testVariableKeys.length > 0 ? testVariableKeys : void 0,
|
|
331277
331356
|
existingTestNames: ctx.existingTestNames ?? [],
|
|
@@ -331820,7 +331899,7 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
|
|
|
331820
331899
|
log2(`First-pass discovery-result invalid or missing: ${firstError.message}`);
|
|
331821
331900
|
}
|
|
331822
331901
|
if (!parsedResult) {
|
|
331823
|
-
const
|
|
331902
|
+
const errMsg3 = firstError?.message ?? (grokError instanceof Error ? grokError.message : "unknown error");
|
|
331824
331903
|
if (planDebounceTimerRef.current) {
|
|
331825
331904
|
clearTimeout(planDebounceTimerRef.current);
|
|
331826
331905
|
planDebounceTimerRef.current = void 0;
|
|
@@ -331845,7 +331924,7 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
|
|
|
331845
331924
|
""
|
|
331846
331925
|
].join("\n");
|
|
331847
331926
|
const fixPrompt = [
|
|
331848
|
-
`Your previous attempt did not produce a valid result file. Error: ${
|
|
331927
|
+
`Your previous attempt did not produce a valid result file. Error: ${errMsg3}`,
|
|
331849
331928
|
"Re-read BRIEF.md.",
|
|
331850
331929
|
phaseContext,
|
|
331851
331930
|
`Continue from where you left off \u2014 already verified tests are in ${testsDirPath}/`,
|
|
@@ -332209,6 +332288,7 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
|
|
|
332209
332288
|
featureObservations: ctx.featureContext?.flowIntel,
|
|
332210
332289
|
notesForTestAuthor: ctx.featureContext?.priorInsight,
|
|
332211
332290
|
appBrief: ctx.appBrief,
|
|
332291
|
+
knownFactsBlock: ctx.knowledgeContext,
|
|
332212
332292
|
credentials: credentials.length > 0 ? credentials : void 0,
|
|
332213
332293
|
testVariableKeys: testVariableKeys.length > 0 ? testVariableKeys : void 0,
|
|
332214
332294
|
existingTestNames: ctx.existingTestNames ?? [],
|
|
@@ -333300,6 +333380,7 @@ ${preview}`);
|
|
|
333300
333380
|
const briefInput = {
|
|
333301
333381
|
baseUrl: ctx.baseUrl,
|
|
333302
333382
|
appBrief: ctx.appBrief,
|
|
333383
|
+
knownFactsBlock: ctx.knowledgeContext,
|
|
333303
333384
|
credentials: credentials.length > 0 ? credentials : void 0,
|
|
333304
333385
|
existingFeatureNames: ctx.existingFeatureNames ?? [],
|
|
333305
333386
|
focusAreas: ctx.focusAreas,
|
|
@@ -333472,10 +333553,10 @@ ${(0, inbox_tools_js_1.buildInboxAgentsGuidance)(inboxReg.email, inboxReg.passwo
|
|
|
333472
333553
|
onLog: parserLog,
|
|
333473
333554
|
onProvenCommands: recordProvenCommands
|
|
333474
333555
|
});
|
|
333475
|
-
const
|
|
333556
|
+
const errMsg3 = firstError?.message ?? (claudeError instanceof Error ? claudeError.message : "unknown error");
|
|
333476
333557
|
const fixPrompt = [
|
|
333477
333558
|
"Your previous attempt did not leave a valid result file.",
|
|
333478
|
-
`Error: ${
|
|
333559
|
+
`Error: ${errMsg3}`,
|
|
333479
333560
|
`Writing a valid SurveyorOutput JSON object to ${resultFilePath} is your ONLY priority now.`,
|
|
333480
333561
|
"Procedure: navigate to at most 6-8 of the main pages, then IMMEDIATELY use",
|
|
333481
333562
|
`the Write tool to save the SurveyorOutput JSON to ${resultFilePath}. Do this`,
|
|
@@ -334159,6 +334240,7 @@ var require_claude_discovery_runner = __commonJS({
|
|
|
334159
334240
|
featureObservations: ctx.featureContext?.flowIntel,
|
|
334160
334241
|
notesForTestAuthor: ctx.featureContext?.priorInsight,
|
|
334161
334242
|
appBrief: ctx.appBrief,
|
|
334243
|
+
knownFactsBlock: ctx.knowledgeContext,
|
|
334162
334244
|
credentials: credentials.length > 0 ? credentials : void 0,
|
|
334163
334245
|
testVariableKeys: testVariableKeys.length > 0 ? testVariableKeys : void 0,
|
|
334164
334246
|
existingTestNames: ctx.existingTestNames ?? [],
|
|
@@ -336013,7 +336095,8 @@ Explore complete: ${exploreResult.pagesDiscovered} pages, ${exploreResult.elemen
|
|
|
336013
336095
|
// scenarioTouchesSignupAuthSurface) still isolate non-auth scenarios. The
|
|
336014
336096
|
// auth-feature scope IS applied at the DEEP call site where ctx.featureName is known.
|
|
336015
336097
|
emailVerifyArmed: mode !== "survey" && plannerEmailVerifyArmed(ctx),
|
|
336016
|
-
testBudget
|
|
336098
|
+
testBudget,
|
|
336099
|
+
knowledgeContext: ctx.knowledgeContext
|
|
336017
336100
|
}, {
|
|
336018
336101
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
336019
336102
|
gateModelOverride: ctx.planGateModel,
|
|
@@ -336125,6 +336208,7 @@ Health: ${surveyHealth.totalIssues} issue(s) - ${surveyHealth.consoleErrors.leng
|
|
|
336125
336208
|
interactionLog: exploreResult.interactionLog,
|
|
336126
336209
|
modelOverride: ctx.generateModel,
|
|
336127
336210
|
appBrief: ctx.appBrief,
|
|
336211
|
+
knowledgeContext: ctx.knowledgeContext,
|
|
336128
336212
|
onAICall,
|
|
336129
336213
|
onProgress,
|
|
336130
336214
|
mcpClient: mcpClient ?? void 0,
|
|
@@ -336604,7 +336688,8 @@ Plan (reused): ${resumeSavedScenarios.length} scenarios from the paused run.`);
|
|
|
336604
336688
|
// configured inbox), so the planner never promises what generation can't do.
|
|
336605
336689
|
// Scoped to AUTH features: a non-auth feature has no signup surface to test.
|
|
336606
336690
|
emailVerifyArmed: plannerEmailVerifyArmed(ctx) && featureLooksLikeAuth(ctx.featureName, augmentedFeatureFocusAreas),
|
|
336607
|
-
testBudget: featureTestBudget
|
|
336691
|
+
testBudget: featureTestBudget,
|
|
336692
|
+
knowledgeContext: ctx.knowledgeContext
|
|
336608
336693
|
}, {
|
|
336609
336694
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
336610
336695
|
gateModelOverride: ctx.planGateModel,
|
|
@@ -336650,6 +336735,7 @@ Plan: ${planResult.scenarios.length} scenarios (budget: ${featureTestBudget})`);
|
|
|
336650
336735
|
interactionLog: exploreResult.interactionLog,
|
|
336651
336736
|
modelOverride: ctx.generateModel,
|
|
336652
336737
|
appBrief: ctx.appBrief,
|
|
336738
|
+
knowledgeContext: ctx.knowledgeContext,
|
|
336653
336739
|
onAICall,
|
|
336654
336740
|
mcpClient: mcpClient ?? void 0,
|
|
336655
336741
|
// Pooled browser for parallel scenario generation (Fargate opt-in via
|
|
@@ -339848,8 +339934,8 @@ var require_adm_zip = __commonJS({
|
|
|
339848
339934
|
return null;
|
|
339849
339935
|
}
|
|
339850
339936
|
function fixPath(zipPath) {
|
|
339851
|
-
const { join:
|
|
339852
|
-
return
|
|
339937
|
+
const { join: join8, normalize, sep } = pth.posix;
|
|
339938
|
+
return join8(pth.isAbsolute(zipPath) ? "/" : ".", normalize(sep + zipPath.split("\\").join(sep) + sep));
|
|
339853
339939
|
}
|
|
339854
339940
|
function filenameFilter(filterfn) {
|
|
339855
339941
|
if (filterfn instanceof RegExp) {
|
|
@@ -342684,11 +342770,11 @@ var require_winchars = __commonJS({
|
|
|
342684
342770
|
var require_strip_absolute_path = __commonJS({
|
|
342685
342771
|
"../../node_modules/tar/lib/strip-absolute-path.js"(exports2, module2) {
|
|
342686
342772
|
"use strict";
|
|
342687
|
-
var { isAbsolute, parse } = require("path").win32;
|
|
342773
|
+
var { isAbsolute: isAbsolute2, parse } = require("path").win32;
|
|
342688
342774
|
module2.exports = (path) => {
|
|
342689
342775
|
let r = "";
|
|
342690
342776
|
let parsed = parse(path);
|
|
342691
|
-
while (
|
|
342777
|
+
while (isAbsolute2(path) || parsed.root) {
|
|
342692
342778
|
const root = path.charAt(0) === "/" && path.slice(0, 4) !== "//?/" ? "/" : parsed.root;
|
|
342693
342779
|
path = path.slice(root.length);
|
|
342694
342780
|
r += root;
|
|
@@ -345200,7 +345286,7 @@ var require_update = __commonJS({
|
|
|
345200
345286
|
var require_opts_arg = __commonJS({
|
|
345201
345287
|
"../../node_modules/tar/node_modules/mkdirp/lib/opts-arg.js"(exports2, module2) {
|
|
345202
345288
|
"use strict";
|
|
345203
|
-
var { promisify:
|
|
345289
|
+
var { promisify: promisify4 } = require("util");
|
|
345204
345290
|
var fs = require("fs");
|
|
345205
345291
|
var optsArg = (opts) => {
|
|
345206
345292
|
if (!opts)
|
|
@@ -345214,9 +345300,9 @@ var require_opts_arg = __commonJS({
|
|
|
345214
345300
|
else
|
|
345215
345301
|
throw new TypeError("invalid options argument");
|
|
345216
345302
|
opts.mkdir = opts.mkdir || opts.fs.mkdir || fs.mkdir;
|
|
345217
|
-
opts.mkdirAsync =
|
|
345303
|
+
opts.mkdirAsync = promisify4(opts.mkdir);
|
|
345218
345304
|
opts.stat = opts.stat || opts.fs.stat || fs.stat;
|
|
345219
|
-
opts.statAsync =
|
|
345305
|
+
opts.statAsync = promisify4(opts.stat);
|
|
345220
345306
|
opts.statSync = opts.statSync || opts.fs.statSync || fs.statSync;
|
|
345221
345307
|
opts.mkdirSync = opts.mkdirSync || opts.fs.mkdirSync || fs.mkdirSync;
|
|
345222
345308
|
return opts;
|
|
@@ -345469,10 +345555,10 @@ var require_chownr = __commonJS({
|
|
|
345469
345555
|
}
|
|
345470
345556
|
} : (path2, uid, gid) => lchownSync(path2, uid, gid);
|
|
345471
345557
|
var nodeVersion = process.version;
|
|
345472
|
-
var
|
|
345558
|
+
var readdir3 = (path2, options, cb) => fs.readdir(path2, options, cb);
|
|
345473
345559
|
var readdirSync = (path2, options) => fs.readdirSync(path2, options);
|
|
345474
345560
|
if (/^v4\./.test(nodeVersion))
|
|
345475
|
-
|
|
345561
|
+
readdir3 = (path2, options, cb) => fs.readdir(path2, cb);
|
|
345476
345562
|
var chown = (cpath, uid, gid, cb) => {
|
|
345477
345563
|
fs[LCHOWN](cpath, uid, gid, handleEISDIR(cpath, uid, gid, (er) => {
|
|
345478
345564
|
cb(er && er.code !== "ENOENT" ? er : null);
|
|
@@ -345499,7 +345585,7 @@ var require_chownr = __commonJS({
|
|
|
345499
345585
|
}
|
|
345500
345586
|
};
|
|
345501
345587
|
var chownr = (p, uid, gid, cb) => {
|
|
345502
|
-
|
|
345588
|
+
readdir3(p, { withFileTypes: true }, (er, children) => {
|
|
345503
345589
|
if (er) {
|
|
345504
345590
|
if (er.code === "ENOENT")
|
|
345505
345591
|
return cb();
|
|
@@ -345773,7 +345859,7 @@ var require_path_reservations = __commonJS({
|
|
|
345773
345859
|
var assert = require("assert");
|
|
345774
345860
|
var normalize = require_normalize_unicode();
|
|
345775
345861
|
var stripSlashes = require_strip_trailing_slashes();
|
|
345776
|
-
var { join:
|
|
345862
|
+
var { join: join8 } = require("path");
|
|
345777
345863
|
var platform3 = process.env.TESTING_TAR_FAKE_PLATFORM || process.platform;
|
|
345778
345864
|
var isWindows = platform3 === "win32";
|
|
345779
345865
|
module2.exports = () => {
|
|
@@ -345782,7 +345868,7 @@ var require_path_reservations = __commonJS({
|
|
|
345782
345868
|
const getDirs = (path) => {
|
|
345783
345869
|
const dirs = path.split("/").slice(0, -1).reduce((set, path2) => {
|
|
345784
345870
|
if (set.length) {
|
|
345785
|
-
path2 =
|
|
345871
|
+
path2 = join8(set[set.length - 1], path2);
|
|
345786
345872
|
}
|
|
345787
345873
|
set.push(path2 || "/");
|
|
345788
345874
|
return set;
|
|
@@ -345850,7 +345936,7 @@ var require_path_reservations = __commonJS({
|
|
|
345850
345936
|
};
|
|
345851
345937
|
const reserve = (paths, fn) => {
|
|
345852
345938
|
paths = isWindows ? ["win32 parallelization disabled"] : paths.map((p) => {
|
|
345853
|
-
return stripSlashes(
|
|
345939
|
+
return stripSlashes(join8(normalize(p))).toLowerCase();
|
|
345854
345940
|
});
|
|
345855
345941
|
const dirs = new Set(
|
|
345856
345942
|
paths.map((path) => getDirs(path)).reduce((a, b) => a.concat(b))
|
|
@@ -347863,6 +347949,12 @@ Write valid JSON only (no markdown fences inside files). Be exhaustive.`;
|
|
|
347863
347949
|
"Use {{TEST_USER_EMAIL}}/{{TEST_USER_PASSWORD}} for credentials. Include",
|
|
347864
347950
|
"assertVisible or assertText unless tagging @needs-review. When an incremental",
|
|
347865
347951
|
"allow-list is present, extract ONLY from those files.",
|
|
347952
|
+
"",
|
|
347953
|
+
"Every tap/type/clear/waitForElement/assertVisible/assertText target MUST",
|
|
347954
|
+
"carry an IDENTITY locator: ~accessibility-id, id=resource-id, visible text,",
|
|
347955
|
+
"or an attribute-EQUALITY xpath (//android.widget.Button[@text='Submit']).",
|
|
347956
|
+
"Positional/index-only xpaths (/hierarchy/\u2026/View[3]) are rejected \u2014 imported",
|
|
347957
|
+
"steps have no recorded coordinates, so such a step can never replay.",
|
|
347866
347958
|
"Write strictly valid JSON."
|
|
347867
347959
|
].join("\n");
|
|
347868
347960
|
}
|
|
@@ -347880,6 +347972,32 @@ Write valid JSON only (no markdown fences inside files). Be exhaustive.`;
|
|
|
347880
347972
|
function isPlainObject2(value) {
|
|
347881
347973
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
347882
347974
|
}
|
|
347975
|
+
var LOCATOR_REQUIRING_IMPORT_ACTIONS = /* @__PURE__ */ new Set([
|
|
347976
|
+
"tap",
|
|
347977
|
+
"type",
|
|
347978
|
+
"clear",
|
|
347979
|
+
"waitForElement",
|
|
347980
|
+
"assertVisible",
|
|
347981
|
+
"assertText"
|
|
347982
|
+
]);
|
|
347983
|
+
function selectorValueHasIdentity(value) {
|
|
347984
|
+
const trimmed = value.trim();
|
|
347985
|
+
if (!trimmed)
|
|
347986
|
+
return false;
|
|
347987
|
+
if (trimmed.startsWith("/") || trimmed.startsWith("(//"))
|
|
347988
|
+
return (0, shared_1.isIdentityMobileXpath)(trimmed);
|
|
347989
|
+
return true;
|
|
347990
|
+
}
|
|
347991
|
+
function stepLocatorLacksIdentity(step) {
|
|
347992
|
+
if (!LOCATOR_REQUIRING_IMPORT_ACTIONS.has(step.action))
|
|
347993
|
+
return false;
|
|
347994
|
+
const target = step.target;
|
|
347995
|
+
if (!target)
|
|
347996
|
+
return true;
|
|
347997
|
+
if ((0, shared_1.mobileLocatorBundleHasIdentity)(target.locatorBundle))
|
|
347998
|
+
return false;
|
|
347999
|
+
return ![target.primary, ...target.fallbacks ?? []].some((value) => typeof value === "string" && selectorValueHasIdentity(value));
|
|
348000
|
+
}
|
|
347883
348001
|
function isValidStep(value) {
|
|
347884
348002
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
347885
348003
|
return false;
|
|
@@ -347931,6 +348049,13 @@ Write valid JSON only (no markdown fences inside files). Be exhaustive.`;
|
|
|
347931
348049
|
if (!o.steps.every(isValidStep)) {
|
|
347932
348050
|
return { ok: false, reason: "steps: invalid step shape" };
|
|
347933
348051
|
}
|
|
348052
|
+
const identityless = o.steps.find(stepLocatorLacksIdentity);
|
|
348053
|
+
if (identityless) {
|
|
348054
|
+
return {
|
|
348055
|
+
ok: false,
|
|
348056
|
+
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'].`
|
|
348057
|
+
};
|
|
348058
|
+
}
|
|
347934
348059
|
const steps = o.steps.map((step, order) => ({
|
|
347935
348060
|
...step,
|
|
347936
348061
|
order,
|
|
@@ -350026,7 +350151,7 @@ var require_process_inventory = __commonJS({
|
|
|
350026
350151
|
var node_child_process_1 = require("child_process");
|
|
350027
350152
|
var node_os_1 = __importDefault(require("os"));
|
|
350028
350153
|
var node_util_1 = require("util");
|
|
350029
|
-
var
|
|
350154
|
+
var execFileAsync4 = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
350030
350155
|
var DEFAULT_LIMIT = 60;
|
|
350031
350156
|
var DEFAULT_TIMEOUT_MS2 = 800;
|
|
350032
350157
|
var MAX_COMMAND_LENGTH = 320;
|
|
@@ -350088,7 +350213,7 @@ var require_process_inventory = __commonJS({
|
|
|
350088
350213
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
350089
350214
|
timer.unref?.();
|
|
350090
350215
|
try {
|
|
350091
|
-
const { stdout } = await
|
|
350216
|
+
const { stdout } = await execFileAsync4(command, args, {
|
|
350092
350217
|
maxBuffer: 1024 * 1024,
|
|
350093
350218
|
signal: controller.signal
|
|
350094
350219
|
});
|
|
@@ -351224,7 +351349,7 @@ var require_package4 = __commonJS({
|
|
|
351224
351349
|
"package.json"(exports2, module2) {
|
|
351225
351350
|
module2.exports = {
|
|
351226
351351
|
name: "@validate.qa/runner",
|
|
351227
|
-
version: "1.0.
|
|
351352
|
+
version: "1.0.69",
|
|
351228
351353
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
351229
351354
|
bin: {
|
|
351230
351355
|
"validate-runner": "dist/cli.js"
|
|
@@ -351311,7 +351436,7 @@ function getConfigDir() {
|
|
|
351311
351436
|
}
|
|
351312
351437
|
|
|
351313
351438
|
// src/runner.ts
|
|
351314
|
-
var
|
|
351439
|
+
var import_path5 = require("path");
|
|
351315
351440
|
var import_os6 = require("os");
|
|
351316
351441
|
var import_node_crypto2 = require("crypto");
|
|
351317
351442
|
|
|
@@ -351906,6 +352031,66 @@ function resetServerState(port = APPIUM_PORT, restartCount = 0) {
|
|
|
351906
352031
|
};
|
|
351907
352032
|
}
|
|
351908
352033
|
|
|
352034
|
+
// src/services/mobile/ios-simulator-reset.ts
|
|
352035
|
+
var import_child_process2 = require("child_process");
|
|
352036
|
+
var import_util = require("util");
|
|
352037
|
+
var import_promises = require("fs/promises");
|
|
352038
|
+
var import_path3 = require("path");
|
|
352039
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
|
|
352040
|
+
var SIMCTL_TIMEOUT_MS = 15e3;
|
|
352041
|
+
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;
|
|
352042
|
+
function isIosSimulatorUdid(udid) {
|
|
352043
|
+
return typeof udid === "string" && SIMULATOR_UDID_RE.test(udid.trim());
|
|
352044
|
+
}
|
|
352045
|
+
function errMsg(err) {
|
|
352046
|
+
return err instanceof Error ? err.message : String(err);
|
|
352047
|
+
}
|
|
352048
|
+
async function simctl(args) {
|
|
352049
|
+
const { stdout } = await execFileAsync("xcrun", ["simctl", ...args], {
|
|
352050
|
+
timeout: SIMCTL_TIMEOUT_MS,
|
|
352051
|
+
encoding: "utf-8"
|
|
352052
|
+
});
|
|
352053
|
+
return stdout.trim();
|
|
352054
|
+
}
|
|
352055
|
+
async function clearIosSimulatorAppData(udid, bundleId, env = process.env) {
|
|
352056
|
+
if (!isIosSimulatorUdid(udid)) {
|
|
352057
|
+
return { ok: false, detail: `'${udid}' is not a simulator UDID \u2014 physical iOS devices cannot be cleared` };
|
|
352058
|
+
}
|
|
352059
|
+
let container;
|
|
352060
|
+
try {
|
|
352061
|
+
container = await simctl(["get_app_container", udid, bundleId, "data"]);
|
|
352062
|
+
} catch (err) {
|
|
352063
|
+
return { ok: false, detail: `simctl get_app_container failed (app not installed?): ${errMsg(err)}` };
|
|
352064
|
+
}
|
|
352065
|
+
if (!(0, import_path3.isAbsolute)(container) || !container.includes("/Containers/Data/Application/")) {
|
|
352066
|
+
return { ok: false, detail: `unexpected app container path: ${container}` };
|
|
352067
|
+
}
|
|
352068
|
+
try {
|
|
352069
|
+
await simctl(["spawn", udid, "defaults", "delete", bundleId]);
|
|
352070
|
+
} catch {
|
|
352071
|
+
}
|
|
352072
|
+
try {
|
|
352073
|
+
const entries = await (0, import_promises.readdir)(container);
|
|
352074
|
+
for (const entry of entries) {
|
|
352075
|
+
await (0, import_promises.rm)((0, import_path3.join)(container, entry), { recursive: true, force: true });
|
|
352076
|
+
}
|
|
352077
|
+
} catch (err) {
|
|
352078
|
+
return { ok: false, detail: `app container wipe failed: ${errMsg(err)}` };
|
|
352079
|
+
}
|
|
352080
|
+
const keychainPolicy = (env.VALIDATEQA_MOBILE_IOS_KEYCHAIN_RESET ?? "").trim().toLowerCase();
|
|
352081
|
+
if (keychainPolicy !== "never") {
|
|
352082
|
+
try {
|
|
352083
|
+
await simctl(["keychain", udid, "reset"]);
|
|
352084
|
+
} catch (err) {
|
|
352085
|
+
return {
|
|
352086
|
+
ok: false,
|
|
352087
|
+
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.`
|
|
352088
|
+
};
|
|
352089
|
+
}
|
|
352090
|
+
}
|
|
352091
|
+
return { ok: true };
|
|
352092
|
+
}
|
|
352093
|
+
|
|
351909
352094
|
// src/services/mobile/appium-actions.ts
|
|
351910
352095
|
var APPIUM_REQUEST_TIMEOUT_MS = 1e4;
|
|
351911
352096
|
var APPIUM_WEBVIEW_CONTEXT_TIMEOUT_MS = 12e4;
|
|
@@ -352088,9 +352273,12 @@ async function tapAt(sessionPath, bounds) {
|
|
|
352088
352273
|
}
|
|
352089
352274
|
}
|
|
352090
352275
|
function shouldResetMobileAppData(platform3, deviceId, env = process.env) {
|
|
352091
|
-
if (platform3 !== "ANDROID") return false;
|
|
352092
352276
|
const policy = (env.VALIDATEQA_MOBILE_APP_RESET ?? "").trim().toLowerCase();
|
|
352093
352277
|
if (policy === "never") return false;
|
|
352278
|
+
if (platform3 === "IOS") {
|
|
352279
|
+
return isIosSimulatorUdid(deviceId);
|
|
352280
|
+
}
|
|
352281
|
+
if (platform3 !== "ANDROID") return false;
|
|
352094
352282
|
if (policy === "always") return true;
|
|
352095
352283
|
return !!deviceId && deviceId.startsWith("emulator-");
|
|
352096
352284
|
}
|
|
@@ -352977,11 +353165,11 @@ var import_node_path2 = require("path");
|
|
|
352977
353165
|
var import_runner_core8 = __toESM(require_dist2());
|
|
352978
353166
|
|
|
352979
353167
|
// src/services/device-discovery.ts
|
|
352980
|
-
var
|
|
353168
|
+
var import_child_process3 = require("child_process");
|
|
352981
353169
|
var import_os3 = require("os");
|
|
352982
353170
|
function runCommand(command, args, timeout = 1e4) {
|
|
352983
353171
|
try {
|
|
352984
|
-
return (0,
|
|
353172
|
+
return (0, import_child_process3.execFileSync)(command, args, {
|
|
352985
353173
|
encoding: "utf-8",
|
|
352986
353174
|
timeout,
|
|
352987
353175
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -353281,9 +353469,6 @@ function countReadyDevicesByKind(platform3, snapshot = getCachedDeviceSnapshot()
|
|
|
353281
353469
|
function selectFreeDevice(platform3, leased, snapshot = getCachedDeviceSnapshot(), requirements = {}) {
|
|
353282
353470
|
const ready = snapshot.filter((d) => d.platform === platform3 && d.isAvailable && d.state === "BOOTED");
|
|
353283
353471
|
if (ready.length === 0) return { kind: "busy", reason: `no booted ${platform3} device` };
|
|
353284
|
-
if (requirements.resetPolicy === "CLEAR_APP_DATA" && platform3 !== "ANDROID") {
|
|
353285
|
-
return { kind: "unsatisfiable", reason: "CLEAR_APP_DATA is unsupported on iOS" };
|
|
353286
|
-
}
|
|
353287
353472
|
let candidates = requirements.resetPolicy === "CLEAR_APP_DATA" ? ready.filter((d) => !d.isPhysical) : [...ready];
|
|
353288
353473
|
const appId = requirements.appId?.trim();
|
|
353289
353474
|
if (appId) {
|
|
@@ -353333,13 +353518,13 @@ function pickFreeDevice(platform3, leased, snapshot = getCachedDeviceSnapshot(),
|
|
|
353333
353518
|
// src/services/mobile/mobile-network-capture.ts
|
|
353334
353519
|
var import_node_child_process = require("child_process");
|
|
353335
353520
|
var import_node_crypto = require("crypto");
|
|
353336
|
-
var
|
|
353521
|
+
var import_promises2 = require("fs/promises");
|
|
353337
353522
|
var import_node_os = require("os");
|
|
353338
353523
|
var import_node_path = require("path");
|
|
353339
353524
|
var import_node_util = require("util");
|
|
353340
353525
|
var import_mockttp = require("mockttp");
|
|
353341
353526
|
var import_runner_core7 = __toESM(require_dist2());
|
|
353342
|
-
var
|
|
353527
|
+
var execFileAsync2 = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
353343
353528
|
var MAX_CALLS = 1e3;
|
|
353344
353529
|
var MAX_BODY_CAPTURE_BYTES = 64 * 1024;
|
|
353345
353530
|
var DEVICE_CMD_TIMEOUT_MS = 15e3;
|
|
@@ -353527,7 +353712,7 @@ var MitmProxy = class {
|
|
|
353527
353712
|
try {
|
|
353528
353713
|
await server3.stop();
|
|
353529
353714
|
} catch (err) {
|
|
353530
|
-
this.onLog?.(`[mobile-net] proxy stop error (ignored): ${
|
|
353715
|
+
this.onLog?.(`[mobile-net] proxy stop error (ignored): ${errMsg2(err)}`);
|
|
353531
353716
|
}
|
|
353532
353717
|
this.onLog?.(
|
|
353533
353718
|
`[mobile-net] proxy closed: ${this.calls.length} calls captured` + (this.dropped > 0 ? ` (${this.dropped} dropped over cap)` : "")
|
|
@@ -353552,7 +353737,7 @@ var MitmProxy = class {
|
|
|
353552
353737
|
isHttps
|
|
353553
353738
|
});
|
|
353554
353739
|
} catch (err) {
|
|
353555
|
-
this.onLog?.(`[mobile-net] request capture error (ignored): ${
|
|
353740
|
+
this.onLog?.(`[mobile-net] request capture error (ignored): ${errMsg2(err)}`);
|
|
353556
353741
|
}
|
|
353557
353742
|
}
|
|
353558
353743
|
// ── response → finalize the matched request ───────────────────────────────
|
|
@@ -353595,7 +353780,7 @@ var MitmProxy = class {
|
|
|
353595
353780
|
intercepted: true
|
|
353596
353781
|
});
|
|
353597
353782
|
} catch (err) {
|
|
353598
|
-
this.onLog?.(`[mobile-net] response capture error (ignored): ${
|
|
353783
|
+
this.onLog?.(`[mobile-net] response capture error (ignored): ${errMsg2(err)}`);
|
|
353599
353784
|
}
|
|
353600
353785
|
}
|
|
353601
353786
|
// ── tls-client-error → record WHY interception failed ─────────────────────
|
|
@@ -353673,16 +353858,16 @@ async function generateCaCert(dir, onLog) {
|
|
|
353673
353858
|
// rather than on a short expiry.
|
|
353674
353859
|
});
|
|
353675
353860
|
const caCertPath = (0, import_node_path.join)(dir, "validateqa-mobile-ca.pem");
|
|
353676
|
-
await (0,
|
|
353861
|
+
await (0, import_promises2.writeFile)(caCertPath, cert, "utf-8");
|
|
353677
353862
|
return { caCertPath, caKeyPem: key, caCertPem: cert };
|
|
353678
353863
|
} catch (err) {
|
|
353679
|
-
onLog?.(`[mobile-net] CA generation failed; running plaintext-only: ${
|
|
353864
|
+
onLog?.(`[mobile-net] CA generation failed; running plaintext-only: ${errMsg2(err)}`);
|
|
353680
353865
|
return null;
|
|
353681
353866
|
}
|
|
353682
353867
|
}
|
|
353683
353868
|
async function androidReadProxy(deviceId) {
|
|
353684
353869
|
try {
|
|
353685
|
-
const { stdout } = await
|
|
353870
|
+
const { stdout } = await execFileAsync2(
|
|
353686
353871
|
"adb",
|
|
353687
353872
|
["-s", deviceId, "shell", "settings", "get", "global", "http_proxy"],
|
|
353688
353873
|
{ timeout: DEVICE_CMD_TIMEOUT_MS }
|
|
@@ -353695,33 +353880,33 @@ async function androidReadProxy(deviceId) {
|
|
|
353695
353880
|
}
|
|
353696
353881
|
async function androidSetProxy(deviceId, hostPort, onLog) {
|
|
353697
353882
|
try {
|
|
353698
|
-
await
|
|
353883
|
+
await execFileAsync2(
|
|
353699
353884
|
"adb",
|
|
353700
353885
|
["-s", deviceId, "shell", "settings", "put", "global", "http_proxy", hostPort],
|
|
353701
353886
|
{ timeout: DEVICE_CMD_TIMEOUT_MS }
|
|
353702
353887
|
);
|
|
353703
353888
|
return true;
|
|
353704
353889
|
} catch (err) {
|
|
353705
|
-
onLog?.(`[mobile-net] adb set http_proxy failed: ${
|
|
353890
|
+
onLog?.(`[mobile-net] adb set http_proxy failed: ${errMsg2(err)}`);
|
|
353706
353891
|
return false;
|
|
353707
353892
|
}
|
|
353708
353893
|
}
|
|
353709
353894
|
async function androidClearProxy(deviceId, originalProxy, onLog) {
|
|
353710
353895
|
const restoreValue = originalProxy && originalProxy.length > 0 ? originalProxy : ":0";
|
|
353711
353896
|
try {
|
|
353712
|
-
await
|
|
353897
|
+
await execFileAsync2(
|
|
353713
353898
|
"adb",
|
|
353714
353899
|
["-s", deviceId, "shell", "settings", "put", "global", "http_proxy", restoreValue],
|
|
353715
353900
|
{ timeout: DEVICE_CMD_TIMEOUT_MS }
|
|
353716
353901
|
);
|
|
353717
353902
|
} catch (err) {
|
|
353718
|
-
onLog?.(`[mobile-net] adb restore http_proxy failed: ${
|
|
353903
|
+
onLog?.(`[mobile-net] adb restore http_proxy failed: ${errMsg2(err)}`);
|
|
353719
353904
|
}
|
|
353720
353905
|
}
|
|
353721
353906
|
async function androidInstallCa(deviceId, caCertPath) {
|
|
353722
353907
|
const devicePath = "/sdcard/validateqa-mobile-ca.pem";
|
|
353723
353908
|
try {
|
|
353724
|
-
await
|
|
353909
|
+
await execFileAsync2("adb", ["-s", deviceId, "push", caCertPath, devicePath], {
|
|
353725
353910
|
timeout: DEVICE_CMD_TIMEOUT_MS
|
|
353726
353911
|
});
|
|
353727
353912
|
return {
|
|
@@ -353729,23 +353914,23 @@ async function androidInstallCa(deviceId, caCertPath) {
|
|
|
353729
353914
|
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.`
|
|
353730
353915
|
};
|
|
353731
353916
|
} catch (err) {
|
|
353732
|
-
return { installed: false, note: `CA push failed: ${
|
|
353917
|
+
return { installed: false, note: `CA push failed: ${errMsg2(err)}` };
|
|
353733
353918
|
}
|
|
353734
353919
|
}
|
|
353735
353920
|
async function androidRemoveCa(deviceId, onLog) {
|
|
353736
353921
|
try {
|
|
353737
|
-
await
|
|
353922
|
+
await execFileAsync2(
|
|
353738
353923
|
"adb",
|
|
353739
353924
|
["-s", deviceId, "shell", "rm", "-f", "/sdcard/validateqa-mobile-ca.pem"],
|
|
353740
353925
|
{ timeout: DEVICE_CMD_TIMEOUT_MS }
|
|
353741
353926
|
);
|
|
353742
353927
|
} catch (err) {
|
|
353743
|
-
onLog?.(`[mobile-net] adb remove CA failed: ${
|
|
353928
|
+
onLog?.(`[mobile-net] adb remove CA failed: ${errMsg2(err)}`);
|
|
353744
353929
|
}
|
|
353745
353930
|
}
|
|
353746
353931
|
async function iosSimTrustCa(deviceId, caCertPath) {
|
|
353747
353932
|
try {
|
|
353748
|
-
await
|
|
353933
|
+
await execFileAsync2("xcrun", ["simctl", "keychain", deviceId, "add-root-cert", caCertPath], {
|
|
353749
353934
|
timeout: DEVICE_CMD_TIMEOUT_MS
|
|
353750
353935
|
});
|
|
353751
353936
|
return {
|
|
@@ -353753,18 +353938,18 @@ async function iosSimTrustCa(deviceId, caCertPath) {
|
|
|
353753
353938
|
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."
|
|
353754
353939
|
};
|
|
353755
353940
|
} catch (err) {
|
|
353756
|
-
return { trusted: false, note: `simctl add-root-cert failed: ${
|
|
353941
|
+
return { trusted: false, note: `simctl add-root-cert failed: ${errMsg2(err)}` };
|
|
353757
353942
|
}
|
|
353758
353943
|
}
|
|
353759
353944
|
async function iosSimRemoveCa(deviceId, onLog) {
|
|
353760
353945
|
if (process.env.VALIDATEQA_MOBILE_SIM_KEYCHAIN_RESET === "1") {
|
|
353761
353946
|
try {
|
|
353762
|
-
await
|
|
353947
|
+
await execFileAsync2("xcrun", ["simctl", "keychain", deviceId, "reset"], {
|
|
353763
353948
|
timeout: DEVICE_CMD_TIMEOUT_MS
|
|
353764
353949
|
});
|
|
353765
353950
|
onLog?.(`[mobile-net] simctl keychain reset run for ${deviceId} (opt-in; cleared ALL sim certs).`);
|
|
353766
353951
|
} catch (err) {
|
|
353767
|
-
onLog?.(`[mobile-net] simctl keychain reset failed: ${
|
|
353952
|
+
onLog?.(`[mobile-net] simctl keychain reset failed: ${errMsg2(err)}`);
|
|
353768
353953
|
}
|
|
353769
353954
|
return;
|
|
353770
353955
|
}
|
|
@@ -353772,7 +353957,7 @@ async function iosSimRemoveCa(deviceId, onLog) {
|
|
|
353772
353957
|
`[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.`
|
|
353773
353958
|
);
|
|
353774
353959
|
}
|
|
353775
|
-
function
|
|
353960
|
+
function errMsg2(err) {
|
|
353776
353961
|
return err instanceof Error ? err.message : String(err);
|
|
353777
353962
|
}
|
|
353778
353963
|
function isProcessAlive(pid) {
|
|
@@ -353863,7 +354048,7 @@ async function iosSimConfigureHostProxy(host, port, onLog) {
|
|
|
353863
354048
|
let service;
|
|
353864
354049
|
let prior;
|
|
353865
354050
|
try {
|
|
353866
|
-
const { stdout: order } = await
|
|
354051
|
+
const { stdout: order } = await execFileAsync2("networksetup", ["-listnetworkserviceorder"], {
|
|
353867
354052
|
timeout: DEVICE_CMD_TIMEOUT_MS
|
|
353868
354053
|
});
|
|
353869
354054
|
const svc = parsePrimaryNetworkService(order);
|
|
@@ -353872,26 +354057,26 @@ async function iosSimConfigureHostProxy(host, port, onLog) {
|
|
|
353872
354057
|
}
|
|
353873
354058
|
service = svc;
|
|
353874
354059
|
const [{ stdout: webOut }, { stdout: secureOut }] = await Promise.all([
|
|
353875
|
-
|
|
353876
|
-
|
|
354060
|
+
execFileAsync2("networksetup", ["-getwebproxy", service], { timeout: DEVICE_CMD_TIMEOUT_MS }),
|
|
354061
|
+
execFileAsync2("networksetup", ["-getsecurewebproxy", service], { timeout: DEVICE_CMD_TIMEOUT_MS })
|
|
353877
354062
|
]);
|
|
353878
354063
|
prior = { web: parseWebProxyState(webOut), secure: parseWebProxyState(secureOut) };
|
|
353879
354064
|
for (const args of buildHostProxySetCommands(service, host, port)) {
|
|
353880
|
-
await
|
|
354065
|
+
await execFileAsync2("networksetup", args, { timeout: DEVICE_CMD_TIMEOUT_MS });
|
|
353881
354066
|
}
|
|
353882
354067
|
onLog?.(`[mobile-net] macOS host proxy set on "${service}" \u2192 ${host}:${port} (iOS simulator shares the host network).`);
|
|
353883
354068
|
return { configured: true, service, prior };
|
|
353884
354069
|
} catch (err) {
|
|
353885
354070
|
if (service && prior) {
|
|
353886
|
-
onLog?.(`[mobile-net] host proxy set failed mid-way \u2014 rolling back "${service}" to prior state: ${
|
|
354071
|
+
onLog?.(`[mobile-net] host proxy set failed mid-way \u2014 rolling back "${service}" to prior state: ${errMsg2(err)}`);
|
|
353887
354072
|
for (const args of buildHostProxyRestoreCommands(service, prior)) {
|
|
353888
354073
|
try {
|
|
353889
|
-
await
|
|
354074
|
+
await execFileAsync2("networksetup", args, { timeout: DEVICE_CMD_TIMEOUT_MS });
|
|
353890
354075
|
} catch {
|
|
353891
354076
|
}
|
|
353892
354077
|
}
|
|
353893
354078
|
}
|
|
353894
|
-
return { configured: false, reason: `iOS simulator host proxy could not be configured: ${
|
|
354079
|
+
return { configured: false, reason: `iOS simulator host proxy could not be configured: ${errMsg2(err)}` };
|
|
353895
354080
|
}
|
|
353896
354081
|
}
|
|
353897
354082
|
async function iosSimRestoreHostProxy(service, prior, onLog) {
|
|
@@ -353901,9 +354086,9 @@ async function iosSimRestoreHostProxy(service, prior, onLog) {
|
|
|
353901
354086
|
};
|
|
353902
354087
|
for (const args of buildHostProxyRestoreCommands(service, restore)) {
|
|
353903
354088
|
try {
|
|
353904
|
-
await
|
|
354089
|
+
await execFileAsync2("networksetup", args, { timeout: DEVICE_CMD_TIMEOUT_MS });
|
|
353905
354090
|
} catch (err) {
|
|
353906
|
-
onLog?.(`[mobile-net] networksetup restore (${args[0]}) failed: ${
|
|
354091
|
+
onLog?.(`[mobile-net] networksetup restore (${args[0]}) failed: ${errMsg2(err)}`);
|
|
353907
354092
|
}
|
|
353908
354093
|
}
|
|
353909
354094
|
onLog?.(`[mobile-net] macOS host proxy restored on "${service}".`);
|
|
@@ -353915,14 +354100,14 @@ function markerPath(sessionKey) {
|
|
|
353915
354100
|
}
|
|
353916
354101
|
async function writePendingCleanup(sessionKey, marker) {
|
|
353917
354102
|
try {
|
|
353918
|
-
await (0,
|
|
353919
|
-
await (0,
|
|
354103
|
+
await (0, import_promises2.mkdir)(PENDING_CLEANUP_DIR, { recursive: true });
|
|
354104
|
+
await (0, import_promises2.writeFile)(markerPath(sessionKey), JSON.stringify(marker), "utf-8");
|
|
353920
354105
|
} catch {
|
|
353921
354106
|
}
|
|
353922
354107
|
}
|
|
353923
354108
|
async function removePendingCleanup(sessionKey) {
|
|
353924
354109
|
try {
|
|
353925
|
-
await (0,
|
|
354110
|
+
await (0, import_promises2.unlink)(markerPath(sessionKey));
|
|
353926
354111
|
} catch {
|
|
353927
354112
|
}
|
|
353928
354113
|
}
|
|
@@ -353955,7 +354140,7 @@ function restoreDeviceSync(marker, onLog) {
|
|
|
353955
354140
|
async function reconcilePendingCleanups(onLog) {
|
|
353956
354141
|
let files;
|
|
353957
354142
|
try {
|
|
353958
|
-
files = await (0,
|
|
354143
|
+
files = await (0, import_promises2.readdir)(PENDING_CLEANUP_DIR);
|
|
353959
354144
|
} catch {
|
|
353960
354145
|
return;
|
|
353961
354146
|
}
|
|
@@ -353965,7 +354150,7 @@ async function reconcilePendingCleanups(onLog) {
|
|
|
353965
354150
|
if (activeCleanups.has(sessionKey)) continue;
|
|
353966
354151
|
const full = (0, import_node_path.join)(PENDING_CLEANUP_DIR, file);
|
|
353967
354152
|
try {
|
|
353968
|
-
const raw = await (0,
|
|
354153
|
+
const raw = await (0, import_promises2.readFile)(full, "utf-8");
|
|
353969
354154
|
const marker = JSON.parse(raw);
|
|
353970
354155
|
if (marker && typeof marker.deviceId === "string" && marker.platform) {
|
|
353971
354156
|
if (!shouldReconcilePendingCleanup(marker)) {
|
|
@@ -353978,7 +354163,7 @@ async function reconcilePendingCleanups(onLog) {
|
|
|
353978
354163
|
} catch {
|
|
353979
354164
|
}
|
|
353980
354165
|
try {
|
|
353981
|
-
await (0,
|
|
354166
|
+
await (0, import_promises2.unlink)(full);
|
|
353982
354167
|
} catch {
|
|
353983
354168
|
}
|
|
353984
354169
|
}
|
|
@@ -354016,7 +354201,7 @@ async function startMobileNetworkCapture(options) {
|
|
|
354016
354201
|
let caKeyPem = null;
|
|
354017
354202
|
let caCertPem = null;
|
|
354018
354203
|
try {
|
|
354019
|
-
tmpDir = await (0,
|
|
354204
|
+
tmpDir = await (0, import_promises2.mkdtemp)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "validateqa-mobile-ca-"));
|
|
354020
354205
|
const ca = await generateCaCert(tmpDir, onLog);
|
|
354021
354206
|
if (ca) {
|
|
354022
354207
|
caCertPath = ca.caCertPath;
|
|
@@ -354024,7 +354209,7 @@ async function startMobileNetworkCapture(options) {
|
|
|
354024
354209
|
caCertPem = ca.caCertPem;
|
|
354025
354210
|
}
|
|
354026
354211
|
} catch (err) {
|
|
354027
|
-
onLog?.(`[mobile-net] CA setup degraded: ${
|
|
354212
|
+
onLog?.(`[mobile-net] CA setup degraded: ${errMsg2(err)}`);
|
|
354028
354213
|
}
|
|
354029
354214
|
if (!caKeyPem || !caCertPem) {
|
|
354030
354215
|
try {
|
|
@@ -354032,9 +354217,9 @@ async function startMobileNetworkCapture(options) {
|
|
|
354032
354217
|
caKeyPem = fallback.key;
|
|
354033
354218
|
caCertPem = fallback.cert;
|
|
354034
354219
|
} catch (err) {
|
|
354035
|
-
onLog?.(`[mobile-net] fatal CA failure, cannot start proxy: ${
|
|
354220
|
+
onLog?.(`[mobile-net] fatal CA failure, cannot start proxy: ${errMsg2(err)}`);
|
|
354036
354221
|
if (tmpDir) {
|
|
354037
|
-
await (0,
|
|
354222
|
+
await (0, import_promises2.rm)(tmpDir, { recursive: true, force: true }).catch(() => void 0);
|
|
354038
354223
|
}
|
|
354039
354224
|
throw err instanceof Error ? err : new Error(String(err));
|
|
354040
354225
|
}
|
|
@@ -354089,7 +354274,7 @@ async function startMobileNetworkCapture(options) {
|
|
|
354089
354274
|
}
|
|
354090
354275
|
}
|
|
354091
354276
|
} catch (err) {
|
|
354092
|
-
wiringReason = `device wiring degraded: ${
|
|
354277
|
+
wiringReason = `device wiring degraded: ${errMsg2(err)}`;
|
|
354093
354278
|
onLog?.(`[mobile-net] ${wiringReason}`);
|
|
354094
354279
|
}
|
|
354095
354280
|
if (proxySet || caInstalledOnDevice || hostProxySet) {
|
|
@@ -354178,9 +354363,9 @@ async function startMobileNetworkCapture(options) {
|
|
|
354178
354363
|
await proxy.close();
|
|
354179
354364
|
if (capturedTmpDir) {
|
|
354180
354365
|
try {
|
|
354181
|
-
await (0,
|
|
354366
|
+
await (0, import_promises2.rm)(capturedTmpDir, { recursive: true, force: true });
|
|
354182
354367
|
} catch (err) {
|
|
354183
|
-
onLog?.(`[mobile-net] temp CA cleanup failed: ${
|
|
354368
|
+
onLog?.(`[mobile-net] temp CA cleanup failed: ${errMsg2(err)}`);
|
|
354184
354369
|
}
|
|
354185
354370
|
}
|
|
354186
354371
|
const apiCount = finalCalls.filter((c) => c.isApi).length;
|
|
@@ -354217,7 +354402,7 @@ var WD_SESSION_CREATE_TIMEOUT_MS = 12e4;
|
|
|
354217
354402
|
var WD_ACTION_TIMEOUT_MS = 3e4;
|
|
354218
354403
|
var APPIUM_DRIVER_DEAD_RE2 = /instrumentation process is not running|cannot be proxied to UiAutomator2 server|socket hang up|ECONNRESET/i;
|
|
354219
354404
|
var MAX_SESSION_CREATE_ATTEMPTS = 3;
|
|
354220
|
-
var
|
|
354405
|
+
var execFileAsync3 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
354221
354406
|
var MOBILE_TEXT_ATTRIBUTE_NAMES = ["text", "content-desc", "contentDescription", "name", "label", "value"];
|
|
354222
354407
|
var WebDriverTransportError = class extends Error {
|
|
354223
354408
|
isTransport = true;
|
|
@@ -354434,7 +354619,7 @@ async function removeStaleAndroidSystemPortForward(target, error2) {
|
|
|
354434
354619
|
const args = androidSystemPortForwardRemovalArgs(target, error2);
|
|
354435
354620
|
if (!args) return false;
|
|
354436
354621
|
try {
|
|
354437
|
-
await
|
|
354622
|
+
await execFileAsync3("adb", args, { timeout: 1e4, windowsHide: true });
|
|
354438
354623
|
return true;
|
|
354439
354624
|
} catch {
|
|
354440
354625
|
return false;
|
|
@@ -354692,7 +354877,7 @@ function boundsForRun(step, platform3, isCrossPlatform) {
|
|
|
354692
354877
|
if (platform3 && hasPlatformLocator(step.target, platform3)) return null;
|
|
354693
354878
|
return boundsFromStep(step);
|
|
354694
354879
|
}
|
|
354695
|
-
function locatorStrategyForValue(value) {
|
|
354880
|
+
function locatorStrategyForValue(value, platform3) {
|
|
354696
354881
|
const trimmed = value?.trim();
|
|
354697
354882
|
if (!trimmed) {
|
|
354698
354883
|
return [];
|
|
@@ -354708,7 +354893,8 @@ function locatorStrategyForValue(value) {
|
|
|
354708
354893
|
}
|
|
354709
354894
|
return [
|
|
354710
354895
|
{ using: "accessibility id", value: trimmed },
|
|
354711
|
-
{ using: "id", value: trimmed }
|
|
354896
|
+
{ using: "id", value: trimmed },
|
|
354897
|
+
...textIdentityStrategies(trimmed, platform3)
|
|
354712
354898
|
];
|
|
354713
354899
|
}
|
|
354714
354900
|
function buildLocatorStrategies(target, platform3) {
|
|
@@ -354718,19 +354904,19 @@ function buildLocatorStrategies(target, platform3) {
|
|
|
354718
354904
|
if (bundle) {
|
|
354719
354905
|
if (bundle.accessibilityId) strategies.push({ using: "accessibility id", value: bundle.accessibilityId });
|
|
354720
354906
|
if (bundle.resourceId) strategies.push({ using: "id", value: bundle.resourceId });
|
|
354907
|
+
if (bundle.xpath && isIdentityXpath(bundle.xpath)) strategies.push({ using: "xpath", value: bundle.xpath });
|
|
354721
354908
|
for (const value of [
|
|
354722
|
-
isEditableMobileClassName(bundle.className) ? void 0 : bundle.text,
|
|
354909
|
+
isEditableMobileClassName(bundle.className) || isStateValueMobileClassName(bundle.className) ? void 0 : bundle.text,
|
|
354723
354910
|
bundle.contentDesc
|
|
354724
354911
|
]) {
|
|
354725
354912
|
const trimmed = value?.trim();
|
|
354726
|
-
if (trimmed) strategies.push(
|
|
354913
|
+
if (trimmed) strategies.push(...textIdentityStrategies(trimmed, platform3));
|
|
354727
354914
|
}
|
|
354728
|
-
if (bundle.xpath) strategies.push({ using: "xpath", value: bundle.xpath });
|
|
354729
354915
|
}
|
|
354730
354916
|
if (!(platform3 && hasPlatformLocator(target, platform3))) {
|
|
354731
|
-
strategies.push(...locatorStrategyForValue(target.primary));
|
|
354917
|
+
strategies.push(...locatorStrategyForValue(target.primary, platform3));
|
|
354732
354918
|
for (const fallback of target.fallbacks ?? []) {
|
|
354733
|
-
strategies.push(...locatorStrategyForValue(fallback));
|
|
354919
|
+
strategies.push(...locatorStrategyForValue(fallback, platform3));
|
|
354734
354920
|
}
|
|
354735
354921
|
}
|
|
354736
354922
|
return strategies.filter((strategy) => strategy.using !== "xpath" || isIdentityXpath(strategy.value)).filter(
|
|
@@ -354752,12 +354938,31 @@ function identityTextXpath2(value) {
|
|
|
354752
354938
|
const lit = xpathLiteral3(value);
|
|
354753
354939
|
return `//*[@text=${lit} or @content-desc=${lit} or @label=${lit} or @name=${lit} or @value=${lit}]`;
|
|
354754
354940
|
}
|
|
354941
|
+
function iosPredicateLiteral(value) {
|
|
354942
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
354943
|
+
}
|
|
354944
|
+
function iosTextIdentityPredicate(value) {
|
|
354945
|
+
const lit = iosPredicateLiteral(value);
|
|
354946
|
+
return `label == ${lit} OR name == ${lit} OR value == ${lit}`;
|
|
354947
|
+
}
|
|
354948
|
+
function textIdentityStrategies(value, platform3) {
|
|
354949
|
+
const strategies = [];
|
|
354950
|
+
if (platform3 === "IOS") {
|
|
354951
|
+
strategies.push({ using: "-ios predicate string", value: iosTextIdentityPredicate(value) });
|
|
354952
|
+
}
|
|
354953
|
+
strategies.push({ using: "xpath", value: identityTextXpath2(value) });
|
|
354954
|
+
return strategies;
|
|
354955
|
+
}
|
|
354956
|
+
function isIdentityIosPredicate(value) {
|
|
354957
|
+
return /(?:^|[\s(])(?:name|label|value)\s*==\s*"/.test(value) && !/\b(?:CONTAINS|BEGINSWITH|ENDSWITH|LIKE|MATCHES|IN)\b/i.test(value);
|
|
354958
|
+
}
|
|
354755
354959
|
function isIdentityXpath(value) {
|
|
354756
354960
|
return /@[A-Za-z_:][-A-Za-z0-9_:.]*\s*=/.test(value) && !/(?:contains|starts-with)\s*\(/i.test(value);
|
|
354757
354961
|
}
|
|
354758
354962
|
function isIdentityStrategy(strategy) {
|
|
354759
354963
|
if (strategy.using === "accessibility id" || strategy.using === "id") return true;
|
|
354760
354964
|
if (strategy.using === "xpath") return isIdentityXpath(strategy.value);
|
|
354965
|
+
if (strategy.using === "-ios predicate string") return isIdentityIosPredicate(strategy.value);
|
|
354761
354966
|
return false;
|
|
354762
354967
|
}
|
|
354763
354968
|
function buildIdentityLocatorStrategies(target, platform3) {
|
|
@@ -354767,15 +354972,18 @@ function buildIdentityLocatorStrategies(target, platform3) {
|
|
|
354767
354972
|
if (bundle) {
|
|
354768
354973
|
if (bundle.accessibilityId) strategies.push({ using: "accessibility id", value: bundle.accessibilityId });
|
|
354769
354974
|
if (bundle.resourceId) strategies.push({ using: "id", value: bundle.resourceId });
|
|
354770
|
-
|
|
354975
|
+
if (bundle.xpath && isIdentityXpath(bundle.xpath)) strategies.push({ using: "xpath", value: bundle.xpath });
|
|
354976
|
+
for (const value of [
|
|
354977
|
+
isStateValueMobileClassName(bundle.className) ? void 0 : bundle.text,
|
|
354978
|
+
bundle.contentDesc
|
|
354979
|
+
]) {
|
|
354771
354980
|
const trimmed = value?.trim();
|
|
354772
|
-
if (trimmed) strategies.push(
|
|
354981
|
+
if (trimmed) strategies.push(...textIdentityStrategies(trimmed, platform3));
|
|
354773
354982
|
}
|
|
354774
|
-
if (bundle.xpath && isIdentityXpath(bundle.xpath)) strategies.push({ using: "xpath", value: bundle.xpath });
|
|
354775
354983
|
}
|
|
354776
354984
|
if (!(platform3 && hasPlatformLocator(target, platform3))) {
|
|
354777
354985
|
for (const value of [target.primary, ...target.fallbacks ?? []]) {
|
|
354778
|
-
for (const strategy of locatorStrategyForValue(value)) {
|
|
354986
|
+
for (const strategy of locatorStrategyForValue(value, platform3)) {
|
|
354779
354987
|
if (isIdentityStrategy(strategy)) strategies.push(strategy);
|
|
354780
354988
|
}
|
|
354781
354989
|
}
|
|
@@ -354885,7 +355093,7 @@ async function prepareAndroidInputEnvironment(deviceId) {
|
|
|
354885
355093
|
];
|
|
354886
355094
|
for (const [namespace, key, value] of settingsToDisable) {
|
|
354887
355095
|
try {
|
|
354888
|
-
await
|
|
355096
|
+
await execFileAsync3("adb", ["-s", deviceId, "shell", "settings", "put", namespace, key, value], {
|
|
354889
355097
|
timeout: 8e3,
|
|
354890
355098
|
windowsHide: true
|
|
354891
355099
|
});
|
|
@@ -354897,14 +355105,14 @@ async function sendAndroidTextToFocusedInput(deviceId, value) {
|
|
|
354897
355105
|
if (!deviceId) {
|
|
354898
355106
|
throw new Error("Android keyboard fallback requires a device id");
|
|
354899
355107
|
}
|
|
354900
|
-
await
|
|
355108
|
+
await execFileAsync3("adb", ["-s", deviceId, "shell", "input", "text", encodeAndroidInputText(value)], {
|
|
354901
355109
|
timeout: 1e4,
|
|
354902
355110
|
windowsHide: true
|
|
354903
355111
|
});
|
|
354904
355112
|
}
|
|
354905
355113
|
async function pressAndroidKeyboardEnter(deviceId) {
|
|
354906
355114
|
if (!deviceId) throw new Error("Android keyboard Enter fallback requires a device id");
|
|
354907
|
-
await
|
|
355115
|
+
await execFileAsync3("adb", ["-s", deviceId, "shell", "input", "keyevent", "66"], {
|
|
354908
355116
|
timeout: 1e4,
|
|
354909
355117
|
windowsHide: true
|
|
354910
355118
|
});
|
|
@@ -355425,7 +355633,14 @@ async function executeMobileTest(options) {
|
|
|
355425
355633
|
const resetAllowed = shouldResetMobileAppData(options.target.platform, selectedDevice.id);
|
|
355426
355634
|
if (resetAllowed) {
|
|
355427
355635
|
try {
|
|
355428
|
-
|
|
355636
|
+
if (options.target.platform === "IOS") {
|
|
355637
|
+
await terminateTargetApp(sessionId, "IOS", options.target.appId).catch(() => {
|
|
355638
|
+
});
|
|
355639
|
+
const wiped = await clearIosSimulatorAppData(selectedDevice.id, options.target.appId);
|
|
355640
|
+
if (!wiped.ok) throw new Error(wiped.detail);
|
|
355641
|
+
} else {
|
|
355642
|
+
await executeMobileScript(sessionId, "mobile: clearApp", [{ appId: options.target.appId }]);
|
|
355643
|
+
}
|
|
355429
355644
|
clearedAppDataBeforeLaunch = true;
|
|
355430
355645
|
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).");
|
|
355431
355646
|
} catch (err) {
|
|
@@ -355770,8 +355985,7 @@ async function readElementAttribute(sessionId, elementId, name) {
|
|
|
355770
355985
|
function harvestFallbackXpath(platform3, attrs) {
|
|
355771
355986
|
if (platform3 === "IOS") {
|
|
355772
355987
|
if (attrs.name) return `//*[@name=${xpathLiteral3(attrs.name)}]`;
|
|
355773
|
-
|
|
355774
|
-
if (text) return identityTextXpath2(text);
|
|
355988
|
+
if (attrs.text) return identityTextXpath2(attrs.text);
|
|
355775
355989
|
return "//*";
|
|
355776
355990
|
}
|
|
355777
355991
|
if (attrs.resourceId) return `//*[@resource-id=${xpathLiteral3(attrs.resourceId)}]`;
|
|
@@ -355781,14 +355995,15 @@ function harvestFallbackXpath(platform3, attrs) {
|
|
|
355781
355995
|
}
|
|
355782
355996
|
async function readNativeElementBundle(sessionId, elementId, platform3) {
|
|
355783
355997
|
const className = await readElementAttribute(sessionId, elementId, platform3 === "IOS" ? "type" : "class") ?? "";
|
|
355998
|
+
const volatileText = isEditableMobileClassName(className) || isStateValueMobileClassName(className);
|
|
355784
355999
|
if (platform3 === "IOS") {
|
|
355785
356000
|
const name = await readElementAttribute(sessionId, elementId, "name");
|
|
355786
356001
|
const label = await readElementAttribute(sessionId, elementId, "label");
|
|
355787
356002
|
const value = await readElementAttribute(sessionId, elementId, "value");
|
|
355788
|
-
const text2 = label ?? value;
|
|
356003
|
+
const text2 = label ?? (volatileText ? void 0 : value);
|
|
355789
356004
|
if (!name && !text2) return null;
|
|
355790
356005
|
const bundle2 = {
|
|
355791
|
-
xpath: harvestFallbackXpath("IOS", { name,
|
|
356006
|
+
xpath: harvestFallbackXpath("IOS", { name, text: text2 }),
|
|
355792
356007
|
className
|
|
355793
356008
|
};
|
|
355794
356009
|
if (name) bundle2.accessibilityId = name;
|
|
@@ -355797,7 +356012,8 @@ async function readNativeElementBundle(sessionId, elementId, platform3) {
|
|
|
355797
356012
|
}
|
|
355798
356013
|
const resourceId = await readElementAttribute(sessionId, elementId, "resource-id");
|
|
355799
356014
|
const contentDesc = await readElementAttribute(sessionId, elementId, "content-desc") ?? await readElementAttribute(sessionId, elementId, "contentDescription");
|
|
355800
|
-
const
|
|
356015
|
+
const rawText = await readElementAttribute(sessionId, elementId, "text");
|
|
356016
|
+
const text = volatileText ? void 0 : rawText;
|
|
355801
356017
|
if (!resourceId && !contentDesc && !text) return null;
|
|
355802
356018
|
const bundle = {
|
|
355803
356019
|
xpath: harvestFallbackXpath("ANDROID", { text, contentDesc, resourceId }),
|
|
@@ -356082,10 +356298,10 @@ function decideMobileDeviceGate(input) {
|
|
|
356082
356298
|
|
|
356083
356299
|
// src/services/mobile-bridge.ts
|
|
356084
356300
|
var import_http = require("http");
|
|
356085
|
-
var
|
|
356301
|
+
var import_child_process4 = require("child_process");
|
|
356086
356302
|
var import_crypto = require("crypto");
|
|
356087
356303
|
var import_os4 = require("os");
|
|
356088
|
-
var
|
|
356304
|
+
var import_path4 = require("path");
|
|
356089
356305
|
var import_fs3 = require("fs");
|
|
356090
356306
|
init_dist();
|
|
356091
356307
|
var MOBILE_BRIDGE_PORT2 = 9515;
|
|
@@ -356192,7 +356408,7 @@ async function getWindowSize3() {
|
|
|
356192
356408
|
}
|
|
356193
356409
|
var BRIDGE_PROCESS_MARKER = "validateqa-mobile-bridge";
|
|
356194
356410
|
var BRIDGE_PORT = MOBILE_BRIDGE_PORT2;
|
|
356195
|
-
var BRIDGE_PID_FILE = (0,
|
|
356411
|
+
var BRIDGE_PID_FILE = (0, import_path4.join)((0, import_os4.homedir)(), ".validate.qa", "mobile-bridge.pid");
|
|
356196
356412
|
function readBridgePidFile() {
|
|
356197
356413
|
try {
|
|
356198
356414
|
const raw = (0, import_fs3.readFileSync)(BRIDGE_PID_FILE, "utf-8").trim();
|
|
@@ -356204,7 +356420,7 @@ function readBridgePidFile() {
|
|
|
356204
356420
|
}
|
|
356205
356421
|
function writeBridgePidFile(pid) {
|
|
356206
356422
|
try {
|
|
356207
|
-
(0, import_fs3.mkdirSync)((0,
|
|
356423
|
+
(0, import_fs3.mkdirSync)((0, import_path4.dirname)(BRIDGE_PID_FILE), { recursive: true, mode: 448 });
|
|
356208
356424
|
(0, import_fs3.writeFileSync)(BRIDGE_PID_FILE, String(pid), { mode: 384 });
|
|
356209
356425
|
} catch {
|
|
356210
356426
|
}
|
|
@@ -356219,19 +356435,19 @@ function killStaleBridge() {
|
|
|
356219
356435
|
const pid = readBridgePidFile();
|
|
356220
356436
|
if (!pid) return;
|
|
356221
356437
|
try {
|
|
356222
|
-
(0,
|
|
356438
|
+
(0, import_child_process4.execFileSync)("kill", ["-0", String(pid)], { stdio: "ignore" });
|
|
356223
356439
|
} catch {
|
|
356224
356440
|
clearBridgePidFile();
|
|
356225
356441
|
return;
|
|
356226
356442
|
}
|
|
356227
356443
|
try {
|
|
356228
|
-
const pidsOnPort = (0,
|
|
356444
|
+
const pidsOnPort = (0, import_child_process4.execFileSync)("lsof", ["-ti", `:${BRIDGE_PORT}`], { encoding: "utf-8" }).trim();
|
|
356229
356445
|
const pidList = pidsOnPort.split("\n").filter(Boolean);
|
|
356230
356446
|
if (!pidList.includes(String(pid))) {
|
|
356231
356447
|
return;
|
|
356232
356448
|
}
|
|
356233
|
-
(0,
|
|
356234
|
-
(0,
|
|
356449
|
+
(0, import_child_process4.execFileSync)("kill", ["-9", String(pid)], { stdio: "ignore" });
|
|
356450
|
+
(0, import_child_process4.execFileSync)("sleep", ["1"], { stdio: "ignore" });
|
|
356235
356451
|
} catch {
|
|
356236
356452
|
} finally {
|
|
356237
356453
|
clearBridgePidFile();
|
|
@@ -357330,6 +357546,7 @@ var MobileBridgeDriver = class {
|
|
|
357330
357546
|
sessionId;
|
|
357331
357547
|
platform;
|
|
357332
357548
|
appId;
|
|
357549
|
+
deviceId;
|
|
357333
357550
|
allowAppReset;
|
|
357334
357551
|
recoverSession;
|
|
357335
357552
|
maxSessionRecoveries;
|
|
@@ -357350,6 +357567,7 @@ var MobileBridgeDriver = class {
|
|
|
357350
357567
|
this.sessionId = config.sessionId;
|
|
357351
357568
|
this.platform = config.platform;
|
|
357352
357569
|
this.appId = config.appId;
|
|
357570
|
+
this.deviceId = config.deviceId;
|
|
357353
357571
|
this.allowAppReset = config.allowAppReset === true;
|
|
357354
357572
|
this.recoverSession = config.recoverSession;
|
|
357355
357573
|
this.maxSessionRecoveries = Math.max(0, Math.floor(config.maxSessionRecoveries ?? 1));
|
|
@@ -357647,17 +357865,34 @@ var MobileBridgeDriver = class {
|
|
|
357647
357865
|
});
|
|
357648
357866
|
}
|
|
357649
357867
|
/**
|
|
357650
|
-
* Deterministic state reset: clear app data + relaunch
|
|
357651
|
-
*
|
|
357652
|
-
*
|
|
357653
|
-
*
|
|
357654
|
-
*
|
|
357868
|
+
* Deterministic state reset: clear app data + relaunch, per the
|
|
357869
|
+
* runner-computed allowAppReset policy (Android emulators / iOS simulators
|
|
357870
|
+
* only — a personal physical device is never wiped). Android clears via
|
|
357871
|
+
* `mobile: clearApp`; iOS terminates the app and wipes its simulator data
|
|
357872
|
+
* container + keychain through `simctl` (see ios-simulator-reset.ts).
|
|
357873
|
+
* Returns false when unsupported/disallowed or when the clear fails —
|
|
357874
|
+
* callers fall back to relaunch(), preserving today's behavior. On success,
|
|
357875
|
+
* waits for cold-boot content so recording and replay agree on the true
|
|
357876
|
+
* entry screen.
|
|
357655
357877
|
*/
|
|
357656
357878
|
async resetAppState() {
|
|
357657
|
-
if (!this.allowAppReset
|
|
357879
|
+
if (!this.allowAppReset) return false;
|
|
357880
|
+
if (this.platform === "IOS" && !isIosSimulatorUdid(this.deviceId)) return false;
|
|
357658
357881
|
return this.withSessionRecovery("app data reset", async () => {
|
|
357659
357882
|
try {
|
|
357660
|
-
|
|
357883
|
+
if (this.platform === "IOS") {
|
|
357884
|
+
try {
|
|
357885
|
+
await this.executeScript("mobile: terminateApp", [{ ...this.appLifecycleArgs(), timeout: 5e3 }]);
|
|
357886
|
+
} catch (err) {
|
|
357887
|
+
if (isRecoverableAppiumSessionFailure(err)) throw err;
|
|
357888
|
+
}
|
|
357889
|
+
const wiped = await clearIosSimulatorAppData(this.deviceId ?? "", this.appId);
|
|
357890
|
+
if (!wiped.ok) {
|
|
357891
|
+
throw new Error(`iOS simulator app-data reset failed: ${wiped.detail}`);
|
|
357892
|
+
}
|
|
357893
|
+
} else {
|
|
357894
|
+
await this.executeScript("mobile: clearApp", [{ appId: this.appId }]);
|
|
357895
|
+
}
|
|
357661
357896
|
await this.executeScript("mobile: activateApp", [this.appLifecycleArgs()]);
|
|
357662
357897
|
try {
|
|
357663
357898
|
await waitForColdBootSettle(this.sessionPath);
|
|
@@ -357796,7 +358031,7 @@ function readActiveBundleId2(result) {
|
|
|
357796
358031
|
}
|
|
357797
358032
|
|
|
357798
358033
|
// src/services/doctor.ts
|
|
357799
|
-
var
|
|
358034
|
+
var import_child_process5 = require("child_process");
|
|
357800
358035
|
var import_fs4 = require("fs");
|
|
357801
358036
|
var import_os5 = require("os");
|
|
357802
358037
|
var C = {
|
|
@@ -357816,7 +358051,7 @@ function computeDoctorReadiness(web, ios, android) {
|
|
|
357816
358051
|
}
|
|
357817
358052
|
function run(cmd) {
|
|
357818
358053
|
try {
|
|
357819
|
-
return (0,
|
|
358054
|
+
return (0, import_child_process5.execSync)(cmd, { encoding: "utf-8", timeout: 15e3, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
357820
358055
|
} catch {
|
|
357821
358056
|
return null;
|
|
357822
358057
|
}
|
|
@@ -358323,7 +358558,7 @@ var log = {
|
|
|
358323
358558
|
dim: (msg) => console.log(`${C2.gray}${msg}${C2.reset}`)
|
|
358324
358559
|
};
|
|
358325
358560
|
function getRunsDir() {
|
|
358326
|
-
const dir = process.env.RUNS_DIR || (0,
|
|
358561
|
+
const dir = process.env.RUNS_DIR || (0, import_path5.join)((0, import_os6.homedir)(), ".validate.qa", "runs");
|
|
358327
358562
|
(0, import_runner_core12.ensurePrivateDirectory)(dir);
|
|
358328
358563
|
return dir;
|
|
358329
358564
|
}
|
|
@@ -360255,9 +360490,11 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
360255
360490
|
sessionId: session.sessionId,
|
|
360256
360491
|
platform: platform3,
|
|
360257
360492
|
appId: session.appId,
|
|
360258
|
-
|
|
360259
|
-
//
|
|
360260
|
-
//
|
|
360493
|
+
deviceId: session.deviceId,
|
|
360494
|
+
// Deterministic-state authorization: Android emulators / iOS
|
|
360495
|
+
// simulators only (VALIDATEQA_MOBILE_APP_RESET overrides the
|
|
360496
|
+
// Android default) — a personal physical device must never have
|
|
360497
|
+
// its app data wiped.
|
|
360261
360498
|
allowAppReset: mobileTarget.resetPolicy !== "PRESERVE_APP_DATA" && shouldResetMobileAppData(platform3, session.deviceId),
|
|
360262
360499
|
maxSessionRecoveries: 1,
|
|
360263
360500
|
recoverSession: async (cause) => {
|
|
@@ -360773,6 +361010,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
360773
361010
|
sessionId: opened.sessionId,
|
|
360774
361011
|
platform: target.platform,
|
|
360775
361012
|
appId: opened.appId,
|
|
361013
|
+
deviceId: opened.deviceId,
|
|
360776
361014
|
allowAppReset: target.resetPolicy !== "PRESERVE_APP_DATA" && shouldResetMobileAppData(target.platform, opened.deviceId)
|
|
360777
361015
|
});
|
|
360778
361016
|
};
|