doc-detective 4.27.0 → 4.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/cli.js +13 -4
  2. package/dist/cli.js.map +1 -1
  3. package/dist/common/src/detectTests.d.ts +9 -0
  4. package/dist/common/src/detectTests.d.ts.map +1 -1
  5. package/dist/common/src/detectTests.js +22 -9
  6. package/dist/common/src/detectTests.js.map +1 -1
  7. package/dist/common/src/schemas/schemas.json +85 -1
  8. package/dist/common/src/types/generated/report_v3.d.ts +34 -0
  9. package/dist/common/src/types/generated/report_v3.d.ts.map +1 -1
  10. package/dist/core/detectTests.d.ts.map +1 -1
  11. package/dist/core/detectTests.js +37 -13
  12. package/dist/core/detectTests.js.map +1 -1
  13. package/dist/core/expressions.d.ts.map +1 -1
  14. package/dist/core/expressions.js +24 -1
  15. package/dist/core/expressions.js.map +1 -1
  16. package/dist/core/index.d.ts +2 -1
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/core/index.js +2 -2
  19. package/dist/core/index.js.map +1 -1
  20. package/dist/core/resolveTests.js +23 -12
  21. package/dist/core/resolveTests.js.map +1 -1
  22. package/dist/core/telem.d.ts +2 -1
  23. package/dist/core/telem.d.ts.map +1 -1
  24. package/dist/core/telem.js +26 -2
  25. package/dist/core/telem.js.map +1 -1
  26. package/dist/core/tests/androidEmulator.d.ts.map +1 -1
  27. package/dist/core/tests/androidEmulator.js +13 -0
  28. package/dist/core/tests/androidEmulator.js.map +1 -1
  29. package/dist/core/tests/appSurface.d.ts +25 -0
  30. package/dist/core/tests/appSurface.d.ts.map +1 -1
  31. package/dist/core/tests/appSurface.js +14 -11
  32. package/dist/core/tests/appSurface.js.map +1 -1
  33. package/dist/core/tests/iosSimulator.d.ts.map +1 -1
  34. package/dist/core/tests/iosSimulator.js +13 -0
  35. package/dist/core/tests/iosSimulator.js.map +1 -1
  36. package/dist/core/tests/mobileBrowser.d.ts +4 -1
  37. package/dist/core/tests/mobileBrowser.d.ts.map +1 -1
  38. package/dist/core/tests/mobileBrowser.js +14 -7
  39. package/dist/core/tests/mobileBrowser.js.map +1 -1
  40. package/dist/core/tests.d.ts +83 -2
  41. package/dist/core/tests.d.ts.map +1 -1
  42. package/dist/core/tests.js +572 -70
  43. package/dist/core/tests.js.map +1 -1
  44. package/dist/core/utils.d.ts +3 -2
  45. package/dist/core/utils.d.ts.map +1 -1
  46. package/dist/core/utils.js +37 -29
  47. package/dist/core/utils.js.map +1 -1
  48. package/dist/core/warmPhase.d.ts +161 -0
  49. package/dist/core/warmPhase.d.ts.map +1 -0
  50. package/dist/core/warmPhase.js +341 -0
  51. package/dist/core/warmPhase.js.map +1 -0
  52. package/dist/hints/context.d.ts +16 -0
  53. package/dist/hints/context.d.ts.map +1 -1
  54. package/dist/hints/context.js +34 -0
  55. package/dist/hints/context.js.map +1 -1
  56. package/dist/hints/hints.d.ts.map +1 -1
  57. package/dist/hints/hints.js +17 -0
  58. package/dist/hints/hints.js.map +1 -1
  59. package/dist/hints/types.d.ts +14 -0
  60. package/dist/hints/types.d.ts.map +1 -1
  61. package/dist/index.cjs +1146 -359
  62. package/dist/runtime/cacheDir.d.ts +5 -0
  63. package/dist/runtime/cacheDir.d.ts.map +1 -1
  64. package/dist/runtime/cacheDir.js +3 -0
  65. package/dist/runtime/cacheDir.js.map +1 -1
  66. package/dist/runtime/iosInstaller.d.ts +44 -1
  67. package/dist/runtime/iosInstaller.d.ts.map +1 -1
  68. package/dist/runtime/iosInstaller.js +381 -13
  69. package/dist/runtime/iosInstaller.js.map +1 -1
  70. package/dist/runtime/lock.d.ts +56 -0
  71. package/dist/runtime/lock.d.ts.map +1 -0
  72. package/dist/runtime/lock.js +218 -0
  73. package/dist/runtime/lock.js.map +1 -0
  74. package/dist/runtime/wdaProducts.d.ts +147 -0
  75. package/dist/runtime/wdaProducts.d.ts.map +1 -0
  76. package/dist/runtime/wdaProducts.js +234 -0
  77. package/dist/runtime/wdaProducts.js.map +1 -0
  78. package/dist/utils.d.ts +2 -1
  79. package/dist/utils.d.ts.map +1 -1
  80. package/dist/utils.js +11 -3
  81. package/dist/utils.js.map +1 -1
  82. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -383844,6 +383844,71 @@ var init_schemas = __esm({
383844
383844
  }
383845
383845
  ]
383846
383846
  }
383847
+ },
383848
+ warm: {
383849
+ type: "object",
383850
+ description: "Results of the run's inline warm phase \u2014 the always-on, best-effort provisioning pass (dependency installs, device boots, session probes) performed between test resolution and test execution. A failed task never gates the run; the per-context paths retry or skip with their normal semantics. Absent when the run ended before the phase (for example, when no specs matched). System-populated.",
383851
+ readOnly: true,
383852
+ additionalProperties: false,
383853
+ required: [
383854
+ "durationMs",
383855
+ "tasks"
383856
+ ],
383857
+ properties: {
383858
+ durationMs: {
383859
+ type: "number",
383860
+ description: "Wall-clock duration of the warm phase in milliseconds. Tasks overlap, so this is less than the sum of the per-task durations."
383861
+ },
383862
+ tasks: {
383863
+ type: "array",
383864
+ description: "One entry per executed warm task.",
383865
+ items: {
383866
+ type: "object",
383867
+ additionalProperties: false,
383868
+ required: [
383869
+ "name",
383870
+ "kind",
383871
+ "outcome",
383872
+ "durationMs"
383873
+ ],
383874
+ properties: {
383875
+ name: {
383876
+ type: "string",
383877
+ description: "Task identity, e.g. `browser-install:chrome` or `device-boot:android:<default>:<latest>`."
383878
+ },
383879
+ kind: {
383880
+ type: "string",
383881
+ enum: [
383882
+ "driver-install",
383883
+ "browser-install",
383884
+ "device-boot",
383885
+ "wda-check",
383886
+ "session-probe",
383887
+ "chromedriver-prefetch"
383888
+ ],
383889
+ description: "What the task pre-pays: a native app driver install, a browser install, a simulator/emulator boot, the managed WebDriverAgent availability check, the concurrent-run driver session probe, or the android mobile-web chromedriver download."
383890
+ },
383891
+ outcome: {
383892
+ type: "string",
383893
+ enum: [
383894
+ "warmed",
383895
+ "skipped",
383896
+ "failed"
383897
+ ],
383898
+ description: "`warmed` \u2014 the work was performed (for device boots, the boot was initiated); `skipped` \u2014 nothing to do or the environment isn't ready (the consuming context handles it as usual); `failed` \u2014 the attempt failed (logged as a warning; never fails the run)."
383899
+ },
383900
+ durationMs: {
383901
+ type: "number",
383902
+ description: "Wall-clock duration of this task in milliseconds."
383903
+ },
383904
+ note: {
383905
+ type: "string",
383906
+ description: "Human-readable detail about the outcome."
383907
+ }
383908
+ }
383909
+ }
383910
+ }
383911
+ }
383847
383912
  }
383848
383913
  },
383849
383914
  required: [
@@ -383865,7 +383930,26 @@ var init_schemas = __esm({
383865
383930
  }
383866
383931
  ]
383867
383932
  }
383868
- ]
383933
+ ],
383934
+ warm: {
383935
+ durationMs: 4211,
383936
+ tasks: [
383937
+ {
383938
+ name: "browser-install:chrome",
383939
+ kind: "browser-install",
383940
+ outcome: "skipped",
383941
+ durationMs: 2,
383942
+ note: "'chrome' is already available"
383943
+ },
383944
+ {
383945
+ name: "session-probe",
383946
+ kind: "session-probe",
383947
+ outcome: "warmed",
383948
+ durationMs: 4205,
383949
+ note: "1 combination ok"
383950
+ }
383951
+ ]
383952
+ }
383869
383953
  },
383870
383954
  {
383871
383955
  specs: [
@@ -632440,6 +632524,8 @@ function readInstalledRecord(ctx = {}) {
632440
632524
  };
632441
632525
  if (isPlainObject(parsed?.android))
632442
632526
  record.android = parsed.android;
632527
+ if (isPlainObject(parsed?.ios))
632528
+ record.ios = parsed.ios;
632443
632529
  if (isPlainObject(parsed?.tools))
632444
632530
  record.tools = parsed.tools;
632445
632531
  return record;
@@ -633975,21 +634061,23 @@ async function loadEnvs(envsFile) {
633975
634061
  return { status: "FAIL", description: "Invalid file." };
633976
634062
  }
633977
634063
  }
634064
+ function logLevelEnabled(config, level) {
634065
+ if (config.logLevel === "error" && level === "error")
634066
+ return true;
634067
+ if (config.logLevel === "warning" && (level === "error" || level === "warning"))
634068
+ return true;
634069
+ if (config.logLevel === "info" && (level === "error" || level === "warning" || level === "info"))
634070
+ return true;
634071
+ if (config.logLevel === "debug" && (level === "error" || level === "warning" || level === "info" || level === "debug"))
634072
+ return true;
634073
+ return false;
634074
+ }
633978
634075
  async function log(config, level, message) {
633979
634076
  if (message === void 0) {
633980
634077
  message = config;
633981
634078
  config = {};
633982
634079
  }
633983
- let logLevelMatch = false;
633984
- if (config.logLevel === "error" && level === "error") {
633985
- logLevelMatch = true;
633986
- } else if (config.logLevel === "warning" && (level === "error" || level === "warning")) {
633987
- logLevelMatch = true;
633988
- } else if (config.logLevel === "info" && (level === "error" || level === "warning" || level === "info")) {
633989
- logLevelMatch = true;
633990
- } else if (config.logLevel === "debug" && (level === "error" || level === "warning" || level === "info" || level === "debug")) {
633991
- logLevelMatch = true;
633992
- }
634080
+ const logLevelMatch = logLevelEnabled(config, level);
633993
634081
  if (logLevelMatch) {
633994
634082
  if (typeof message === "string") {
633995
634083
  let logMessage = `(${level.toUpperCase()}) ${message}`;
@@ -634074,8 +634162,7 @@ function replaceEnvs(stringOrObject) {
634074
634162
  stringOrObject[key] = replaceEnvs(stringOrObject[key]);
634075
634163
  });
634076
634164
  } else if (typeof stringOrObject === "string") {
634077
- const variableRegex = new RegExp(/\$[a-zA-Z0-9_]+(?![a-zA-Z0-9_$])/, "g");
634078
- const matches = stringOrObject.match(variableRegex);
634165
+ const matches = stringOrObject.match(ENV_VAR_REGEX);
634079
634166
  if (!matches)
634080
634167
  return stringOrObject;
634081
634168
  matches.forEach((match) => {
@@ -634319,7 +634406,7 @@ function llevenshteinDistance(s, t) {
634319
634406
  }
634320
634407
  return arr[t.length][s.length];
634321
634408
  }
634322
- var import_node_fs7, import_node_os2, import_node_path5, import_node_crypto2, import_promises, import_node_net, import_axios, import_node_child_process4, BACKGROUND_BUFFER_LIMIT, PTY_PACKAGE, READY_POLL_INTERVAL_MS, TRANSIENT_SESSION_ERROR, SESSION_TIMEOUT_ABORT, TRANSIENT_PROCESS_INIT_EXIT_CODES, SETTLE_CEILING_MAX_MS, PRESERVED_TEMP_ENTRIES, FETCH_BINARY_DEFAULTS, posixBashProbe;
634409
+ var import_node_fs7, import_node_os2, import_node_path5, import_node_crypto2, import_promises, import_node_net, import_axios, import_node_child_process4, BACKGROUND_BUFFER_LIMIT, PTY_PACKAGE, READY_POLL_INTERVAL_MS, TRANSIENT_SESSION_ERROR, SESSION_TIMEOUT_ABORT, TRANSIENT_PROCESS_INIT_EXIT_CODES, SETTLE_CEILING_MAX_MS, PRESERVED_TEMP_ENTRIES, FETCH_BINARY_DEFAULTS, ENV_VAR_REGEX, posixBashProbe;
634323
634410
  var init_utils = __esm({
634324
634411
  "dist/core/utils.js"() {
634325
634412
  "use strict";
@@ -634361,6 +634448,7 @@ var init_utils = __esm({
634361
634448
  maxBodyLength: 50 * 1024 * 1024,
634362
634449
  maxRedirects: 5
634363
634450
  };
634451
+ ENV_VAR_REGEX = /\$[a-zA-Z0-9_]+(?![a-zA-Z0-9_$])/g;
634364
634452
  }
634365
634453
  });
634366
634454
 
@@ -634500,12 +634588,12 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
634500
634588
  }
634501
634589
  if (!force && existing && isStillFresh(existing.latestCheckedAt, now)) {
634502
634590
  if (existing.latestKnownVersion === existing.installedVersion) {
634503
- const path28 = await locateExecutable(browsersModule, name, existing.installedVersion, cacheDir, platform);
634504
- return { path: path28, version: existing.installedVersion, outdated: false };
634591
+ const path29 = await locateExecutable(browsersModule, name, existing.installedVersion, cacheDir, platform);
634592
+ return { path: path29, version: existing.installedVersion, outdated: false };
634505
634593
  }
634506
- const path27 = await locateExecutable(browsersModule, name, existing.installedVersion, cacheDir, platform);
634594
+ const path28 = await locateExecutable(browsersModule, name, existing.installedVersion, cacheDir, platform);
634507
634595
  logger(`${name} ${existing.installedVersion} installed in ${cacheDir}; "${BROWSER_CHANNELS[name]}" channel is now ${existing.latestKnownVersion}. Run \`doc-detective install browsers ${name} --force\` to update.`, "warn");
634508
- return { path: path27, version: existing.installedVersion, outdated: true };
634596
+ return { path: path28, version: existing.installedVersion, outdated: true };
634509
634597
  }
634510
634598
  let latest;
634511
634599
  try {
@@ -634513,8 +634601,8 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
634513
634601
  } catch (err) {
634514
634602
  logger(`Channel resolution for ${name} skipped: ${String(err)}`, "debug");
634515
634603
  if (existing) {
634516
- const path27 = await locateExecutable(browsersModule, name, existing.installedVersion, cacheDir, platform);
634517
- return { path: path27, version: existing.installedVersion, outdated: false };
634604
+ const path28 = await locateExecutable(browsersModule, name, existing.installedVersion, cacheDir, platform);
634605
+ return { path: path28, version: existing.installedVersion, outdated: false };
634518
634606
  }
634519
634607
  throw err;
634520
634608
  }
@@ -634525,8 +634613,8 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
634525
634613
  latestCheckedAt: now.toISOString()
634526
634614
  };
634527
634615
  writeInstalledRecord(record, ctx);
634528
- const path27 = await locateExecutable(browsersModule, name, latest, cacheDir, platform);
634529
- return { path: path27, version: latest, outdated: false };
634616
+ const path28 = await locateExecutable(browsersModule, name, latest, cacheDir, platform);
634617
+ return { path: path28, version: latest, outdated: false };
634530
634618
  }
634531
634619
  if (!force && existing) {
634532
634620
  record.browsers[name] = {
@@ -634536,8 +634624,8 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
634536
634624
  };
634537
634625
  writeInstalledRecord(record, ctx);
634538
634626
  logger(`${name} ${existing.installedVersion} installed in ${cacheDir}; "${BROWSER_CHANNELS[name]}" channel is now ${latest}. Run \`doc-detective install browsers ${name} --force\` to update.`, "warn");
634539
- const path27 = await locateExecutable(browsersModule, name, existing.installedVersion, cacheDir, platform);
634540
- return { path: path27, version: existing.installedVersion, outdated: true };
634627
+ const path28 = await locateExecutable(browsersModule, name, existing.installedVersion, cacheDir, platform);
634628
+ return { path: path28, version: existing.installedVersion, outdated: true };
634541
634629
  }
634542
634630
  logger(`Installing ${name} ${latest} into ${cacheDir}`, "info");
634543
634631
  await browsersModule.install({
@@ -634545,9 +634633,9 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
634545
634633
  buildId: latest,
634546
634634
  cacheDir
634547
634635
  });
634548
- let path26 = await locateExecutable(browsersModule, name, latest, cacheDir, platform);
634549
- if (name === "chromedriver" && isAllowedDriverPath(path26)) {
634550
- let verify = await verifyDriverBinary(name, path26, { exec: deps.verifyExec });
634636
+ let path27 = await locateExecutable(browsersModule, name, latest, cacheDir, platform);
634637
+ if (name === "chromedriver" && isAllowedDriverPath(path27)) {
634638
+ let verify = await verifyDriverBinary(name, path27, { exec: deps.verifyExec });
634551
634639
  if (!verify.ok) {
634552
634640
  logger(`Installed ${name} ${latest} failed validation (${verify.error}); reinstalling once.`, "warn");
634553
634641
  try {
@@ -634556,11 +634644,11 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
634556
634644
  logger(`Failed to prune broken ${name} ${latest}: ${String(err)}`, "debug");
634557
634645
  }
634558
634646
  await browsersModule.install({ browser: name, buildId: latest, cacheDir });
634559
- path26 = await locateExecutable(browsersModule, name, latest, cacheDir, platform);
634560
- verify = await verifyDriverBinary(name, path26, { exec: deps.verifyExec });
634647
+ path27 = await locateExecutable(browsersModule, name, latest, cacheDir, platform);
634648
+ verify = await verifyDriverBinary(name, path27, { exec: deps.verifyExec });
634561
634649
  }
634562
634650
  if (!verify.ok) {
634563
- throw new Error(`${name} ${latest} is present but non-functional after a reinstall (${verify.error}). It may be a partial or corrupt download; delete ${path26} or reinstall.`);
634651
+ throw new Error(`${name} ${latest} is present but non-functional after a reinstall (${verify.error}). It may be a partial or corrupt download; delete ${path27} or reinstall.`);
634564
634652
  }
634565
634653
  }
634566
634654
  if (existing && existing.installedVersion !== latest) {
@@ -634581,7 +634669,7 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
634581
634669
  latestCheckedAt: now.toISOString()
634582
634670
  };
634583
634671
  writeInstalledRecord(record, ctx);
634584
- return { path: path26, version: latest, outdated: false };
634672
+ return { path: path27, version: latest, outdated: false };
634585
634673
  }
634586
634674
  async function locateExecutable(browsersModule, name, buildId, cacheDir, platform) {
634587
634675
  if (typeof browsersModule.computeExecutablePath === "function") {
@@ -635027,10 +635115,10 @@ async function getOperation(definition = {}, operationId = "", responseCode = ""
635027
635115
  if (!operationId) {
635028
635116
  throw new Error("OperationId is required.");
635029
635117
  }
635030
- for (const path26 in definition.paths) {
635031
- for (const method in definition.paths[path26]) {
635032
- if (definition.paths[path26][method].operationId === operationId) {
635033
- const operation = definition.paths[path26][method];
635118
+ for (const path27 in definition.paths) {
635119
+ for (const method in definition.paths[path27]) {
635120
+ if (definition.paths[path27][method].operationId === operationId) {
635121
+ const operation = definition.paths[path27][method];
635034
635122
  if (!server) {
635035
635123
  if (definition.servers && definition.servers.length > 0) {
635036
635124
  server = definition.servers[0].url;
@@ -635038,9 +635126,9 @@ async function getOperation(definition = {}, operationId = "", responseCode = ""
635038
635126
  throw new Error("No server URL provided and no servers defined in the OpenAPI definition.");
635039
635127
  }
635040
635128
  }
635041
- const example = await compileExample(operation, server + path26, responseCode, exampleKey);
635129
+ const example = await compileExample(operation, server + path27, responseCode, exampleKey);
635042
635130
  const schemas2 = getSchemas(operation, responseCode);
635043
- return { path: path26, method, definition: operation, schemas: schemas2, example };
635131
+ return { path: path27, method, definition: operation, schemas: schemas2, example };
635044
635132
  }
635045
635133
  }
635046
635134
  }
@@ -635061,15 +635149,15 @@ function getSchemas(definition = {}, responseCode = "") {
635061
635149
  schemas2.response = definition.responses[responseCode].content[Object.keys(definition.responses[responseCode].content)[0]].schema;
635062
635150
  return schemas2;
635063
635151
  }
635064
- async function compileExample(operation = {}, path26 = "", responseCode = "", exampleKey = "") {
635152
+ async function compileExample(operation = {}, path27 = "", responseCode = "", exampleKey = "") {
635065
635153
  if (!operation) {
635066
635154
  throw new Error("Operation is required.");
635067
635155
  }
635068
- if (!path26) {
635156
+ if (!path27) {
635069
635157
  throw new Error("Path is required.");
635070
635158
  }
635071
635159
  let example = {
635072
- url: path26,
635160
+ url: path27,
635073
635161
  request: { parameters: {}, headers: {}, body: {} },
635074
635162
  response: { headers: {}, body: {} }
635075
635163
  };
@@ -636494,17 +636582,24 @@ function detectFileTypeFromContent(content) {
636494
636582
  }
636495
636583
 
636496
636584
  // dist/common/src/detectTests.js
636585
+ var compiledRegExpCache = /* @__PURE__ */ new Map();
636497
636586
  function safeRegExp(pattern, flags) {
636498
636587
  if (typeof pattern !== "string" || pattern.length === 0)
636499
636588
  return null;
636500
636589
  if (pattern.length > 1500)
636501
636590
  return null;
636502
- const sanitized = Array.from(pattern, (c) => String.fromCharCode(c.charCodeAt(0))).join("");
636591
+ const cacheKey = pattern + " " + flags;
636592
+ const cached = compiledRegExpCache.get(cacheKey);
636593
+ if (cached !== void 0)
636594
+ return cached;
636595
+ let compiled;
636503
636596
  try {
636504
- return new RegExp(sanitized, flags);
636597
+ compiled = new RegExp(pattern, flags);
636505
636598
  } catch {
636506
- return null;
636599
+ compiled = null;
636507
636600
  }
636601
+ compiledRegExpCache.set(cacheKey, compiled);
636602
+ return compiled;
636508
636603
  }
636509
636604
  var HASH_EXCLUDED_KEYS = /* @__PURE__ */ new Set([
636510
636605
  "location",
@@ -638217,12 +638312,16 @@ function generateSpecId(filePath) {
638217
638312
  const normalizedPath = relativePath.split(import_node_path11.default.sep).join("/").replace(/^\.\//, "").replace(/[^a-zA-Z0-9._\-\/]/g, "_");
638218
638313
  return normalizedPath;
638219
638314
  }
638220
- async function isValidSourceFile({ config, files, source }) {
638221
- log(config, "debug", `validation: ${source}`);
638222
- let allowedExtensions = ["json", "yaml", "yml"];
638223
- config.fileTypes.forEach((fileType) => {
638224
- allowedExtensions = allowedExtensions.concat(fileType.extensions);
638315
+ function buildAllowedExtensions(config) {
638316
+ const allowed = /* @__PURE__ */ new Set(["json", "yaml", "yml"]);
638317
+ (config.fileTypes || []).forEach((fileType) => {
638318
+ fileType.extensions.forEach((ext) => allowed.add(ext));
638225
638319
  });
638320
+ return allowed;
638321
+ }
638322
+ async function isValidSourceFile({ config, files, source, allowedExtensions }) {
638323
+ log(config, "debug", `validation: ${source}`);
638324
+ const allowed = allowedExtensions;
638226
638325
  if (files.indexOf(source) >= 0)
638227
638326
  return false;
638228
638327
  if (import_node_path11.default.extname(source) === ".json" || import_node_path11.default.extname(source) === ".yaml" || import_node_path11.default.extname(source) === ".yml") {
@@ -638269,7 +638368,7 @@ async function isValidSourceFile({ config, files, source }) {
638269
638368
  }
638270
638369
  }
638271
638370
  const extension = import_node_path11.default.extname(source).substring(1);
638272
- if (!allowedExtensions.includes(extension)) {
638371
+ if (!allowed.has(extension)) {
638273
638372
  log(config, "debug", `${source} extension isn't specified in a \`config.fileTypes\` object. Skipping.`);
638274
638373
  return false;
638275
638374
  }
@@ -638321,6 +638420,7 @@ async function qualifyFiles({ config }) {
638321
638420
  return [];
638322
638421
  }
638323
638422
  const ignoredDitaMaps = [];
638423
+ const allowedExtensions = buildAllowedExtensions(config);
638324
638424
  if (!config._herettoPathMapping) {
638325
638425
  config._herettoPathMapping = {};
638326
638426
  }
@@ -638388,8 +638488,9 @@ async function qualifyFiles({ config }) {
638388
638488
  let isFile = false;
638389
638489
  let isDir = false;
638390
638490
  try {
638391
- isFile = import_node_fs13.default.statSync(source).isFile();
638392
- isDir = import_node_fs13.default.statSync(source).isDirectory();
638491
+ const stat = import_node_fs13.default.statSync(source);
638492
+ isFile = stat.isFile();
638493
+ isDir = stat.isDirectory();
638393
638494
  } catch {
638394
638495
  log(config, "warning", `Cannot access path: ${source}. Skipping.`);
638395
638496
  continue;
@@ -638404,7 +638505,7 @@ async function qualifyFiles({ config }) {
638404
638505
  continue;
638405
638506
  }
638406
638507
  const resolved = import_node_path11.default.resolve(source);
638407
- if (isFile && await isValidSourceFile({ config, files, source: resolved })) {
638508
+ if (isFile && await isValidSourceFile({ config, files, allowedExtensions, source: resolved })) {
638408
638509
  files.push(resolved);
638409
638510
  if (!phaseByFile.has(resolved))
638410
638511
  phaseByFile.set(resolved, phase);
@@ -638417,9 +638518,10 @@ async function qualifyFiles({ config }) {
638417
638518
  const content = import_node_path11.default.resolve(dir + "/" + object);
638418
638519
  if (content.includes("node_modules"))
638419
638520
  continue;
638420
- const isFile2 = import_node_fs13.default.statSync(content).isFile();
638421
- const isDir2 = import_node_fs13.default.statSync(content).isDirectory();
638422
- if (isFile2 && await isValidSourceFile({ config, files, source: content })) {
638521
+ const stat = import_node_fs13.default.statSync(content);
638522
+ const isFile2 = stat.isFile();
638523
+ const isDir2 = stat.isDirectory();
638524
+ if (isFile2 && await isValidSourceFile({ config, files, allowedExtensions, source: content })) {
638423
638525
  const resolved2 = import_node_path11.default.resolve(content);
638424
638526
  files.push(resolved2);
638425
638527
  if (!phaseByFile.has(resolved2))
@@ -638783,12 +638885,14 @@ function resolveContexts({ contexts, test, config }) {
638783
638885
  if (resolvedContexts.length === 0) {
638784
638886
  resolvedContexts.push({});
638785
638887
  }
638786
- log(config, "debug", `Resolved contexts for test ${test.testId}:
638888
+ if (logLevelEnabled(config, "debug"))
638889
+ log(config, "debug", `Resolved contexts for test ${test.testId}:
638787
638890
  ${JSON.stringify(resolvedContexts, null, 2)}`);
638788
638891
  return resolvedContexts;
638789
638892
  }
638790
638893
  async function fetchOpenApiDocuments({ config, documentArray }) {
638791
- log(config, "debug", `Fetching OpenAPI documents:
638894
+ if (logLevelEnabled(config, "debug"))
638895
+ log(config, "debug", `Fetching OpenAPI documents:
638792
638896
  ${JSON.stringify(documentArray, null, 2)}`);
638793
638897
  const openApiDocuments = [];
638794
638898
  if (config?.integrations?.openApi?.length > 0)
@@ -638809,7 +638913,8 @@ ${JSON.stringify(documentArray, null, 2)}`);
638809
638913
  openApiDocuments.push(definition);
638810
638914
  }
638811
638915
  }
638812
- log(config, "debug", `Fetched OpenAPI documents:
638916
+ if (logLevelEnabled(config, "debug"))
638917
+ log(config, "debug", `Fetched OpenAPI documents:
638813
638918
  ${JSON.stringify(openApiDocuments, null, 2)}`);
638814
638919
  return openApiDocuments;
638815
638920
  }
@@ -638829,7 +638934,8 @@ async function resolveContext({ config, test, context, usedContextIds, openApi }
638829
638934
  context.contextId = context.contextId ? uniqueId(context.contextId, usedContextIds) : deriveContextId({ context, usedIds: usedContextIds });
638830
638935
  const contextId = context.contextId;
638831
638936
  usedContextIds.add(contextId);
638832
- log(config, "debug", `RESOLVING CONTEXT ID ${contextId}:
638937
+ if (logLevelEnabled(config, "debug"))
638938
+ log(config, "debug", `RESOLVING CONTEXT ID ${contextId}:
638833
638939
  ${JSON.stringify(context, null, 2)}`);
638834
638940
  const resolvedContext = {
638835
638941
  ...context,
@@ -638842,13 +638948,15 @@ ${JSON.stringify(context, null, 2)}`);
638842
638948
  steps: [...test.steps],
638843
638949
  contextId
638844
638950
  };
638845
- log(config, "debug", `RESOLVED CONTEXT ${contextId}:
638951
+ if (logLevelEnabled(config, "debug"))
638952
+ log(config, "debug", `RESOLVED CONTEXT ${contextId}:
638846
638953
  ${JSON.stringify(resolvedContext, null, 2)}`);
638847
638954
  return resolvedContext;
638848
638955
  }
638849
638956
  async function resolveTest({ config, spec, test }) {
638850
638957
  const testId = test.testId || `${spec.specId}~${contentHash(test)}`;
638851
- log(config, "debug", `RESOLVING TEST ID ${testId}:
638958
+ if (logLevelEnabled(config, "debug"))
638959
+ log(config, "debug", `RESOLVING TEST ID ${testId}:
638852
638960
  ${JSON.stringify(test, null, 2)}`);
638853
638961
  const resolvedTest = {
638854
638962
  ...test,
@@ -638878,13 +638986,15 @@ ${JSON.stringify(test, null, 2)}`);
638878
638986
  });
638879
638987
  resolvedTest.contexts.push(resolvedContext);
638880
638988
  }
638881
- log(config, "debug", `RESOLVED TEST ${testId}:
638989
+ if (logLevelEnabled(config, "debug"))
638990
+ log(config, "debug", `RESOLVED TEST ${testId}:
638882
638991
  ${JSON.stringify(resolvedTest, null, 2)}`);
638883
638992
  return resolvedTest;
638884
638993
  }
638885
638994
  async function resolveSpec({ config, spec }) {
638886
638995
  const specId = spec.specId || (spec.contentPath ? generateSpecId(spec.contentPath) : import_node_crypto5.default.randomUUID());
638887
- log(config, "debug", `RESOLVING SPEC ID ${specId}:
638996
+ if (logLevelEnabled(config, "debug"))
638997
+ log(config, "debug", `RESOLVING SPEC ID ${specId}:
638888
638998
  ${JSON.stringify(spec, null, 2)}`);
638889
638999
  const resolvedSpec = {
638890
639000
  ...spec,
@@ -638904,12 +639014,14 @@ ${JSON.stringify(spec, null, 2)}`);
638904
639014
  });
638905
639015
  resolvedSpec.tests.push(resolvedTest);
638906
639016
  }
638907
- log(config, "debug", `RESOLVED SPEC ${specId}:
639017
+ if (logLevelEnabled(config, "debug"))
639018
+ log(config, "debug", `RESOLVED SPEC ${specId}:
638908
639019
  ${JSON.stringify(resolvedSpec, null, 2)}`);
638909
639020
  return resolvedSpec;
638910
639021
  }
638911
639022
  async function resolveTests({ config, detectedTests }) {
638912
- log(config, "debug", `RESOLVING DETECTED TEST SPECS:
639023
+ if (logLevelEnabled(config, "debug"))
639024
+ log(config, "debug", `RESOLVING DETECTED TEST SPECS:
638913
639025
  ${JSON.stringify(detectedTests, null, 2)}`);
638914
639026
  const resolvedTests = {
638915
639027
  resolvedTestsId: import_node_crypto5.default.randomUUID(),
@@ -638921,7 +639033,8 @@ ${JSON.stringify(detectedTests, null, 2)}`);
638921
639033
  const resolvedSpec = await resolveSpec({ config, spec });
638922
639034
  resolvedTests.specs.push(resolvedSpec);
638923
639035
  }
638924
- log(config, "debug", `RESOLVED TEST SPECS:
639036
+ if (logLevelEnabled(config, "debug"))
639037
+ log(config, "debug", `RESOLVED TEST SPECS:
638925
639038
  ${JSON.stringify(resolvedTests, null, 2)}`);
638926
639039
  return resolvedTests;
638927
639040
  }
@@ -638931,7 +639044,7 @@ init_utils();
638931
639044
 
638932
639045
  // dist/core/tests.js
638933
639046
  var import_tree_kill3 = __toESM(require("tree-kill"), 1);
638934
- var import_node_fs28 = __toESM(require("node:fs"), 1);
639047
+ var import_node_fs29 = __toESM(require("node:fs"), 1);
638935
639048
  init_loader();
638936
639049
  init_browsers();
638937
639050
  init_browserStepKeys();
@@ -639644,12 +639757,116 @@ async function resolveCloseTargets(driver, ref, opts = {}) {
639644
639757
  }
639645
639758
 
639646
639759
  // dist/core/tests/appSurface.js
639647
- var import_node_fs16 = __toESM(require("node:fs"), 1);
639648
- var import_node_path13 = __toESM(require("node:path"), 1);
639649
- var import_node_child_process9 = require("node:child_process");
639760
+ var import_node_fs17 = __toESM(require("node:fs"), 1);
639761
+ var import_node_path14 = __toESM(require("node:path"), 1);
639762
+ var import_node_child_process10 = require("node:child_process");
639650
639763
  init_loader();
639651
639764
  init_appium();
639652
639765
  init_cacheDir();
639766
+
639767
+ // dist/runtime/wdaProducts.js
639768
+ var import_node_child_process7 = require("node:child_process");
639769
+ var import_node_fs14 = __toESM(require("node:fs"), 1);
639770
+ var import_node_path12 = __toESM(require("node:path"), 1);
639771
+ init_cacheDir();
639772
+ init_loader();
639773
+ function parseXcodebuildVersion(output) {
639774
+ const text = String(output ?? "");
639775
+ const version = /^\s*Xcode\s+(\S+)/m.exec(text)?.[1];
639776
+ const build = /^\s*Build version\s+(\S+)/m.exec(text)?.[1];
639777
+ if (!version || !build)
639778
+ return null;
639779
+ return { version, build };
639780
+ }
639781
+ function getWdaRoot(ctx = {}) {
639782
+ return import_node_path12.default.join(getCacheDir(ctx), "ios", "wda");
639783
+ }
639784
+ function sanitizeKeySegment(value) {
639785
+ return (value || "unknown").trim().replace(/[^A-Za-z0-9._-]+/g, "-") || "unknown";
639786
+ }
639787
+ function computeWdaKey(xcode, driverVersion) {
639788
+ return `xcode-${sanitizeKeySegment(xcode.version)}-${sanitizeKeySegment(xcode.build)}-driver-${sanitizeKeySegment(driverVersion)}`;
639789
+ }
639790
+ var RUNNER_APP_RELATIVE = import_node_path12.default.join("DerivedData", "Build", "Products", "Debug-iphonesimulator", "WebDriverAgentRunner-Runner.app");
639791
+ var PRODUCTS_MARKER = "products.json";
639792
+ var LAST_USED_STAMP = "last-used";
639793
+ function readProductsMarker(keyDir, fs28) {
639794
+ try {
639795
+ const parsed = JSON.parse(String(fs28.readFileSync(import_node_path12.default.join(keyDir, PRODUCTS_MARKER))));
639796
+ if (typeof parsed?.key !== "string" || typeof parsed?.driverVersion !== "string") {
639797
+ return null;
639798
+ }
639799
+ if (!fs28.existsSync(import_node_path12.default.join(keyDir, RUNNER_APP_RELATIVE)))
639800
+ return null;
639801
+ return parsed;
639802
+ } catch {
639803
+ return null;
639804
+ }
639805
+ }
639806
+ function touchLastUsed(keyDir, fs28, now) {
639807
+ try {
639808
+ fs28.writeFileSync(import_node_path12.default.join(keyDir, LAST_USED_STAMP), String(now()));
639809
+ } catch {
639810
+ }
639811
+ }
639812
+ var MIN_PREBUILT_WDA_DRIVER_MAJOR = 10;
639813
+ var cachedXcodeProbe;
639814
+ function probeXcodeVersionCached() {
639815
+ if (cachedXcodeProbe === void 0) {
639816
+ try {
639817
+ const result = (0, import_node_child_process7.spawnSync)("xcodebuild", ["-version"], {
639818
+ encoding: "utf8",
639819
+ windowsHide: true,
639820
+ timeout: 15e3
639821
+ });
639822
+ cachedXcodeProbe = result.status === 0 ? parseXcodebuildVersion(result.stdout) : null;
639823
+ } catch {
639824
+ cachedXcodeProbe = null;
639825
+ }
639826
+ }
639827
+ return cachedXcodeProbe;
639828
+ }
639829
+ function locateManagedWda(options = {}) {
639830
+ const { ctx = {}, fs: fs28 = import_node_fs14.default, platform = process.platform, probeXcode = probeXcodeVersionCached, resolveDriverVersion = resolveHeavyDepVersion, now = Date.now } = options;
639831
+ try {
639832
+ if (platform !== "darwin")
639833
+ return null;
639834
+ const driverVersion = resolveDriverVersion("appium-xcuitest-driver", ctx);
639835
+ if (!driverVersion)
639836
+ return null;
639837
+ const driverMajor = Number.parseInt(driverVersion, 10);
639838
+ if (!Number.isFinite(driverMajor) || driverMajor < MIN_PREBUILT_WDA_DRIVER_MAJOR) {
639839
+ return null;
639840
+ }
639841
+ const xcode = probeXcode();
639842
+ if (!xcode)
639843
+ return null;
639844
+ const wdaRoot = import_node_path12.default.normalize(options.wdaRootDir ?? getWdaRoot(ctx));
639845
+ const key = computeWdaKey(xcode, driverVersion);
639846
+ const keyDir = import_node_path12.default.join(wdaRoot, key);
639847
+ const marker = readProductsMarker(keyDir, fs28);
639848
+ if (!marker || marker.key !== key)
639849
+ return null;
639850
+ touchLastUsed(keyDir, fs28, now);
639851
+ return { key, derivedDataPath: import_node_path12.default.join(keyDir, "DerivedData") };
639852
+ } catch {
639853
+ return null;
639854
+ }
639855
+ }
639856
+ function applyManagedWdaCapabilities(capabilities, locateWda) {
639857
+ const derivedDataPath = process.env.DOC_DETECTIVE_IOS_WDA_DERIVED_DATA_PATH;
639858
+ if (derivedDataPath && derivedDataPath.trim()) {
639859
+ capabilities["appium:derivedDataPath"] = derivedDataPath.trim();
639860
+ return;
639861
+ }
639862
+ const managed = locateWda?.();
639863
+ if (managed) {
639864
+ capabilities["appium:derivedDataPath"] = managed.derivedDataPath;
639865
+ capabilities["appium:usePrebuiltWDA"] = true;
639866
+ }
639867
+ }
639868
+
639869
+ // dist/core/tests/appSurface.js
639653
639870
  init_utils();
639654
639871
 
639655
639872
  // dist/core/tests/appWindows.js
@@ -640088,17 +640305,17 @@ async function closeAppWindow({ entry, selector }) {
640088
640305
  }
640089
640306
 
640090
640307
  // dist/core/tests/androidEmulator.js
640091
- var import_node_child_process8 = require("node:child_process");
640308
+ var import_node_child_process9 = require("node:child_process");
640092
640309
 
640093
640310
  // dist/runtime/androidInstaller.js
640094
- var import_node_fs15 = __toESM(require("node:fs"), 1);
640311
+ var import_node_fs16 = __toESM(require("node:fs"), 1);
640095
640312
  var import_node_os6 = __toESM(require("node:os"), 1);
640096
- var import_node_path12 = __toESM(require("node:path"), 1);
640097
- var import_node_child_process7 = require("node:child_process");
640313
+ var import_node_path13 = __toESM(require("node:path"), 1);
640314
+ var import_node_child_process8 = require("node:child_process");
640098
640315
  init_cacheDir();
640099
640316
 
640100
640317
  // dist/runtime/androidSdk.js
640101
- var import_node_fs14 = __toESM(require("node:fs"), 1);
640318
+ var import_node_fs15 = __toESM(require("node:fs"), 1);
640102
640319
  init_cacheDir();
640103
640320
  function trimTrailingSeparators(s) {
640104
640321
  let end = s.length;
@@ -640142,7 +640359,7 @@ function usable(tools) {
640142
640359
  }
640143
640360
  function detectAndroidSdk(ctx = {}, deps = {}) {
640144
640361
  const env = deps.env ?? process.env;
640145
- const existsSync2 = deps.existsSync ?? import_node_fs14.default.existsSync;
640362
+ const existsSync2 = deps.existsSync ?? import_node_fs15.default.existsSync;
640146
640363
  const platform = deps.platform ?? process.platform;
640147
640364
  const cacheRoot = safeCacheDir(ctx);
640148
640365
  const cacheAndroidSdk = deps.cacheAndroidSdk ?? (cacheRoot ? joinFor(platform, cacheRoot, "android-sdk") : void 0);
@@ -640248,11 +640465,11 @@ function resolveJavaHome(extractDir, entries, platform = process.platform) {
640248
640465
  const top = entries.find((e) => /jdk|jre/i.test(e));
640249
640466
  if (!top)
640250
640467
  return null;
640251
- const base = import_node_path12.default.join(extractDir, top);
640252
- return platform === "darwin" ? import_node_path12.default.join(base, "Contents", "Home") : base;
640468
+ const base = import_node_path13.default.join(extractDir, top);
640469
+ return platform === "darwin" ? import_node_path13.default.join(base, "Contents", "Home") : base;
640253
640470
  }
640254
640471
  function javaBinPath(javaHome, platform = process.platform) {
640255
- return import_node_path12.default.join(javaHome, "bin", platform === "win32" ? "java.exe" : "java");
640472
+ return import_node_path13.default.join(javaHome, "bin", platform === "win32" ? "java.exe" : "java");
640256
640473
  }
640257
640474
  function cmdlineToolsUrl(platform) {
640258
640475
  const token = platform === "win32" ? "win" : platform === "darwin" ? "mac" : "linux";
@@ -640293,9 +640510,9 @@ function pickSystemImage(images, { osVersion, abi }) {
640293
640510
  return candidates.length > 0 ? candidates[0].pkg : null;
640294
640511
  }
640295
640512
  function listInstalledSystemImages(sdkRoot, deps = {}) {
640296
- const existsSync2 = deps.existsSync ?? import_node_fs15.default.existsSync;
640297
- const readdirSync = deps.readdirSync ?? ((p) => import_node_fs15.default.readdirSync(p));
640298
- const base = import_node_path12.default.join(sdkRoot, "system-images");
640513
+ const existsSync2 = deps.existsSync ?? import_node_fs16.default.existsSync;
640514
+ const readdirSync = deps.readdirSync ?? ((p) => import_node_fs16.default.readdirSync(p));
640515
+ const base = import_node_path13.default.join(sdkRoot, "system-images");
640299
640516
  if (!existsSync2(base))
640300
640517
  return [];
640301
640518
  const out = [];
@@ -640308,14 +640525,14 @@ function listInstalledSystemImages(sdkRoot, deps = {}) {
640308
640525
  for (const api of apis) {
640309
640526
  let tags = [];
640310
640527
  try {
640311
- tags = readdirSync(import_node_path12.default.join(base, api));
640528
+ tags = readdirSync(import_node_path13.default.join(base, api));
640312
640529
  } catch {
640313
640530
  continue;
640314
640531
  }
640315
640532
  for (const tag of tags) {
640316
640533
  let abis = [];
640317
640534
  try {
640318
- abis = readdirSync(import_node_path12.default.join(base, api, tag));
640535
+ abis = readdirSync(import_node_path13.default.join(base, api, tag));
640319
640536
  } catch {
640320
640537
  continue;
640321
640538
  }
@@ -640331,22 +640548,22 @@ function systemImageDir(sdkRoot, pkg) {
640331
640548
  const parts = pkg.split(";");
640332
640549
  if (parts.length !== 4 || parts[0] !== "system-images")
640333
640550
  return null;
640334
- const base = import_node_path12.default.join(sdkRoot, "system-images");
640335
- const dir = import_node_path12.default.join(base, parts[1], parts[2], parts[3]);
640336
- const rel = import_node_path12.default.relative(base, dir);
640337
- if (rel === "" || rel.startsWith("..") || import_node_path12.default.isAbsolute(rel))
640551
+ const base = import_node_path13.default.join(sdkRoot, "system-images");
640552
+ const dir = import_node_path13.default.join(base, parts[1], parts[2], parts[3]);
640553
+ const rel = import_node_path13.default.relative(base, dir);
640554
+ if (rel === "" || rel.startsWith("..") || import_node_path13.default.isAbsolute(rel))
640338
640555
  return null;
640339
640556
  return dir;
640340
640557
  }
640341
640558
  function isSystemImageComplete(sdkRoot, pkg, deps = {}) {
640342
- const existsSync2 = deps.existsSync ?? import_node_fs15.default.existsSync;
640559
+ const existsSync2 = deps.existsSync ?? import_node_fs16.default.existsSync;
640343
640560
  const dir = systemImageDir(sdkRoot, pkg);
640344
640561
  if (!dir)
640345
640562
  return false;
640346
- return SYSTEM_IMAGE_MARKERS.every((marker) => existsSync2(import_node_path12.default.join(dir, marker)));
640563
+ return SYSTEM_IMAGE_MARKERS.every((marker) => existsSync2(import_node_path13.default.join(dir, marker)));
640347
640564
  }
640348
640565
  function wipeSystemImage(sdkRoot, pkg, deps = {}) {
640349
- const rmSync = deps.rmSync ?? import_node_fs15.default.rmSync;
640566
+ const rmSync = deps.rmSync ?? import_node_fs16.default.rmSync;
640350
640567
  const dir = systemImageDir(sdkRoot, pkg);
640351
640568
  if (!dir)
640352
640569
  return;
@@ -640444,7 +640661,7 @@ async function runSdkInstallWithRetry(run2, command, args, opts = {}, deps = {})
640444
640661
  }
640445
640662
  function applyJavaEnv(javaHome, platform) {
640446
640663
  process.env.JAVA_HOME = javaHome;
640447
- const bin = import_node_path12.default.join(javaHome, "bin");
640664
+ const bin = import_node_path13.default.join(javaHome, "bin");
640448
640665
  const sep = platform === "win32" ? ";" : ":";
640449
640666
  if (!(process.env.PATH ?? "").split(sep).includes(bin)) {
640450
640667
  process.env.PATH = `${bin}${sep}${process.env.PATH ?? ""}`;
@@ -640479,7 +640696,7 @@ async function installAndroid({ yes = false, force = false, dryRun = false, osVe
640479
640696
  const abi = hostAbi(deps.arch);
640480
640697
  const detect = deps.detect ?? detectAndroidSdk;
640481
640698
  const fsDeps = deps.fs ?? {};
640482
- const cacheSdkRoot = import_node_path12.default.join(getCacheDir(ctx), "android-sdk");
640699
+ const cacheSdkRoot = import_node_path13.default.join(getCacheDir(ctx), "android-sdk");
640483
640700
  const detected = detect(ctx, {});
640484
640701
  const installedImages = detected ? listInstalledSystemImages(detected.sdkRoot, fsDeps) : [];
640485
640702
  const needAvailable = pickSystemImage(installedImages, { osVersion, abi }) === null;
@@ -640519,7 +640736,7 @@ async function installAndroid({ yes = false, force = false, dryRun = false, osVe
640519
640736
  logger("Android SDK licenses: https://developer.android.com/studio/terms", "info");
640520
640737
  return [{ kind: "android", assetId: "confirmation", action: "declined" }];
640521
640738
  }
640522
- const cacheJreRoot = import_node_path12.default.join(getCacheDir(ctx), "jre");
640739
+ const cacheJreRoot = import_node_path13.default.join(getCacheDir(ctx), "jre");
640523
640740
  const java = await ensureJava({
640524
640741
  javaPresent: deps.javaPresent ?? realJavaPresent,
640525
640742
  cacheJreRoot,
@@ -640635,7 +640852,7 @@ function actionAssetId(a) {
640635
640852
  }
640636
640853
  function toolPath(sdkRoot, tool, platform) {
640637
640854
  const suffix = platform === "win32" ? ".bat" : "";
640638
- return import_node_path12.default.join(sdkRoot, "cmdline-tools", "latest", "bin", tool + suffix);
640855
+ return import_node_path13.default.join(sdkRoot, "cmdline-tools", "latest", "bin", tool + suffix);
640639
640856
  }
640640
640857
  var SAFE_SHELL_TOKEN = /^[A-Za-z0-9 ._:;=\\/@+~()-]*$/;
640641
640858
  function winShellCommand(command, args) {
@@ -640670,16 +640887,16 @@ function nowIso() {
640670
640887
  }
640671
640888
  function realJavaPresent() {
640672
640889
  try {
640673
- const res = (0, import_node_child_process7.spawnSync)("java", ["-version"], { stdio: "ignore" });
640890
+ const res = (0, import_node_child_process8.spawnSync)("java", ["-version"], { stdio: "ignore" });
640674
640891
  return res.status === 0;
640675
640892
  } catch {
640676
640893
  return false;
640677
640894
  }
640678
640895
  }
640679
640896
  function androidAvdHome() {
640680
- const home = import_node_path12.default.join(import_node_os6.default.homedir(), ".android", "avd");
640897
+ const home = import_node_path13.default.join(import_node_os6.default.homedir(), ".android", "avd");
640681
640898
  try {
640682
- import_node_fs15.default.mkdirSync(home, { recursive: true });
640899
+ import_node_fs16.default.mkdirSync(home, { recursive: true });
640683
640900
  } catch {
640684
640901
  }
640685
640902
  return home;
@@ -640690,12 +640907,12 @@ async function realRun(command, args, opts = {}) {
640690
640907
  ANDROID_AVD_HOME: androidAvdHome()
640691
640908
  };
640692
640909
  const useShell = process.platform === "win32" && /\.(bat|cmd)$/i.test(command);
640693
- const child = useShell ? (0, import_node_child_process7.spawn)(winShellCommand(command, args), {
640910
+ const child = useShell ? (0, import_node_child_process8.spawn)(winShellCommand(command, args), {
640694
640911
  cwd: opts.cwd,
640695
640912
  stdio: ["pipe", "pipe", "pipe"],
640696
640913
  shell: true,
640697
640914
  env
640698
- }) : (0, import_node_child_process7.spawn)(command, args, {
640915
+ }) : (0, import_node_child_process8.spawn)(command, args, {
640699
640916
  cwd: opts.cwd,
640700
640917
  stdio: ["pipe", "pipe", "pipe"],
640701
640918
  env
@@ -640713,20 +640930,20 @@ async function realRun(command, args, opts = {}) {
640713
640930
  });
640714
640931
  }
640715
640932
  function realDetectCachedJavaHome(cacheJreRoot) {
640716
- if (!import_node_fs15.default.existsSync(cacheJreRoot))
640933
+ if (!import_node_fs16.default.existsSync(cacheJreRoot))
640717
640934
  return null;
640718
- const entries = import_node_fs15.default.readdirSync(cacheJreRoot);
640935
+ const entries = import_node_fs16.default.readdirSync(cacheJreRoot);
640719
640936
  const home = resolveJavaHome(cacheJreRoot, entries, process.platform);
640720
- return home && import_node_fs15.default.existsSync(javaBinPath(home)) ? home : null;
640937
+ return home && import_node_fs16.default.existsSync(javaBinPath(home)) ? home : null;
640721
640938
  }
640722
640939
  async function realBootstrapJava(cacheJreRoot) {
640723
640940
  const url = jreDownloadUrl();
640724
- const tmpDir = import_node_fs15.default.mkdtempSync(import_node_path12.default.join(import_node_os6.default.tmpdir(), "dd-jre-"));
640941
+ const tmpDir = import_node_fs16.default.mkdtempSync(import_node_path13.default.join(import_node_os6.default.tmpdir(), "dd-jre-"));
640725
640942
  try {
640726
- const archive = import_node_path12.default.join(tmpDir, jreArchiveFilename());
640943
+ const archive = import_node_path13.default.join(tmpDir, jreArchiveFilename());
640727
640944
  await downloadFile(url, archive);
640728
- import_node_fs15.default.rmSync(cacheJreRoot, { recursive: true, force: true });
640729
- import_node_fs15.default.mkdirSync(cacheJreRoot, { recursive: true });
640945
+ import_node_fs16.default.rmSync(cacheJreRoot, { recursive: true, force: true });
640946
+ import_node_fs16.default.mkdirSync(cacheJreRoot, { recursive: true });
640730
640947
  await extractZip(archive, cacheJreRoot);
640731
640948
  const home = realDetectCachedJavaHome(cacheJreRoot);
640732
640949
  if (!home) {
@@ -640734,31 +640951,31 @@ async function realBootstrapJava(cacheJreRoot) {
640734
640951
  }
640735
640952
  return home;
640736
640953
  } finally {
640737
- import_node_fs15.default.rmSync(tmpDir, { recursive: true, force: true });
640954
+ import_node_fs16.default.rmSync(tmpDir, { recursive: true, force: true });
640738
640955
  }
640739
640956
  }
640740
640957
  async function realBootstrap(url, destSdkRoot) {
640741
- const tmpDir = import_node_fs15.default.mkdtempSync(import_node_path12.default.join(import_node_os6.default.tmpdir(), "dd-android-cli-"));
640958
+ const tmpDir = import_node_fs16.default.mkdtempSync(import_node_path13.default.join(import_node_os6.default.tmpdir(), "dd-android-cli-"));
640742
640959
  try {
640743
- const zipPath = import_node_path12.default.join(tmpDir, "cmdline-tools.zip");
640960
+ const zipPath = import_node_path13.default.join(tmpDir, "cmdline-tools.zip");
640744
640961
  await downloadFile(url, zipPath);
640745
- const extractDir = import_node_path12.default.join(tmpDir, "extracted");
640746
- import_node_fs15.default.mkdirSync(extractDir, { recursive: true });
640962
+ const extractDir = import_node_path13.default.join(tmpDir, "extracted");
640963
+ import_node_fs16.default.mkdirSync(extractDir, { recursive: true });
640747
640964
  await extractZip(zipPath, extractDir);
640748
- const inner = import_node_path12.default.join(extractDir, "cmdline-tools");
640749
- if (!import_node_fs15.default.existsSync(inner)) {
640965
+ const inner = import_node_path13.default.join(extractDir, "cmdline-tools");
640966
+ if (!import_node_fs16.default.existsSync(inner)) {
640750
640967
  throw new Error("unexpected command-line tools archive layout (no top-level cmdline-tools/)");
640751
640968
  }
640752
- const dest = import_node_path12.default.join(destSdkRoot, "cmdline-tools", "latest");
640753
- import_node_fs15.default.mkdirSync(import_node_path12.default.dirname(dest), { recursive: true });
640754
- import_node_fs15.default.rmSync(dest, { recursive: true, force: true });
640969
+ const dest = import_node_path13.default.join(destSdkRoot, "cmdline-tools", "latest");
640970
+ import_node_fs16.default.mkdirSync(import_node_path13.default.dirname(dest), { recursive: true });
640971
+ import_node_fs16.default.rmSync(dest, { recursive: true, force: true });
640755
640972
  try {
640756
- import_node_fs15.default.renameSync(inner, dest);
640973
+ import_node_fs16.default.renameSync(inner, dest);
640757
640974
  } catch {
640758
- import_node_fs15.default.cpSync(inner, dest, { recursive: true });
640975
+ import_node_fs16.default.cpSync(inner, dest, { recursive: true });
640759
640976
  }
640760
640977
  } finally {
640761
- import_node_fs15.default.rmSync(tmpDir, { recursive: true, force: true });
640978
+ import_node_fs16.default.rmSync(tmpDir, { recursive: true, force: true });
640762
640979
  }
640763
640980
  }
640764
640981
 
@@ -640978,6 +641195,13 @@ async function teardownDeviceRegistry(registry, kill4) {
640978
641195
  for (const entry of registry.values()) {
640979
641196
  if (!entry.bootedByUs)
640980
641197
  continue;
641198
+ if (entry.ready) {
641199
+ try {
641200
+ await entry.ready;
641201
+ } catch {
641202
+ continue;
641203
+ }
641204
+ }
640981
641205
  try {
640982
641206
  await kill4(entry);
640983
641207
  } catch {
@@ -640987,7 +641211,7 @@ async function teardownDeviceRegistry(registry, kill4) {
640987
641211
  }
640988
641212
  function runTool(command, args, timeout = 3e4, env) {
640989
641213
  return new Promise((resolve) => {
640990
- (0, import_node_child_process8.execFile)(command, args, { timeout, env }, (error, stdout) => {
641214
+ (0, import_node_child_process9.execFile)(command, args, { timeout, env }, (error, stdout) => {
640991
641215
  resolve({ code: error?.code ?? 0, stdout: String(stdout ?? "") });
640992
641216
  });
640993
641217
  });
@@ -641031,11 +641255,11 @@ async function realCreateAvd(avdmanagerPath, { name, systemImage, device }, env)
641031
641255
  "--force"
641032
641256
  ];
641033
641257
  await new Promise((resolve, reject) => {
641034
- const child = useShell ? (0, import_node_child_process8.spawn)(winShellCommand(avdmanagerPath, args), {
641258
+ const child = useShell ? (0, import_node_child_process9.spawn)(winShellCommand(avdmanagerPath, args), {
641035
641259
  stdio: ["pipe", "ignore", "pipe"],
641036
641260
  shell: true,
641037
641261
  env
641038
- }) : (0, import_node_child_process8.spawn)(avdmanagerPath, args, { stdio: ["pipe", "ignore", "pipe"], env });
641262
+ }) : (0, import_node_child_process9.spawn)(avdmanagerPath, args, { stdio: ["pipe", "ignore", "pipe"], env });
641039
641263
  let err = "";
641040
641264
  child.stderr?.on("data", (d) => err += d.toString());
641041
641265
  child.on("error", reject);
@@ -641048,7 +641272,7 @@ async function realBootEmulator(emulatorPath, adbPath, desc, port, env, timeout
641048
641272
  const udid = udidForPort(port);
641049
641273
  const bootDesc = { ...desc, headless: effectiveHeadless(desc) };
641050
641274
  const bootArgs = emulatorBootArgs(bootDesc, port);
641051
- const child = (0, import_node_child_process8.spawn)(emulatorPath, bootArgs, {
641275
+ const child = (0, import_node_child_process9.spawn)(emulatorPath, bootArgs, {
641052
641276
  detached: false,
641053
641277
  stdio: ["ignore", "pipe", "pipe"],
641054
641278
  env
@@ -641088,7 +641312,7 @@ function buildAcquireDeviceDeps(sdk, abi, log3) {
641088
641312
  const emulator = sdk.emulator ?? "emulator";
641089
641313
  const avdmanager = sdk.avdmanager ?? "avdmanager";
641090
641314
  const javaPresent = () => {
641091
- const { status } = (0, import_node_child_process8.spawnSync)("java", ["-version"], { stdio: "ignore" });
641315
+ const { status } = (0, import_node_child_process9.spawnSync)("java", ["-version"], { stdio: "ignore" });
641092
641316
  return status === 0;
641093
641317
  };
641094
641318
  const env = {
@@ -641736,7 +641960,7 @@ var APP_DRIVER_PLATFORMS = {
641736
641960
  if (extras?.udid)
641737
641961
  capabilities["appium:udid"] = extras.udid;
641738
641962
  if (descriptor.install) {
641739
- capabilities["appium:app"] = import_node_path13.default.resolve(descriptor.install);
641963
+ capabilities["appium:app"] = import_node_path14.default.resolve(descriptor.install);
641740
641964
  }
641741
641965
  if (descriptor.activity) {
641742
641966
  capabilities["appium:appActivity"] = descriptor.activity;
@@ -641779,20 +642003,17 @@ var APP_DRIVER_PLATFORMS = {
641779
642003
  if (classifyAppIdentifier(appId) === "id") {
641780
642004
  capabilities["appium:bundleId"] = appId;
641781
642005
  } else {
641782
- capabilities["appium:app"] = import_node_path13.default.resolve(appId);
642006
+ capabilities["appium:app"] = import_node_path14.default.resolve(appId);
641783
642007
  }
641784
642008
  if (descriptor.install) {
641785
- capabilities["appium:app"] = import_node_path13.default.resolve(descriptor.install);
642009
+ capabilities["appium:app"] = import_node_path14.default.resolve(descriptor.install);
641786
642010
  }
641787
642011
  if (extras?.udid)
641788
642012
  capabilities["appium:udid"] = extras.udid;
641789
642013
  const timeout = descriptor.timeout ?? 6e4;
641790
642014
  capabilities["appium:wdaLaunchTimeout"] = Math.max(timeout, 12e4);
641791
642015
  capabilities["appium:wdaConnectionTimeout"] = Math.max(timeout, 12e4);
641792
- const derivedDataPath = process.env.DOC_DETECTIVE_IOS_WDA_DERIVED_DATA_PATH;
641793
- if (derivedDataPath && derivedDataPath.trim()) {
641794
- capabilities["appium:derivedDataPath"] = derivedDataPath.trim();
641795
- }
642016
+ applyManagedWdaCapabilities(capabilities, extras?.locateWda);
641796
642017
  return capabilities;
641797
642018
  },
641798
642019
  unsupportedFields: [
@@ -641852,18 +642073,18 @@ function resolveAppSurfaceRef(surface, appSession) {
641852
642073
  return null;
641853
642074
  }
641854
642075
  function invalidateStaleAppiumManifest(home, driverPackage) {
641855
- const cacheDir = import_node_path13.default.join(home, "node_modules", ".cache", "appium");
642076
+ const cacheDir = import_node_path14.default.join(home, "node_modules", ".cache", "appium");
641856
642077
  try {
641857
- const manifest = import_node_fs16.default.readFileSync(import_node_path13.default.join(cacheDir, "extensions.yaml"), "utf8");
642078
+ const manifest = import_node_fs17.default.readFileSync(import_node_path14.default.join(cacheDir, "extensions.yaml"), "utf8");
641858
642079
  if (!manifest.includes(driverPackage)) {
641859
- import_node_fs16.default.rmSync(cacheDir, { recursive: true, force: true });
642080
+ import_node_fs17.default.rmSync(cacheDir, { recursive: true, force: true });
641860
642081
  }
641861
642082
  } catch {
641862
642083
  }
641863
642084
  }
641864
642085
  async function probeMacAccessibility() {
641865
642086
  return await new Promise((resolve) => {
641866
- (0, import_node_child_process9.execFile)("osascript", [
642087
+ (0, import_node_child_process10.execFile)("osascript", [
641867
642088
  "-l",
641868
642089
  "JavaScript",
641869
642090
  "-e",
@@ -641879,7 +642100,7 @@ async function probeMacAccessibility() {
641879
642100
  function probeIosToolchain(deps = {}) {
641880
642101
  const platform = deps.platform ?? process.platform;
641881
642102
  const run2 = deps.run ?? ((command, args) => {
641882
- const result = (0, import_node_child_process9.spawnSync)(command, args, {
642103
+ const result = (0, import_node_child_process10.spawnSync)(command, args, {
641883
642104
  encoding: "utf8",
641884
642105
  windowsHide: true,
641885
642106
  // xcrun/simctl gets 2 minutes for the cold CoreSimulator warm-up;
@@ -642170,7 +642391,8 @@ async function startAppSurface({ config, step, appSession, platform, serverDeps
642170
642391
  const deviceSession = sessions.get(deviceName);
642171
642392
  if (!deviceSession) {
642172
642393
  const capabilities = platformDriver.buildCapabilities(descriptor, appId, {
642173
- udid: acquired.entry.udid
642394
+ udid: acquired.entry.udid,
642395
+ locateWda: serverDeps.locateWda ?? (() => locateManagedWda({ ctx: { cacheDir: config?.cacheDir } }))
642174
642396
  });
642175
642397
  Object.assign(capabilities, descriptor.driverOptions ?? {});
642176
642398
  try {
@@ -642189,7 +642411,7 @@ async function startAppSurface({ config, step, appSession, platform, serverDeps
642189
642411
  driver = deviceSession.driver;
642190
642412
  try {
642191
642413
  if (descriptor.install)
642192
- await driver.installApp(import_node_path13.default.resolve(descriptor.install));
642414
+ await driver.installApp(import_node_path14.default.resolve(descriptor.install));
642193
642415
  if (descriptor.activity) {
642194
642416
  await driver.execute("mobile: startActivity", {
642195
642417
  appPackage: appId,
@@ -642355,7 +642577,7 @@ function defaultForceKill(pid) {
642355
642577
  function queryWindowsProcessTable() {
642356
642578
  return new Promise((resolve, reject) => {
642357
642579
  const script = 'Get-CimInstance Win32_Process | ForEach-Object { "$($_.ProcessId),$($_.ParentProcessId)" }';
642358
- (0, import_node_child_process9.execFile)("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { windowsHide: true, timeout: 1e4, maxBuffer: 16 * 1024 * 1024 }, (error, stdout) => {
642580
+ (0, import_node_child_process10.execFile)("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { windowsHide: true, timeout: 1e4, maxBuffer: 16 * 1024 * 1024 }, (error, stdout) => {
642359
642581
  if (error)
642360
642582
  return reject(error);
642361
642583
  const rows = [];
@@ -642830,10 +643052,10 @@ init_validate();
642830
643052
  init_loader();
642831
643053
 
642832
643054
  // dist/core/tests/ffmpegRecorder.js
642833
- var import_node_child_process10 = require("node:child_process");
643055
+ var import_node_child_process11 = require("node:child_process");
642834
643056
  var import_node_os7 = __toESM(require("node:os"), 1);
642835
- var import_node_path14 = __toESM(require("node:path"), 1);
642836
- var import_node_fs17 = __toESM(require("node:fs"), 1);
643057
+ var import_node_path15 = __toESM(require("node:path"), 1);
643058
+ var import_node_fs18 = __toESM(require("node:fs"), 1);
642837
643059
  var import_node_crypto6 = __toESM(require("node:crypto"), 1);
642838
643060
  init_loader();
642839
643061
  init_utils();
@@ -642912,7 +643134,7 @@ function browserCaptureTitle(contextId) {
642912
643134
  return `RECORD_ME_${safeContextId(contextId)}`;
642913
643135
  }
642914
643136
  function browserDownloadDir(contextId) {
642915
- return import_node_path14.default.join(import_node_os7.default.tmpdir(), "doc-detective", "recordings", safeContextId(contextId));
643137
+ return import_node_path15.default.join(import_node_os7.default.tmpdir(), "doc-detective", "recordings", safeContextId(contextId));
642916
643138
  }
642917
643139
  function engineFields(record) {
642918
643140
  const engine = record && typeof record === "object" ? record.engine : void 0;
@@ -643051,11 +643273,11 @@ async function resolveCropGeometry({ driver, target }) {
643051
643273
  return null;
643052
643274
  }
643053
643275
  function ffmpegPathEnv(ffmpegPath, baseEnv = process.env) {
643054
- const dir = import_node_path14.default.dirname(ffmpegPath);
643276
+ const dir = import_node_path15.default.dirname(ffmpegPath);
643055
643277
  const pathKey = Object.keys(baseEnv).find((k) => k.toUpperCase() === "PATH") ?? "PATH";
643056
643278
  const existing = baseEnv[pathKey];
643057
643279
  return {
643058
- [pathKey]: existing ? `${dir}${import_node_path14.default.delimiter}${existing}` : dir
643280
+ [pathKey]: existing ? `${dir}${import_node_path15.default.delimiter}${existing}` : dir
643059
643281
  };
643060
643282
  }
643061
643283
  function parseCaptureFrameSize(stderr) {
@@ -643079,7 +643301,7 @@ async function detectDisplayPointSize() {
643079
643301
  if (process.platform !== "darwin")
643080
643302
  return null;
643081
643303
  return await new Promise((resolve) => {
643082
- (0, import_node_child_process10.execFile)("osascript", [
643304
+ (0, import_node_child_process11.execFile)("osascript", [
643083
643305
  "-l",
643084
643306
  "JavaScript",
643085
643307
  "-e",
@@ -643225,7 +643447,7 @@ async function detectMacScreenIndex(ffmpegPath) {
643225
643447
  resolve(v);
643226
643448
  };
643227
643449
  try {
643228
- proc = (0, import_node_child_process10.spawn)(ffmpegPath, ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { stdio: ["ignore", "ignore", "pipe"] });
643450
+ proc = (0, import_node_child_process11.spawn)(ffmpegPath, ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { stdio: ["ignore", "ignore", "pipe"] });
643229
643451
  proc.stderr?.on("data", (d) => {
643230
643452
  out += d.toString();
643231
643453
  });
@@ -643254,7 +643476,7 @@ async function detectX11ScreenSize(display) {
643254
643476
  };
643255
643477
  try {
643256
643478
  const env = display ? { ...process.env, DISPLAY: display } : process.env;
643257
- proc = (0, import_node_child_process10.spawn)("xdpyinfo", [], { env, stdio: ["ignore", "pipe", "ignore"] });
643479
+ proc = (0, import_node_child_process11.spawn)("xdpyinfo", [], { env, stdio: ["ignore", "pipe", "ignore"] });
643258
643480
  proc.stdout?.on("data", (d) => {
643259
643481
  out += d.toString();
643260
643482
  });
@@ -643272,7 +643494,7 @@ async function detectX11ScreenSize(display) {
643272
643494
  async function checkSystemBinary(name) {
643273
643495
  return new Promise((resolve) => {
643274
643496
  try {
643275
- const proc = (0, import_node_child_process10.spawn)(name, ["-help"], { stdio: "ignore" });
643497
+ const proc = (0, import_node_child_process11.spawn)(name, ["-help"], { stdio: "ignore" });
643276
643498
  proc.on("error", () => resolve(false));
643277
643499
  proc.on("close", () => resolve(true));
643278
643500
  } catch {
@@ -643289,7 +643511,7 @@ async function startXvfb(display, opts = {}) {
643289
643511
  const w = opts.width ?? defW;
643290
643512
  const h = opts.height ?? defH;
643291
643513
  const startMs = Date.now();
643292
- const proc = (0, import_node_child_process10.spawn)("Xvfb", [display, "-screen", "0", `${w}x${h}x24`, "-nolisten", "tcp"], { stdio: "ignore" });
643514
+ const proc = (0, import_node_child_process11.spawn)("Xvfb", [display, "-screen", "0", `${w}x${h}x24`, "-nolisten", "tcp"], { stdio: "ignore" });
643293
643515
  let spawnErr = null;
643294
643516
  proc.on("error", (e) => {
643295
643517
  spawnErr = e;
@@ -643301,7 +643523,7 @@ async function startXvfb(display, opts = {}) {
643301
643523
  if (proc.exitCode !== null)
643302
643524
  throw new Error(`Xvfb exited early on ${display} (code ${proc.exitCode})`);
643303
643525
  try {
643304
- if (import_node_fs17.default.statSync(lock).mtimeMs >= startMs)
643526
+ if (import_node_fs18.default.statSync(lock).mtimeMs >= startMs)
643305
643527
  return proc;
643306
643528
  } catch {
643307
643529
  }
@@ -643573,11 +643795,11 @@ function hasUnresolvedMetaReference(expression, context) {
643573
643795
  }
643574
643796
  return false;
643575
643797
  }
643576
- function getMetaValue(path26, context) {
643798
+ function getMetaValue(path27, context) {
643577
643799
  if (!context) {
643578
643800
  return void 0;
643579
643801
  }
643580
- const [basePath, jsonPointer] = path26.split("#");
643802
+ const [basePath, jsonPointer] = path27.split("#");
643581
643803
  const resolvedPath = resolvePathTemplateVariables(basePath, context);
643582
643804
  let value = getNestedProperty(context, resolvedPath);
643583
643805
  if (jsonPointer && value) {
@@ -643594,9 +643816,9 @@ function getMetaValue(path26, context) {
643594
643816
  }
643595
643817
  return value;
643596
643818
  }
643597
- function resolvePathTemplateVariables(path26, context) {
643819
+ function resolvePathTemplateVariables(path27, context) {
643598
643820
  const templateRegex = /\{\{(\w+)\}\}/g;
643599
- return path26.replace(templateRegex, (match, varName) => {
643821
+ return path27.replace(templateRegex, (match, varName) => {
643600
643822
  if (context && context.id && varName === "id") {
643601
643823
  return context.id;
643602
643824
  }
@@ -643633,10 +643855,10 @@ async function resolveEmbeddedExpressions(str, context) {
643633
643855
  parts.push(str.slice(lastIdx));
643634
643856
  return parts.join("");
643635
643857
  }
643636
- function getNestedProperty(obj, path26) {
643637
- if (!obj || !path26)
643858
+ function getNestedProperty(obj, path27) {
643859
+ if (!obj || !path27)
643638
643860
  return void 0;
643639
- const parts = path26.split(".");
643861
+ const parts = path27.split(".");
643640
643862
  let current = obj;
643641
643863
  for (const part of parts) {
643642
643864
  if (current === null || current === void 0)
@@ -643673,6 +643895,7 @@ function containsOperators(expression, allowOperators = false) {
643673
643895
  }
643674
643896
  return false;
643675
643897
  }
643898
+ var compiledEvaluatorCache = /* @__PURE__ */ new Map();
643676
643899
  async function evaluateExpression(expression, context) {
643677
643900
  try {
643678
643901
  expression = preprocessExpression(expression);
@@ -643746,7 +643969,13 @@ async function evaluateExpression(expression, context) {
643746
643969
  }
643747
643970
  })
643748
643971
  };
643749
- const evaluator = new Function(...Object.keys(evalContext), `return ${expression};`);
643972
+ const argNames = Object.keys(evalContext);
643973
+ const cacheKey = `${argNames.join(",")}\0${expression}`;
643974
+ let evaluator = compiledEvaluatorCache.get(cacheKey);
643975
+ if (!evaluator) {
643976
+ evaluator = new Function(...argNames, `return ${expression};`);
643977
+ compiledEvaluatorCache.set(cacheKey, evaluator);
643978
+ }
643750
643979
  return evaluator(...Object.values(evalContext));
643751
643980
  } catch (error) {
643752
643981
  log(`Error evaluating expression '${expression}': ${error.message}`, "error");
@@ -645317,8 +645546,8 @@ async function startBackgroundProcessSurface({ config, descriptor, processRegist
645317
645546
  }
645318
645547
 
645319
645548
  // dist/core/tests/runShell.js
645320
- var import_node_fs18 = __toESM(require("node:fs"), 1);
645321
- var import_node_path15 = __toESM(require("node:path"), 1);
645549
+ var import_node_fs19 = __toESM(require("node:fs"), 1);
645550
+ var import_node_path16 = __toESM(require("node:path"), 1);
645322
645551
  async function runShell({ config, step, driver, processRegistry }) {
645323
645552
  const result = {
645324
645553
  status: "PASS",
@@ -645431,19 +645660,19 @@ async function runShell({ config, step, driver, processRegistry }) {
645431
645660
  descriptions.push(stdioMatched ? `Found expected output (${step.runShell.stdio}) in stdio.` : isRegex ? `Couldn't find expected output (${step.runShell.stdio}) in actual output (stdout or stderr).` : `Couldn't find expected output (${step.runShell.stdio}) in stdio (stdout or stderr).`);
645432
645661
  }
645433
645662
  if (step.runShell.path) {
645434
- const dir = import_node_path15.default.dirname(step.runShell.path);
645435
- if (!import_node_fs18.default.existsSync(dir)) {
645436
- import_node_fs18.default.mkdirSync(dir, { recursive: true });
645663
+ const dir = import_node_path16.default.dirname(step.runShell.path);
645664
+ if (!import_node_fs19.default.existsSync(dir)) {
645665
+ import_node_fs19.default.mkdirSync(dir, { recursive: true });
645437
645666
  }
645438
645667
  let filePath = step.runShell.path;
645439
645668
  log(config, "debug", `Saving stdio to file: ${filePath}`);
645440
- if (!import_node_fs18.default.existsSync(filePath)) {
645441
- import_node_fs18.default.writeFileSync(filePath, result.outputs.stdio.stdout);
645669
+ if (!import_node_fs19.default.existsSync(filePath)) {
645670
+ import_node_fs19.default.writeFileSync(filePath, result.outputs.stdio.stdout);
645442
645671
  } else {
645443
645672
  if (step.runShell.overwrite == "false") {
645444
645673
  descriptions.push(`Didn't save output. File already exists.`);
645445
645674
  }
645446
- const existingFile = import_node_fs18.default.readFileSync(filePath, "utf8");
645675
+ const existingFile = import_node_fs19.default.readFileSync(filePath, "utf8");
645447
645676
  const fractionalDiff = calculateFractionalDifference(existingFile, result.outputs.stdio.stdout);
645448
645677
  log(config, "debug", `Fractional difference: ${fractionalDiff}`);
645449
645678
  result.outputs.variation = fractionalDiff;
@@ -645453,13 +645682,13 @@ async function runShell({ config, step, driver, processRegistry }) {
645453
645682
  });
645454
645683
  if (fractionalDiff > step.runShell.maxVariation) {
645455
645684
  if (step.runShell.overwrite == "aboveVariation") {
645456
- import_node_fs18.default.writeFileSync(filePath, result.outputs.stdio.stdout);
645685
+ import_node_fs19.default.writeFileSync(filePath, result.outputs.stdio.stdout);
645457
645686
  descriptions.push(`Saved output to file.`);
645458
645687
  }
645459
645688
  descriptions.push(`The difference between the existing output and the new output (${fractionalDiff.toFixed(2)}) is greater than the max accepted variation (${step.runShell.maxVariation}).`);
645460
645689
  } else {
645461
645690
  if (step.runShell.overwrite == "true") {
645462
- import_node_fs18.default.writeFileSync(filePath, result.outputs.stdio.stdout);
645691
+ import_node_fs19.default.writeFileSync(filePath, result.outputs.stdio.stdout);
645463
645692
  descriptions.push(`Saved output to file.`);
645464
645693
  }
645465
645694
  descriptions.push(`Saved-file variation (${fractionalDiff.toFixed(2)}) is within the max accepted variation (${step.runShell.maxVariation}).`);
@@ -645806,8 +646035,8 @@ async function swipeSurface({ config, step, driver, appSession }) {
645806
646035
  // dist/core/tests/saveScreenshot.js
645807
646036
  init_validate();
645808
646037
  init_utils();
645809
- var import_node_path16 = __toESM(require("node:path"), 1);
645810
- var import_node_fs19 = __toESM(require("node:fs"), 1);
646038
+ var import_node_path17 = __toESM(require("node:path"), 1);
646039
+ var import_node_fs20 = __toESM(require("node:fs"), 1);
645811
646040
  init_loader();
645812
646041
  var _pngjs = null;
645813
646042
  var _sharp = null;
@@ -645958,7 +646187,7 @@ async function saveScreenshot({ config, step, driver, appSession }) {
645958
646187
  if (typeof step.screenshot.path === "undefined") {
645959
646188
  step.screenshot.path = `${step.stepId}.png`;
645960
646189
  if (step.screenshot.directory) {
645961
- step.screenshot.path = import_node_path16.default.resolve(step.screenshot.directory, step.screenshot.path);
646190
+ step.screenshot.path = import_node_path17.default.resolve(step.screenshot.directory, step.screenshot.path);
645962
646191
  }
645963
646192
  }
645964
646193
  step.screenshot = {
@@ -645994,17 +646223,17 @@ async function saveScreenshot({ config, step, driver, appSession }) {
645994
646223
  } catch {
645995
646224
  urlPathname = originalUrlPath;
645996
646225
  }
645997
- const rawBase = import_node_path16.default.basename(urlPathname.split("?")[0].split("#")[0].replace(/\\/g, "/"));
646226
+ const rawBase = import_node_path17.default.basename(urlPathname.split("?")[0].split("#")[0].replace(/\\/g, "/"));
645998
646227
  const safeBase = sanitizeFilesystemName(rawBase, `${step.stepId}.png`);
645999
- dir = import_node_path16.default.join(process.cwd(), "doc-detective-runs", getOrInitRunTimestamp(config));
646000
- if (!import_node_fs19.default.existsSync(dir)) {
646001
- import_node_fs19.default.mkdirSync(dir, { recursive: true });
646228
+ dir = import_node_path17.default.join(process.cwd(), "doc-detective-runs", getOrInitRunTimestamp(config));
646229
+ if (!import_node_fs20.default.existsSync(dir)) {
646230
+ import_node_fs20.default.mkdirSync(dir, { recursive: true });
646002
646231
  }
646003
646232
  const captureId = `${step.stepId || "screenshot"}_${Date.now()}`;
646004
- filePath = import_node_path16.default.join(dir, `${captureId}_${safeBase}`);
646005
- const resolvedDir = import_node_path16.default.resolve(dir);
646006
- const resolvedFile = import_node_path16.default.resolve(filePath);
646007
- if (!resolvedFile.startsWith(resolvedDir + import_node_path16.default.sep)) {
646233
+ filePath = import_node_path17.default.join(dir, `${captureId}_${safeBase}`);
646234
+ const resolvedDir = import_node_path17.default.resolve(dir);
646235
+ const resolvedFile = import_node_path17.default.resolve(filePath);
646236
+ if (!resolvedFile.startsWith(resolvedDir + import_node_path17.default.sep)) {
646008
646237
  result.status = "FAIL";
646009
646238
  result.description = `Refusing to write screenshot outside run folder: ${resolvedFile}`;
646010
646239
  return result;
@@ -646013,11 +646242,11 @@ async function saveScreenshot({ config, step, driver, appSession }) {
646013
646242
  log(config, "debug", `Screenshot path is a URL (${redactedUrl}); overwrite is ignored, running comparison only.`);
646014
646243
  }
646015
646244
  } else {
646016
- dir = import_node_path16.default.dirname(step.screenshot.path);
646017
- if (!import_node_fs19.default.existsSync(dir)) {
646018
- import_node_fs19.default.mkdirSync(dir, { recursive: true });
646245
+ dir = import_node_path17.default.dirname(step.screenshot.path);
646246
+ if (!import_node_fs20.default.existsSync(dir)) {
646247
+ import_node_fs20.default.mkdirSync(dir, { recursive: true });
646019
646248
  }
646020
- if (import_node_fs19.default.existsSync(filePath)) {
646249
+ if (import_node_fs20.default.existsSync(filePath)) {
646021
646250
  if (step.screenshot.overwrite == "false") {
646022
646251
  result.status = "SKIPPED";
646023
646252
  result.description = `File already exists: ${filePath}`;
@@ -646025,7 +646254,7 @@ async function saveScreenshot({ config, step, driver, appSession }) {
646025
646254
  return result;
646026
646255
  } else {
646027
646256
  existFilePath = filePath;
646028
- filePath = import_node_path16.default.join(dir, `${step.stepId}_${Date.now()}.png`);
646257
+ filePath = import_node_path17.default.join(dir, `${step.stepId}_${Date.now()}.png`);
646029
646258
  }
646030
646259
  }
646031
646260
  }
@@ -646127,8 +646356,8 @@ async function saveScreenshot({ config, step, driver, appSession }) {
646127
646356
  } catch (error) {
646128
646357
  result.status = "FAIL";
646129
646358
  result.description = `Couldn't save screenshot. ${error}`;
646130
- if (existFilePath && filePath !== existFilePath && import_node_fs19.default.existsSync(filePath)) {
646131
- import_node_fs19.default.unlinkSync(filePath);
646359
+ if (existFilePath && filePath !== existFilePath && import_node_fs20.default.existsSync(filePath)) {
646360
+ import_node_fs20.default.unlinkSync(filePath);
646132
646361
  }
646133
646362
  return result;
646134
646363
  } finally {
@@ -646183,7 +646412,7 @@ async function saveScreenshot({ config, step, driver, appSession }) {
646183
646412
  rect.width = clamped.width;
646184
646413
  rect.height = clamped.height;
646185
646414
  log(config, "debug", { padded_rect: rect });
646186
- const croppedPath = import_node_path16.default.join(dir, `cropped_${step.stepId || Date.now()}.png`);
646415
+ const croppedPath = import_node_path17.default.join(dir, `cropped_${step.stepId || Date.now()}.png`);
646187
646416
  try {
646188
646417
  await sharp(filePath).extract({
646189
646418
  left: rect.x,
@@ -646191,12 +646420,12 @@ async function saveScreenshot({ config, step, driver, appSession }) {
646191
646420
  width: rect.width,
646192
646421
  height: rect.height
646193
646422
  }).toFile(croppedPath);
646194
- import_node_fs19.default.renameSync(croppedPath, filePath);
646423
+ import_node_fs20.default.renameSync(croppedPath, filePath);
646195
646424
  } catch (error) {
646196
646425
  result.status = "FAIL";
646197
646426
  result.description = `Couldn't crop image. ${error}`;
646198
- if (existFilePath && filePath !== existFilePath && import_node_fs19.default.existsSync(filePath)) {
646199
- import_node_fs19.default.unlinkSync(filePath);
646427
+ if (existFilePath && filePath !== existFilePath && import_node_fs20.default.existsSync(filePath)) {
646428
+ import_node_fs20.default.unlinkSync(filePath);
646200
646429
  }
646201
646430
  return result;
646202
646431
  }
@@ -646204,7 +646433,7 @@ async function saveScreenshot({ config, step, driver, appSession }) {
646204
646433
  if (existFilePath) {
646205
646434
  if (step.screenshot.overwrite == "true" && !isUrlPath) {
646206
646435
  result.description += ` Overwrote existing file.`;
646207
- import_node_fs19.default.renameSync(filePath, existFilePath);
646436
+ import_node_fs20.default.renameSync(filePath, existFilePath);
646208
646437
  result.outputs.screenshotPath = existFilePath;
646209
646438
  result.outputs.changed = true;
646210
646439
  if (step.screenshot.sourceIntegration) {
@@ -646217,13 +646446,13 @@ async function saveScreenshot({ config, step, driver, appSession }) {
646217
646446
  let img1;
646218
646447
  let img2;
646219
646448
  try {
646220
- img1 = PNG.sync.read(import_node_fs19.default.readFileSync(existFilePath));
646221
- img2 = PNG.sync.read(import_node_fs19.default.readFileSync(filePath));
646449
+ img1 = PNG.sync.read(import_node_fs20.default.readFileSync(existFilePath));
646450
+ img2 = PNG.sync.read(import_node_fs20.default.readFileSync(filePath));
646222
646451
  } catch (error) {
646223
646452
  result.status = "FAIL";
646224
646453
  result.description = isUrlPath ? `Couldn't decode PNG for comparison. The URL reference (${redactedUrl}) may not be a valid PNG. ${error}` : `Couldn't decode PNG for comparison. ${error}`;
646225
- if (!isUrlPath && filePath !== existFilePath && import_node_fs19.default.existsSync(filePath)) {
646226
- import_node_fs19.default.unlinkSync(filePath);
646454
+ if (!isUrlPath && filePath !== existFilePath && import_node_fs20.default.existsSync(filePath)) {
646455
+ import_node_fs20.default.unlinkSync(filePath);
646227
646456
  }
646228
646457
  return result;
646229
646458
  }
@@ -646235,8 +646464,8 @@ async function saveScreenshot({ config, step, driver, appSession }) {
646235
646464
  });
646236
646465
  if (!aspectRatioMatch) {
646237
646466
  result.description = `Couldn't compare images. Images have different aspect ratios.`;
646238
- if (!isUrlPath && filePath !== existFilePath && import_node_fs19.default.existsSync(filePath)) {
646239
- import_node_fs19.default.unlinkSync(filePath);
646467
+ if (!isUrlPath && filePath !== existFilePath && import_node_fs20.default.existsSync(filePath)) {
646468
+ import_node_fs20.default.unlinkSync(filePath);
646240
646469
  }
646241
646470
  return await evaluateApplicable();
646242
646471
  }
@@ -646263,8 +646492,8 @@ async function saveScreenshot({ config, step, driver, appSession }) {
646263
646492
  } catch (error) {
646264
646493
  result.status = "FAIL";
646265
646494
  result.description = `Couldn't load screenshot comparison dependency (pixelmatch). ${error?.message ?? error}`;
646266
- if (!isUrlPath && filePath !== existFilePath && import_node_fs19.default.existsSync(filePath)) {
646267
- import_node_fs19.default.unlinkSync(filePath);
646495
+ if (!isUrlPath && filePath !== existFilePath && import_node_fs20.default.existsSync(filePath)) {
646496
+ import_node_fs20.default.unlinkSync(filePath);
646268
646497
  }
646269
646498
  return result;
646270
646499
  }
@@ -646282,7 +646511,7 @@ async function saveScreenshot({ config, step, driver, appSession }) {
646282
646511
  });
646283
646512
  if (fractionalDiff > step.screenshot.maxVariation) {
646284
646513
  if (step.screenshot.overwrite == "aboveVariation" && !isUrlPath) {
646285
- import_node_fs19.default.renameSync(filePath, existFilePath);
646514
+ import_node_fs20.default.renameSync(filePath, existFilePath);
646286
646515
  }
646287
646516
  result.description += ` The difference between the existing screenshot and new screenshot (${fractionalDiff.toFixed(2)}) is greater than the max accepted variation (${step.screenshot.maxVariation}).`;
646288
646517
  if (isUrlPath) {
@@ -646307,7 +646536,7 @@ async function saveScreenshot({ config, step, driver, appSession }) {
646307
646536
  result.outputs.sourceIntegration = step.screenshot.sourceIntegration;
646308
646537
  }
646309
646538
  if (step.screenshot.overwrite != "true") {
646310
- import_node_fs19.default.unlinkSync(filePath);
646539
+ import_node_fs20.default.unlinkSync(filePath);
646311
646540
  }
646312
646541
  }
646313
646542
  }
@@ -646326,10 +646555,10 @@ async function saveScreenshot({ config, step, driver, appSession }) {
646326
646555
  // dist/core/tests/startRecording.js
646327
646556
  init_validate();
646328
646557
  init_utils();
646329
- var import_node_child_process11 = require("node:child_process");
646558
+ var import_node_child_process12 = require("node:child_process");
646330
646559
  var import_node_crypto7 = require("node:crypto");
646331
- var import_node_path17 = __toESM(require("node:path"), 1);
646332
- var import_node_fs20 = __toESM(require("node:fs"), 1);
646560
+ var import_node_path18 = __toESM(require("node:path"), 1);
646561
+ var import_node_fs21 = __toESM(require("node:fs"), 1);
646333
646562
  var import_node_os8 = __toESM(require("node:os"), 1);
646334
646563
  async function startRecording({ config, context, step, driver, recordingHost, appSession, deps = {} }) {
646335
646564
  let result = {
@@ -646398,7 +646627,7 @@ async function startRecording({ config, context, step, driver, recordingHost, ap
646398
646627
  if (typeof step.record.path === "undefined") {
646399
646628
  step.record.path = `${step.stepId}.mp4`;
646400
646629
  if (step.record.directory) {
646401
- step.record.path = import_node_path17.default.resolve(step.record.directory, step.record.path);
646630
+ step.record.path = import_node_path18.default.resolve(step.record.directory, step.record.path);
646402
646631
  }
646403
646632
  }
646404
646633
  step.record = {
@@ -646406,18 +646635,18 @@ async function startRecording({ config, context, step, driver, recordingHost, ap
646406
646635
  overwrite: step.record.overwrite || "false"
646407
646636
  };
646408
646637
  let filePath = step.record.path;
646409
- const baseName = import_node_path17.default.basename(filePath, import_node_path17.default.extname(filePath));
646410
- const dir = import_node_path17.default.dirname(step.record.path);
646411
- if (!import_node_fs20.default.existsSync(dir)) {
646412
- import_node_fs20.default.mkdirSync(dir, { recursive: true });
646638
+ const baseName = import_node_path18.default.basename(filePath, import_node_path18.default.extname(filePath));
646639
+ const dir = import_node_path18.default.dirname(step.record.path);
646640
+ if (!import_node_fs21.default.existsSync(dir)) {
646641
+ import_node_fs21.default.mkdirSync(dir, { recursive: true });
646413
646642
  }
646414
- if (import_node_fs20.default.existsSync(filePath) && step.record.overwrite == "false") {
646643
+ if (import_node_fs21.default.existsSync(filePath) && step.record.overwrite == "false") {
646415
646644
  result.status = "SKIPPED";
646416
646645
  result.description = `File already exists: ${filePath}`;
646417
646646
  return result;
646418
646647
  }
646419
646648
  const normalizeActiveTarget = (p) => {
646420
- const resolved = import_node_path17.default.resolve(p);
646649
+ const resolved = import_node_path18.default.resolve(p);
646421
646650
  return process.platform === "win32" || process.platform === "darwin" ? resolved.toLowerCase() : resolved;
646422
646651
  };
646423
646652
  const normalizedTarget = normalizeActiveTarget(filePath);
@@ -646505,13 +646734,13 @@ async function startRecording({ config, context, step, driver, recordingHost, ap
646505
646734
  }
646506
646735
  const captureTitle = browserCaptureTitle(context.contextId);
646507
646736
  const downloadDir = browserDownloadDir(context.contextId);
646508
- if (!import_node_fs20.default.existsSync(downloadDir)) {
646509
- import_node_fs20.default.mkdirSync(downloadDir, { recursive: true });
646737
+ if (!import_node_fs21.default.existsSync(downloadDir)) {
646738
+ import_node_fs21.default.mkdirSync(downloadDir, { recursive: true });
646510
646739
  }
646511
- const downloadPath = import_node_path17.default.join(downloadDir, `${baseName}.webm`);
646512
- if (import_node_fs20.default.existsSync(downloadPath)) {
646740
+ const downloadPath = import_node_path18.default.join(downloadDir, `${baseName}.webm`);
646741
+ if (import_node_fs21.default.existsSync(downloadPath)) {
646513
646742
  try {
646514
- import_node_fs20.default.unlinkSync(downloadPath);
646743
+ import_node_fs21.default.unlinkSync(downloadPath);
646515
646744
  } catch {
646516
646745
  }
646517
646746
  }
@@ -646651,10 +646880,10 @@ async function startRecording({ config, context, step, driver, recordingHost, ap
646651
646880
  } catch {
646652
646881
  }
646653
646882
  }
646654
- const tempDir = import_node_path17.default.join(import_node_os8.default.tmpdir(), "doc-detective", "recordings");
646655
- if (!import_node_fs20.default.existsSync(tempDir))
646656
- import_node_fs20.default.mkdirSync(tempDir, { recursive: true });
646657
- const tempPath = import_node_path17.default.join(tempDir, `${safeContextId(context.contextId)}-${baseName}-${(0, import_node_crypto7.randomUUID)().slice(0, 8)}.mkv`);
646883
+ const tempDir = import_node_path18.default.join(import_node_os8.default.tmpdir(), "doc-detective", "recordings");
646884
+ if (!import_node_fs21.default.existsSync(tempDir))
646885
+ import_node_fs21.default.mkdirSync(tempDir, { recursive: true });
646886
+ const tempPath = import_node_path18.default.join(tempDir, `${safeContextId(context.contextId)}-${baseName}-${(0, import_node_crypto7.randomUUID)().slice(0, 8)}.mkv`);
646658
646887
  let ffmpegPath;
646659
646888
  try {
646660
646889
  ffmpegPath = await (deps.getFfmpegPath ?? getFfmpegPath)({
@@ -646685,7 +646914,7 @@ async function startRecording({ config, context, step, driver, recordingHost, ap
646685
646914
  screenSize
646686
646915
  });
646687
646916
  log(config, "debug", `ffmpeg recording: platform=${process.platform} target=${plan.target}${screenIndex !== void 0 ? ` screen=${screenIndex}` : ""}${context.__display ? ` display=${context.__display}` : ""} -> ${tempPath}`);
646688
- const proc = (deps.spawn ?? import_node_child_process11.spawn)(ffmpegPath, args, {
646917
+ const proc = (deps.spawn ?? import_node_child_process12.spawn)(ffmpegPath, args, {
646689
646918
  stdio: ["pipe", "ignore", "pipe"]
646690
646919
  });
646691
646920
  let spawnError = null;
@@ -646724,10 +646953,10 @@ async function startRecording({ config, context, step, driver, recordingHost, ap
646724
646953
  // dist/core/tests/stopRecording.js
646725
646954
  init_validate();
646726
646955
  init_utils();
646727
- var import_node_child_process12 = require("node:child_process");
646956
+ var import_node_child_process13 = require("node:child_process");
646728
646957
  var import_node_crypto8 = require("node:crypto");
646729
- var import_node_path18 = __toESM(require("node:path"), 1);
646730
- var import_node_fs21 = __toESM(require("node:fs"), 1);
646958
+ var import_node_path19 = __toESM(require("node:path"), 1);
646959
+ var import_node_fs22 = __toESM(require("node:fs"), 1);
646731
646960
  var import_node_os9 = __toESM(require("node:os"), 1);
646732
646961
  async function stopRecording({ config, step, driver, deps = {} }) {
646733
646962
  let result = {
@@ -646922,14 +647151,14 @@ async function stopRecording({ config, step, driver, deps = {} }) {
646922
647151
  dropHandle();
646923
647152
  return result;
646924
647153
  }
646925
- if (import_node_path18.default.extname(recording.targetPath) === ".mp4") {
646926
- import_node_fs21.default.writeFileSync(recording.targetPath, buffer);
647154
+ if (import_node_path19.default.extname(recording.targetPath) === ".mp4") {
647155
+ import_node_fs22.default.writeFileSync(recording.targetPath, buffer);
646927
647156
  } else {
646928
- const tempDir = import_node_path18.default.join(import_node_os9.default.tmpdir(), "doc-detective", "recordings");
646929
- if (!import_node_fs21.default.existsSync(tempDir))
646930
- import_node_fs21.default.mkdirSync(tempDir, { recursive: true });
646931
- const tempPath = import_node_path18.default.join(tempDir, `device-${(0, import_node_crypto8.randomUUID)().slice(0, 8)}.mp4`);
646932
- import_node_fs21.default.writeFileSync(tempPath, buffer);
647157
+ const tempDir = import_node_path19.default.join(import_node_os9.default.tmpdir(), "doc-detective", "recordings");
647158
+ if (!import_node_fs22.default.existsSync(tempDir))
647159
+ import_node_fs22.default.mkdirSync(tempDir, { recursive: true });
647160
+ const tempPath = import_node_path19.default.join(tempDir, `device-${(0, import_node_crypto8.randomUUID)().slice(0, 8)}.mp4`);
647161
+ import_node_fs22.default.writeFileSync(tempPath, buffer);
646933
647162
  await transcode({
646934
647163
  config,
646935
647164
  sourcePath: tempPath,
@@ -646957,7 +647186,7 @@ async function transcode({ config, sourcePath, targetPath, deleteSource, crop })
646957
647186
  const cy = `max(0\\,min(${crop.y}\\,ih-${ch}))`;
646958
647187
  filters.push(`crop=w=${cw}:h=${ch}:x=${cx}:y=${cy}`);
646959
647188
  }
646960
- if (import_node_path18.default.extname(targetPath) === ".gif") {
647189
+ if (import_node_path19.default.extname(targetPath) === ".gif") {
646961
647190
  filters.push("scale=iw:-1:flags=lanczos");
646962
647191
  }
646963
647192
  if (filters.length > 0) {
@@ -646966,7 +647195,7 @@ async function transcode({ config, sourcePath, targetPath, deleteSource, crop })
646966
647195
  ffmpegArgs.push(`${targetPath}`);
646967
647196
  const ffmpegPath = await getFfmpegPath({ cacheDir: config?.cacheDir });
646968
647197
  await new Promise((resolve, reject) => {
646969
- const child = (0, import_node_child_process12.spawn)(ffmpegPath, ffmpegArgs);
647198
+ const child = (0, import_node_child_process13.spawn)(ffmpegPath, ffmpegArgs);
646970
647199
  let stderr = "";
646971
647200
  child.stderr?.on("data", (d) => {
646972
647201
  stderr = (stderr + d.toString()).slice(-2e3);
@@ -646975,7 +647204,7 @@ async function transcode({ config, sourcePath, targetPath, deleteSource, crop })
646975
647204
  if (code === 0) {
646976
647205
  if (deleteSource && sourcePath !== targetPath) {
646977
647206
  try {
646978
- import_node_fs21.default.unlinkSync(sourcePath);
647207
+ import_node_fs22.default.unlinkSync(sourcePath);
646979
647208
  } catch {
646980
647209
  }
646981
647210
  }
@@ -646994,7 +647223,7 @@ async function waitForStableFile(filePath, maxSeconds) {
646994
647223
  for (let i = 0; i < deadline; i++) {
646995
647224
  let size = -1;
646996
647225
  try {
646997
- size = import_node_fs21.default.statSync(filePath).size;
647226
+ size = import_node_fs22.default.statSync(filePath).size;
646998
647227
  } catch {
646999
647228
  size = -1;
647000
647229
  }
@@ -647034,8 +647263,8 @@ async function loadVariables({ step }) {
647034
647263
  // dist/core/tests/saveCookie.js
647035
647264
  init_validate();
647036
647265
  init_utils();
647037
- var import_node_path19 = __toESM(require("node:path"), 1);
647038
- var import_node_fs22 = __toESM(require("node:fs"), 1);
647266
+ var import_node_path20 = __toESM(require("node:path"), 1);
647267
+ var import_node_fs23 = __toESM(require("node:fs"), 1);
647039
647268
  async function saveCookie({ config, step, driver }) {
647040
647269
  let result = {
647041
647270
  status: "PASS",
@@ -647052,7 +647281,7 @@ async function saveCookie({ config, step, driver }) {
647052
647281
  let cookieName, filePath, directory, overwrite, domain, variable;
647053
647282
  if (typeof step.saveCookie === "string") {
647054
647283
  if (step.saveCookie.endsWith(".txt")) {
647055
- cookieName = import_node_path19.default.basename(step.saveCookie, ".txt");
647284
+ cookieName = import_node_path20.default.basename(step.saveCookie, ".txt");
647056
647285
  filePath = step.saveCookie;
647057
647286
  } else {
647058
647287
  cookieName = step.saveCookie;
@@ -647101,15 +647330,15 @@ async function saveCookie({ config, step, driver }) {
647101
647330
  }
647102
647331
  if (filePath) {
647103
647332
  const outputDirectory = directory || config.output || process.cwd();
647104
- const fullPath = import_node_path19.default.resolve(outputDirectory, filePath);
647105
- if (import_node_fs22.default.existsSync(fullPath) && !(overwrite === true || overwrite === "true")) {
647333
+ const fullPath = import_node_path20.default.resolve(outputDirectory, filePath);
647334
+ if (import_node_fs23.default.existsSync(fullPath) && !(overwrite === true || overwrite === "true")) {
647106
647335
  result.status = "FAIL";
647107
647336
  result.description = `File '${fullPath}' already exists and overwrite is not enabled.`;
647108
647337
  return result;
647109
647338
  }
647110
- const dir = import_node_path19.default.dirname(fullPath);
647111
- if (!import_node_fs22.default.existsSync(dir)) {
647112
- import_node_fs22.default.mkdirSync(dir, { recursive: true });
647339
+ const dir = import_node_path20.default.dirname(fullPath);
647340
+ if (!import_node_fs23.default.existsSync(dir)) {
647341
+ import_node_fs23.default.mkdirSync(dir, { recursive: true });
647113
647342
  }
647114
647343
  if (targetCookie) {
647115
647344
  const netscapeCookie = formatCookieForNetscape(targetCookie);
@@ -647117,11 +647346,11 @@ async function saveCookie({ config, step, driver }) {
647117
647346
  # This is a cookie file saved by Doc Detective
647118
647347
  ${netscapeCookie}
647119
647348
  `;
647120
- import_node_fs22.default.writeFileSync(fullPath, content, "utf8");
647349
+ import_node_fs23.default.writeFileSync(fullPath, content, "utf8");
647121
647350
  result.description = `Saved cookie '${cookieName}' to '${fullPath}'.`;
647122
647351
  log(config, "debug", `Saved cookie '${cookieName}' to file: ${fullPath}`);
647123
647352
  } else {
647124
- import_node_fs22.default.writeFileSync(fullPath, "# No cookie data\n", "utf8");
647353
+ import_node_fs23.default.writeFileSync(fullPath, "# No cookie data\n", "utf8");
647125
647354
  result.description = `Created empty cookie file at '${fullPath}'.`;
647126
647355
  log(config, "debug", `Created empty cookie file: ${fullPath}`);
647127
647356
  }
@@ -647148,8 +647377,8 @@ function formatCookieForNetscape(cookie) {
647148
647377
  // dist/core/tests/loadCookie.js
647149
647378
  init_validate();
647150
647379
  init_utils();
647151
- var import_node_path20 = __toESM(require("node:path"), 1);
647152
- var import_node_fs23 = __toESM(require("node:fs"), 1);
647380
+ var import_node_path21 = __toESM(require("node:path"), 1);
647381
+ var import_node_fs24 = __toESM(require("node:fs"), 1);
647153
647382
  async function loadCookie({ config, step, driver }) {
647154
647383
  let result = {
647155
647384
  status: "PASS",
@@ -647166,7 +647395,7 @@ async function loadCookie({ config, step, driver }) {
647166
647395
  let cookieName, filePath, directory, domain, variable;
647167
647396
  if (typeof step.loadCookie === "string") {
647168
647397
  if (step.loadCookie.endsWith(".txt")) {
647169
- cookieName = import_node_path20.default.basename(step.loadCookie, ".txt");
647398
+ cookieName = import_node_path21.default.basename(step.loadCookie, ".txt");
647170
647399
  filePath = step.loadCookie;
647171
647400
  } else {
647172
647401
  cookieName = step.loadCookie;
@@ -647179,9 +647408,9 @@ async function loadCookie({ config, step, driver }) {
647179
647408
  domain = step.loadCookie.domain;
647180
647409
  variable = step.loadCookie.variable;
647181
647410
  if (filePath && !cookieName) {
647182
- const ext = import_node_path20.default.extname(filePath).toLowerCase();
647411
+ const ext = import_node_path21.default.extname(filePath).toLowerCase();
647183
647412
  if (ext === ".txt") {
647184
- cookieName = import_node_path20.default.basename(filePath, ext);
647413
+ cookieName = import_node_path21.default.basename(filePath, ext);
647185
647414
  }
647186
647415
  }
647187
647416
  }
@@ -647204,14 +647433,14 @@ async function loadCookie({ config, step, driver }) {
647204
647433
  }
647205
647434
  } else if (filePath) {
647206
647435
  const inputDirectory = directory || config.output || process.cwd();
647207
- const fullPath = import_node_path20.default.resolve(inputDirectory, filePath);
647208
- if (!import_node_fs23.default.existsSync(fullPath)) {
647436
+ const fullPath = import_node_path21.default.resolve(inputDirectory, filePath);
647437
+ if (!import_node_fs24.default.existsSync(fullPath)) {
647209
647438
  result.status = "FAIL";
647210
647439
  result.description = `Cookie file '${fullPath}' not found`;
647211
647440
  return result;
647212
647441
  }
647213
647442
  try {
647214
- const fileContent = import_node_fs23.default.readFileSync(fullPath, "utf8");
647443
+ const fileContent = import_node_fs24.default.readFileSync(fullPath, "utf8");
647215
647444
  const cookies = parseNetscapeCookieFile(fileContent);
647216
647445
  if (cookies.length === 0) {
647217
647446
  result.status = "FAIL";
@@ -647377,8 +647606,8 @@ function isDomainCompatible(currentDomain, cookieDomain) {
647377
647606
  // dist/core/tests/httpRequest.js
647378
647607
  init_validate();
647379
647608
  var import_axios5 = __toESM(require("axios"), 1);
647380
- var import_node_fs24 = __toESM(require("node:fs"), 1);
647381
- var import_node_path21 = __toESM(require("node:path"), 1);
647609
+ var import_node_fs25 = __toESM(require("node:fs"), 1);
647610
+ var import_node_path22 = __toESM(require("node:path"), 1);
647382
647611
  var import_ajv2 = __toESM(require("ajv"), 1);
647383
647612
  init_openapi();
647384
647613
  init_utils();
@@ -647408,9 +647637,9 @@ async function httpRequest({ config, step, openApiDefinitions = [] }) {
647408
647637
  delete step.httpRequest.openApi.definition;
647409
647638
  } else if (openApiDefinitions.length > 0) {
647410
647639
  findOperation: for (const openApiConfig of openApiDefinitions) {
647411
- for (const path26 in openApiConfig.definition.paths) {
647412
- for (const method in openApiConfig.definition.paths[path26]) {
647413
- if (openApiConfig.definition.paths[path26][method].operationId === step.httpRequest.openApi.operationId) {
647640
+ for (const path27 in openApiConfig.definition.paths) {
647641
+ for (const method in openApiConfig.definition.paths[path27]) {
647642
+ if (openApiConfig.definition.paths[path27][method].operationId === step.httpRequest.openApi.operationId) {
647414
647643
  openApiDefinition = openApiConfig.definition;
647415
647644
  step.httpRequest.openApi = {
647416
647645
  ...openApiConfig,
@@ -647724,20 +647953,20 @@ async function httpRequest({ config, step, openApiDefinitions = [] }) {
647724
647953
  }
647725
647954
  }
647726
647955
  if (step.httpRequest.path) {
647727
- const dir = import_node_path21.default.dirname(step.httpRequest.path);
647728
- if (!import_node_fs24.default.existsSync(dir)) {
647729
- import_node_fs24.default.mkdirSync(dir, { recursive: true });
647956
+ const dir = import_node_path22.default.dirname(step.httpRequest.path);
647957
+ if (!import_node_fs25.default.existsSync(dir)) {
647958
+ import_node_fs25.default.mkdirSync(dir, { recursive: true });
647730
647959
  }
647731
647960
  let filePath = step.httpRequest.path;
647732
647961
  log(config, "debug", `Saving output to file: ${filePath}`);
647733
- if (!import_node_fs24.default.existsSync(filePath)) {
647734
- await import_node_fs24.default.promises.writeFile(filePath, JSON.stringify(response.data, null, 2));
647962
+ if (!import_node_fs25.default.existsSync(filePath)) {
647963
+ await import_node_fs25.default.promises.writeFile(filePath, JSON.stringify(response.data, null, 2));
647735
647964
  descriptions.push(`Saved output to file.`);
647736
647965
  } else {
647737
647966
  if (step.httpRequest.overwrite == "false") {
647738
647967
  descriptions.push(`Didn't save output. File already exists.`);
647739
647968
  }
647740
- const existingFile = import_node_fs24.default.readFileSync(filePath, "utf8");
647969
+ const existingFile = import_node_fs25.default.readFileSync(filePath, "utf8");
647741
647970
  const fractionalDiff = calculateFractionalDifference(existingFile, JSON.stringify(response.data, null, 2));
647742
647971
  log(config, "debug", `Fractional difference: ${fractionalDiff}`);
647743
647972
  result.outputs.variation = fractionalDiff;
@@ -647747,13 +647976,13 @@ async function httpRequest({ config, step, openApiDefinitions = [] }) {
647747
647976
  });
647748
647977
  if (fractionalDiff > step.httpRequest.maxVariation) {
647749
647978
  if (step.httpRequest.overwrite == "aboveVariation") {
647750
- await import_node_fs24.default.promises.writeFile(filePath, JSON.stringify(response.data, null, 2));
647979
+ await import_node_fs25.default.promises.writeFile(filePath, JSON.stringify(response.data, null, 2));
647751
647980
  descriptions.push(`Saved response to file.`);
647752
647981
  }
647753
647982
  descriptions.push(`The difference between the existing saved response and the new response (${fractionalDiff.toFixed(2)}) is greater than the max accepted variation (${step.httpRequest.maxVariation}).`);
647754
647983
  } else {
647755
647984
  if (step.httpRequest.overwrite == "true") {
647756
- import_node_fs24.default.writeFileSync(filePath, JSON.stringify(response.data, null, 2));
647985
+ import_node_fs25.default.writeFileSync(filePath, JSON.stringify(response.data, null, 2));
647757
647986
  descriptions.push(`Saved response to file.`);
647758
647987
  }
647759
647988
  }
@@ -647766,8 +647995,8 @@ async function httpRequest({ config, step, openApiDefinitions = [] }) {
647766
647995
  result.description = descriptions.join(" ").trim();
647767
647996
  return result;
647768
647997
  }
647769
- function fieldExistsAtPath(obj, path26) {
647770
- const segments = path26.match(/[^.[\]]+/g);
647998
+ function fieldExistsAtPath(obj, path27) {
647999
+ const segments = path27.match(/[^.[\]]+/g);
647771
648000
  if (!segments) {
647772
648001
  return false;
647773
648002
  }
@@ -647957,8 +648186,8 @@ async function clickElement({ config, step, driver, appSession }) {
647957
648186
  // dist/core/tests/runCode.js
647958
648187
  init_validate();
647959
648188
  init_utils();
647960
- var import_node_fs25 = __toESM(require("node:fs"), 1);
647961
- var import_node_path22 = __toESM(require("node:path"), 1);
648189
+ var import_node_fs26 = __toESM(require("node:fs"), 1);
648190
+ var import_node_path23 = __toESM(require("node:path"), 1);
647962
648191
  var import_node_os10 = __toESM(require("node:os"), 1);
647963
648192
  function createTempScript(code, language) {
647964
648193
  let extension;
@@ -647983,9 +648212,9 @@ function createTempScript(code, language) {
647983
648212
  extension = "";
647984
648213
  }
647985
648214
  const tmpDir = import_node_os10.default.tmpdir();
647986
- const tmpFile = import_node_path22.default.join(tmpDir, `doc-detective-${Date.now()}${extension}`);
648215
+ const tmpFile = import_node_path23.default.join(tmpDir, `doc-detective-${Date.now()}${extension}`);
647987
648216
  try {
647988
- import_node_fs25.default.writeFileSync(tmpFile, code);
648217
+ import_node_fs26.default.writeFileSync(tmpFile, code);
647989
648218
  } catch (error) {
647990
648219
  throw new Error(`Failed to create temporary script: ${error.message}`);
647991
648220
  }
@@ -648090,7 +648319,7 @@ async function runCode({ config, step, driver, processRegistry }) {
648090
648319
  if (typeof step.runCode.stdio !== "undefined")
648091
648320
  runShellOptions.stdio = step.runCode.stdio;
648092
648321
  if (typeof step.runCode.path !== "undefined") {
648093
- runShellOptions.path = step.runCode.directory ? import_node_path22.default.join(step.runCode.directory, step.runCode.path) : step.runCode.path;
648322
+ runShellOptions.path = step.runCode.directory ? import_node_path23.default.join(step.runCode.directory, step.runCode.path) : step.runCode.path;
648094
648323
  }
648095
648324
  if (step.runCode.background) {
648096
648325
  runShellOptions.background = step.runCode.background;
@@ -648119,7 +648348,7 @@ async function runCode({ config, step, driver, processRegistry }) {
648119
648348
  } finally {
648120
648349
  if (!deferTempCleanup) {
648121
648350
  try {
648122
- import_node_fs25.default.unlinkSync(scriptPath);
648351
+ import_node_fs26.default.unlinkSync(scriptPath);
648123
648352
  log(config, "debug", `Removed temporary script: ${scriptPath}`);
648124
648353
  } catch (error) {
648125
648354
  log(config, "warning", `Failed to remove temporary script: ${scriptPath}`);
@@ -648133,7 +648362,7 @@ async function runCode({ config, step, driver, processRegistry }) {
648133
648362
  init_validate();
648134
648363
  init_utils();
648135
648364
  var import_tree_kill2 = __toESM(require("tree-kill"), 1);
648136
- var import_node_fs26 = __toESM(require("node:fs"), 1);
648365
+ var import_node_fs27 = __toESM(require("node:fs"), 1);
648137
648366
  async function closeSurface({ config, step, driver, processRegistry, appSession }) {
648138
648367
  const result = {
648139
648368
  status: "PASS",
@@ -648282,7 +648511,7 @@ async function closeSurface({ config, step, driver, processRegistry, appSession
648282
648511
  }
648283
648512
  if (entry.tempPath) {
648284
648513
  try {
648285
- import_node_fs26.default.unlinkSync(entry.tempPath);
648514
+ import_node_fs27.default.unlinkSync(entry.tempPath);
648286
648515
  } catch {
648287
648516
  }
648288
648517
  }
@@ -648535,7 +648764,7 @@ async function applyViewport(driver, viewport) {
648535
648764
  }
648536
648765
 
648537
648766
  // dist/core/tests/mobileBrowser.js
648538
- var import_node_path23 = __toESM(require("node:path"), 1);
648767
+ var import_node_path24 = __toESM(require("node:path"), 1);
648539
648768
  var SUPPORTED_MOBILE_BROWSER = {
648540
648769
  android: "chrome",
648541
648770
  ios: "safari"
@@ -648568,6 +648797,10 @@ function mobileBrowserConfigError(browser) {
648568
648797
  }
648569
648798
  return null;
648570
648799
  }
648800
+ var CHROMEDRIVER_AUTODOWNLOAD_ARGS = [
648801
+ "--allow-insecure",
648802
+ "uiautomator2:chromedriver_autodownload"
648803
+ ];
648571
648804
  function mobileBrowserGate({ platform, browser, hasBrowserStep, hasAppStep }) {
648572
648805
  if (!hasBrowserStep)
648573
648806
  return { action: "proceed", browserName: null };
@@ -648592,7 +648825,7 @@ function mobileBrowserGate({ platform, browser, hasBrowserStep, hasAppStep }) {
648592
648825
  }
648593
648826
  return { action: "proceed", browserName };
648594
648827
  }
648595
- function buildMobileBrowserCapabilities({ platform, udid, cacheDir, timeout }) {
648828
+ function buildMobileBrowserCapabilities({ platform, udid, cacheDir, timeout, locateWda = () => locateManagedWda({ ctx: { cacheDir } }) }) {
648596
648829
  if (platform === "android") {
648597
648830
  return {
648598
648831
  platformName: "Android",
@@ -648610,7 +648843,7 @@ function buildMobileBrowserCapabilities({ platform, udid, cacheDir, timeout }) {
648610
648843
  // --allow-insecure=uiautomator2:chromedriver_autodownload. The
648611
648844
  // download lands in the Doc Detective cache so later runs reuse it.
648612
648845
  "appium:chromedriverAutodownload": true,
648613
- "appium:chromedriverExecutableDir": import_node_path23.default.join(cacheDir, MOBILE_CHROMEDRIVER_DIR)
648846
+ "appium:chromedriverExecutableDir": import_node_path24.default.join(cacheDir, MOBILE_CHROMEDRIVER_DIR)
648614
648847
  };
648615
648848
  }
648616
648849
  const capabilities = {
@@ -648624,18 +648857,225 @@ function buildMobileBrowserCapabilities({ platform, udid, cacheDir, timeout }) {
648624
648857
  const effectiveTimeout = timeout ?? 9e5;
648625
648858
  capabilities["appium:wdaLaunchTimeout"] = Math.max(effectiveTimeout, 12e4);
648626
648859
  capabilities["appium:wdaConnectionTimeout"] = Math.max(effectiveTimeout, 12e4);
648627
- const derivedDataPath = process.env.DOC_DETECTIVE_IOS_WDA_DERIVED_DATA_PATH;
648628
- if (derivedDataPath && derivedDataPath.trim()) {
648629
- capabilities["appium:derivedDataPath"] = derivedDataPath.trim();
648630
- }
648860
+ applyManagedWdaCapabilities(capabilities, locateWda);
648631
648861
  return capabilities;
648632
648862
  }
648633
648863
 
648864
+ // dist/core/warmPhase.js
648865
+ init_utils();
648866
+ var WARM_POOL_LIMIT = 4;
648867
+ var RUNTIME_INSTALL_RESOURCE = "runtime-install";
648868
+ function deviceIdentity(platform, desc) {
648869
+ if (desc?.name)
648870
+ return `${platform}:name:${desc.name}`;
648871
+ return `${platform}:default:${desc?.deviceType ?? "<any>"}:${desc?.osVersion ?? "<latest>"}`;
648872
+ }
648873
+ function deviceResourceTag(platform, desc) {
648874
+ return `warm-device:${deviceIdentity(platform, desc)}`;
648875
+ }
648876
+ function wrapInitiationEffects(deps, keys, signal) {
648877
+ const wrapped = { ...deps };
648878
+ for (const key of keys) {
648879
+ const original = deps[key];
648880
+ if (typeof original !== "function")
648881
+ continue;
648882
+ wrapped[key] = (...args) => {
648883
+ const result = original(...args);
648884
+ signal();
648885
+ return result;
648886
+ };
648887
+ }
648888
+ return wrapped;
648889
+ }
648890
+ function planWarmTasks({ sizingJobs, runnerDetails, limit, hasAppiumPool, deps }) {
648891
+ const hostPlatform = runnerDetails?.environment?.platform;
648892
+ const tasks = [];
648893
+ const seen = /* @__PURE__ */ new Set();
648894
+ const addTask = (task) => {
648895
+ if (seen.has(task.name))
648896
+ return;
648897
+ seen.add(task.name);
648898
+ tasks.push(task);
648899
+ };
648900
+ let probeEligible = false;
648901
+ let defaultBrowser;
648902
+ const getDefaultBrowser2 = () => defaultBrowser ??= deps.getDefaultBrowser({ runnerDetails });
648903
+ const bootPlannedFor = /* @__PURE__ */ new Set();
648904
+ for (const job of sizingJobs ?? []) {
648905
+ const context = job?.context;
648906
+ if (!context)
648907
+ continue;
648908
+ if (deps.contextRequirementsSkipMessage({ context }))
648909
+ continue;
648910
+ const effPlatform = context.platform || hostPlatform;
648911
+ const mobileTarget = deps.isMobileTargetPlatform(effPlatform);
648912
+ if (mobileTarget) {
648913
+ if (mobileTarget === "ios" && hostPlatform !== "mac")
648914
+ continue;
648915
+ const hasAppStep = deps.isAppDriverRequired({ test: context });
648916
+ const gate = deps.mobileBrowserGate({
648917
+ platform: mobileTarget,
648918
+ browser: context.browser,
648919
+ hasBrowserStep: deps.isBrowserRequired({ test: context }),
648920
+ hasAppStep
648921
+ });
648922
+ if (gate.action !== "proceed")
648923
+ continue;
648924
+ const isMobileWeb = typeof gate.browserName === "string";
648925
+ if (!isMobileWeb && !hasAppStep)
648926
+ continue;
648927
+ const driverPackage = deps.appDriverPlatforms[mobileTarget]?.driverPackage;
648928
+ if (driverPackage) {
648929
+ addTask({
648930
+ name: `driver-install:${driverPackage}`,
648931
+ kind: "driver-install",
648932
+ exclusiveResources: [RUNTIME_INSTALL_RESOURCE],
648933
+ payload: { driverPackage, platform: mobileTarget }
648934
+ });
648935
+ }
648936
+ for (const stepDevice of deps.collectDeviceDescriptors(context)) {
648937
+ const desc = deps.normalizeDeviceDescriptor({
648938
+ contextDevice: context.device,
648939
+ stepDevice,
648940
+ platform: mobileTarget
648941
+ });
648942
+ const identity = deviceIdentity(mobileTarget, desc);
648943
+ const tag = deviceResourceTag(mobileTarget, desc);
648944
+ if (!bootPlannedFor.has(mobileTarget)) {
648945
+ bootPlannedFor.add(mobileTarget);
648946
+ addTask({
648947
+ name: `device-boot:${identity}`,
648948
+ kind: "device-boot",
648949
+ exclusiveResources: [tag],
648950
+ payload: { platform: mobileTarget, desc }
648951
+ });
648952
+ }
648953
+ if (mobileTarget === "android" && isMobileWeb) {
648954
+ addTask({
648955
+ name: `chromedriver-prefetch:${identity}`,
648956
+ kind: "chromedriver-prefetch",
648957
+ exclusiveResources: [tag],
648958
+ payload: { platform: mobileTarget, desc }
648959
+ });
648960
+ }
648961
+ }
648962
+ if (mobileTarget === "ios") {
648963
+ addTask({
648964
+ name: "wda-check",
648965
+ kind: "wda-check",
648966
+ // Read-only (plus the last-used stamp) — contends with nothing.
648967
+ exclusiveResources: [],
648968
+ payload: {}
648969
+ });
648970
+ }
648971
+ continue;
648972
+ }
648973
+ if (deps.isBrowserRequired({ test: context })) {
648974
+ const effBrowser = context.browser ?? getDefaultBrowser2();
648975
+ const browserName = effBrowser?.name;
648976
+ if (browserName && effPlatform === hostPlatform) {
648977
+ probeEligible = true;
648978
+ if (deps.requiredBrowserAssets(browserName).length > 0) {
648979
+ addTask({
648980
+ name: `browser-install:${browserName.toLowerCase()}`,
648981
+ kind: "browser-install",
648982
+ exclusiveResources: [RUNTIME_INSTALL_RESOURCE],
648983
+ payload: { browserName }
648984
+ });
648985
+ }
648986
+ }
648987
+ }
648988
+ if ((effPlatform === "windows" || effPlatform === "mac") && effPlatform === hostPlatform && deps.isAppDriverRequired({ test: context })) {
648989
+ const driverPackage = deps.appDriverPlatforms[effPlatform]?.driverPackage;
648990
+ if (driverPackage) {
648991
+ addTask({
648992
+ name: `driver-install:${driverPackage}`,
648993
+ kind: "driver-install",
648994
+ exclusiveResources: [RUNTIME_INSTALL_RESOURCE],
648995
+ payload: { driverPackage, platform: effPlatform }
648996
+ });
648997
+ }
648998
+ }
648999
+ }
649000
+ if (limit > 1 && hasAppiumPool && probeEligible) {
649001
+ addTask({
649002
+ name: "session-probe",
649003
+ kind: "session-probe",
649004
+ // Its install half mutates the shared caches, exactly like the
649005
+ // dedicated install tasks (usually a memo hit by the time it runs).
649006
+ exclusiveResources: [RUNTIME_INSTALL_RESOURCE],
649007
+ payload: {}
649008
+ });
649009
+ }
649010
+ return tasks;
649011
+ }
649012
+ async function executeWarmTasks({ tasks, registry, runTask, log: log3, now = Date.now }) {
649013
+ const results = [];
649014
+ const start = now();
649015
+ await runResourceAware(tasks, WARM_POOL_LIMIT, registry, async (task) => {
649016
+ const taskStart = now();
649017
+ try {
649018
+ const { outcome, note } = await runTask(task);
649019
+ results.push({
649020
+ name: task.name,
649021
+ kind: task.kind,
649022
+ outcome,
649023
+ durationMs: now() - taskStart,
649024
+ ...note ? { note } : {}
649025
+ });
649026
+ if (outcome === "failed") {
649027
+ log3("warning", `Warm task '${task.name}' failed (continuing)${note ? `: ${note}` : "."}`);
649028
+ } else {
649029
+ log3("debug", `Warm task '${task.name}': ${outcome}${note ? ` (${note})` : ""}.`);
649030
+ }
649031
+ } catch (error) {
649032
+ const note = error?.message ?? String(error);
649033
+ results.push({
649034
+ name: task.name,
649035
+ kind: task.kind,
649036
+ outcome: "failed",
649037
+ durationMs: now() - taskStart,
649038
+ note
649039
+ });
649040
+ log3("warning", `Warm task '${task.name}' failed (continuing): ${note}`);
649041
+ }
649042
+ });
649043
+ return { durationMs: now() - start, tasks: results };
649044
+ }
649045
+ function raceBootInitiation({ startAcquire, onError }) {
649046
+ let initiatedResolve;
649047
+ const initiated = new Promise((resolve) => {
649048
+ initiatedResolve = resolve;
649049
+ });
649050
+ const acquired = (async () => {
649051
+ try {
649052
+ const result = await startAcquire(() => initiatedResolve());
649053
+ if (result && typeof result === "object" && "skip" in result) {
649054
+ return { outcome: "skipped", note: result.skip };
649055
+ }
649056
+ return { outcome: "warmed", note: "device ready" };
649057
+ } catch (error) {
649058
+ onError(error);
649059
+ return {
649060
+ outcome: "failed",
649061
+ note: error?.message ?? String(error)
649062
+ };
649063
+ }
649064
+ })();
649065
+ return Promise.race([
649066
+ initiated.then(() => ({
649067
+ outcome: "warmed",
649068
+ note: "boot initiated"
649069
+ })),
649070
+ acquired
649071
+ ]);
649072
+ }
649073
+
648634
649074
  // dist/core/tests.js
648635
649075
  init_cacheDir();
648636
649076
 
648637
649077
  // dist/core/tests/iosSimulator.js
648638
- var import_node_child_process13 = require("node:child_process");
649078
+ var import_node_child_process14 = require("node:child_process");
648639
649079
  function parseSimctlDevices(text) {
648640
649080
  let data;
648641
649081
  try {
@@ -648900,6 +649340,13 @@ async function teardownSimulatorRegistry(registry, shutdown) {
648900
649340
  for (const entry of registry.values()) {
648901
649341
  if (!entry.bootedByUs)
648902
649342
  continue;
649343
+ if (entry.ready) {
649344
+ try {
649345
+ await entry.ready;
649346
+ } catch {
649347
+ continue;
649348
+ }
649349
+ }
648903
649350
  try {
648904
649351
  await shutdown(entry);
648905
649352
  } catch {
@@ -648909,7 +649356,7 @@ async function teardownSimulatorRegistry(registry, shutdown) {
648909
649356
  }
648910
649357
  function runSimctl(args, timeout = 6e4) {
648911
649358
  return new Promise((resolve) => {
648912
- (0, import_node_child_process13.execFile)("xcrun", ["simctl", ...args], { timeout, maxBuffer: 32 * 1024 * 1024 }, (error, stdout, stderr) => {
649359
+ (0, import_node_child_process14.execFile)("xcrun", ["simctl", ...args], { timeout, maxBuffer: 32 * 1024 * 1024 }, (error, stdout, stderr) => {
648913
649360
  resolve({
648914
649361
  // A timeout or spawn error sets no numeric `code` (it's null) — treat
648915
649362
  // that as a failure, not success, so a hung `simctl` doesn't look like
@@ -648966,8 +649413,8 @@ function buildAcquireSimulatorDeps(log3) {
648966
649413
  // dist/core/tests/runBrowserScript.js
648967
649414
  init_validate();
648968
649415
  init_utils();
648969
- var import_node_fs27 = __toESM(require("node:fs"), 1);
648970
- var import_node_path24 = __toESM(require("node:path"), 1);
649416
+ var import_node_fs28 = __toESM(require("node:fs"), 1);
649417
+ var import_node_path25 = __toESM(require("node:path"), 1);
648971
649418
  async function runBrowserScript({ config, step, driver }) {
648972
649419
  const result = {
648973
649420
  status: "PASS",
@@ -649042,16 +649489,16 @@ async function runBrowserScript({ config, step, driver }) {
649042
649489
  }
649043
649490
  if (step.runBrowserScript.path) {
649044
649491
  try {
649045
- const dir = import_node_path24.default.dirname(step.runBrowserScript.path);
649046
- if (!import_node_fs27.default.existsSync(dir)) {
649047
- import_node_fs27.default.mkdirSync(dir, { recursive: true });
649492
+ const dir = import_node_path25.default.dirname(step.runBrowserScript.path);
649493
+ if (!import_node_fs28.default.existsSync(dir)) {
649494
+ import_node_fs28.default.mkdirSync(dir, { recursive: true });
649048
649495
  }
649049
649496
  const filePath = step.runBrowserScript.path;
649050
649497
  log(config, "debug", `Saving script result to file: ${filePath}`);
649051
- if (!import_node_fs27.default.existsSync(filePath)) {
649052
- import_node_fs27.default.writeFileSync(filePath, serialized);
649498
+ if (!import_node_fs28.default.existsSync(filePath)) {
649499
+ import_node_fs28.default.writeFileSync(filePath, serialized);
649053
649500
  } else {
649054
- const existingFile = import_node_fs27.default.readFileSync(filePath, "utf8");
649501
+ const existingFile = import_node_fs28.default.readFileSync(filePath, "utf8");
649055
649502
  const fractionalDiff = calculateFractionalDifference(existingFile, serialized);
649056
649503
  log(config, "debug", `Fractional difference: ${fractionalDiff}`);
649057
649504
  result.outputs.variation = fractionalDiff;
@@ -649061,7 +649508,7 @@ async function runBrowserScript({ config, step, driver }) {
649061
649508
  });
649062
649509
  if (fractionalDiff > step.runBrowserScript.maxVariation) {
649063
649510
  if (step.runBrowserScript.overwrite == "aboveVariation" || step.runBrowserScript.overwrite == "true") {
649064
- import_node_fs27.default.writeFileSync(filePath, serialized);
649511
+ import_node_fs28.default.writeFileSync(filePath, serialized);
649065
649512
  descriptions.push(`Saved output to file.`);
649066
649513
  } else {
649067
649514
  descriptions.push(`Didn't overwrite the existing file.`);
@@ -649069,7 +649516,7 @@ async function runBrowserScript({ config, step, driver }) {
649069
649516
  descriptions.push(`The difference between the existing output and the new output (${fractionalDiff.toFixed(2)}) is greater than the max accepted variation (${step.runBrowserScript.maxVariation}).`);
649070
649517
  } else {
649071
649518
  if (step.runBrowserScript.overwrite == "true") {
649072
- import_node_fs27.default.writeFileSync(filePath, serialized);
649519
+ import_node_fs28.default.writeFileSync(filePath, serialized);
649073
649520
  descriptions.push(`Saved output to file.`);
649074
649521
  }
649075
649522
  }
@@ -649237,8 +649684,8 @@ async function dragAndDropElement({ config, step, driver }) {
649237
649684
  }
649238
649685
 
649239
649686
  // dist/core/tests.js
649240
- var import_node_path25 = __toESM(require("node:path"), 1);
649241
- var import_node_child_process14 = require("node:child_process");
649687
+ var import_node_path26 = __toESM(require("node:path"), 1);
649688
+ var import_node_child_process15 = require("node:child_process");
649242
649689
  var import_node_crypto9 = require("node:crypto");
649243
649690
  init_appium();
649244
649691
  init_config();
@@ -649391,7 +649838,7 @@ function getIntegrationConfig(config, sourceIntegration) {
649391
649838
  var import_node_http2 = __toESM(require("node:http"), 1);
649392
649839
  var import_node_https3 = __toESM(require("node:https"), 1);
649393
649840
  var import_node_url6 = require("node:url");
649394
- var __dirname5 = import_node_path25.default.dirname((0, import_node_url6.fileURLToPath)(importMetaUrl));
649841
+ var __dirname5 = import_node_path26.default.dirname((0, import_node_url6.fileURLToPath)(importMetaUrl));
649395
649842
  var KNOWN_BROWSERS = ["firefox", "chrome", "safari", "webkit"];
649396
649843
  function killTree(pid, timeoutMs = 5e3) {
649397
649844
  return new Promise((resolve) => {
@@ -649436,6 +649883,20 @@ function combinationKey(context) {
649436
649883
  function warmUpDecision(prev) {
649437
649884
  return prev === "failed" ? "skip" : "attempt";
649438
649885
  }
649886
+ function buildWarmPlanDeps() {
649887
+ return {
649888
+ isBrowserRequired,
649889
+ isAppDriverRequired,
649890
+ isMobileTargetPlatform,
649891
+ getDefaultBrowser,
649892
+ requiredBrowserAssets,
649893
+ collectDeviceDescriptors,
649894
+ normalizeDeviceDescriptor,
649895
+ mobileBrowserGate,
649896
+ contextRequirementsSkipMessage,
649897
+ appDriverPlatforms: APP_DRIVER_PLATFORMS
649898
+ };
649899
+ }
649439
649900
  function getDriverCapabilities({ runnerDetails, name, options }) {
649440
649901
  let capabilities = {};
649441
649902
  let args = [];
@@ -650015,7 +650476,7 @@ async function runSpecs({ resolvedTests }) {
650015
650476
  const runDir = getRunOutputDir(config, {
650016
650477
  create: runArchivesArtifacts(config, specs)
650017
650478
  });
650018
- const runId = import_node_path25.default.basename(runDir);
650479
+ const runId = import_node_path26.default.basename(runDir);
650019
650480
  const report = {
650020
650481
  runId,
650021
650482
  runDir,
@@ -650045,7 +650506,12 @@ async function runSpecs({ resolvedTests }) {
650045
650506
  skipped: 0
650046
650507
  }
650047
650508
  },
650048
- specs: []
650509
+ specs: [],
650510
+ // Inline warm phase results (docs/design/warm-phase.md). Present on the
650511
+ // skeleton so a run that plans nothing (or whose planning fails —
650512
+ // best-effort) still reports the structural empty block; the phase
650513
+ // overwrites it with real task results below.
650514
+ warm: { durationMs: 0, tasks: [] }
650049
650515
  };
650050
650516
  let limit = resolveConcurrentRunners(config);
650051
650517
  log(config, "info", "Running test specs.");
@@ -650200,6 +650666,7 @@ async function runSpecs({ resolvedTests }) {
650200
650666
  const serverCount = Math.min(limit, browserPoolJobCount);
650201
650667
  log(config, "debug", `Starting ${serverCount} Appium server(s).`);
650202
650668
  try {
650669
+ const readinessWaits = [];
650203
650670
  for (let i = 0; i < serverCount; i++) {
650204
650671
  let display;
650205
650672
  if (useXvfbDisplays) {
@@ -650207,7 +650674,16 @@ async function runSpecs({ resolvedTests }) {
650207
650674
  xvfbProcesses.push(await startXvfb(display));
650208
650675
  log(config, "debug", `Started Xvfb on ${display} for recording.`);
650209
650676
  }
650210
- appiumServers.push(await startAppiumServer(appiumEntry, config, display));
650677
+ const server = await spawnAppiumServer(appiumEntry, config, display);
650678
+ appiumServers.push(server);
650679
+ const wait2 = appiumIsReady(server.port);
650680
+ wait2.catch(() => {
650681
+ });
650682
+ readinessWaits.push(wait2);
650683
+ }
650684
+ await Promise.all(readinessWaits);
650685
+ for (const server of appiumServers) {
650686
+ log(config, "debug", `Appium is ready on port ${server.port}.`);
650211
650687
  }
650212
650688
  } catch (error) {
650213
650689
  await Promise.all(appiumServers.map((server) => {
@@ -650230,6 +650706,7 @@ async function runSpecs({ resolvedTests }) {
650230
650706
  const processRegistry = /* @__PURE__ */ new Map();
650231
650707
  const deviceRegistry = createDeviceRegistry();
650232
650708
  const simulatorRegistry = createSimulatorRegistry();
650709
+ const resourceRegistry = createResourceRegistry();
650233
650710
  const killAllRegistered = async () => {
650234
650711
  const entries = [...processRegistry.entries()];
650235
650712
  processRegistry.clear();
@@ -650241,7 +650718,7 @@ async function runSpecs({ resolvedTests }) {
650241
650718
  }
650242
650719
  if (entry?.tempPath) {
650243
650720
  try {
650244
- import_node_fs28.default.unlinkSync(entry.tempPath);
650721
+ import_node_fs29.default.unlinkSync(entry.tempPath);
650245
650722
  } catch {
650246
650723
  }
650247
650724
  }
@@ -650266,15 +650743,35 @@ async function runSpecs({ resolvedTests }) {
650266
650743
  process.on("SIGINT", onSignal);
650267
650744
  process.on("SIGTERM", onSignal);
650268
650745
  try {
650269
- if (limit > 1 && appiumPool) {
650270
- await warmUpContexts({
650271
- jobs: sizingJobs,
650272
- config,
650746
+ try {
650747
+ const warmTasks = planWarmTasks({
650748
+ sizingJobs,
650273
650749
  runnerDetails,
650274
- appiumPool,
650275
- installAttempts,
650276
- warmUpResults
650750
+ limit,
650751
+ hasAppiumPool: !!appiumPool,
650752
+ deps: buildWarmPlanDeps()
650277
650753
  });
650754
+ if (warmTasks.length > 0) {
650755
+ log(config, "debug", `Warm phase: ${warmTasks.length} task(s).`);
650756
+ report.warm = await executeWarmTasks({
650757
+ tasks: warmTasks,
650758
+ registry: resourceRegistry,
650759
+ runTask: buildWarmTaskRunner({
650760
+ config,
650761
+ runnerDetails,
650762
+ sizingJobs,
650763
+ appiumPool,
650764
+ installAttempts,
650765
+ warmUpResults,
650766
+ deviceRegistry,
650767
+ simulatorRegistry,
650768
+ resourceRegistry
650769
+ }),
650770
+ log: (level, message) => log(config, level, message)
650771
+ });
650772
+ }
650773
+ } catch (error) {
650774
+ log(config, "warning", `Warm phase skipped (planning failed; the run proceeds with on-demand provisioning): ${error?.message ?? error}`);
650278
650775
  }
650279
650776
  const runJob = async (job) => {
650280
650777
  try {
@@ -650326,7 +650823,6 @@ async function runSpecs({ resolvedTests }) {
650326
650823
  const phase = PHASES.includes(entry.spec?._phase) ? entry.spec._phase : "main";
650327
650824
  routedByPhase[phase].push(entry);
650328
650825
  }
650329
- const resourceRegistry = createResourceRegistry();
650330
650826
  for (const phase of PHASES) {
650331
650827
  if (limit > 1) {
650332
650828
  await runResourceAware(jobsByPhase[phase], limit, resourceRegistry, runJob);
@@ -650640,27 +651136,17 @@ async function warmUpContexts({ jobs, config, runnerDetails, appiumPool, install
650640
651136
  platform
650641
651137
  });
650642
651138
  if (!supported && context.platform === platform && Array.isArray(context?.steps) && requiredBrowserAssets(context.browser?.name).length > 0) {
650643
- const firstAttempt = !installAttempts.has((context.browser?.name ?? "<none>").toLowerCase());
650644
- const outcome = await ensureContextBrowserInstalled({
651139
+ await warmBrowserInstall({
650645
651140
  browserName: context.browser?.name,
650646
651141
  config,
650647
- installAttempts,
650648
- deps: {
650649
- ensureBrowser: (asset, options) => ensureBrowserInstalled(asset, options),
650650
- log
650651
- },
650652
- // Repair a present-but-broken driver, not just install-if-missing.
650653
- repair: true
651142
+ runnerDetails,
651143
+ installAttempts
651144
+ });
651145
+ supported = isSupportedContext({
651146
+ context,
651147
+ apps: runnerDetails.availableApps,
651148
+ platform
650654
651149
  });
650655
- if (firstAttempt && (outcome === "installed" || outcome === "failed")) {
650656
- clearAppCache(config);
650657
- runnerDetails.availableApps = await getAvailableApps({ config });
650658
- supported = isSupportedContext({
650659
- context,
650660
- apps: runnerDetails.availableApps,
650661
- platform
650662
- });
650663
- }
650664
651150
  }
650665
651151
  if (!supported)
650666
651152
  continue;
@@ -650702,6 +651188,292 @@ async function warmUpContexts({ jobs, config, runnerDetails, appiumPool, install
650702
651188
  }
650703
651189
  }
650704
651190
  }
651191
+ async function warmBrowserInstall({ browserName, config, runnerDetails, installAttempts, deps = {} }) {
651192
+ const appName = normalizeBrowserName(browserName);
651193
+ if (runnerDetails.availableApps?.find((app) => app.name === appName)) {
651194
+ return {
651195
+ outcome: "skipped",
651196
+ note: `'${browserName}' is already available`
651197
+ };
651198
+ }
651199
+ const firstAttempt = !installAttempts.has((browserName ?? "<none>").toLowerCase());
651200
+ const outcome = await ensureContextBrowserInstalled({
651201
+ browserName,
651202
+ config,
651203
+ installAttempts,
651204
+ deps: {
651205
+ ensureBrowser: deps.ensureBrowser ?? ((asset, options) => ensureBrowserInstalled(asset, options)),
651206
+ log
651207
+ },
651208
+ // Repair a present-but-broken driver, not just install-if-missing.
651209
+ repair: true
651210
+ });
651211
+ if (firstAttempt && (outcome === "installed" || outcome === "failed")) {
651212
+ (deps.clearAppCache ?? clearAppCache)(config);
651213
+ runnerDetails.availableApps = await (deps.getAvailableApps ?? getAvailableApps)({ config });
651214
+ }
651215
+ if (outcome === "installed")
651216
+ return { outcome: "warmed" };
651217
+ if (outcome === "failed") {
651218
+ return { outcome: "failed", note: `couldn't install '${browserName}'` };
651219
+ }
651220
+ return {
651221
+ outcome: "skipped",
651222
+ note: `'${browserName}' has no installable assets`
651223
+ };
651224
+ }
651225
+ async function resolveAndroidWarmEnv(config) {
651226
+ try {
651227
+ const abi = hostAbi();
651228
+ const sdk = detectAndroidSdk({ cacheDir: config?.cacheDir });
651229
+ if (!sdk?.emulator)
651230
+ return null;
651231
+ const deviceDeps = buildAcquireDeviceDeps(sdk, abi, (m) => log(config, "debug", m));
651232
+ const running = await deviceDeps.listRunning();
651233
+ const capable = running.length > 0 || await checkEmulatorAcceleration(sdk.emulator);
651234
+ if (!capable)
651235
+ return null;
651236
+ return { sdkRoot: sdk.sdkRoot, deviceDeps };
651237
+ } catch {
651238
+ return null;
651239
+ }
651240
+ }
651241
+ async function probeIosToolchainWarm() {
651242
+ if (process.platform !== "darwin")
651243
+ return probeIosToolchain();
651244
+ const runAsync = (command, args, timeout) => new Promise((resolve) => {
651245
+ let stdout = "";
651246
+ let stderr = "";
651247
+ try {
651248
+ const child = (0, import_node_child_process15.spawn)(command, args, { windowsHide: true, timeout });
651249
+ child.stdout?.on("data", (d) => stdout += String(d));
651250
+ child.stderr?.on("data", (d) => stderr += String(d));
651251
+ child.on("error", () => resolve({ status: null, stdout, stderr }));
651252
+ child.on("close", (status) => resolve({ status, stdout, stderr }));
651253
+ } catch {
651254
+ resolve({ status: null, stdout, stderr });
651255
+ }
651256
+ });
651257
+ const [xcodeSelect, simctl] = await Promise.all([
651258
+ runAsync("xcode-select", ["-p"], 15e3),
651259
+ // Same generous ceiling as the sync probe's xcrun spawn: the first cold
651260
+ // simctl call launches CoreSimulatorService.
651261
+ runAsync("xcrun", ["simctl", "list", "devices", "available"], 12e4)
651262
+ ]);
651263
+ return probeIosToolchain({
651264
+ run: (command) => command === "xcrun" ? simctl : xcodeSelect
651265
+ });
651266
+ }
651267
+ function buildWarmTaskRunner({ config, runnerDetails, sizingJobs, appiumPool, installAttempts, warmUpResults, deviceRegistry, simulatorRegistry, resourceRegistry }) {
651268
+ let androidEnv;
651269
+ const getAndroidEnv = () => androidEnv ??= resolveAndroidWarmEnv(config);
651270
+ let iosToolchain;
651271
+ const getIosToolchain = () => iosToolchain ??= probeIosToolchainWarm();
651272
+ const acquireLease = async (names) => {
651273
+ while (!resourceRegistry.tryAcquire(names)) {
651274
+ await resourceRegistry.waitForFree();
651275
+ }
651276
+ let released = false;
651277
+ return () => {
651278
+ if (released)
651279
+ return;
651280
+ released = true;
651281
+ resourceRegistry.release(names);
651282
+ };
651283
+ };
651284
+ const withRuntimeInstallLock = async (fn) => {
651285
+ const release = await acquireLease([RUNTIME_INSTALL_RESOURCE]);
651286
+ try {
651287
+ return await fn();
651288
+ } finally {
651289
+ release();
651290
+ }
651291
+ };
651292
+ let androidPreflight;
651293
+ const getAndroidPreflight = () => androidPreflight ??= withRuntimeInstallLock(() => appSurfacePreflight({ config, platform: "android" }));
651294
+ return async (task) => {
651295
+ switch (task.kind) {
651296
+ case "browser-install":
651297
+ return warmBrowserInstall({
651298
+ browserName: task.payload.browserName,
651299
+ config,
651300
+ runnerDetails,
651301
+ installAttempts
651302
+ });
651303
+ case "driver-install": {
651304
+ if (task.payload.platform === "android") {
651305
+ const env = await getAndroidEnv();
651306
+ if (!env) {
651307
+ return {
651308
+ outcome: "skipped",
651309
+ note: "Android toolchain not ready; the driver install stays with the consuming context"
651310
+ };
651311
+ }
651312
+ }
651313
+ if (task.payload.platform === "ios") {
651314
+ const toolchain = await getIosToolchain();
651315
+ if (!toolchain.ok) {
651316
+ return { outcome: "skipped", note: toolchain.reason };
651317
+ }
651318
+ }
651319
+ const { ensureRuntimeInstalled: ensureRuntimeInstalled2 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
651320
+ await ensureRuntimeInstalled2([task.payload.driverPackage], {
651321
+ ctx: { cacheDir: config?.cacheDir },
651322
+ deps: { logger: (m) => log(config, "debug", m) }
651323
+ });
651324
+ return { outcome: "warmed" };
651325
+ }
651326
+ case "device-boot": {
651327
+ const desc = task.payload.desc;
651328
+ const onError = (error) => log(config, "warning", `Warm boot of '${task.name}' failed (a consuming context will retry): ${error?.message ?? String(error)}`);
651329
+ if (task.payload.platform === "android") {
651330
+ const env = await getAndroidEnv();
651331
+ if (!env) {
651332
+ return {
651333
+ outcome: "skipped",
651334
+ note: "Android toolchain not ready; device setup stays with the consuming context"
651335
+ };
651336
+ }
651337
+ const releaseEmulatorLease = await acquireLease([
651338
+ "android-emulator"
651339
+ ]);
651340
+ return raceBootInitiation({
651341
+ onError,
651342
+ startAcquire: (signalInitiated) => {
651343
+ const acquiring = acquireDevice({
651344
+ desc,
651345
+ registry: deviceRegistry,
651346
+ sdkRoot: env.sdkRoot,
651347
+ deps: wrapInitiationEffects(env.deviceDeps, ["createAvd", "boot"], signalInitiated)
651348
+ });
651349
+ acquiring.then(() => releaseEmulatorLease(), () => releaseEmulatorLease());
651350
+ return acquiring;
651351
+ }
651352
+ });
651353
+ }
651354
+ const toolchain = await getIosToolchain();
651355
+ if (!toolchain.ok) {
651356
+ return { outcome: "skipped", note: toolchain.reason };
651357
+ }
651358
+ const simDeps = buildAcquireSimulatorDeps((m) => log(config, "debug", m));
651359
+ return raceBootInitiation({
651360
+ onError,
651361
+ startAcquire: (signalInitiated) => acquireSimulator({
651362
+ desc,
651363
+ registry: simulatorRegistry,
651364
+ deps: wrapInitiationEffects(simDeps, ["create", "boot"], signalInitiated)
651365
+ })
651366
+ });
651367
+ }
651368
+ case "wda-check": {
651369
+ const hit = locateManagedWda({ ctx: { cacheDir: config?.cacheDir } });
651370
+ if (hit) {
651371
+ return {
651372
+ outcome: "warmed",
651373
+ note: `prebuilt WebDriverAgent available (${hit.key})`
651374
+ };
651375
+ }
651376
+ return {
651377
+ outcome: "skipped",
651378
+ note: "no prebuilt WebDriverAgent for the current toolchain \u2014 `doc-detective install ios` prebuilds it"
651379
+ };
651380
+ }
651381
+ case "session-probe": {
651382
+ if (!appiumPool) {
651383
+ return { outcome: "skipped", note: "no browser Appium pool" };
651384
+ }
651385
+ await warmUpContexts({
651386
+ jobs: sizingJobs,
651387
+ config,
651388
+ runnerDetails,
651389
+ appiumPool,
651390
+ installAttempts,
651391
+ warmUpResults
651392
+ });
651393
+ const ok = [...warmUpResults.values()].filter((v) => v === "ok").length;
651394
+ const failed = warmUpResults.size - ok;
651395
+ return {
651396
+ outcome: "warmed",
651397
+ note: `${ok} combination${ok === 1 ? "" : "s"} ok${failed ? `, ${failed} failed` : ""}`
651398
+ };
651399
+ }
651400
+ case "chromedriver-prefetch":
651401
+ return prefetchMobileChromedriver({
651402
+ config,
651403
+ desc: task.payload.desc,
651404
+ deviceRegistry,
651405
+ getAndroidEnv,
651406
+ deps: {
651407
+ // The cache-mutating half (driver install + Appium co-homing)
651408
+ // runs once per run under the manual runtime-install lease, so
651409
+ // the prefetch task itself only holds its device tag while it
651410
+ // awaits readiness and runs the throwaway session.
651411
+ appSurfacePreflight: () => getAndroidPreflight(),
651412
+ acquireEmulatorLease: () => acquireLease(["android-emulator"])
651413
+ }
651414
+ });
651415
+ }
651416
+ };
651417
+ }
651418
+ async function prefetchMobileChromedriver({ config, desc, deviceRegistry, getAndroidEnv, deps = {} }) {
651419
+ const preflight = deps.appSurfacePreflight ?? appSurfacePreflight;
651420
+ const acquire = deps.acquireDevice ?? acquireDevice;
651421
+ const startServer = deps.startAppiumServer ?? startAppiumServer;
651422
+ const startDriver = deps.driverStart ?? driverStart;
651423
+ const kill4 = deps.killTree ?? killTree;
651424
+ const env = await getAndroidEnv();
651425
+ if (!env) {
651426
+ return {
651427
+ outcome: "skipped",
651428
+ note: "Android toolchain not ready; the first mobile-web session downloads chromedriver as needed"
651429
+ };
651430
+ }
651431
+ const pre = await preflight({ config, platform: "android" });
651432
+ if (!pre.ok)
651433
+ return { outcome: "skipped", note: pre.reason };
651434
+ const releaseLease = deps.acquireEmulatorLease ? await deps.acquireEmulatorLease() : void 0;
651435
+ let acquired;
651436
+ try {
651437
+ acquired = await acquire({
651438
+ desc,
651439
+ registry: deviceRegistry,
651440
+ sdkRoot: env.sdkRoot,
651441
+ deps: env.deviceDeps
651442
+ });
651443
+ } finally {
651444
+ releaseLease?.();
651445
+ }
651446
+ if ("skip" in acquired)
651447
+ return { outcome: "skipped", note: acquired.skip };
651448
+ let server;
651449
+ let driver;
651450
+ try {
651451
+ server = await startServer(pre.appiumEntry, config, void 0, {
651452
+ APPIUM_HOME: pre.appiumHome,
651453
+ ANDROID_HOME: env.sdkRoot,
651454
+ ANDROID_SDK_ROOT: env.sdkRoot
651455
+ }, CHROMEDRIVER_AUTODOWNLOAD_ARGS);
651456
+ driver = await startDriver(buildMobileBrowserCapabilities({
651457
+ platform: "android",
651458
+ udid: acquired.entry.udid,
651459
+ cacheDir: getCacheDir({ cacheDir: config?.cacheDir })
651460
+ }), server.port, 2, { cacheDir: config?.cacheDir });
651461
+ return {
651462
+ outcome: "warmed",
651463
+ note: `chromedriver ready for device '${acquired.entry.name}'`
651464
+ };
651465
+ } finally {
651466
+ if (driver) {
651467
+ try {
651468
+ await driver.deleteSession();
651469
+ } catch {
651470
+ }
651471
+ }
651472
+ if (server) {
651473
+ await kill4(server.process?.pid);
651474
+ }
651475
+ }
651476
+ }
650705
651477
  function specIsRouted(spec) {
650706
651478
  if (!spec || !Array.isArray(spec.tests))
650707
651479
  return false;
@@ -650818,7 +651590,7 @@ function buildAutoRecordStep({ config, spec, test, context }) {
650818
651590
  return null;
650819
651591
  const runDir = getRunOutputDir(config, { create: false });
650820
651592
  const contextSegment = capPathSegment(sanitizeFilesystemName(String(context.contextId ?? ""), "context"));
650821
- const recordPath = import_node_path25.default.join(runDir, "specs", capPathSegment(sanitizeFilesystemName(String(spec.specId ?? ""), "spec")), "tests", capPathSegment(sanitizeFilesystemName(String(test.testId ?? ""), "test")), "contexts", contextSegment, "recordings", `${contextSegment}.mp4`);
651593
+ const recordPath = import_node_path26.default.join(runDir, "specs", capPathSegment(sanitizeFilesystemName(String(spec.specId ?? ""), "spec")), "tests", capPathSegment(sanitizeFilesystemName(String(test.testId ?? ""), "test")), "contexts", contextSegment, "recordings", `${contextSegment}.mp4`);
650822
651594
  return {
650823
651595
  // Mobile-target contexts record the device screen through the app driver
650824
651596
  // (the internal device plan, resolved from the platform) — pinning ffmpeg
@@ -650847,7 +651619,7 @@ async function captureAutoScreenshot({ config, driver, spec, test, context, step
650847
651619
  const action = BROWSER_STEP_KEYS.find((key) => typeof step[key] !== "undefined") || "step";
650848
651620
  const sanitizedTestId = sanitizeFilesystemName(String(test.testId ?? ""), "test");
650849
651621
  const runDir = getRunOutputDir(config);
650850
- const dir = import_node_path25.default.join(runDir, "specs", capPathSegment(sanitizeFilesystemName(String(spec.specId ?? ""), "spec")), "tests", capPathSegment(sanitizedTestId), "contexts", capPathSegment(sanitizeFilesystemName(String(context.contextId ?? ""), "context")), "screenshots");
651622
+ const dir = import_node_path26.default.join(runDir, "specs", capPathSegment(sanitizeFilesystemName(String(spec.specId ?? ""), "spec")), "tests", capPathSegment(sanitizedTestId), "contexts", capPathSegment(sanitizeFilesystemName(String(context.contextId ?? ""), "context")), "screenshots");
650851
651623
  const stepIdString = sanitizeFilesystemName(String(step.stepId ?? ""), "step");
650852
651624
  const stepRef = capPathSegment(stepIdString.startsWith(`${sanitizedTestId}~`) ? stepIdString.slice(sanitizedTestId.length + 1) : stepIdString);
650853
651625
  const pad = Math.max(2, String(stepCount).length);
@@ -650856,7 +651628,7 @@ async function captureAutoScreenshot({ config, driver, spec, test, context, step
650856
651628
  stepId: `${step.stepId}_auto`,
650857
651629
  description: "Automatic post-step screenshot",
650858
651630
  screenshot: {
650859
- path: import_node_path25.default.join(dir, fileName),
651631
+ path: import_node_path26.default.join(dir, fileName),
650860
651632
  overwrite: "true"
650861
651633
  }
650862
651634
  };
@@ -650869,7 +651641,7 @@ async function captureAutoScreenshot({ config, driver, spec, test, context, step
650869
651641
  log(config, "warning", `Auto screenshot failed after step ${step.stepId}: ${captureResult.description}`);
650870
651642
  return null;
650871
651643
  }
650872
- return import_node_path25.default.relative(runDir, screenshotStep.screenshot.path).split(import_node_path25.default.sep).join("/");
651644
+ return import_node_path26.default.relative(runDir, screenshotStep.screenshot.path).split(import_node_path26.default.sep).join("/");
650873
651645
  } catch (error) {
650874
651646
  log(config, "warning", `Auto screenshot failed after step ${step.stepId}: ${error?.message ?? error}`);
650875
651647
  return null;
@@ -651049,7 +651821,8 @@ async function runContext({ config, spec, test, context, runnerDetails, appiumPo
651049
651821
  contextReport.resultDescription = errorMessage;
651050
651822
  return contextReport;
651051
651823
  }
651052
- clog("debug", `CONTEXT:
651824
+ if (logLevelEnabled(config, "debug"))
651825
+ clog("debug", `CONTEXT:
651053
651826
  ${JSON.stringify(context, null, 2)}`);
651054
651827
  let driver;
651055
651828
  let appiumPort;
@@ -651068,7 +651841,7 @@ ${JSON.stringify(context, null, 2)}`);
651068
651841
  env.ANDROID_HOME = appSession.androidSdkRoot;
651069
651842
  env.ANDROID_SDK_ROOT = appSession.androidSdkRoot;
651070
651843
  }
651071
- const extraArgs = mobileTarget === "android" ? ["--allow-insecure", "uiautomator2:chromedriver_autodownload"] : [];
651844
+ const extraArgs = mobileTarget === "android" ? CHROMEDRIVER_AUTODOWNLOAD_ARGS : [];
651072
651845
  let acquired;
651073
651846
  try {
651074
651847
  const server = await startAppiumServer(appSession.appiumEntry, config, void 0, env, extraArgs);
@@ -651361,7 +652134,8 @@ ${JSON.stringify(context, null, 2)}`);
651361
652134
  stepExecutionFailed = true;
651362
652135
  break;
651363
652136
  }
651364
- clog("debug", `STEP:
652137
+ if (logLevelEnabled(config, "debug"))
652138
+ clog("debug", `STEP:
651365
652139
  ${JSON.stringify(step, null, 2)}`);
651366
652140
  if (step.unsafe && runnerDetails.allowUnsafeSteps === false) {
651367
652141
  clog("warning", `Skipping unsafe step: ${step.description} in test ${test.testId} context ${context.contextId}`);
@@ -651427,7 +652201,8 @@ ${JSON.stringify(step, null, 2)}`);
651427
652201
  processRegistry,
651428
652202
  appSession
651429
652203
  });
651430
- clog("debug", `RESULT: ${r.status}
652204
+ if (logLevelEnabled(config, "debug"))
652205
+ clog("debug", `RESULT: ${r.status}
651431
652206
  ${JSON.stringify(r, null, 2)}`);
651432
652207
  r.result = r.status;
651433
652208
  r.resultDescription = r.description;
@@ -651824,7 +652599,7 @@ async function runStep({ config = {}, context = {}, step, driver, metaValues = {
651824
652599
  }
651825
652600
  return actionResult;
651826
652601
  }
651827
- async function startAppiumServer(appiumEntry, config, display, extraEnv, extraArgs) {
652602
+ async function spawnAppiumServer(appiumEntry, config, display, extraEnv, extraArgs) {
651828
652603
  const port = await findFreePort();
651829
652604
  log(config, "debug", `Starting Appium on port ${port}`);
651830
652605
  const env = display || extraEnv ? {
@@ -651832,9 +652607,9 @@ async function startAppiumServer(appiumEntry, config, display, extraEnv, extraAr
651832
652607
  ...display ? { DISPLAY: display } : {},
651833
652608
  ...extraEnv ?? {}
651834
652609
  } : process.env;
651835
- const proc = (0, import_node_child_process14.spawn)(process.execPath, [appiumEntry, "-a", "127.0.0.1", "-p", String(port), ...extraArgs ?? []], {
652610
+ const proc = (0, import_node_child_process15.spawn)(process.execPath, [appiumEntry, "-a", "127.0.0.1", "-p", String(port), ...extraArgs ?? []], {
651836
652611
  windowsHide: true,
651837
- cwd: import_node_path25.default.join(__dirname5, "../.."),
652612
+ cwd: import_node_path26.default.join(__dirname5, "../.."),
651838
652613
  env
651839
652614
  });
651840
652615
  proc.on("error", (err) => {
@@ -651844,31 +652619,41 @@ async function startAppiumServer(appiumEntry, config, display, extraEnv, extraAr
651844
652619
  });
651845
652620
  proc.stderr.on("data", () => {
651846
652621
  });
652622
+ return { port, process: proc, display };
652623
+ }
652624
+ async function startAppiumServer(appiumEntry, config, display, extraEnv, extraArgs) {
652625
+ const server = await spawnAppiumServer(appiumEntry, config, display, extraEnv, extraArgs);
651847
652626
  try {
651848
- await appiumIsReady(port);
652627
+ await appiumIsReady(server.port);
651849
652628
  } catch (error) {
651850
- await killTree(proc?.pid);
652629
+ await killTree(server.process?.pid);
651851
652630
  throw error;
651852
652631
  }
651853
- log(config, "debug", `Appium is ready on port ${port}.`);
651854
- return { port, process: proc, display };
652632
+ log(config, "debug", `Appium is ready on port ${server.port}.`);
652633
+ return server;
651855
652634
  }
651856
- async function appiumIsReady(port, timeoutMs = 12e4) {
651857
- let isReady = false;
652635
+ var STATUS_PROBE_TIMEOUT_MS = 1e4;
652636
+ async function appiumIsReady(port, timeoutMs = 12e4, deps = {}) {
652637
+ const probe = deps.probe ?? (async (p) => {
652638
+ try {
652639
+ const resp = await import_axios6.default.get(`http://127.0.0.1:${p}/status`, {
652640
+ timeout: STATUS_PROBE_TIMEOUT_MS
652641
+ });
652642
+ return resp.status === 200;
652643
+ } catch {
652644
+ return false;
652645
+ }
652646
+ });
652647
+ const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
651858
652648
  const start = Date.now();
651859
- while (!isReady) {
652649
+ while (true) {
652650
+ if (await probe(port))
652651
+ return true;
651860
652652
  if (Date.now() - start > timeoutMs) {
651861
652653
  throw new Error(`Appium server on port ${port} failed to start within ${timeoutMs / 1e3} seconds`);
651862
652654
  }
651863
- await new Promise((resolve) => setTimeout(resolve, 1e3));
651864
- try {
651865
- let resp = await import_axios6.default.get(`http://127.0.0.1:${port}/status`);
651866
- if (resp.status === 200)
651867
- isReady = true;
651868
- } catch {
651869
- }
652655
+ await sleep4(250);
651870
652656
  }
651871
- return isReady;
651872
652657
  }
651873
652658
  async function driverStart(capabilities, port, maxAttempts = 4, ctx = {}) {
651874
652659
  const wdio = await loadHeavyDep("webdriverio", { ctx });
@@ -651980,9 +652765,9 @@ async function getRunner(options = {}) {
651980
652765
  if (!appiumEntry) {
651981
652766
  throw new Error("appium is not installed. Run `doc-detective install runtime appium` to install it.");
651982
652767
  }
651983
- const appium = (0, import_node_child_process14.spawn)(process.execPath, [appiumEntry, "-a", "127.0.0.1", "-p", String(appiumPort)], {
652768
+ const appium = (0, import_node_child_process15.spawn)(process.execPath, [appiumEntry, "-a", "127.0.0.1", "-p", String(appiumPort)], {
651984
652769
  windowsHide: true,
651985
- cwd: import_node_path25.default.join(__dirname5, "../..")
652770
+ cwd: import_node_path26.default.join(__dirname5, "../..")
651986
652771
  });
651987
652772
  appium.on("error", (err) => {
651988
652773
  log(config, "warning", `Appium process error: ${err?.stack ?? err?.message ?? String(err)}`);
@@ -652058,6 +652843,8 @@ function telemetryNotice(config) {
652058
652843
  log(config, "info", "Doc Detective collects basic anonymous telemetry to understand product issues and usage. To disable telemetry, set 'telemetry.send' to 'false' in your .doc-detective.json config file.");
652059
652844
  }
652060
652845
  }
652846
+ var TELEMETRY_FLUSH_TIMEOUT_MS = 2e3;
652847
+ var pendingTelemetryFlush = null;
652061
652848
  function sendTelemetry(config, command, results) {
652062
652849
  if (config?.telemetry?.send === false)
652063
652850
  return;
@@ -652098,7 +652885,7 @@ function sendTelemetry(config, command, results) {
652098
652885
  const event = { distinctId, event: command, properties: telemetryData };
652099
652886
  const client = new import_posthog_node.PostHog("phc_rjV0MH3nsAd45zFISLgaKAdAXbgDeXt2mOBV2EBHomB", { host: "https://app.posthog.com" });
652100
652887
  client.capture(event);
652101
- client.shutdown();
652888
+ pendingTelemetryFlush = Promise.resolve(client.shutdown(TELEMETRY_FLUSH_TIMEOUT_MS)).then(() => void 0).catch(() => void 0);
652102
652889
  }
652103
652890
 
652104
652891
  // dist/core/index.js