prowl-tools 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +334 -183
- package/dist/{chunk-WHAMB4TY.js → chunk-O3OUTZ2P.js} +18 -2
- package/dist/{chunk-WHAMB4TY.js.map → chunk-O3OUTZ2P.js.map} +1 -1
- package/dist/{chunk-5KQR3IR3.js → chunk-R7NUH44M.js} +551 -107
- package/dist/chunk-R7NUH44M.js.map +1 -0
- package/dist/index.cjs +691 -165
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +108 -13
- package/dist/index.js.map +1 -1
- package/dist/lib.cjs +693 -220
- package/dist/lib.cjs.map +1 -1
- package/dist/lib.d.cts +164 -7
- package/dist/lib.d.ts +164 -7
- package/dist/lib.js +58 -4
- package/dist/{loader-PBBYV3U7.js → loader-X37URHUV.js} +2 -2
- package/examples/hunts/hello.yml +1 -1
- package/examples/hunts/login-flow.yml +58 -0
- package/package.json +6 -3
- package/dist/chunk-5KQR3IR3.js.map +0 -1
- /package/dist/{loader-PBBYV3U7.js.map → loader-X37URHUV.js.map} +0 -0
package/dist/lib.cjs
CHANGED
|
@@ -37,12 +37,12 @@ __export(visual_exports, {
|
|
|
37
37
|
ensureBaselineDir: () => ensureBaselineDir
|
|
38
38
|
});
|
|
39
39
|
async function compareScreenshots(baselinePath, currentPath, diffPath, threshold) {
|
|
40
|
-
const baselineData = import_pngjs.PNG.sync.read(
|
|
41
|
-
const currentData = import_pngjs.PNG.sync.read(
|
|
40
|
+
const baselineData = import_pngjs.PNG.sync.read(import_node_fs9.default.readFileSync(baselinePath));
|
|
41
|
+
const currentData = import_pngjs.PNG.sync.read(import_node_fs9.default.readFileSync(currentPath));
|
|
42
42
|
const { width, height } = baselineData;
|
|
43
43
|
if (currentData.width !== width || currentData.height !== height) {
|
|
44
44
|
const diff2 = new import_pngjs.PNG({ width, height });
|
|
45
|
-
|
|
45
|
+
import_node_fs9.default.writeFileSync(diffPath, import_pngjs.PNG.sync.write(diff2));
|
|
46
46
|
return {
|
|
47
47
|
match: false,
|
|
48
48
|
diffPercentage: 1,
|
|
@@ -60,7 +60,7 @@ async function compareScreenshots(baselinePath, currentPath, diffPath, threshold
|
|
|
60
60
|
);
|
|
61
61
|
const totalPixels = width * height;
|
|
62
62
|
const diffPercentage = diffPixels / totalPixels;
|
|
63
|
-
|
|
63
|
+
import_node_fs9.default.writeFileSync(diffPath, import_pngjs.PNG.sync.write(diff));
|
|
64
64
|
return {
|
|
65
65
|
match: diffPercentage <= threshold,
|
|
66
66
|
diffPercentage,
|
|
@@ -68,16 +68,16 @@ async function compareScreenshots(baselinePath, currentPath, diffPath, threshold
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
function ensureBaselineDir(configDir) {
|
|
71
|
-
const baselineDir =
|
|
72
|
-
|
|
71
|
+
const baselineDir = import_node_path10.default.join(configDir, "baselines");
|
|
72
|
+
import_node_fs9.default.mkdirSync(baselineDir, { recursive: true });
|
|
73
73
|
return baselineDir;
|
|
74
74
|
}
|
|
75
|
-
var
|
|
75
|
+
var import_node_fs9, import_node_path10, import_pngjs, import_pixelmatch;
|
|
76
76
|
var init_visual = __esm({
|
|
77
77
|
"src/runner/visual.ts"() {
|
|
78
78
|
"use strict";
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
import_node_fs9 = __toESM(require("fs"), 1);
|
|
80
|
+
import_node_path10 = __toESM(require("path"), 1);
|
|
81
81
|
import_pngjs = require("pngjs");
|
|
82
82
|
import_pixelmatch = __toESM(require("pixelmatch"), 1);
|
|
83
83
|
}
|
|
@@ -95,11 +95,17 @@ __export(src_exports, {
|
|
|
95
95
|
DEFAULT_REQUEST_TIMEOUT_MS: () => DEFAULT_REQUEST_TIMEOUT_MS,
|
|
96
96
|
DEFAULT_SIMULATOR_LOCK_ROOT: () => DEFAULT_SIMULATOR_LOCK_ROOT,
|
|
97
97
|
DEFAULT_WDA_REQUEST_TIMEOUT_MS: () => DEFAULT_WDA_REQUEST_TIMEOUT_MS,
|
|
98
|
+
HELPER_BINARY: () => HELPER_BINARY,
|
|
98
99
|
INTERACTIVE_ROLES: () => INTERACTIVE_ROLES,
|
|
99
100
|
IOS_INTERACTIVE_TYPES: () => IOS_INTERACTIVE_TYPES,
|
|
100
101
|
IOS_MATCH_DIALECT: () => IOS_MATCH_DIALECT,
|
|
101
102
|
IOS_PRESS_KEYS: () => IOS_PRESS_KEYS,
|
|
102
103
|
IOS_WINDOW_TYPE: () => IOS_WINDOW_TYPE,
|
|
104
|
+
MACDRIVER_REPO: () => MACDRIVER_REPO,
|
|
105
|
+
MACDRIVER_SIGNING_AUTHORITY_PREFIX: () => MACDRIVER_SIGNING_AUTHORITY_PREFIX,
|
|
106
|
+
MACDRIVER_SIGNING_IDENTIFIER: () => MACDRIVER_SIGNING_IDENTIFIER,
|
|
107
|
+
MACDRIVER_VERSION: () => MACDRIVER_VERSION,
|
|
108
|
+
MACDRIVER_VERSION_PATTERN: () => MACDRIVER_VERSION_PATTERN,
|
|
103
109
|
MACOS_MATCH_DIALECT: () => MACOS_MATCH_DIALECT,
|
|
104
110
|
NATIVE_ATTRIBUTE_MAP: () => NATIVE_ATTRIBUTE_MAP,
|
|
105
111
|
SpawnMacHelperClient: () => SpawnMacHelperClient,
|
|
@@ -129,6 +135,8 @@ __export(src_exports, {
|
|
|
129
135
|
closeIosSession: () => closeIosSession,
|
|
130
136
|
closeMacSession: () => closeMacSession,
|
|
131
137
|
clusterFailures: () => clusterFailures,
|
|
138
|
+
codesignVerifier: () => codesignVerifier,
|
|
139
|
+
collectMacdriverStatus: () => collectMacdriverStatus,
|
|
132
140
|
computeFlakeScore: () => computeFlakeScore,
|
|
133
141
|
configSchema: () => configSchema,
|
|
134
142
|
createAndroidDriver: () => createAndroidDriver,
|
|
@@ -142,6 +150,8 @@ __export(src_exports, {
|
|
|
142
150
|
defaultAgentConnector: () => defaultAgentConnector,
|
|
143
151
|
defaultIosAgentConnector: () => defaultIosAgentConnector,
|
|
144
152
|
defaultWdaTestRunPreparer: () => defaultWdaTestRunPreparer,
|
|
153
|
+
dittoExtractor: () => dittoExtractor,
|
|
154
|
+
downloadAndVerify: () => downloadAndVerify,
|
|
145
155
|
escapePredicateArg: () => escapePredicateArg,
|
|
146
156
|
escapeUiSelectorArg: () => escapeUiSelectorArg,
|
|
147
157
|
extractElementId: () => extractElementId,
|
|
@@ -152,6 +162,7 @@ __export(src_exports, {
|
|
|
152
162
|
healSelector: () => healSelector,
|
|
153
163
|
huntSchema: () => huntSchema,
|
|
154
164
|
injectUsePortIntoXctestrun: () => injectUsePortIntoXctestrun,
|
|
165
|
+
installMacdriver: () => installMacdriver,
|
|
155
166
|
interpolateHunt: () => interpolateHunt,
|
|
156
167
|
iosAppAllowedIdentities: () => iosAppAllowedIdentities,
|
|
157
168
|
iosNodeToNative: () => iosNodeToNative,
|
|
@@ -168,7 +179,14 @@ __export(src_exports, {
|
|
|
168
179
|
loadHunt: () => loadHunt,
|
|
169
180
|
loadHuntMeta: () => loadHuntMeta,
|
|
170
181
|
loadHuntTags: () => loadHuntTags,
|
|
182
|
+
macdriverAssetName: () => macdriverAssetName,
|
|
183
|
+
macdriverAssetUrl: () => macdriverAssetUrl,
|
|
171
184
|
macdriverBuildInstructions: () => macdriverBuildInstructions,
|
|
185
|
+
macdriverChecksumName: () => macdriverChecksumName,
|
|
186
|
+
macdriverInstallRoot: () => macdriverInstallRoot,
|
|
187
|
+
macdriverInstalledBinary: () => macdriverInstalledBinary,
|
|
188
|
+
macdriverReleaseTag: () => macdriverReleaseTag,
|
|
189
|
+
macdriverVersionDir: () => macdriverVersionDir,
|
|
172
190
|
matchAndroidSelector: () => matchAndroidSelector,
|
|
173
191
|
matchIosSelector: () => matchIosSelector,
|
|
174
192
|
matchNativeTree: () => matchNativeTree,
|
|
@@ -178,6 +196,8 @@ __export(src_exports, {
|
|
|
178
196
|
parseAdbDevices: () => parseAdbDevices,
|
|
179
197
|
parseAndroidHierarchy: () => parseAndroidHierarchy,
|
|
180
198
|
parseAndroidSelector: () => parseAndroidSelector,
|
|
199
|
+
parseChecksumFile: () => parseChecksumFile,
|
|
200
|
+
parseCodesignDetails: () => parseCodesignDetails,
|
|
181
201
|
parseForwardPort: () => parseForwardPort,
|
|
182
202
|
parseIosHierarchy: () => parseIosHierarchy,
|
|
183
203
|
parseIosSelector: () => parseIosSelector,
|
|
@@ -204,20 +224,27 @@ __export(src_exports, {
|
|
|
204
224
|
resolveWdaTestRun: () => resolveWdaTestRun,
|
|
205
225
|
runHunt: () => runHunt,
|
|
206
226
|
runSuite: () => runSuite,
|
|
227
|
+
runVersionProbe: () => runVersionProbe,
|
|
207
228
|
selectDeviceSerial: () => selectDeviceSerial,
|
|
208
229
|
selectSimulatorUdid: () => selectSimulatorUdid,
|
|
230
|
+
sha256Hex: () => sha256Hex,
|
|
209
231
|
shortIosType: () => shortIosType,
|
|
210
232
|
stepSchema: () => stepSchema,
|
|
233
|
+
tccGuidance: () => tccGuidance,
|
|
211
234
|
unquoteSelectorValue: () => unquoteSelectorValue,
|
|
212
235
|
unwrapAndroidTextSelector: () => unwrapAndroidTextSelector,
|
|
213
236
|
unwrapIosTextSelector: () => unwrapIosTextSelector,
|
|
214
237
|
unwrapNativeTextSelector: () => unwrapNativeTextSelector,
|
|
215
238
|
updateBacklogFromSuite: () => updateBacklogFromSuite,
|
|
239
|
+
validateMacdriverArchiveEntries: () => validateMacdriverArchiveEntries,
|
|
240
|
+
validateMacdriverVersion: () => validateMacdriverVersion,
|
|
241
|
+
verifyMacdriverSignature: () => verifyMacdriverSignature,
|
|
216
242
|
waitForAgentReady: () => waitForAgentReady,
|
|
217
243
|
waitForWdaReady: () => waitForWdaReady,
|
|
218
244
|
wdaCacheDir: () => wdaCacheDir,
|
|
219
245
|
wdaTestRunArgs: () => wdaTestRunArgs,
|
|
220
|
-
webOnlyReason: () => webOnlyReason
|
|
246
|
+
webOnlyReason: () => webOnlyReason,
|
|
247
|
+
zipinfoArchiveLister: () => zipinfoArchiveLister
|
|
221
248
|
});
|
|
222
249
|
module.exports = __toCommonJS(src_exports);
|
|
223
250
|
|
|
@@ -400,25 +427,71 @@ function createMacDriver(client, options = {}) {
|
|
|
400
427
|
// src/browser/mac-helper.ts
|
|
401
428
|
var import_node_child_process = require("child_process");
|
|
402
429
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
403
|
-
var
|
|
430
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
431
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
404
432
|
var import_node_url = require("url");
|
|
405
|
-
|
|
433
|
+
|
|
434
|
+
// src/browser/macdriver-release.ts
|
|
435
|
+
var import_node_os = __toESM(require("os"), 1);
|
|
436
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
406
437
|
var HELPER_BINARY = "prowl-macdriver";
|
|
438
|
+
var MACDRIVER_VERSION = "0.1.0";
|
|
439
|
+
var MACDRIVER_REPO = "prowl-tools/prowl";
|
|
440
|
+
var MACDRIVER_SIGNING_IDENTIFIER = "tools.prowl.macdriver";
|
|
441
|
+
var MACDRIVER_SIGNING_AUTHORITY_PREFIX = "Developer ID Application: Genkei Labs";
|
|
442
|
+
var MACDRIVER_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?(?:\+[0-9A-Za-z][0-9A-Za-z.-]*)?$/;
|
|
443
|
+
function validateMacdriverVersion(version) {
|
|
444
|
+
if (!MACDRIVER_VERSION_PATTERN.test(version)) {
|
|
445
|
+
throw new Error(
|
|
446
|
+
`Invalid prowl-macdriver version "${version}". Expected a release version like 0.1.0.`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
return version;
|
|
450
|
+
}
|
|
451
|
+
function macdriverReleaseTag(version = MACDRIVER_VERSION) {
|
|
452
|
+
return `macdriver-v${validateMacdriverVersion(version)}`;
|
|
453
|
+
}
|
|
454
|
+
function macdriverAssetName(version = MACDRIVER_VERSION) {
|
|
455
|
+
return `prowl-macdriver-v${validateMacdriverVersion(version)}-universal.zip`;
|
|
456
|
+
}
|
|
457
|
+
function macdriverChecksumName(version = MACDRIVER_VERSION) {
|
|
458
|
+
return `${macdriverAssetName(version)}.sha256`;
|
|
459
|
+
}
|
|
460
|
+
function macdriverAssetUrl(assetName, version = MACDRIVER_VERSION) {
|
|
461
|
+
return `https://github.com/${MACDRIVER_REPO}/releases/download/${macdriverReleaseTag(version)}/${assetName}`;
|
|
462
|
+
}
|
|
463
|
+
function macdriverInstallRoot(homedir = import_node_os.default.homedir()) {
|
|
464
|
+
return import_node_path.default.join(homedir, ".prowl", "macdriver");
|
|
465
|
+
}
|
|
466
|
+
function macdriverVersionDir(version = MACDRIVER_VERSION, homedir = import_node_os.default.homedir()) {
|
|
467
|
+
const root = import_node_path.default.resolve(macdriverInstallRoot(homedir));
|
|
468
|
+
const versionDir = import_node_path.default.resolve(root, validateMacdriverVersion(version));
|
|
469
|
+
if (!versionDir.startsWith(root + import_node_path.default.sep)) {
|
|
470
|
+
throw new Error(`Resolved prowl-macdriver version directory escaped install root: ${versionDir}`);
|
|
471
|
+
}
|
|
472
|
+
return versionDir;
|
|
473
|
+
}
|
|
474
|
+
function macdriverInstalledBinary(version = MACDRIVER_VERSION, homedir = import_node_os.default.homedir()) {
|
|
475
|
+
return import_node_path.default.join(macdriverVersionDir(version, homedir), HELPER_BINARY);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/browser/mac-helper.ts
|
|
479
|
+
var import_meta = {};
|
|
407
480
|
function macdriverBuildInstructions() {
|
|
408
|
-
return "The macOS target
|
|
481
|
+
return "The macOS target needs the `prowl-macdriver` helper. Install the prebuilt, signed binary (recommended):\n prowl macdriver install\nContributors building from source can instead run:\n cd macdriver && swift build -c release\nor point Prowl at a prebuilt binary via the PROWL_MACDRIVER_BIN environment variable.";
|
|
409
482
|
}
|
|
410
483
|
function getPackageRoot() {
|
|
411
|
-
let dir =
|
|
412
|
-
const root =
|
|
484
|
+
let dir = import_node_path2.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
485
|
+
const root = import_node_path2.default.parse(dir).root;
|
|
413
486
|
while (dir !== root) {
|
|
414
|
-
if (import_node_fs.default.existsSync(
|
|
487
|
+
if (import_node_fs.default.existsSync(import_node_path2.default.join(dir, "package.json"))) {
|
|
415
488
|
return dir;
|
|
416
489
|
}
|
|
417
|
-
dir =
|
|
490
|
+
dir = import_node_path2.default.dirname(dir);
|
|
418
491
|
}
|
|
419
492
|
return root;
|
|
420
493
|
}
|
|
421
|
-
function resolveHelperBinary(env = process.env) {
|
|
494
|
+
function resolveHelperBinary(env = process.env, options = {}) {
|
|
422
495
|
const override = env.PROWL_MACDRIVER_BIN;
|
|
423
496
|
if (override) {
|
|
424
497
|
if (!import_node_fs.default.existsSync(override)) {
|
|
@@ -429,10 +502,15 @@ ${macdriverBuildInstructions()}`
|
|
|
429
502
|
}
|
|
430
503
|
return override;
|
|
431
504
|
}
|
|
505
|
+
const homedir = options.homedir ?? import_node_os2.default.homedir();
|
|
506
|
+
const userBinary = macdriverInstalledBinary(MACDRIVER_VERSION, homedir);
|
|
507
|
+
if (import_node_fs.default.existsSync(userBinary)) {
|
|
508
|
+
return userBinary;
|
|
509
|
+
}
|
|
432
510
|
const root = getPackageRoot();
|
|
433
511
|
const candidates = [
|
|
434
|
-
|
|
435
|
-
|
|
512
|
+
import_node_path2.default.join(root, "macdriver", ".build", "release", HELPER_BINARY),
|
|
513
|
+
import_node_path2.default.join(root, "macdriver", ".build", "debug", HELPER_BINARY)
|
|
436
514
|
];
|
|
437
515
|
for (const candidate of candidates) {
|
|
438
516
|
if (import_node_fs.default.existsSync(candidate)) {
|
|
@@ -604,8 +682,300 @@ async function closeMacSession(session) {
|
|
|
604
682
|
}
|
|
605
683
|
}
|
|
606
684
|
|
|
607
|
-
// src/browser/
|
|
685
|
+
// src/browser/macdriver-install.ts
|
|
686
|
+
var import_node_child_process2 = require("child_process");
|
|
687
|
+
var import_node_crypto = require("crypto");
|
|
608
688
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
689
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
690
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
691
|
+
var import_node_util = require("util");
|
|
692
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
|
|
693
|
+
function parseChecksumFile(text) {
|
|
694
|
+
const token = text.trim().split(/\s+/, 1)[0] ?? "";
|
|
695
|
+
const digest = token.toLowerCase();
|
|
696
|
+
if (!/^[0-9a-f]{64}$/.test(digest)) {
|
|
697
|
+
throw new Error(`Malformed .sha256 checksum file (expected a 64-char hex digest, got: ${text.trim().slice(0, 80)})`);
|
|
698
|
+
}
|
|
699
|
+
return digest;
|
|
700
|
+
}
|
|
701
|
+
function sha256Hex(bytes) {
|
|
702
|
+
return (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex");
|
|
703
|
+
}
|
|
704
|
+
var commandRunner = async (file, args) => execFileAsync(file, args);
|
|
705
|
+
function commandOutput(result) {
|
|
706
|
+
return [result.stdout, result.stderr].filter((value) => value !== void 0).map((value) => value.toString()).join("\n").trim();
|
|
707
|
+
}
|
|
708
|
+
function commandErrorDetail(error) {
|
|
709
|
+
const err = error;
|
|
710
|
+
return [err.stderr, err.stdout, err.message].filter((value) => value !== void 0 && value !== "").map((value) => value.toString()).join("\n").trim();
|
|
711
|
+
}
|
|
712
|
+
async function runRequiredCommand(run, binaryPath, command, args, label) {
|
|
713
|
+
try {
|
|
714
|
+
return await run(command, args);
|
|
715
|
+
} catch (error) {
|
|
716
|
+
const detail = commandErrorDetail(error);
|
|
717
|
+
throw new Error(`${label} failed for ${binaryPath}${detail ? `: ${detail}` : ""}`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
var zipinfoArchiveLister = async (zipPath) => {
|
|
721
|
+
try {
|
|
722
|
+
const { stdout } = await execFileAsync("zipinfo", ["-1", zipPath]);
|
|
723
|
+
return stdout.toString().split(/\r?\n/).filter((entry) => entry.length > 0);
|
|
724
|
+
} catch (error) {
|
|
725
|
+
const detail = commandErrorDetail(error);
|
|
726
|
+
throw new Error(`Failed to inspect release archive ${zipPath} with zipinfo${detail ? `: ${detail}` : ""}`);
|
|
727
|
+
}
|
|
728
|
+
};
|
|
729
|
+
function normalizeArchiveEntryName(entry) {
|
|
730
|
+
if (entry.length === 0 || entry !== entry.trim() || entry.includes("\0") || entry.includes("\\")) {
|
|
731
|
+
throw new Error(`Unsafe path in prowl-macdriver release archive: ${JSON.stringify(entry)}`);
|
|
732
|
+
}
|
|
733
|
+
if (entry.endsWith("/")) {
|
|
734
|
+
throw new Error(`Unexpected directory in prowl-macdriver release archive: ${entry}`);
|
|
735
|
+
}
|
|
736
|
+
if (import_node_path3.default.posix.isAbsolute(entry)) {
|
|
737
|
+
throw new Error(`Unsafe absolute path in prowl-macdriver release archive: ${entry}`);
|
|
738
|
+
}
|
|
739
|
+
const parts = entry.split("/");
|
|
740
|
+
if (parts.some((part) => part === "" || part === "." || part === "..")) {
|
|
741
|
+
throw new Error(`Unsafe path in prowl-macdriver release archive: ${entry}`);
|
|
742
|
+
}
|
|
743
|
+
return entry;
|
|
744
|
+
}
|
|
745
|
+
function validateMacdriverArchiveEntries(entries) {
|
|
746
|
+
const normalized = entries.map(normalizeArchiveEntryName);
|
|
747
|
+
if (normalized.length !== 1 || normalized[0] !== HELPER_BINARY) {
|
|
748
|
+
const shown = normalized.length > 0 ? normalized.join(", ") : "(empty archive)";
|
|
749
|
+
throw new Error(
|
|
750
|
+
`Unexpected prowl-macdriver release archive contents: ${shown}. Expected exactly "${HELPER_BINARY}" at the archive root.`
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
var dittoExtractor = async (zipPath, destDir) => {
|
|
755
|
+
await execFileAsync("ditto", ["-x", "-k", zipPath, destDir]);
|
|
756
|
+
};
|
|
757
|
+
function parseCodesignDetails(text) {
|
|
758
|
+
const details = { identifier: null, authorities: [], teamIdentifier: null };
|
|
759
|
+
for (const line of text.split(/\r?\n/)) {
|
|
760
|
+
const trimmed = line.trim();
|
|
761
|
+
if (trimmed.startsWith("Identifier=")) {
|
|
762
|
+
details.identifier = trimmed.slice("Identifier=".length);
|
|
763
|
+
} else if (trimmed.startsWith("Authority=")) {
|
|
764
|
+
details.authorities.push(trimmed.slice("Authority=".length));
|
|
765
|
+
} else if (trimmed.startsWith("TeamIdentifier=")) {
|
|
766
|
+
details.teamIdentifier = trimmed.slice("TeamIdentifier=".length);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return details;
|
|
770
|
+
}
|
|
771
|
+
function validateCodesignDetails(details, binaryPath) {
|
|
772
|
+
if (details.identifier !== MACDRIVER_SIGNING_IDENTIFIER) {
|
|
773
|
+
throw new Error(
|
|
774
|
+
`codesign verification failed for ${binaryPath}: expected identifier "${MACDRIVER_SIGNING_IDENTIFIER}", got "${details.identifier ?? "missing"}"`
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
const developerIdAuthority = details.authorities.find(
|
|
778
|
+
(authority) => authority.startsWith(`${MACDRIVER_SIGNING_AUTHORITY_PREFIX} (`)
|
|
779
|
+
);
|
|
780
|
+
const authorityTeamId = developerIdAuthority?.match(/\(([A-Z0-9]{10})\)$/)?.[1] ?? null;
|
|
781
|
+
if (!developerIdAuthority || !authorityTeamId) {
|
|
782
|
+
const shown = details.authorities.length > 0 ? details.authorities.join(" / ") : "missing";
|
|
783
|
+
throw new Error(
|
|
784
|
+
`codesign verification failed for ${binaryPath}: expected ${MACDRIVER_SIGNING_AUTHORITY_PREFIX} signer, got ${shown}`
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
if (!details.teamIdentifier) {
|
|
788
|
+
throw new Error(`codesign verification failed for ${binaryPath}: missing TeamIdentifier`);
|
|
789
|
+
}
|
|
790
|
+
if (details.teamIdentifier !== authorityTeamId) {
|
|
791
|
+
throw new Error(
|
|
792
|
+
`codesign verification failed for ${binaryPath}: TeamIdentifier ${details.teamIdentifier} does not match Developer ID authority team ${authorityTeamId}`
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
async function verifyMacdriverSignature(binaryPath, run = commandRunner) {
|
|
797
|
+
await runRequiredCommand(run, binaryPath, "codesign", ["--verify", "--strict", binaryPath], "codesign verification");
|
|
798
|
+
const display = await runRequiredCommand(
|
|
799
|
+
run,
|
|
800
|
+
binaryPath,
|
|
801
|
+
"codesign",
|
|
802
|
+
["--display", "--verbose=4", binaryPath],
|
|
803
|
+
"codesign detail inspection"
|
|
804
|
+
);
|
|
805
|
+
validateCodesignDetails(parseCodesignDetails(commandOutput(display)), binaryPath);
|
|
806
|
+
await runRequiredCommand(
|
|
807
|
+
run,
|
|
808
|
+
binaryPath,
|
|
809
|
+
"spctl",
|
|
810
|
+
["--assess", "--type", "execute", "--verbose=4", binaryPath],
|
|
811
|
+
"spctl assessment"
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
var codesignVerifier = async (binaryPath) => {
|
|
815
|
+
await verifyMacdriverSignature(binaryPath);
|
|
816
|
+
};
|
|
817
|
+
async function downloadAndVerify(options = {}) {
|
|
818
|
+
const version = options.version ?? MACDRIVER_VERSION;
|
|
819
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
820
|
+
const assetName = macdriverAssetName(version);
|
|
821
|
+
const zipUrl = macdriverAssetUrl(assetName, version);
|
|
822
|
+
const sumUrl = macdriverAssetUrl(macdriverChecksumName(version), version);
|
|
823
|
+
const [zipRes, sumRes] = await Promise.all([
|
|
824
|
+
fetchImpl(zipUrl, { redirect: "follow" }),
|
|
825
|
+
fetchImpl(sumUrl, { redirect: "follow" })
|
|
826
|
+
]);
|
|
827
|
+
if (zipRes.status === 404 || sumRes.status === 404) {
|
|
828
|
+
throw new Error(
|
|
829
|
+
`No published prowl-macdriver release for ${macdriverReleaseTag(version)} yet.
|
|
830
|
+
The signed binary is cut by the maintainer; until then build from source:
|
|
831
|
+
cd macdriver && swift build -c release`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
if (!zipRes.ok) {
|
|
835
|
+
throw new Error(`Failed to download ${assetName} (HTTP ${zipRes.status}) from ${zipUrl}`);
|
|
836
|
+
}
|
|
837
|
+
if (!sumRes.ok) {
|
|
838
|
+
throw new Error(`Failed to download the checksum (HTTP ${sumRes.status}) from ${sumUrl}`);
|
|
839
|
+
}
|
|
840
|
+
const zipBytes = Buffer.from(await zipRes.arrayBuffer());
|
|
841
|
+
const expected = parseChecksumFile(await sumRes.text());
|
|
842
|
+
const actual = sha256Hex(zipBytes);
|
|
843
|
+
if (actual !== expected) {
|
|
844
|
+
throw new Error(
|
|
845
|
+
`Checksum mismatch for ${assetName}.
|
|
846
|
+
expected: ${expected}
|
|
847
|
+
actual: ${actual}
|
|
848
|
+
The download was rejected and discarded; re-run the install, and report it if it repeats.`
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
return zipBytes;
|
|
852
|
+
}
|
|
853
|
+
async function installMacdriver(options = {}) {
|
|
854
|
+
const version = validateMacdriverVersion(options.version ?? MACDRIVER_VERSION);
|
|
855
|
+
const homedir = options.homedir ?? import_node_os3.default.homedir();
|
|
856
|
+
const listArchiveEntries = options.listArchiveEntries ?? zipinfoArchiveLister;
|
|
857
|
+
const extract = options.extract ?? dittoExtractor;
|
|
858
|
+
const verifySignature = options.verifySignature ?? codesignVerifier;
|
|
859
|
+
const installRoot = macdriverInstallRoot(homedir);
|
|
860
|
+
const versionDir = macdriverVersionDir(version, homedir);
|
|
861
|
+
const binaryPath = macdriverInstalledBinary(version, homedir);
|
|
862
|
+
if (!options.force && import_node_fs2.default.existsSync(binaryPath)) {
|
|
863
|
+
return { version, binaryPath, alreadyInstalled: true };
|
|
864
|
+
}
|
|
865
|
+
const zipBytes = await downloadAndVerify({ version, fetchImpl: options.fetchImpl });
|
|
866
|
+
import_node_fs2.default.mkdirSync(installRoot, { recursive: true });
|
|
867
|
+
const stagingDir = import_node_fs2.default.mkdtempSync(import_node_path3.default.join(installRoot, `.tmp-${version}-`));
|
|
868
|
+
const extractDir = import_node_path3.default.join(stagingDir, "extract");
|
|
869
|
+
const zipPath = import_node_path3.default.join(stagingDir, macdriverAssetName(version));
|
|
870
|
+
try {
|
|
871
|
+
import_node_fs2.default.mkdirSync(extractDir);
|
|
872
|
+
import_node_fs2.default.writeFileSync(zipPath, zipBytes);
|
|
873
|
+
validateMacdriverArchiveEntries(await listArchiveEntries(zipPath));
|
|
874
|
+
await extract(zipPath, extractDir);
|
|
875
|
+
const stagedBinaryPath = import_node_path3.default.join(extractDir, HELPER_BINARY);
|
|
876
|
+
assertExtractedHelper(extractDir, stagedBinaryPath);
|
|
877
|
+
import_node_fs2.default.chmodSync(stagedBinaryPath, 493);
|
|
878
|
+
await verifySignature(stagedBinaryPath);
|
|
879
|
+
replaceVersionDir(versionDir, extractDir, installRoot);
|
|
880
|
+
} finally {
|
|
881
|
+
import_node_fs2.default.rmSync(stagingDir, { recursive: true, force: true });
|
|
882
|
+
}
|
|
883
|
+
return { version, binaryPath, alreadyInstalled: false };
|
|
884
|
+
}
|
|
885
|
+
function assertExtractedHelper(extractDir, binaryPath) {
|
|
886
|
+
const entries = import_node_fs2.default.readdirSync(extractDir);
|
|
887
|
+
if (!entries.includes(HELPER_BINARY)) {
|
|
888
|
+
throw new Error(`The release archive did not contain a "${HELPER_BINARY}" binary.`);
|
|
889
|
+
}
|
|
890
|
+
if (entries.length !== 1 || entries[0] !== HELPER_BINARY) {
|
|
891
|
+
const shown = entries.length > 0 ? entries.join(", ") : "(empty directory)";
|
|
892
|
+
throw new Error(
|
|
893
|
+
`Unexpected extracted prowl-macdriver archive contents: ${shown}. Expected exactly "${HELPER_BINARY}".`
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
let stat = null;
|
|
897
|
+
try {
|
|
898
|
+
stat = import_node_fs2.default.lstatSync(binaryPath);
|
|
899
|
+
} catch {
|
|
900
|
+
}
|
|
901
|
+
if (!stat?.isFile()) {
|
|
902
|
+
throw new Error(`The release archive "${HELPER_BINARY}" entry is not a regular file.`);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
function replaceVersionDir(versionDir, stagedVersionDir, installRoot) {
|
|
906
|
+
const backupDir = import_node_path3.default.join(installRoot, `.previous-${import_node_path3.default.basename(versionDir)}-${process.pid}-${Date.now()}`);
|
|
907
|
+
let backedUp = false;
|
|
908
|
+
try {
|
|
909
|
+
if (import_node_fs2.default.existsSync(versionDir)) {
|
|
910
|
+
import_node_fs2.default.renameSync(versionDir, backupDir);
|
|
911
|
+
backedUp = true;
|
|
912
|
+
}
|
|
913
|
+
import_node_fs2.default.renameSync(stagedVersionDir, versionDir);
|
|
914
|
+
if (backedUp) {
|
|
915
|
+
import_node_fs2.default.rmSync(backupDir, { recursive: true, force: true });
|
|
916
|
+
}
|
|
917
|
+
} catch (error) {
|
|
918
|
+
if (backedUp && !import_node_fs2.default.existsSync(versionDir) && import_node_fs2.default.existsSync(backupDir)) {
|
|
919
|
+
import_node_fs2.default.renameSync(backupDir, versionDir);
|
|
920
|
+
}
|
|
921
|
+
throw error;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
var runVersionProbe = async (binaryPath) => {
|
|
925
|
+
try {
|
|
926
|
+
const { stdout } = await execFileAsync(binaryPath, ["version"], { timeout: 5e3 });
|
|
927
|
+
const match = stdout.match(/prowl-macdriver\s+(\S+)/);
|
|
928
|
+
return match ? match[1] : stdout.trim() || null;
|
|
929
|
+
} catch {
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
async function collectMacdriverStatus(options = {}) {
|
|
934
|
+
const env = options.env ?? process.env;
|
|
935
|
+
const homedir = options.homedir ?? import_node_os3.default.homedir();
|
|
936
|
+
const probe = options.probe ?? runVersionProbe;
|
|
937
|
+
let resolved = null;
|
|
938
|
+
try {
|
|
939
|
+
const resolvedPath = resolveHelperBinary(env, { homedir });
|
|
940
|
+
resolved = { path: resolvedPath, source: classifyResolvedSource(resolvedPath, env, homedir) };
|
|
941
|
+
} catch {
|
|
942
|
+
resolved = null;
|
|
943
|
+
}
|
|
944
|
+
const installed = [];
|
|
945
|
+
const root = macdriverInstallRoot(homedir);
|
|
946
|
+
if (import_node_fs2.default.existsSync(root)) {
|
|
947
|
+
for (const entry of import_node_fs2.default.readdirSync(root, { withFileTypes: true })) {
|
|
948
|
+
if (!entry.isDirectory()) continue;
|
|
949
|
+
let binaryPath;
|
|
950
|
+
try {
|
|
951
|
+
binaryPath = macdriverInstalledBinary(entry.name, homedir);
|
|
952
|
+
} catch {
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
if (import_node_fs2.default.existsSync(binaryPath)) {
|
|
956
|
+
installed.push({ version: entry.name, binaryPath });
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
installed.sort((a, b) => a.version.localeCompare(b.version));
|
|
960
|
+
}
|
|
961
|
+
const probedVersion = resolved ? await probe(resolved.path) : null;
|
|
962
|
+
return { resolved, pinnedVersion: MACDRIVER_VERSION, installed, probedVersion };
|
|
963
|
+
}
|
|
964
|
+
function classifyResolvedSource(resolvedPath, env, homedir) {
|
|
965
|
+
if (env.PROWL_MACDRIVER_BIN && resolvedPath === env.PROWL_MACDRIVER_BIN) {
|
|
966
|
+
return "env";
|
|
967
|
+
}
|
|
968
|
+
if (resolvedPath.startsWith(macdriverInstallRoot(homedir) + import_node_path3.default.sep)) {
|
|
969
|
+
return "user-install";
|
|
970
|
+
}
|
|
971
|
+
return "source-build";
|
|
972
|
+
}
|
|
973
|
+
function tccGuidance() {
|
|
974
|
+
return "macOS permissions: the app that hosts Prowl (your terminal \u2014 Terminal, iTerm, VS Code, \u2026)\nmust be granted, in System Settings \u2192 Privacy & Security:\n \u2022 Accessibility \u2014 required to drive the target app\n \u2022 Screen Recording \u2014 required for screenshots / visual baselines\nGrant both to the terminal app, not to prowl-macdriver itself, then re-run your hunt.";
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// src/browser/android-driver.ts
|
|
978
|
+
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
609
979
|
|
|
610
980
|
// src/analyzer/xml.ts
|
|
611
981
|
var ENTITIES = {
|
|
@@ -1097,7 +1467,7 @@ function createAndroidDriver(client, options = {}) {
|
|
|
1097
1467
|
},
|
|
1098
1468
|
async screenshot(screenshotOptions) {
|
|
1099
1469
|
const png = await client.screenshotPng();
|
|
1100
|
-
|
|
1470
|
+
import_node_fs3.default.writeFileSync(screenshotOptions.path, png);
|
|
1101
1471
|
},
|
|
1102
1472
|
// network / dialogs / downloads (all web-only) -------------------------
|
|
1103
1473
|
onResponse(_handler) {
|
|
@@ -1123,14 +1493,14 @@ function createAndroidDriver(client, options = {}) {
|
|
|
1123
1493
|
|
|
1124
1494
|
// src/browser/android-helper.ts
|
|
1125
1495
|
var import_node_module = require("module");
|
|
1126
|
-
var
|
|
1127
|
-
var
|
|
1128
|
-
var
|
|
1496
|
+
var import_node_child_process5 = require("child_process");
|
|
1497
|
+
var import_node_fs5 = __toESM(require("fs"), 1);
|
|
1498
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
1129
1499
|
|
|
1130
1500
|
// src/browser/android-adb.ts
|
|
1131
|
-
var
|
|
1501
|
+
var import_node_child_process3 = require("child_process");
|
|
1132
1502
|
var execFileAdbRunner = (args, options) => new Promise((resolve) => {
|
|
1133
|
-
(0,
|
|
1503
|
+
(0, import_node_child_process3.execFile)(
|
|
1134
1504
|
"adb",
|
|
1135
1505
|
args,
|
|
1136
1506
|
{ encoding: "utf-8", timeout: options?.timeoutMs, maxBuffer: 16 * 1024 * 1024 },
|
|
@@ -1146,7 +1516,7 @@ var execFileAdbRunner = (args, options) => new Promise((resolve) => {
|
|
|
1146
1516
|
);
|
|
1147
1517
|
});
|
|
1148
1518
|
var spawnAdbProcess = (args) => {
|
|
1149
|
-
const child = (0,
|
|
1519
|
+
const child = (0, import_node_child_process3.spawn)("adb", args, { stdio: "ignore" });
|
|
1150
1520
|
child.on("error", () => {
|
|
1151
1521
|
});
|
|
1152
1522
|
return { kill: () => child.kill() };
|
|
@@ -1370,12 +1740,12 @@ var Uia2Transport = class {
|
|
|
1370
1740
|
* {@link Uia2HttpError} on a non-2xx response, or a timeout error when the
|
|
1371
1741
|
* per-request deadline elapses.
|
|
1372
1742
|
*/
|
|
1373
|
-
async request(method,
|
|
1743
|
+
async request(method, path20, body, timeoutMs) {
|
|
1374
1744
|
const requestTimeoutMs = timeoutMs ?? this.requestTimeoutMs;
|
|
1375
1745
|
const controller = new AbortController();
|
|
1376
1746
|
const timer = setTimeout(() => controller.abort(), requestTimeoutMs);
|
|
1377
1747
|
timer.unref?.();
|
|
1378
|
-
const url = `${this.baseUrl}${
|
|
1748
|
+
const url = `${this.baseUrl}${path20}`;
|
|
1379
1749
|
let response;
|
|
1380
1750
|
try {
|
|
1381
1751
|
response = await this.fetchImpl(url, {
|
|
@@ -1387,7 +1757,7 @@ var Uia2Transport = class {
|
|
|
1387
1757
|
} catch (error) {
|
|
1388
1758
|
if (controller.signal.aborted) {
|
|
1389
1759
|
const shown = requestTimeoutMs >= 1e3 ? `${Math.round(requestTimeoutMs / 1e3)}s` : `${requestTimeoutMs}ms`;
|
|
1390
|
-
throw new Error(`uiautomator2 request ${method} ${
|
|
1760
|
+
throw new Error(`uiautomator2 request ${method} ${path20} timed out after ${shown}`);
|
|
1391
1761
|
}
|
|
1392
1762
|
throw error instanceof Error ? error : new Error(String(error));
|
|
1393
1763
|
} finally {
|
|
@@ -1398,7 +1768,7 @@ var Uia2Transport = class {
|
|
|
1398
1768
|
if (!response.ok) {
|
|
1399
1769
|
const wdError = extractWebdriverError(parsed);
|
|
1400
1770
|
throw new Uia2HttpError(
|
|
1401
|
-
`uiautomator2 ${method} ${
|
|
1771
|
+
`uiautomator2 ${method} ${path20} failed (${response.status})${wdError ? `: ${wdError}` : ""}`,
|
|
1402
1772
|
response.status,
|
|
1403
1773
|
wdError
|
|
1404
1774
|
);
|
|
@@ -1481,10 +1851,10 @@ function sleep(ms) {
|
|
|
1481
1851
|
}
|
|
1482
1852
|
function createUia2AgentClient(transport, sessionId, options = {}) {
|
|
1483
1853
|
const base = `/session/${sessionId}`;
|
|
1484
|
-
async function locate(query,
|
|
1854
|
+
async function locate(query, path20) {
|
|
1485
1855
|
return transport.request(
|
|
1486
1856
|
"POST",
|
|
1487
|
-
`${base}${
|
|
1857
|
+
`${base}${path20}`,
|
|
1488
1858
|
androidQueryToLocator(query, { appPackage: options.appPackage })
|
|
1489
1859
|
);
|
|
1490
1860
|
}
|
|
@@ -1540,9 +1910,9 @@ function createUia2AgentClient(transport, sessionId, options = {}) {
|
|
|
1540
1910
|
}
|
|
1541
1911
|
|
|
1542
1912
|
// src/config/target.ts
|
|
1543
|
-
var
|
|
1544
|
-
var
|
|
1545
|
-
var
|
|
1913
|
+
var import_node_child_process4 = require("child_process");
|
|
1914
|
+
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
1915
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
1546
1916
|
var WEB_ONLY_STEP_TYPES = /* @__PURE__ */ new Set([
|
|
1547
1917
|
"navigate",
|
|
1548
1918
|
"waitForUrl",
|
|
@@ -1627,7 +1997,7 @@ function looksLikeIosAppPath(app) {
|
|
|
1627
1997
|
}
|
|
1628
1998
|
if (trimmed.toLowerCase().endsWith(".app")) {
|
|
1629
1999
|
try {
|
|
1630
|
-
return
|
|
2000
|
+
return import_node_fs4.default.statSync(normalizeAppPath(app)).isDirectory();
|
|
1631
2001
|
} catch {
|
|
1632
2002
|
return false;
|
|
1633
2003
|
}
|
|
@@ -1635,29 +2005,29 @@ function looksLikeIosAppPath(app) {
|
|
|
1635
2005
|
return false;
|
|
1636
2006
|
}
|
|
1637
2007
|
function normalizeAppPath(app) {
|
|
1638
|
-
return
|
|
2008
|
+
return import_node_path4.default.resolve(trimTrailingPathSeparators(app));
|
|
1639
2009
|
}
|
|
1640
2010
|
function parseBundleIdentifier(plist) {
|
|
1641
2011
|
const match = /<key>\s*CFBundleIdentifier\s*<\/key>\s*<string>\s*([^<]+?)\s*<\/string>/s.exec(plist);
|
|
1642
2012
|
return match?.[1]?.trim() || null;
|
|
1643
2013
|
}
|
|
1644
2014
|
function readBundleIdentifier(appPath, ...plistSubPath) {
|
|
1645
|
-
const infoPlistPath =
|
|
1646
|
-
if (!
|
|
2015
|
+
const infoPlistPath = import_node_path4.default.join(normalizeAppPath(appPath), ...plistSubPath);
|
|
2016
|
+
if (!import_node_fs4.default.existsSync(infoPlistPath)) {
|
|
1647
2017
|
return null;
|
|
1648
2018
|
}
|
|
1649
2019
|
try {
|
|
1650
|
-
const parsed = parseBundleIdentifier(
|
|
2020
|
+
const parsed = parseBundleIdentifier(import_node_fs4.default.readFileSync(infoPlistPath, "utf-8"));
|
|
1651
2021
|
if (parsed) {
|
|
1652
2022
|
return parsed;
|
|
1653
2023
|
}
|
|
1654
2024
|
} catch {
|
|
1655
2025
|
}
|
|
1656
|
-
if (process.platform !== "darwin" || !
|
|
2026
|
+
if (process.platform !== "darwin" || !import_node_fs4.default.existsSync("/usr/bin/plutil")) {
|
|
1657
2027
|
return null;
|
|
1658
2028
|
}
|
|
1659
2029
|
try {
|
|
1660
|
-
const output = (0,
|
|
2030
|
+
const output = (0, import_node_child_process4.execFileSync)(
|
|
1661
2031
|
"/usr/bin/plutil",
|
|
1662
2032
|
["-extract", "CFBundleIdentifier", "raw", "-o", "-", infoPlistPath],
|
|
1663
2033
|
{ encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 1e3 }
|
|
@@ -1679,7 +2049,7 @@ function appBundleAllowedIdentities(app, readBundleId, isAppPath = looksLikeAppB
|
|
|
1679
2049
|
const normalizedPath = normalizeAppPath(app);
|
|
1680
2050
|
identities.add(trimTrailingPathSeparators(app));
|
|
1681
2051
|
identities.add(normalizedPath);
|
|
1682
|
-
const bundleName =
|
|
2052
|
+
const bundleName = import_node_path4.default.basename(normalizedPath).replace(/\.app$/i, "");
|
|
1683
2053
|
if (bundleName) {
|
|
1684
2054
|
identities.add(bundleName);
|
|
1685
2055
|
}
|
|
@@ -1707,9 +2077,9 @@ function looksLikeApkPath(app) {
|
|
|
1707
2077
|
return trimmed.includes("/") || trimmed.includes("\\") || trimmed.toLowerCase().endsWith(".apk");
|
|
1708
2078
|
}
|
|
1709
2079
|
function normalizeAndroidApkPath(app) {
|
|
1710
|
-
const resolved =
|
|
2080
|
+
const resolved = import_node_path4.default.resolve(trimTrailingPathSeparators(app));
|
|
1711
2081
|
try {
|
|
1712
|
-
return
|
|
2082
|
+
return import_node_fs4.default.realpathSync.native(resolved);
|
|
1713
2083
|
} catch {
|
|
1714
2084
|
return resolved;
|
|
1715
2085
|
}
|
|
@@ -1752,12 +2122,12 @@ function resolveAgentApks(requireFn = (0, import_node_module.createRequire)(impo
|
|
|
1752
2122
|
"The Android target requires the `appium-uiautomator2-server` package (its prebuilt APKs). It is an optional dependency of prowl-tools; it may have been skipped (--omit=optional) or failed to install. Restore it for a global Prowl install with: npm install -g appium-uiautomator2-server@10.6.2. If Prowl is installed locally in a project, run: npm install appium-uiautomator2-server@10.6.2"
|
|
1753
2123
|
);
|
|
1754
2124
|
}
|
|
1755
|
-
const pkgDir =
|
|
2125
|
+
const pkgDir = import_node_path5.default.dirname(pkgJsonPath);
|
|
1756
2126
|
const version = requireFn(pkgJsonPath).version;
|
|
1757
|
-
const serverApk =
|
|
1758
|
-
const testApk =
|
|
2127
|
+
const serverApk = import_node_path5.default.join(pkgDir, "apks", `appium-uiautomator2-server-v${version}.apk`);
|
|
2128
|
+
const testApk = import_node_path5.default.join(pkgDir, "apks", "appium-uiautomator2-server-debug-androidTest.apk");
|
|
1759
2129
|
for (const apk of [serverApk, testApk]) {
|
|
1760
|
-
if (!
|
|
2130
|
+
if (!import_node_fs5.default.existsSync(apk)) {
|
|
1761
2131
|
throw new Error(`Expected uiautomator2 agent APK is missing: ${apk}. Reinstall dependencies.`);
|
|
1762
2132
|
}
|
|
1763
2133
|
}
|
|
@@ -1769,7 +2139,7 @@ function looksLikeApk(app) {
|
|
|
1769
2139
|
var execFileAaptResolver = async (apkPath) => {
|
|
1770
2140
|
for (const tool of ["aapt", "aapt2"]) {
|
|
1771
2141
|
const output = await new Promise((resolve) => {
|
|
1772
|
-
(0,
|
|
2142
|
+
(0, import_node_child_process5.execFile)(
|
|
1773
2143
|
tool,
|
|
1774
2144
|
["dump", "badging", apkPath],
|
|
1775
2145
|
{ encoding: "utf-8", timeout: 2e4, maxBuffer: 8 * 1024 * 1024 },
|
|
@@ -1803,8 +2173,8 @@ async function resolvePackage(app, runner, serial, aaptResolver, allowedApps) {
|
|
|
1803
2173
|
assertAndroidAppAllowed(allowedApps, app);
|
|
1804
2174
|
return app;
|
|
1805
2175
|
}
|
|
1806
|
-
const apkPath =
|
|
1807
|
-
if (!
|
|
2176
|
+
const apkPath = import_node_path5.default.resolve(app);
|
|
2177
|
+
if (!import_node_fs5.default.existsSync(apkPath)) {
|
|
1808
2178
|
throw new Error(`APK not found: ${apkPath}`);
|
|
1809
2179
|
}
|
|
1810
2180
|
const pkg = await aaptResolver(apkPath);
|
|
@@ -2095,19 +2465,19 @@ function createIosDriver(client, options) {
|
|
|
2095
2465
|
|
|
2096
2466
|
// src/browser/ios-helper.ts
|
|
2097
2467
|
var import_node_module2 = require("module");
|
|
2098
|
-
var
|
|
2099
|
-
var
|
|
2100
|
-
var
|
|
2101
|
-
var
|
|
2468
|
+
var import_node_child_process7 = require("child_process");
|
|
2469
|
+
var import_node_fs6 = __toESM(require("fs"), 1);
|
|
2470
|
+
var import_node_os5 = __toESM(require("os"), 1);
|
|
2471
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
2102
2472
|
|
|
2103
2473
|
// src/browser/ios-simctl.ts
|
|
2104
|
-
var
|
|
2474
|
+
var import_node_child_process6 = require("child_process");
|
|
2105
2475
|
var import_promises = require("fs/promises");
|
|
2106
2476
|
var import_node_net = __toESM(require("net"), 1);
|
|
2107
|
-
var
|
|
2108
|
-
var
|
|
2477
|
+
var import_node_os4 = __toESM(require("os"), 1);
|
|
2478
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
2109
2479
|
var spawnXcrunProcess = (args, options) => {
|
|
2110
|
-
const child = (0,
|
|
2480
|
+
const child = (0, import_node_child_process6.spawn)("xcrun", args, {
|
|
2111
2481
|
stdio: "ignore",
|
|
2112
2482
|
env: options?.env ? { ...process.env, ...options.env } : process.env
|
|
2113
2483
|
});
|
|
@@ -2119,10 +2489,10 @@ var spawnXcrunProcess = (args, options) => {
|
|
|
2119
2489
|
}
|
|
2120
2490
|
};
|
|
2121
2491
|
};
|
|
2122
|
-
var DEFAULT_SIMULATOR_LOCK_ROOT =
|
|
2492
|
+
var DEFAULT_SIMULATOR_LOCK_ROOT = import_node_path6.default.join(import_node_os4.default.tmpdir(), "prowl-ios-simulator-locks");
|
|
2123
2493
|
var SIMULATOR_LOCK_OWNER_FILE = "owner.json";
|
|
2124
2494
|
var execFileXcrunRunner = (args, options) => new Promise((resolve) => {
|
|
2125
|
-
(0,
|
|
2495
|
+
(0, import_node_child_process6.execFile)(
|
|
2126
2496
|
"xcrun",
|
|
2127
2497
|
args,
|
|
2128
2498
|
{
|
|
@@ -2159,7 +2529,7 @@ function isProcessAlive(pid) {
|
|
|
2159
2529
|
}
|
|
2160
2530
|
async function removeStaleSimulatorLock(lockPath) {
|
|
2161
2531
|
try {
|
|
2162
|
-
const ownerText = await (0, import_promises.readFile)(
|
|
2532
|
+
const ownerText = await (0, import_promises.readFile)(import_node_path6.default.join(lockPath, SIMULATOR_LOCK_OWNER_FILE), "utf8");
|
|
2163
2533
|
const owner = JSON.parse(ownerText);
|
|
2164
2534
|
if (typeof owner.pid === "number" && Number.isInteger(owner.pid) && owner.pid > 0 && !isProcessAlive(owner.pid)) {
|
|
2165
2535
|
await (0, import_promises.rm)(lockPath, { recursive: true, force: true });
|
|
@@ -2177,7 +2547,7 @@ function simulatorReservedError(udid) {
|
|
|
2177
2547
|
}
|
|
2178
2548
|
async function reserveSimulatorUdid(udid, options = {}) {
|
|
2179
2549
|
const lockRoot = options.lockRoot ?? DEFAULT_SIMULATOR_LOCK_ROOT;
|
|
2180
|
-
const lockPath =
|
|
2550
|
+
const lockPath = import_node_path6.default.join(lockRoot, simulatorLockName(udid));
|
|
2181
2551
|
await (0, import_promises.mkdir)(lockRoot, { recursive: true });
|
|
2182
2552
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
2183
2553
|
try {
|
|
@@ -2201,7 +2571,7 @@ async function reserveSimulatorUdid(udid, options = {}) {
|
|
|
2201
2571
|
};
|
|
2202
2572
|
try {
|
|
2203
2573
|
await (0, import_promises.writeFile)(
|
|
2204
|
-
|
|
2574
|
+
import_node_path6.default.join(lockPath, SIMULATOR_LOCK_OWNER_FILE),
|
|
2205
2575
|
`${JSON.stringify({ pid: process.pid, udid, createdAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
2206
2576
|
`,
|
|
2207
2577
|
{ flag: "wx" }
|
|
@@ -2393,15 +2763,15 @@ var WdaTransport = class {
|
|
|
2393
2763
|
* {@link WdaHttpError} on a non-2xx response, or a timeout error when the
|
|
2394
2764
|
* per-request deadline elapses.
|
|
2395
2765
|
*/
|
|
2396
|
-
async requestFull(method,
|
|
2766
|
+
async requestFull(method, path20, body, timeoutMs) {
|
|
2397
2767
|
const requestTimeoutMs = timeoutMs ?? this.requestTimeoutMs;
|
|
2398
2768
|
const controller = new AbortController();
|
|
2399
2769
|
const timer = setTimeout(() => controller.abort(), requestTimeoutMs);
|
|
2400
2770
|
timer.unref?.();
|
|
2401
|
-
const url = `${this.baseUrl}${
|
|
2771
|
+
const url = `${this.baseUrl}${path20}`;
|
|
2402
2772
|
const timeoutError = () => {
|
|
2403
2773
|
const shown = requestTimeoutMs >= 1e3 ? `${Math.round(requestTimeoutMs / 1e3)}s` : `${requestTimeoutMs}ms`;
|
|
2404
|
-
return new Error(`WebDriverAgent request ${method} ${
|
|
2774
|
+
return new Error(`WebDriverAgent request ${method} ${path20} timed out after ${shown}`);
|
|
2405
2775
|
};
|
|
2406
2776
|
let response;
|
|
2407
2777
|
try {
|
|
@@ -2433,7 +2803,7 @@ var WdaTransport = class {
|
|
|
2433
2803
|
if (!response.ok) {
|
|
2434
2804
|
const wdError = extractWebdriverError2(parsed);
|
|
2435
2805
|
throw new WdaHttpError(
|
|
2436
|
-
`WebDriverAgent ${method} ${
|
|
2806
|
+
`WebDriverAgent ${method} ${path20} failed (${response.status})${wdError ? `: ${wdError}` : ""}`,
|
|
2437
2807
|
response.status,
|
|
2438
2808
|
wdError
|
|
2439
2809
|
);
|
|
@@ -2441,8 +2811,8 @@ var WdaTransport = class {
|
|
|
2441
2811
|
return parsed;
|
|
2442
2812
|
}
|
|
2443
2813
|
/** Like {@link requestFull} but returns just the `value` field. */
|
|
2444
|
-
async request(method,
|
|
2445
|
-
const parsed = await this.requestFull(method,
|
|
2814
|
+
async request(method, path20, body, timeoutMs) {
|
|
2815
|
+
const parsed = await this.requestFull(method, path20, body, timeoutMs);
|
|
2446
2816
|
return parsed?.value;
|
|
2447
2817
|
}
|
|
2448
2818
|
};
|
|
@@ -2518,8 +2888,8 @@ function sleep2(ms) {
|
|
|
2518
2888
|
}
|
|
2519
2889
|
function createWdaAgentClient(transport, sessionId) {
|
|
2520
2890
|
const base = `/session/${sessionId}`;
|
|
2521
|
-
async function locate(query,
|
|
2522
|
-
return transport.request("POST", `${base}${
|
|
2891
|
+
async function locate(query, path20) {
|
|
2892
|
+
return transport.request("POST", `${base}${path20}`, iosQueryToLocator(query));
|
|
2523
2893
|
}
|
|
2524
2894
|
return {
|
|
2525
2895
|
async findElement(query) {
|
|
@@ -2595,41 +2965,41 @@ function resolveWdaProject(requireFn = (0, import_node_module2.createRequire)(im
|
|
|
2595
2965
|
"The iOS target requires the `appium-webdriveragent` package (its WDA Xcode project). It is an optional dependency of prowl-tools; it may have been skipped (--omit=optional) or failed to install. Restore it for a global Prowl install with: npm install -g appium-webdriveragent@16.4.0. If Prowl is installed locally in a project, run: npm install appium-webdriveragent@16.4.0"
|
|
2596
2966
|
);
|
|
2597
2967
|
}
|
|
2598
|
-
const pkgDir =
|
|
2968
|
+
const pkgDir = import_node_path7.default.dirname(pkgJsonPath);
|
|
2599
2969
|
const version = requireFn(pkgJsonPath).version;
|
|
2600
|
-
const projectPath =
|
|
2601
|
-
if (!
|
|
2970
|
+
const projectPath = import_node_path7.default.join(pkgDir, "WebDriverAgent.xcodeproj");
|
|
2971
|
+
if (!import_node_fs6.default.existsSync(projectPath)) {
|
|
2602
2972
|
throw new Error(`Expected WebDriverAgent project is missing: ${projectPath}. Reinstall dependencies.`);
|
|
2603
2973
|
}
|
|
2604
2974
|
return { projectPath, version };
|
|
2605
2975
|
}
|
|
2606
|
-
function wdaCacheDir(wdaVersion, xcode, homeDir =
|
|
2607
|
-
return
|
|
2976
|
+
function wdaCacheDir(wdaVersion, xcode, homeDir = import_node_os5.default.homedir()) {
|
|
2977
|
+
return import_node_path7.default.join(homeDir, ".prowl", "wda", `${wdaVersion}-xcode${xcode}`);
|
|
2608
2978
|
}
|
|
2609
2979
|
function productsDir(derivedDataPath) {
|
|
2610
|
-
return
|
|
2980
|
+
return import_node_path7.default.join(derivedDataPath, "Build", "Products");
|
|
2611
2981
|
}
|
|
2612
2982
|
function findXctestrunIn(dir) {
|
|
2613
2983
|
let entries;
|
|
2614
2984
|
try {
|
|
2615
|
-
entries =
|
|
2985
|
+
entries = import_node_fs6.default.readdirSync(dir);
|
|
2616
2986
|
} catch {
|
|
2617
2987
|
return null;
|
|
2618
2988
|
}
|
|
2619
2989
|
const match = entries.filter((name) => name.endsWith(".xctestrun") && !name.startsWith(PREPARED_XCTESTRUN_PREFIX)).sort()[0];
|
|
2620
|
-
return match ?
|
|
2990
|
+
return match ? import_node_path7.default.join(dir, match) : null;
|
|
2621
2991
|
}
|
|
2622
2992
|
function resolveOverrideXctestrun(override) {
|
|
2623
|
-
if (!
|
|
2993
|
+
if (!import_node_fs6.default.existsSync(override)) {
|
|
2624
2994
|
throw new Error(`PROWL_WDA_RUNNER points at a missing path: ${override}`);
|
|
2625
2995
|
}
|
|
2626
2996
|
if (override.endsWith(".xctestrun")) {
|
|
2627
2997
|
return override;
|
|
2628
2998
|
}
|
|
2629
2999
|
const candidates = [];
|
|
2630
|
-
const stat =
|
|
3000
|
+
const stat = import_node_fs6.default.statSync(override);
|
|
2631
3001
|
if (override.endsWith(".app")) {
|
|
2632
|
-
candidates.push(
|
|
3002
|
+
candidates.push(import_node_path7.default.dirname(import_node_path7.default.dirname(override)));
|
|
2633
3003
|
} else if (stat.isDirectory()) {
|
|
2634
3004
|
candidates.push(override, productsDir(override));
|
|
2635
3005
|
}
|
|
@@ -2646,7 +3016,7 @@ function resolveOverrideXctestrun(override) {
|
|
|
2646
3016
|
async function resolveWdaTestRun(options = {}) {
|
|
2647
3017
|
const runner = options.runner ?? execFileXcrunRunner;
|
|
2648
3018
|
const env = options.env ?? process.env;
|
|
2649
|
-
const homeDir = options.homeDir ??
|
|
3019
|
+
const homeDir = options.homeDir ?? import_node_os5.default.homedir();
|
|
2650
3020
|
const log = options.logger ?? ((message) => process.stderr.write(`${message}
|
|
2651
3021
|
`));
|
|
2652
3022
|
const override = env.PROWL_WDA_RUNNER;
|
|
@@ -2668,7 +3038,7 @@ async function resolveWdaTestRun(options = {}) {
|
|
|
2668
3038
|
log(
|
|
2669
3039
|
`Prowl: building WebDriverAgent for the iOS target (first run only; this can take a few minutes)\u2026`
|
|
2670
3040
|
);
|
|
2671
|
-
|
|
3041
|
+
import_node_fs6.default.mkdirSync(cacheDir, { recursive: true });
|
|
2672
3042
|
const result = await runner(
|
|
2673
3043
|
[
|
|
2674
3044
|
"xcodebuild",
|
|
@@ -2734,7 +3104,7 @@ function injectUsePortIntoXctestrun(plist, port) {
|
|
|
2734
3104
|
}
|
|
2735
3105
|
function runPlutil(args) {
|
|
2736
3106
|
return new Promise((resolve, reject) => {
|
|
2737
|
-
(0,
|
|
3107
|
+
(0, import_node_child_process7.execFile)(
|
|
2738
3108
|
"plutil",
|
|
2739
3109
|
args,
|
|
2740
3110
|
{ encoding: "utf-8", maxBuffer: 32 * 1024 * 1024 },
|
|
@@ -2762,13 +3132,13 @@ var defaultWdaTestRunPreparer = async ({ xctestrunPath, port }) => {
|
|
|
2762
3132
|
}
|
|
2763
3133
|
const injected = injectUsePortIntoXctestrun(parsed, port);
|
|
2764
3134
|
const stem = `${PREPARED_XCTESTRUN_PREFIX}${port}-${process.pid}`;
|
|
2765
|
-
const outPath =
|
|
2766
|
-
const jsonPath =
|
|
2767
|
-
|
|
3135
|
+
const outPath = import_node_path7.default.join(import_node_path7.default.dirname(xctestrunPath), `${stem}.xctestrun`);
|
|
3136
|
+
const jsonPath = import_node_path7.default.join(import_node_os5.default.tmpdir(), `${stem}.json`);
|
|
3137
|
+
import_node_fs6.default.writeFileSync(jsonPath, JSON.stringify(injected));
|
|
2768
3138
|
try {
|
|
2769
3139
|
await runPlutil(["-convert", "xml1", jsonPath, "-o", outPath]);
|
|
2770
3140
|
} finally {
|
|
2771
|
-
|
|
3141
|
+
import_node_fs6.default.rmSync(jsonPath, { force: true });
|
|
2772
3142
|
}
|
|
2773
3143
|
return outPath;
|
|
2774
3144
|
};
|
|
@@ -2776,10 +3146,10 @@ function cleanupPreparedTestRun(preparedPath) {
|
|
|
2776
3146
|
if (!preparedPath) {
|
|
2777
3147
|
return;
|
|
2778
3148
|
}
|
|
2779
|
-
if (!
|
|
3149
|
+
if (!import_node_path7.default.basename(preparedPath).startsWith(PREPARED_XCTESTRUN_PREFIX)) {
|
|
2780
3150
|
return;
|
|
2781
3151
|
}
|
|
2782
|
-
|
|
3152
|
+
import_node_fs6.default.rmSync(preparedPath, { force: true });
|
|
2783
3153
|
}
|
|
2784
3154
|
function wdaTestRunArgs(xctestrunPath, udid) {
|
|
2785
3155
|
return [
|
|
@@ -2801,8 +3171,8 @@ async function resolveBundleId(app, runner, udid, coldStart, allowedApps) {
|
|
|
2801
3171
|
}
|
|
2802
3172
|
return app;
|
|
2803
3173
|
}
|
|
2804
|
-
const appPath =
|
|
2805
|
-
if (!
|
|
3174
|
+
const appPath = import_node_path7.default.resolve(app);
|
|
3175
|
+
if (!import_node_fs6.default.existsSync(appPath)) {
|
|
2806
3176
|
throw new Error(`.app bundle not found: ${appPath}`);
|
|
2807
3177
|
}
|
|
2808
3178
|
const bundleId = readIosBundleIdentifier(appPath);
|
|
@@ -2905,12 +3275,12 @@ async function closeIosSession(session) {
|
|
|
2905
3275
|
}
|
|
2906
3276
|
|
|
2907
3277
|
// src/runner/index.ts
|
|
2908
|
-
var
|
|
2909
|
-
var
|
|
3278
|
+
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
3279
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
2910
3280
|
|
|
2911
3281
|
// src/config/loader.ts
|
|
2912
|
-
var
|
|
2913
|
-
var
|
|
3282
|
+
var import_node_fs7 = __toESM(require("fs"), 1);
|
|
3283
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
2914
3284
|
var import_yaml = __toESM(require("yaml"), 1);
|
|
2915
3285
|
var import_dotenv = __toESM(require("dotenv"), 1);
|
|
2916
3286
|
|
|
@@ -2928,7 +3298,7 @@ function isValidHuntName(name) {
|
|
|
2928
3298
|
function assertValidHuntName(name) {
|
|
2929
3299
|
if (!isValidHuntName(name)) {
|
|
2930
3300
|
throw new Error(
|
|
2931
|
-
`Invalid hunt name: "${name}".
|
|
3301
|
+
`Invalid hunt name: "${name}". A hunt name is its file name under .prowl/hunts/ (letters, numbers, hyphens, underscores, and forward slashes for nested hunts). Accepted forms: a bare name (homepage), a nested name (admin/users), or a path (.prowl/hunts/homepage.yml or hunts/homepage.yml).`
|
|
2932
3302
|
);
|
|
2933
3303
|
}
|
|
2934
3304
|
}
|
|
@@ -3305,12 +3675,12 @@ function findConfigPath(startDir) {
|
|
|
3305
3675
|
let current = startDir;
|
|
3306
3676
|
while (current) {
|
|
3307
3677
|
for (const dir of [CONFIG_DIR, LEGACY_CONFIG_DIR]) {
|
|
3308
|
-
const candidate =
|
|
3309
|
-
if (
|
|
3678
|
+
const candidate = import_node_path8.default.join(current, dir, "config.yml");
|
|
3679
|
+
if (import_node_fs7.default.existsSync(candidate)) {
|
|
3310
3680
|
return candidate;
|
|
3311
3681
|
}
|
|
3312
3682
|
}
|
|
3313
|
-
const parent =
|
|
3683
|
+
const parent = import_node_path8.default.dirname(current);
|
|
3314
3684
|
if (parent === current) {
|
|
3315
3685
|
break;
|
|
3316
3686
|
}
|
|
@@ -3417,19 +3787,19 @@ function ensureAllowedDomain(allowed, urlValue) {
|
|
|
3417
3787
|
return allowed;
|
|
3418
3788
|
}
|
|
3419
3789
|
function loadConfig(configPath) {
|
|
3420
|
-
const resolvedPath = configPath ?
|
|
3790
|
+
const resolvedPath = configPath ? import_node_path8.default.resolve(configPath) : findConfigPath(process.cwd());
|
|
3421
3791
|
if (!resolvedPath) {
|
|
3422
3792
|
throw new Error("Could not find .prowl/config.yml. Run `prowl init` first.");
|
|
3423
3793
|
}
|
|
3424
|
-
if (!
|
|
3794
|
+
if (!import_node_fs7.default.existsSync(resolvedPath)) {
|
|
3425
3795
|
throw new Error(`Config file not found at ${resolvedPath}`);
|
|
3426
3796
|
}
|
|
3427
|
-
const configDir =
|
|
3428
|
-
if (
|
|
3797
|
+
const configDir = import_node_path8.default.dirname(resolvedPath);
|
|
3798
|
+
if (import_node_path8.default.basename(configDir) === LEGACY_CONFIG_DIR) {
|
|
3429
3799
|
warnLegacyConfigDir();
|
|
3430
3800
|
}
|
|
3431
|
-
import_dotenv.default.config({ path:
|
|
3432
|
-
const raw =
|
|
3801
|
+
import_dotenv.default.config({ path: import_node_path8.default.join(configDir, ".env"), override: false });
|
|
3802
|
+
const raw = import_node_fs7.default.readFileSync(resolvedPath, "utf-8");
|
|
3433
3803
|
const parsed = import_yaml.default.parse(raw) ?? {};
|
|
3434
3804
|
const validated = configSchema.parse(parsed);
|
|
3435
3805
|
const config = mergeConfig(validated);
|
|
@@ -3443,32 +3813,32 @@ function loadConfig(configPath) {
|
|
|
3443
3813
|
}
|
|
3444
3814
|
function loadHunt(huntName, configDir) {
|
|
3445
3815
|
assertValidHuntName(huntName);
|
|
3446
|
-
const huntPath =
|
|
3447
|
-
if (!
|
|
3816
|
+
const huntPath = import_node_path8.default.join(configDir, "hunts", `${huntName}.yml`);
|
|
3817
|
+
if (!import_node_fs7.default.existsSync(huntPath)) {
|
|
3448
3818
|
throw new Error(`Hunt file not found: ${huntPath}`);
|
|
3449
3819
|
}
|
|
3450
|
-
const raw =
|
|
3820
|
+
const raw = import_node_fs7.default.readFileSync(huntPath, "utf-8");
|
|
3451
3821
|
const parsed = import_yaml.default.parse(raw) ?? {};
|
|
3452
3822
|
const validated = huntSchema.parse(parsed);
|
|
3453
3823
|
return validated;
|
|
3454
3824
|
}
|
|
3455
3825
|
function loadHuntTags(huntName, configDir) {
|
|
3456
3826
|
assertValidHuntName(huntName);
|
|
3457
|
-
const huntPath =
|
|
3458
|
-
if (!
|
|
3827
|
+
const huntPath = import_node_path8.default.join(configDir, "hunts", `${huntName}.yml`);
|
|
3828
|
+
if (!import_node_fs7.default.existsSync(huntPath)) {
|
|
3459
3829
|
return [];
|
|
3460
3830
|
}
|
|
3461
|
-
const raw =
|
|
3831
|
+
const raw = import_node_fs7.default.readFileSync(huntPath, "utf-8");
|
|
3462
3832
|
const parsed = import_yaml.default.parse(raw) ?? {};
|
|
3463
3833
|
return Array.isArray(parsed.tags) ? parsed.tags : [];
|
|
3464
3834
|
}
|
|
3465
3835
|
function loadHuntMeta(huntName, configDir) {
|
|
3466
3836
|
assertValidHuntName(huntName);
|
|
3467
|
-
const huntPath =
|
|
3468
|
-
if (!
|
|
3837
|
+
const huntPath = import_node_path8.default.join(configDir, "hunts", `${huntName}.yml`);
|
|
3838
|
+
if (!import_node_fs7.default.existsSync(huntPath)) {
|
|
3469
3839
|
return { tags: [] };
|
|
3470
3840
|
}
|
|
3471
|
-
const raw =
|
|
3841
|
+
const raw = import_node_fs7.default.readFileSync(huntPath, "utf-8");
|
|
3472
3842
|
const parsed = import_yaml.default.parse(raw) ?? {};
|
|
3473
3843
|
return {
|
|
3474
3844
|
description: typeof parsed.description === "string" ? parsed.description : void 0,
|
|
@@ -3476,24 +3846,24 @@ function loadHuntMeta(huntName, configDir) {
|
|
|
3476
3846
|
};
|
|
3477
3847
|
}
|
|
3478
3848
|
function listHunts(configDir) {
|
|
3479
|
-
const huntsDir =
|
|
3480
|
-
if (!
|
|
3849
|
+
const huntsDir = import_node_path8.default.join(configDir, "hunts");
|
|
3850
|
+
if (!import_node_fs7.default.existsSync(huntsDir)) {
|
|
3481
3851
|
return [];
|
|
3482
3852
|
}
|
|
3483
|
-
const stats =
|
|
3853
|
+
const stats = import_node_fs7.default.statSync(huntsDir);
|
|
3484
3854
|
if (!stats.isDirectory()) {
|
|
3485
3855
|
throw new Error(`Hunts path is not a directory: ${huntsDir}`);
|
|
3486
3856
|
}
|
|
3487
3857
|
const results = [];
|
|
3488
3858
|
function scanDir(dir) {
|
|
3489
|
-
const entries =
|
|
3859
|
+
const entries = import_node_fs7.default.readdirSync(dir, { withFileTypes: true });
|
|
3490
3860
|
for (const entry of entries) {
|
|
3491
3861
|
if (entry.isFile() && entry.name.endsWith(".yml")) {
|
|
3492
|
-
const fullPath =
|
|
3493
|
-
const relative =
|
|
3862
|
+
const fullPath = import_node_path8.default.join(dir, entry.name);
|
|
3863
|
+
const relative = import_node_path8.default.relative(huntsDir, fullPath);
|
|
3494
3864
|
results.push(relative.replace(/\.yml$/, ""));
|
|
3495
3865
|
} else if (entry.isDirectory()) {
|
|
3496
|
-
scanDir(
|
|
3866
|
+
scanDir(import_node_path8.default.join(dir, entry.name));
|
|
3497
3867
|
}
|
|
3498
3868
|
}
|
|
3499
3869
|
}
|
|
@@ -3502,7 +3872,7 @@ function listHunts(configDir) {
|
|
|
3502
3872
|
}
|
|
3503
3873
|
|
|
3504
3874
|
// src/config/interpolate.ts
|
|
3505
|
-
var
|
|
3875
|
+
var import_node_crypto2 = __toESM(require("crypto"), 1);
|
|
3506
3876
|
var VAR_PATTERN = /\{\{([A-Z0-9_]+)\}\}/g;
|
|
3507
3877
|
function collectInterpolatedValues(input, vars, values) {
|
|
3508
3878
|
if (typeof input === "string") {
|
|
@@ -3541,8 +3911,8 @@ var RANDOM_FIRST_NAMES = ["Alex", "Jordan", "Morgan", "Taylor", "Casey", "Riley"
|
|
|
3541
3911
|
var RANDOM_LAST_NAMES = ["Smith", "Johnson", "Brown", "Davis", "Wilson", "Clark", "Hall", "Young"];
|
|
3542
3912
|
function generateRandomVars(randomSource) {
|
|
3543
3913
|
const random = randomSource?.random ?? Math.random;
|
|
3544
|
-
const randomBytes = randomSource?.randomBytes ??
|
|
3545
|
-
const randomUUID = randomSource?.randomUUID ??
|
|
3914
|
+
const randomBytes = randomSource?.randomBytes ?? import_node_crypto2.default.randomBytes;
|
|
3915
|
+
const randomUUID = randomSource?.randomUUID ?? import_node_crypto2.default.randomUUID;
|
|
3546
3916
|
const hex = randomBytes(4).toString("hex");
|
|
3547
3917
|
const firstIndex = Math.floor(random() * RANDOM_FIRST_NAMES.length);
|
|
3548
3918
|
const lastIndex = Math.floor(random() * RANDOM_LAST_NAMES.length);
|
|
@@ -3873,8 +4243,8 @@ function interpolateHunt(hunt, env, randomVars = generateRandomVars()) {
|
|
|
3873
4243
|
}
|
|
3874
4244
|
|
|
3875
4245
|
// src/browser/playwright-driver.ts
|
|
3876
|
-
var
|
|
3877
|
-
var
|
|
4246
|
+
var import_node_fs8 = __toESM(require("fs"), 1);
|
|
4247
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
3878
4248
|
var import_playwright = require("playwright");
|
|
3879
4249
|
var ENGINES = { chromium: import_playwright.chromium, firefox: import_playwright.firefox, webkit: import_playwright.webkit };
|
|
3880
4250
|
async function launchBrowser(options) {
|
|
@@ -3896,14 +4266,14 @@ async function launchBrowser(options) {
|
|
|
3896
4266
|
contextOptions.viewport = options.viewport;
|
|
3897
4267
|
}
|
|
3898
4268
|
if (options.storageStatePath) {
|
|
3899
|
-
if (
|
|
4269
|
+
if (import_node_fs8.default.existsSync(options.storageStatePath)) {
|
|
3900
4270
|
contextOptions.storageState = options.storageStatePath;
|
|
3901
4271
|
} else {
|
|
3902
4272
|
console.warn(`Auth state file not found: ${options.storageStatePath}. Run "prowl login" to create it.`);
|
|
3903
4273
|
}
|
|
3904
4274
|
}
|
|
3905
4275
|
if (options.recordHar) {
|
|
3906
|
-
contextOptions.recordHar = { path:
|
|
4276
|
+
contextOptions.recordHar = { path: import_node_path9.default.join(options.runDir, "network.har") };
|
|
3907
4277
|
}
|
|
3908
4278
|
const context = await browser.newContext(contextOptions);
|
|
3909
4279
|
const page = await context.newPage();
|
|
@@ -3911,7 +4281,7 @@ async function launchBrowser(options) {
|
|
|
3911
4281
|
page.setDefaultNavigationTimeout(options.timeout);
|
|
3912
4282
|
let tracePath;
|
|
3913
4283
|
if (options.trace) {
|
|
3914
|
-
tracePath =
|
|
4284
|
+
tracePath = import_node_path9.default.join(options.runDir, "trace.zip");
|
|
3915
4285
|
await context.tracing.start({ screenshots: true, snapshots: true, sources: true });
|
|
3916
4286
|
}
|
|
3917
4287
|
return { browser, context, page, tracePath };
|
|
@@ -4086,8 +4456,8 @@ function createPlaywrightDriver(page) {
|
|
|
4086
4456
|
}
|
|
4087
4457
|
|
|
4088
4458
|
// src/runner/steps.ts
|
|
4089
|
-
var
|
|
4090
|
-
var
|
|
4459
|
+
var import_node_fs10 = __toESM(require("fs"), 1);
|
|
4460
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
4091
4461
|
|
|
4092
4462
|
// src/runner/healing.ts
|
|
4093
4463
|
var INTERACTIVE_TAGS = ["button", "a", "input", "select", "textarea"];
|
|
@@ -4821,7 +5191,7 @@ async function runInlineAssert(driver, policy, assertion) {
|
|
|
4821
5191
|
throw new Error("assert step is missing an assertion type");
|
|
4822
5192
|
}
|
|
4823
5193
|
function screenshotPath(screenshotsDir, fileName) {
|
|
4824
|
-
return
|
|
5194
|
+
return import_node_path11.default.join(screenshotsDir, fileName);
|
|
4825
5195
|
}
|
|
4826
5196
|
function stepPath(prefix, index) {
|
|
4827
5197
|
return prefix ? `${prefix}.${index}` : `${index}`;
|
|
@@ -4838,7 +5208,7 @@ function validateDownloadFilename(suggestedFilename) {
|
|
|
4838
5208
|
const safeFilename = suggestedFilename.trim();
|
|
4839
5209
|
const allowedFilenamePattern = /^[^<>:"/\\|?*]+$/;
|
|
4840
5210
|
const hasControlCharacter = Array.from(safeFilename).some((char) => char.charCodeAt(0) < 32);
|
|
4841
|
-
if (safeFilename.length === 0 || safeFilename !== suggestedFilename || safeFilename !==
|
|
5211
|
+
if (safeFilename.length === 0 || safeFilename !== suggestedFilename || safeFilename !== import_node_path11.default.basename(safeFilename) || safeFilename.includes("..") || /[/\\]/.test(safeFilename) || hasControlCharacter || !allowedFilenamePattern.test(safeFilename)) {
|
|
4842
5212
|
throw new Error(`Invalid download filename: "${suggestedFilename}"`);
|
|
4843
5213
|
}
|
|
4844
5214
|
return safeFilename;
|
|
@@ -5018,7 +5388,7 @@ var STEP_HANDLERS = {
|
|
|
5018
5388
|
if (!("setInputFiles" in h.step)) unknownStep();
|
|
5019
5389
|
const resolvedInput = await h.policy.resolveActionSelector(h.step.setInputFiles.selector);
|
|
5020
5390
|
const rawFiles = h.step.setInputFiles.files;
|
|
5021
|
-
const resolveFile = (f) =>
|
|
5391
|
+
const resolveFile = (f) => import_node_path11.default.isAbsolute(f) ? f : import_node_path11.default.join(h.context.configDir, f);
|
|
5022
5392
|
const resolvedFiles = Array.isArray(rawFiles) ? rawFiles.map(resolveFile) : resolveFile(rawFiles);
|
|
5023
5393
|
await h.driver.setInputFiles(resolvedInput.selector, resolvedFiles);
|
|
5024
5394
|
h.policy.ensureLocationAllowed(h.driver);
|
|
@@ -5380,15 +5750,15 @@ var STEP_HANDLERS = {
|
|
|
5380
5750
|
if (!responseFile) {
|
|
5381
5751
|
throw new Error("mock.response must include either body or file");
|
|
5382
5752
|
}
|
|
5383
|
-
const candidateFilePath =
|
|
5384
|
-
const resolvedConfigDir =
|
|
5385
|
-
const resolvedFilePath =
|
|
5386
|
-
const relativePath =
|
|
5387
|
-
const isWithinConfigDir = relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${
|
|
5753
|
+
const candidateFilePath = import_node_path11.default.isAbsolute(responseFile) ? responseFile : import_node_path11.default.join(h.context.configDir, responseFile);
|
|
5754
|
+
const resolvedConfigDir = import_node_path11.default.resolve(h.context.configDir);
|
|
5755
|
+
const resolvedFilePath = import_node_path11.default.resolve(candidateFilePath);
|
|
5756
|
+
const relativePath = import_node_path11.default.relative(resolvedConfigDir, resolvedFilePath);
|
|
5757
|
+
const isWithinConfigDir = relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${import_node_path11.default.sep}`) && !import_node_path11.default.isAbsolute(relativePath);
|
|
5388
5758
|
if (!isWithinConfigDir) {
|
|
5389
5759
|
throw new Error("mock.response.file must resolve within config directory");
|
|
5390
5760
|
}
|
|
5391
|
-
responseBody = await
|
|
5761
|
+
responseBody = await import_node_fs10.default.promises.readFile(resolvedFilePath, "utf-8");
|
|
5392
5762
|
}
|
|
5393
5763
|
const contentType = mock.response.contentType ?? "application/json";
|
|
5394
5764
|
const status = mock.response.status;
|
|
@@ -5451,8 +5821,8 @@ var STEP_HANDLERS = {
|
|
|
5451
5821
|
capabilities: ["evaluate"],
|
|
5452
5822
|
run: async (h) => {
|
|
5453
5823
|
if (!("runScript" in h.step)) unknownStep();
|
|
5454
|
-
const filePath =
|
|
5455
|
-
const fileContents =
|
|
5824
|
+
const filePath = import_node_path11.default.isAbsolute(h.step.runScript.file) ? h.step.runScript.file : import_node_path11.default.join(h.context.configDir, h.step.runScript.file);
|
|
5825
|
+
const fileContents = import_node_fs10.default.readFileSync(filePath, "utf-8");
|
|
5456
5826
|
await h.driver.evaluate(fileContents);
|
|
5457
5827
|
return {
|
|
5458
5828
|
kind: "result",
|
|
@@ -5473,19 +5843,19 @@ var STEP_HANDLERS = {
|
|
|
5473
5843
|
const name = h.step.assertScreenshot.name;
|
|
5474
5844
|
const threshold = h.step.assertScreenshot.threshold ?? 0.1;
|
|
5475
5845
|
const baselineDir = ensureBaselineDir2(h.context.configDir);
|
|
5476
|
-
const baselinePath =
|
|
5477
|
-
const currentScreenshotPath =
|
|
5478
|
-
|
|
5846
|
+
const baselinePath = import_node_path11.default.join(baselineDir, `${name}.png`);
|
|
5847
|
+
const currentScreenshotPath = import_node_path11.default.join(h.context.runDir, "screenshots", `${name}-current.png`);
|
|
5848
|
+
import_node_fs10.default.mkdirSync(import_node_path11.default.dirname(currentScreenshotPath), { recursive: true });
|
|
5479
5849
|
await h.driver.screenshot({ path: currentScreenshotPath, fullPage: true });
|
|
5480
|
-
h.screenshots.push(
|
|
5481
|
-
if (!
|
|
5482
|
-
|
|
5850
|
+
h.screenshots.push(import_node_path11.default.join("screenshots", `${name}-current.png`));
|
|
5851
|
+
if (!import_node_fs10.default.existsSync(baselinePath)) {
|
|
5852
|
+
import_node_fs10.default.copyFileSync(currentScreenshotPath, baselinePath);
|
|
5483
5853
|
return {
|
|
5484
5854
|
kind: "result",
|
|
5485
5855
|
result: { type: "assertScreenshot", status: "pass", durationMs: Date.now() - h.stepStart, value: "baseline created" }
|
|
5486
5856
|
};
|
|
5487
5857
|
}
|
|
5488
|
-
const diffPath =
|
|
5858
|
+
const diffPath = import_node_path11.default.join(h.context.runDir, "screenshots", `${name}-diff.png`);
|
|
5489
5859
|
const comparison = await compareScreenshots2(baselinePath, currentScreenshotPath, diffPath, threshold);
|
|
5490
5860
|
if (comparison.match) {
|
|
5491
5861
|
return {
|
|
@@ -5498,7 +5868,7 @@ var STEP_HANDLERS = {
|
|
|
5498
5868
|
}
|
|
5499
5869
|
};
|
|
5500
5870
|
}
|
|
5501
|
-
h.screenshots.push(
|
|
5871
|
+
h.screenshots.push(import_node_path11.default.join("screenshots", `${name}-diff.png`));
|
|
5502
5872
|
throw new Error(
|
|
5503
5873
|
`Visual regression: ${(comparison.diffPercentage * 100).toFixed(2)}% diff exceeds threshold ${(threshold * 100).toFixed(0)}%`
|
|
5504
5874
|
);
|
|
@@ -5524,8 +5894,8 @@ var STEP_HANDLERS = {
|
|
|
5524
5894
|
}
|
|
5525
5895
|
const fileName = `assertWithAI_step_${h.index + 1}.png`;
|
|
5526
5896
|
const relative = await h.addScreenshot(fileName);
|
|
5527
|
-
const screenshotFullPath =
|
|
5528
|
-
const imageBase64 =
|
|
5897
|
+
const screenshotFullPath = import_node_path11.default.join(h.context.runDir, relative);
|
|
5898
|
+
const imageBase64 = import_node_fs10.default.readFileSync(screenshotFullPath).toString("base64");
|
|
5529
5899
|
const assertVision = h.context.assertVision ?? assertWithAiVision;
|
|
5530
5900
|
const verdict = await assertVision(
|
|
5531
5901
|
{ imageBase64, mediaType: "image/png", assertion },
|
|
@@ -5582,7 +5952,7 @@ var STEP_HANDLERS = {
|
|
|
5582
5952
|
`Download filename mismatch: expected "${opts.filename}", got "${suggestedFilename}"`
|
|
5583
5953
|
);
|
|
5584
5954
|
}
|
|
5585
|
-
const savePath =
|
|
5955
|
+
const savePath = import_node_path11.default.join(h.context.runDir, suggestedFilename);
|
|
5586
5956
|
await download.saveAs(savePath);
|
|
5587
5957
|
return {
|
|
5588
5958
|
kind: "result",
|
|
@@ -5611,8 +5981,8 @@ async function executeSteps(context) {
|
|
|
5611
5981
|
maxSteps: context.maxSteps,
|
|
5612
5982
|
selfHealing: context.selfHealing
|
|
5613
5983
|
});
|
|
5614
|
-
const screenshotsDir =
|
|
5615
|
-
|
|
5984
|
+
const screenshotsDir = import_node_path11.default.join(context.runDir, "screenshots");
|
|
5985
|
+
import_node_fs10.default.mkdirSync(screenshotsDir, { recursive: true });
|
|
5616
5986
|
const currentHuntName = context.huntStack?.[context.huntStack.length - 1];
|
|
5617
5987
|
policy.assertWithinMaxSteps(context.steps.length, currentHuntName);
|
|
5618
5988
|
const results = [];
|
|
@@ -5622,7 +5992,7 @@ async function executeSteps(context) {
|
|
|
5622
5992
|
const addScreenshot = async (fileName) => {
|
|
5623
5993
|
const fullPath = screenshotPath(screenshotsDir, fileName);
|
|
5624
5994
|
await captureScreenshot2(driver, fullPath);
|
|
5625
|
-
const relative =
|
|
5995
|
+
const relative = import_node_path11.default.join("screenshots", fileName);
|
|
5626
5996
|
screenshots.push(relative);
|
|
5627
5997
|
return relative;
|
|
5628
5998
|
};
|
|
@@ -5710,12 +6080,12 @@ async function executeSteps(context) {
|
|
|
5710
6080
|
return { results, screenshots, failed: false };
|
|
5711
6081
|
}
|
|
5712
6082
|
async function captureFinalScreenshot(page, runDir) {
|
|
5713
|
-
const screenshotsDir =
|
|
5714
|
-
|
|
6083
|
+
const screenshotsDir = import_node_path11.default.join(runDir, "screenshots");
|
|
6084
|
+
import_node_fs10.default.mkdirSync(screenshotsDir, { recursive: true });
|
|
5715
6085
|
const fileName = "final.png";
|
|
5716
6086
|
const filePath = screenshotPath(screenshotsDir, fileName);
|
|
5717
6087
|
await captureScreenshot2(page, filePath);
|
|
5718
|
-
return
|
|
6088
|
+
return import_node_path11.default.join("screenshots", fileName);
|
|
5719
6089
|
}
|
|
5720
6090
|
|
|
5721
6091
|
// src/runner/assertions.ts
|
|
@@ -5837,6 +6207,59 @@ async function evaluateAssertions(options) {
|
|
|
5837
6207
|
}
|
|
5838
6208
|
return results;
|
|
5839
6209
|
}
|
|
6210
|
+
async function evaluateNativeAssertions(options) {
|
|
6211
|
+
const assertions = mergeAssertions(options.config, options.huntAssertions);
|
|
6212
|
+
const authoredTypes = new Set(
|
|
6213
|
+
(options.huntAssertions ?? []).map((assertion) => Object.keys(assertion)[0])
|
|
6214
|
+
);
|
|
6215
|
+
const results = [];
|
|
6216
|
+
const warnings = [];
|
|
6217
|
+
for (const assertion of assertions) {
|
|
6218
|
+
const type = Object.keys(assertion)[0] ?? "assertion";
|
|
6219
|
+
try {
|
|
6220
|
+
if ("selectorExists" in assertion) {
|
|
6221
|
+
options.assertAllowedSelector?.(assertion.selectorExists);
|
|
6222
|
+
const count = await options.driver.count(assertion.selectorExists);
|
|
6223
|
+
results.push({
|
|
6224
|
+
type: "selectorExists",
|
|
6225
|
+
value: assertion.selectorExists,
|
|
6226
|
+
status: count > 0 ? "pass" : "fail",
|
|
6227
|
+
error: count > 0 ? void 0 : "Selector not found"
|
|
6228
|
+
});
|
|
6229
|
+
continue;
|
|
6230
|
+
}
|
|
6231
|
+
if ("selectorNotExists" in assertion) {
|
|
6232
|
+
options.assertAllowedSelector?.(assertion.selectorNotExists);
|
|
6233
|
+
const count = await options.driver.count(assertion.selectorNotExists);
|
|
6234
|
+
results.push({
|
|
6235
|
+
type: "selectorNotExists",
|
|
6236
|
+
value: assertion.selectorNotExists,
|
|
6237
|
+
status: count === 0 ? "pass" : "fail",
|
|
6238
|
+
error: count === 0 ? void 0 : "Selector exists"
|
|
6239
|
+
});
|
|
6240
|
+
continue;
|
|
6241
|
+
}
|
|
6242
|
+
const rawValue = assertion[type];
|
|
6243
|
+
results.push({
|
|
6244
|
+
type,
|
|
6245
|
+
value: rawValue,
|
|
6246
|
+
status: "skipped",
|
|
6247
|
+
error: `skipped (web-only): not supported on the ${options.targetLabel} target`
|
|
6248
|
+
});
|
|
6249
|
+
if (authoredTypes.has(type)) {
|
|
6250
|
+
warnings.push(`${type} is web-only; skipped on ${options.targetLabel} target`);
|
|
6251
|
+
}
|
|
6252
|
+
} catch (error) {
|
|
6253
|
+
const detail = error instanceof Error ? error.message : "no error details";
|
|
6254
|
+
results.push({
|
|
6255
|
+
type,
|
|
6256
|
+
status: "fail",
|
|
6257
|
+
error: `Native assertion "${type}" failed: ${detail}`
|
|
6258
|
+
});
|
|
6259
|
+
}
|
|
6260
|
+
}
|
|
6261
|
+
return { results, warnings };
|
|
6262
|
+
}
|
|
5840
6263
|
|
|
5841
6264
|
// src/runner/tracing.ts
|
|
5842
6265
|
var DEFAULT_TRACE_HEADER = "traceparent";
|
|
@@ -5875,18 +6298,18 @@ function captureTraceCorrelation(response, headerName, sink, redactionValues = [
|
|
|
5875
6298
|
}
|
|
5876
6299
|
|
|
5877
6300
|
// src/reporter/result.ts
|
|
5878
|
-
var
|
|
5879
|
-
var
|
|
6301
|
+
var import_node_fs11 = __toESM(require("fs"), 1);
|
|
6302
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
5880
6303
|
function writeResult(runDir, result) {
|
|
5881
6304
|
const fileName = "result.json";
|
|
5882
|
-
const fullPath =
|
|
5883
|
-
|
|
6305
|
+
const fullPath = import_node_path12.default.join(runDir, fileName);
|
|
6306
|
+
import_node_fs11.default.writeFileSync(fullPath, JSON.stringify(result, null, 2));
|
|
5884
6307
|
return fileName;
|
|
5885
6308
|
}
|
|
5886
6309
|
|
|
5887
6310
|
// src/reporter/summary.ts
|
|
5888
|
-
var
|
|
5889
|
-
var
|
|
6311
|
+
var import_node_fs12 = __toESM(require("fs"), 1);
|
|
6312
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
5890
6313
|
function escapeMd(text) {
|
|
5891
6314
|
return text.replace(/([|`*_{}[\]()#+\-!\\])/g, "\\$1");
|
|
5892
6315
|
}
|
|
@@ -5964,28 +6387,29 @@ function writeSummary(runDir, result) {
|
|
|
5964
6387
|
}
|
|
5965
6388
|
}
|
|
5966
6389
|
const fileName = "summary.md";
|
|
5967
|
-
const fullPath =
|
|
5968
|
-
|
|
6390
|
+
const fullPath = import_node_path13.default.join(runDir, fileName);
|
|
6391
|
+
import_node_fs12.default.writeFileSync(fullPath, `${lines.join("\n")}
|
|
5969
6392
|
`);
|
|
5970
6393
|
return fileName;
|
|
5971
6394
|
}
|
|
5972
6395
|
|
|
5973
6396
|
// src/reporter/junit.ts
|
|
5974
|
-
var
|
|
5975
|
-
var
|
|
6397
|
+
var import_node_fs13 = __toESM(require("fs"), 1);
|
|
6398
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
5976
6399
|
function escapeXml(text) {
|
|
5977
6400
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5978
6401
|
}
|
|
5979
6402
|
function writeJunit(runDir, result) {
|
|
5980
6403
|
const totalTests = result.steps.length + result.assertions.length;
|
|
5981
6404
|
const failures = result.steps.filter((s) => s.status === "fail").length + result.assertions.filter((a) => a.status === "fail").length;
|
|
6405
|
+
const skipped = result.assertions.filter((a) => a.status === "skipped").length;
|
|
5982
6406
|
const timeSeconds = (result.durationMs / 1e3).toFixed(3);
|
|
5983
6407
|
const huntName = escapeXml(result.hunt);
|
|
5984
6408
|
const lines = [];
|
|
5985
6409
|
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
5986
6410
|
lines.push("<testsuites>");
|
|
5987
6411
|
lines.push(
|
|
5988
|
-
` <testsuite name="${huntName}" tests="${totalTests}" failures="${failures}" errors="0" time="${timeSeconds}" timestamp="${escapeXml(result.startedAt)}">`
|
|
6412
|
+
` <testsuite name="${huntName}" tests="${totalTests}" failures="${failures}" errors="0" skipped="${skipped}" time="${timeSeconds}" timestamp="${escapeXml(result.startedAt)}">`
|
|
5989
6413
|
);
|
|
5990
6414
|
for (let i = 0; i < result.steps.length; i++) {
|
|
5991
6415
|
const step = result.steps[i];
|
|
@@ -6009,6 +6433,11 @@ function writeJunit(runDir, result) {
|
|
|
6009
6433
|
lines.push(` <testcase name="${caseName}" classname="${huntName}" time="0">`);
|
|
6010
6434
|
lines.push(` <failure message="${escapedFailureText}" type="assertion">${escapedFailureText}</failure>`);
|
|
6011
6435
|
lines.push(" </testcase>");
|
|
6436
|
+
} else if (assertion.status === "skipped") {
|
|
6437
|
+
const skipText = escapeXml(assertion.error ?? "skipped");
|
|
6438
|
+
lines.push(` <testcase name="${caseName}" classname="${huntName}" time="0">`);
|
|
6439
|
+
lines.push(` <skipped message="${skipText}"/>`);
|
|
6440
|
+
lines.push(" </testcase>");
|
|
6012
6441
|
} else {
|
|
6013
6442
|
lines.push(` <testcase name="${caseName}" classname="${huntName}" time="0"/>`);
|
|
6014
6443
|
}
|
|
@@ -6016,8 +6445,8 @@ function writeJunit(runDir, result) {
|
|
|
6016
6445
|
lines.push(" </testsuite>");
|
|
6017
6446
|
lines.push("</testsuites>");
|
|
6018
6447
|
const fileName = "junit.xml";
|
|
6019
|
-
const fullPath =
|
|
6020
|
-
|
|
6448
|
+
const fullPath = import_node_path14.default.join(runDir, fileName);
|
|
6449
|
+
import_node_fs13.default.writeFileSync(fullPath, `${lines.join("\n")}
|
|
6021
6450
|
`);
|
|
6022
6451
|
return fileName;
|
|
6023
6452
|
}
|
|
@@ -6051,15 +6480,15 @@ function timestamp(prefix) {
|
|
|
6051
6480
|
}
|
|
6052
6481
|
|
|
6053
6482
|
// src/runner/history.ts
|
|
6054
|
-
var
|
|
6055
|
-
var
|
|
6483
|
+
var import_node_fs14 = __toESM(require("fs"), 1);
|
|
6484
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
6056
6485
|
var HISTORY_FILE = "history.json";
|
|
6057
6486
|
var LOCK_FILE_SUFFIX = ".lock";
|
|
6058
6487
|
var LOCK_RETRY_MS = 10;
|
|
6059
6488
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
6060
6489
|
var SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
|
|
6061
6490
|
function historyPath(configDir) {
|
|
6062
|
-
return
|
|
6491
|
+
return import_node_path15.default.join(configDir, HISTORY_FILE);
|
|
6063
6492
|
}
|
|
6064
6493
|
function isHistoryEntry(value) {
|
|
6065
6494
|
if (!value || typeof value !== "object") {
|
|
@@ -6070,11 +6499,11 @@ function isHistoryEntry(value) {
|
|
|
6070
6499
|
}
|
|
6071
6500
|
function readHistory(configDir) {
|
|
6072
6501
|
const filePath = historyPath(configDir);
|
|
6073
|
-
if (!
|
|
6502
|
+
if (!import_node_fs14.default.existsSync(filePath)) {
|
|
6074
6503
|
return { entries: [] };
|
|
6075
6504
|
}
|
|
6076
6505
|
try {
|
|
6077
|
-
const raw =
|
|
6506
|
+
const raw = import_node_fs14.default.readFileSync(filePath, "utf-8");
|
|
6078
6507
|
const parsed = JSON.parse(raw);
|
|
6079
6508
|
if (parsed && typeof parsed === "object" && "entries" in parsed && Array.isArray(parsed.entries)) {
|
|
6080
6509
|
const validatedEntries = parsed.entries.filter(isHistoryEntry);
|
|
@@ -6113,12 +6542,12 @@ function sleepSync(ms) {
|
|
|
6113
6542
|
function withHistoryLock(configDir, fn) {
|
|
6114
6543
|
const filePath = historyPath(configDir);
|
|
6115
6544
|
const lockPath = `${filePath}${LOCK_FILE_SUFFIX}`;
|
|
6116
|
-
|
|
6545
|
+
import_node_fs14.default.mkdirSync(import_node_path15.default.dirname(filePath), { recursive: true });
|
|
6117
6546
|
const startedAt = Date.now();
|
|
6118
6547
|
while (Date.now() - startedAt < LOCK_TIMEOUT_MS) {
|
|
6119
6548
|
let fd;
|
|
6120
6549
|
try {
|
|
6121
|
-
fd =
|
|
6550
|
+
fd = import_node_fs14.default.openSync(lockPath, "wx");
|
|
6122
6551
|
} catch (error) {
|
|
6123
6552
|
if (error.code === "EEXIST") {
|
|
6124
6553
|
sleepSync(LOCK_RETRY_MS);
|
|
@@ -6130,10 +6559,10 @@ function withHistoryLock(configDir, fn) {
|
|
|
6130
6559
|
return fn();
|
|
6131
6560
|
} finally {
|
|
6132
6561
|
try {
|
|
6133
|
-
|
|
6562
|
+
import_node_fs14.default.closeSync(fd);
|
|
6134
6563
|
} catch {
|
|
6135
6564
|
}
|
|
6136
|
-
|
|
6565
|
+
import_node_fs14.default.rmSync(lockPath, { force: true });
|
|
6137
6566
|
}
|
|
6138
6567
|
}
|
|
6139
6568
|
throw new Error(
|
|
@@ -6146,9 +6575,9 @@ function appendEntry(configDir, entry, maxRuns) {
|
|
|
6146
6575
|
const current = readHistory(configDir);
|
|
6147
6576
|
const next = pruneEntries([...current.entries, entry], maxRuns);
|
|
6148
6577
|
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
6149
|
-
|
|
6578
|
+
import_node_fs14.default.writeFileSync(tempPath, `${JSON.stringify({ entries: next }, null, 2)}
|
|
6150
6579
|
`);
|
|
6151
|
-
|
|
6580
|
+
import_node_fs14.default.renameSync(tempPath, filePath);
|
|
6152
6581
|
});
|
|
6153
6582
|
}
|
|
6154
6583
|
|
|
@@ -6161,11 +6590,11 @@ function parseViewportFlag(value) {
|
|
|
6161
6590
|
return value;
|
|
6162
6591
|
}
|
|
6163
6592
|
function resolvePath(configDir, inputPath) {
|
|
6164
|
-
if (
|
|
6593
|
+
if (import_node_path16.default.isAbsolute(inputPath)) {
|
|
6165
6594
|
return inputPath;
|
|
6166
6595
|
}
|
|
6167
|
-
const projectRoot =
|
|
6168
|
-
return
|
|
6596
|
+
const projectRoot = import_node_path16.default.dirname(configDir);
|
|
6597
|
+
return import_node_path16.default.join(projectRoot, inputPath);
|
|
6169
6598
|
}
|
|
6170
6599
|
function buildRunResult(options) {
|
|
6171
6600
|
return {
|
|
@@ -6184,12 +6613,12 @@ function buildRunResult(options) {
|
|
|
6184
6613
|
}
|
|
6185
6614
|
function writeConsoleLog(runDir, entries) {
|
|
6186
6615
|
const fileName = "console.log";
|
|
6187
|
-
const filePath =
|
|
6616
|
+
const filePath = import_node_path16.default.join(runDir, fileName);
|
|
6188
6617
|
const lines = entries.map((entry) => {
|
|
6189
6618
|
const location = entry.location ? ` (${entry.location})` : "";
|
|
6190
6619
|
return `[${entry.type}] ${entry.text}${location}`;
|
|
6191
6620
|
});
|
|
6192
|
-
|
|
6621
|
+
import_node_fs15.default.writeFileSync(filePath, `${lines.join("\n")}
|
|
6193
6622
|
`);
|
|
6194
6623
|
return fileName;
|
|
6195
6624
|
}
|
|
@@ -6197,8 +6626,8 @@ async function executeHuntAttempt(options, config, configDir, interpolatedHunt,
|
|
|
6197
6626
|
const headless = options.headed ? false : config.browser.headless;
|
|
6198
6627
|
const slowMo = options.slowMo ?? config.browser.slowMo;
|
|
6199
6628
|
const maxSteps = config.guardrails.maxSteps;
|
|
6200
|
-
const runDir =
|
|
6201
|
-
|
|
6629
|
+
const runDir = import_node_path16.default.join(configDir, "runs", timestamp());
|
|
6630
|
+
import_node_fs15.default.mkdirSync(runDir, { recursive: true });
|
|
6202
6631
|
const storageStatePath = config.auth.storageStatePath ? resolvePath(configDir, config.auth.storageStatePath) : void 0;
|
|
6203
6632
|
const engine = options.browser ?? config.browser.engine;
|
|
6204
6633
|
const channel = options.channel ?? config.browser.channel;
|
|
@@ -6381,8 +6810,8 @@ async function runHunt(options) {
|
|
|
6381
6810
|
}
|
|
6382
6811
|
async function executeNativeHuntAttempt(options, config, configDir, interpolatedHunt, redactedFillSteps, randomVars, allowedApps, native) {
|
|
6383
6812
|
const maxSteps = config.guardrails.maxSteps;
|
|
6384
|
-
const runDir =
|
|
6385
|
-
|
|
6813
|
+
const runDir = import_node_path16.default.join(configDir, "runs", timestamp());
|
|
6814
|
+
import_node_fs15.default.mkdirSync(runDir, { recursive: true });
|
|
6386
6815
|
const session = await native.launchSession();
|
|
6387
6816
|
let result;
|
|
6388
6817
|
try {
|
|
@@ -6390,6 +6819,13 @@ async function executeNativeHuntAttempt(options, config, configDir, interpolated
|
|
|
6390
6819
|
const appIdentity = native.sessionAppIdentity(session);
|
|
6391
6820
|
const targetLabel = `${native.targetType}:${appIdentity}`;
|
|
6392
6821
|
const effectiveAllowedApps = [.../* @__PURE__ */ new Set([...allowedApps, native.targetApp, appIdentity])];
|
|
6822
|
+
const assertionPolicy = createRunPolicy(driver, {
|
|
6823
|
+
forbiddenSelectors: config.guardrails.forbiddenSelectors,
|
|
6824
|
+
allowedDomains: [],
|
|
6825
|
+
allowedApps: effectiveAllowedApps,
|
|
6826
|
+
maxSteps,
|
|
6827
|
+
selfHealing: config.guardrails.selfHealing
|
|
6828
|
+
});
|
|
6393
6829
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6394
6830
|
const startTime = Date.now();
|
|
6395
6831
|
let stepResults = [];
|
|
@@ -6429,8 +6865,19 @@ async function executeNativeHuntAttempt(options, config, configDir, interpolated
|
|
|
6429
6865
|
} catch {
|
|
6430
6866
|
finalScreenshot = void 0;
|
|
6431
6867
|
}
|
|
6868
|
+
const { results: assertionResults, warnings: assertionWarnings } = await evaluateNativeAssertions({
|
|
6869
|
+
driver,
|
|
6870
|
+
config,
|
|
6871
|
+
huntAssertions: interpolatedHunt.assertions,
|
|
6872
|
+
assertAllowedSelector: assertionPolicy.assertAllowedSelector,
|
|
6873
|
+
targetLabel: nativeTargetLabel(native.targetType)
|
|
6874
|
+
});
|
|
6875
|
+
for (const warning of assertionWarnings) {
|
|
6876
|
+
console.warn(warning);
|
|
6877
|
+
}
|
|
6432
6878
|
const durationMs = Date.now() - startTime;
|
|
6433
|
-
const
|
|
6879
|
+
const assertionsFailed = assertionResults.some((assertion) => assertion.status === "fail");
|
|
6880
|
+
const status = stepFailed || assertionsFailed ? "fail" : "pass";
|
|
6434
6881
|
const artifacts = {
|
|
6435
6882
|
screenshots: finalScreenshot ? [...stepScreenshots, finalScreenshot] : stepScreenshots
|
|
6436
6883
|
};
|
|
@@ -6441,7 +6888,7 @@ async function executeNativeHuntAttempt(options, config, configDir, interpolated
|
|
|
6441
6888
|
hunt: options.huntName,
|
|
6442
6889
|
targetUrl: targetLabel,
|
|
6443
6890
|
steps: stepResults,
|
|
6444
|
-
assertions:
|
|
6891
|
+
assertions: assertionResults,
|
|
6445
6892
|
artifacts
|
|
6446
6893
|
});
|
|
6447
6894
|
result = writeReports(runDir, runResult, { junit: options.junit ?? config.artifacts.junit });
|
|
@@ -6554,7 +7001,6 @@ async function runNativeHunt(options, config, configDir, target, native) {
|
|
|
6554
7001
|
const hunt = loadHunt(options.huntName, configDir);
|
|
6555
7002
|
const { hunt: interpolatedHunt, redactedFillSteps, randomVars } = interpolateHunt(hunt, process.env);
|
|
6556
7003
|
assertStepsSupportedByTarget(interpolatedHunt.steps, native.targetType);
|
|
6557
|
-
assertHuntAssertionsSupportedByTarget(interpolatedHunt.assertions, native.targetType);
|
|
6558
7004
|
native.assertAppAllowed(config.guardrails.allowedApps, target);
|
|
6559
7005
|
const maxSteps = config.guardrails.maxSteps;
|
|
6560
7006
|
if (interpolatedHunt.steps.length > maxSteps) {
|
|
@@ -6595,7 +7041,7 @@ async function runNativeHunt(options, config, configDir, target, native) {
|
|
|
6595
7041
|
}
|
|
6596
7042
|
function recordHistory(configDir, outcome, maxRuns) {
|
|
6597
7043
|
try {
|
|
6598
|
-
const relativeRunDir =
|
|
7044
|
+
const relativeRunDir = import_node_path16.default.relative(configDir, outcome.runDir);
|
|
6599
7045
|
appendEntry(
|
|
6600
7046
|
configDir,
|
|
6601
7047
|
{
|
|
@@ -6612,11 +7058,11 @@ function recordHistory(configDir, outcome, maxRuns) {
|
|
|
6612
7058
|
}
|
|
6613
7059
|
|
|
6614
7060
|
// src/runner/suite.ts
|
|
6615
|
-
var
|
|
7061
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
6616
7062
|
|
|
6617
7063
|
// src/reporter/ci-summary.ts
|
|
6618
|
-
var
|
|
6619
|
-
var
|
|
7064
|
+
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
7065
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
6620
7066
|
var import_chalk = __toESM(require("chalk"), 1);
|
|
6621
7067
|
function countCiResults(results) {
|
|
6622
7068
|
return {
|
|
@@ -6646,9 +7092,9 @@ function writeCiResult(ciRunDir, results, startedAt, totalDurationMs, flaky = []
|
|
|
6646
7092
|
...flaky.length > 0 ? { flaky } : {},
|
|
6647
7093
|
...clusters.length > 0 ? { clusters } : {}
|
|
6648
7094
|
};
|
|
6649
|
-
|
|
6650
|
-
const filePath =
|
|
6651
|
-
|
|
7095
|
+
import_node_fs16.default.mkdirSync(ciRunDir, { recursive: true });
|
|
7096
|
+
const filePath = import_node_path17.default.join(ciRunDir, "ci-result.json");
|
|
7097
|
+
import_node_fs16.default.writeFileSync(filePath, JSON.stringify(ciResult, null, 2) + "\n");
|
|
6652
7098
|
return filePath;
|
|
6653
7099
|
}
|
|
6654
7100
|
|
|
@@ -6715,7 +7161,7 @@ function rankFlaky(configDir, options = {}) {
|
|
|
6715
7161
|
}
|
|
6716
7162
|
|
|
6717
7163
|
// src/backlog/fingerprint.ts
|
|
6718
|
-
var
|
|
7164
|
+
var import_node_crypto3 = require("crypto");
|
|
6719
7165
|
function normalizeError(error) {
|
|
6720
7166
|
return error.toLowerCase().replace(/0x[0-9a-f]+/g, "").replace(/\b\d+(?:\.\d+)?\s*(?:ms|s|px)\b/g, "").replace(/\s+/g, " ").trim();
|
|
6721
7167
|
}
|
|
@@ -6738,7 +7184,7 @@ function computeFingerprint(failure) {
|
|
|
6738
7184
|
failure.selector ?? "",
|
|
6739
7185
|
normalizeError(failure.error)
|
|
6740
7186
|
];
|
|
6741
|
-
return (0,
|
|
7187
|
+
return (0, import_node_crypto3.createHash)("sha1").update(parts.join("|")).digest("hex").slice(0, 8);
|
|
6742
7188
|
}
|
|
6743
7189
|
function buildMarker(failure, hash) {
|
|
6744
7190
|
const hunt = sanitizeMarkerValue(failure.hunt);
|
|
@@ -6782,8 +7228,8 @@ function clusterFailures(failures) {
|
|
|
6782
7228
|
}
|
|
6783
7229
|
|
|
6784
7230
|
// src/backlog/index.ts
|
|
6785
|
-
var
|
|
6786
|
-
var
|
|
7231
|
+
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
7232
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
6787
7233
|
|
|
6788
7234
|
// src/backlog/parse.ts
|
|
6789
7235
|
var MARKER_FP = /<!--\s*prowl:fp=([0-9a-f]+)/;
|
|
@@ -6873,7 +7319,7 @@ ${after}`;
|
|
|
6873
7319
|
// src/backlog/index.ts
|
|
6874
7320
|
function readFileOrEmpty(filePath) {
|
|
6875
7321
|
try {
|
|
6876
|
-
return
|
|
7322
|
+
return import_node_fs17.default.readFileSync(filePath, "utf-8");
|
|
6877
7323
|
} catch (error) {
|
|
6878
7324
|
const err = error;
|
|
6879
7325
|
if (err.code === "ENOENT") return "";
|
|
@@ -6889,7 +7335,7 @@ function buildFailure(hunt) {
|
|
|
6889
7335
|
if (!hunt.runDir) return failure;
|
|
6890
7336
|
let run;
|
|
6891
7337
|
try {
|
|
6892
|
-
const resultJson = readFileOrEmpty(
|
|
7338
|
+
const resultJson = readFileOrEmpty(import_node_path18.default.join(hunt.runDir, "result.json"));
|
|
6893
7339
|
if (!resultJson) return failure;
|
|
6894
7340
|
run = JSON.parse(resultJson);
|
|
6895
7341
|
} catch (error) {
|
|
@@ -6918,8 +7364,8 @@ function extractFailures(suiteResult) {
|
|
|
6918
7364
|
}
|
|
6919
7365
|
function updateBacklogFromSuite(suiteResult, options = {}) {
|
|
6920
7366
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
6921
|
-
const backlogPath = options.backlogPath ??
|
|
6922
|
-
const resolvedPath = options.resolvedPath ??
|
|
7367
|
+
const backlogPath = options.backlogPath ?? import_node_path18.default.join(projectRoot, "docs", "backlog.md");
|
|
7368
|
+
const resolvedPath = options.resolvedPath ?? import_node_path18.default.join(projectRoot, "docs", "resolved.md");
|
|
6923
7369
|
const date = options.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6924
7370
|
const summary = { created: [], regressions: [], skipped: [], backlogPath };
|
|
6925
7371
|
const failures = extractFailures(suiteResult);
|
|
@@ -6951,8 +7397,8 @@ function updateBacklogFromSuite(suiteResult, options = {}) {
|
|
|
6951
7397
|
}
|
|
6952
7398
|
}
|
|
6953
7399
|
if (ticketsToAdd.length > 0) {
|
|
6954
|
-
|
|
6955
|
-
|
|
7400
|
+
import_node_fs17.default.mkdirSync(import_node_path18.default.dirname(backlogPath), { recursive: true });
|
|
7401
|
+
import_node_fs17.default.writeFileSync(backlogPath, insertTickets(backlogContent, ticketsToAdd));
|
|
6956
7402
|
}
|
|
6957
7403
|
return summary;
|
|
6958
7404
|
}
|
|
@@ -7113,7 +7559,7 @@ async function runSuite(options = {}) {
|
|
|
7113
7559
|
const clusters = clusterFailures(
|
|
7114
7560
|
extractFailures({ result: { hunts: results }, resultPath: null })
|
|
7115
7561
|
).filter((cluster) => cluster.count > 1);
|
|
7116
|
-
const ciRunDir =
|
|
7562
|
+
const ciRunDir = import_node_path19.default.join(configDir, "runs", timestamp("ci"));
|
|
7117
7563
|
const resultPath = writeCiResult(ciRunDir, results, startedAt, totalDurationMs, flaky, clusters);
|
|
7118
7564
|
const { passed, failed, skipped } = countCiResults(results);
|
|
7119
7565
|
return {
|
|
@@ -7798,11 +8244,17 @@ async function generateHunt(options) {
|
|
|
7798
8244
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
7799
8245
|
DEFAULT_SIMULATOR_LOCK_ROOT,
|
|
7800
8246
|
DEFAULT_WDA_REQUEST_TIMEOUT_MS,
|
|
8247
|
+
HELPER_BINARY,
|
|
7801
8248
|
INTERACTIVE_ROLES,
|
|
7802
8249
|
IOS_INTERACTIVE_TYPES,
|
|
7803
8250
|
IOS_MATCH_DIALECT,
|
|
7804
8251
|
IOS_PRESS_KEYS,
|
|
7805
8252
|
IOS_WINDOW_TYPE,
|
|
8253
|
+
MACDRIVER_REPO,
|
|
8254
|
+
MACDRIVER_SIGNING_AUTHORITY_PREFIX,
|
|
8255
|
+
MACDRIVER_SIGNING_IDENTIFIER,
|
|
8256
|
+
MACDRIVER_VERSION,
|
|
8257
|
+
MACDRIVER_VERSION_PATTERN,
|
|
7806
8258
|
MACOS_MATCH_DIALECT,
|
|
7807
8259
|
NATIVE_ATTRIBUTE_MAP,
|
|
7808
8260
|
SpawnMacHelperClient,
|
|
@@ -7832,6 +8284,8 @@ async function generateHunt(options) {
|
|
|
7832
8284
|
closeIosSession,
|
|
7833
8285
|
closeMacSession,
|
|
7834
8286
|
clusterFailures,
|
|
8287
|
+
codesignVerifier,
|
|
8288
|
+
collectMacdriverStatus,
|
|
7835
8289
|
computeFlakeScore,
|
|
7836
8290
|
configSchema,
|
|
7837
8291
|
createAndroidDriver,
|
|
@@ -7845,6 +8299,8 @@ async function generateHunt(options) {
|
|
|
7845
8299
|
defaultAgentConnector,
|
|
7846
8300
|
defaultIosAgentConnector,
|
|
7847
8301
|
defaultWdaTestRunPreparer,
|
|
8302
|
+
dittoExtractor,
|
|
8303
|
+
downloadAndVerify,
|
|
7848
8304
|
escapePredicateArg,
|
|
7849
8305
|
escapeUiSelectorArg,
|
|
7850
8306
|
extractElementId,
|
|
@@ -7855,6 +8311,7 @@ async function generateHunt(options) {
|
|
|
7855
8311
|
healSelector,
|
|
7856
8312
|
huntSchema,
|
|
7857
8313
|
injectUsePortIntoXctestrun,
|
|
8314
|
+
installMacdriver,
|
|
7858
8315
|
interpolateHunt,
|
|
7859
8316
|
iosAppAllowedIdentities,
|
|
7860
8317
|
iosNodeToNative,
|
|
@@ -7871,7 +8328,14 @@ async function generateHunt(options) {
|
|
|
7871
8328
|
loadHunt,
|
|
7872
8329
|
loadHuntMeta,
|
|
7873
8330
|
loadHuntTags,
|
|
8331
|
+
macdriverAssetName,
|
|
8332
|
+
macdriverAssetUrl,
|
|
7874
8333
|
macdriverBuildInstructions,
|
|
8334
|
+
macdriverChecksumName,
|
|
8335
|
+
macdriverInstallRoot,
|
|
8336
|
+
macdriverInstalledBinary,
|
|
8337
|
+
macdriverReleaseTag,
|
|
8338
|
+
macdriverVersionDir,
|
|
7875
8339
|
matchAndroidSelector,
|
|
7876
8340
|
matchIosSelector,
|
|
7877
8341
|
matchNativeTree,
|
|
@@ -7881,6 +8345,8 @@ async function generateHunt(options) {
|
|
|
7881
8345
|
parseAdbDevices,
|
|
7882
8346
|
parseAndroidHierarchy,
|
|
7883
8347
|
parseAndroidSelector,
|
|
8348
|
+
parseChecksumFile,
|
|
8349
|
+
parseCodesignDetails,
|
|
7884
8350
|
parseForwardPort,
|
|
7885
8351
|
parseIosHierarchy,
|
|
7886
8352
|
parseIosSelector,
|
|
@@ -7907,19 +8373,26 @@ async function generateHunt(options) {
|
|
|
7907
8373
|
resolveWdaTestRun,
|
|
7908
8374
|
runHunt,
|
|
7909
8375
|
runSuite,
|
|
8376
|
+
runVersionProbe,
|
|
7910
8377
|
selectDeviceSerial,
|
|
7911
8378
|
selectSimulatorUdid,
|
|
8379
|
+
sha256Hex,
|
|
7912
8380
|
shortIosType,
|
|
7913
8381
|
stepSchema,
|
|
8382
|
+
tccGuidance,
|
|
7914
8383
|
unquoteSelectorValue,
|
|
7915
8384
|
unwrapAndroidTextSelector,
|
|
7916
8385
|
unwrapIosTextSelector,
|
|
7917
8386
|
unwrapNativeTextSelector,
|
|
7918
8387
|
updateBacklogFromSuite,
|
|
8388
|
+
validateMacdriverArchiveEntries,
|
|
8389
|
+
validateMacdriverVersion,
|
|
8390
|
+
verifyMacdriverSignature,
|
|
7919
8391
|
waitForAgentReady,
|
|
7920
8392
|
waitForWdaReady,
|
|
7921
8393
|
wdaCacheDir,
|
|
7922
8394
|
wdaTestRunArgs,
|
|
7923
|
-
webOnlyReason
|
|
8395
|
+
webOnlyReason,
|
|
8396
|
+
zipinfoArchiveLister
|
|
7924
8397
|
});
|
|
7925
8398
|
//# sourceMappingURL=lib.cjs.map
|