doc-detective 4.6.0 → 4.7.0-runtime-dependency-detection.1

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 (66) hide show
  1. package/dist/cli.js +57 -6
  2. package/dist/cli.js.map +1 -1
  3. package/dist/common/src/schemas/schemas.json +6 -18
  4. package/dist/common/src/types/generated/config_v3.d.ts +2 -1
  5. package/dist/common/src/types/generated/config_v3.d.ts.map +1 -1
  6. package/dist/common/src/types/generated/resolvedTests_v3.d.ts +2 -1
  7. package/dist/common/src/types/generated/resolvedTests_v3.d.ts.map +1 -1
  8. package/dist/core/config.d.ts +17 -1
  9. package/dist/core/config.d.ts.map +1 -1
  10. package/dist/core/config.js +250 -68
  11. package/dist/core/config.js.map +1 -1
  12. package/dist/core/index.d.ts.map +1 -1
  13. package/dist/core/index.js +7 -10
  14. package/dist/core/index.js.map +1 -1
  15. package/dist/core/tests.d.ts +41 -1
  16. package/dist/core/tests.d.ts.map +1 -1
  17. package/dist/core/tests.js +166 -7
  18. package/dist/core/tests.js.map +1 -1
  19. package/dist/debug/command.d.ts +10 -0
  20. package/dist/debug/command.d.ts.map +1 -0
  21. package/dist/debug/command.js +72 -0
  22. package/dist/debug/command.js.map +1 -0
  23. package/dist/debug/envvars.d.ts +9 -0
  24. package/dist/debug/envvars.d.ts.map +1 -0
  25. package/dist/debug/envvars.js +231 -0
  26. package/dist/debug/envvars.js.map +1 -0
  27. package/dist/debug/index.d.ts +85 -0
  28. package/dist/debug/index.d.ts.map +1 -0
  29. package/dist/debug/index.js +465 -0
  30. package/dist/debug/index.js.map +1 -0
  31. package/dist/debug/redact.d.ts +7 -0
  32. package/dist/debug/redact.d.ts.map +1 -0
  33. package/dist/debug/redact.js +190 -0
  34. package/dist/debug/redact.js.map +1 -0
  35. package/dist/debug/render.d.ts +8 -0
  36. package/dist/debug/render.d.ts.map +1 -0
  37. package/dist/debug/render.js +42 -0
  38. package/dist/debug/render.js.map +1 -0
  39. package/dist/debug/system.d.ts +24 -0
  40. package/dist/debug/system.d.ts.map +1 -0
  41. package/dist/debug/system.js +59 -0
  42. package/dist/debug/system.js.map +1 -0
  43. package/dist/debug/tools.d.ts +11 -0
  44. package/dist/debug/tools.d.ts.map +1 -0
  45. package/dist/debug/tools.js +170 -0
  46. package/dist/debug/tools.js.map +1 -0
  47. package/dist/hints/hints.d.ts.map +1 -1
  48. package/dist/hints/hints.js +17 -0
  49. package/dist/hints/hints.js.map +1 -1
  50. package/dist/index.cjs +393 -151
  51. package/dist/runtime/browsers.d.ts +14 -0
  52. package/dist/runtime/browsers.d.ts.map +1 -1
  53. package/dist/runtime/browsers.js +23 -0
  54. package/dist/runtime/browsers.js.map +1 -1
  55. package/dist/runtime/installer.d.ts.map +1 -1
  56. package/dist/runtime/installer.js +44 -9
  57. package/dist/runtime/installer.js.map +1 -1
  58. package/dist/runtime/loader.d.ts +20 -0
  59. package/dist/runtime/loader.d.ts.map +1 -1
  60. package/dist/runtime/loader.js +50 -0
  61. package/dist/runtime/loader.js.map +1 -1
  62. package/dist/utils.d.ts +2 -1
  63. package/dist/utils.d.ts.map +1 -1
  64. package/dist/utils.js +38 -11
  65. package/dist/utils.js.map +1 -1
  66. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -9923,7 +9923,8 @@ var init_schemas = __esm({
9923
9923
  title: "Environment details"
9924
9924
  },
9925
9925
  debug: {
9926
- description: "Enable debugging mode. `true` allows pausing on breakpoints, waiting for user input before continuing. `stepThrough` pauses at every step, waiting for user input before continuing. `false` disables all debugging.",
9926
+ description: "Deprecated and ignored. Previously reserved for an interactive step-through debugger that was never implemented. Retained so existing configs continue to validate. For diagnostics, run `doc-detective debug` or set the `DOC_DETECTIVE_DEBUG` environment variable.",
9927
+ deprecated: true,
9927
9928
  anyOf: [
9928
9929
  {
9929
9930
  type: "boolean"
@@ -9934,8 +9935,7 @@ var init_schemas = __esm({
9934
9935
  "stepThrough"
9935
9936
  ]
9936
9937
  }
9937
- ],
9938
- default: false
9938
+ ]
9939
9939
  },
9940
9940
  dryRun: {
9941
9941
  description: "If `true`, fully resolve tests (file/env config merge, schema validation, file detection, inline-test extraction) and emit the resolved test plan as JSON, but do not execute any steps. Equivalent to `--dry-run` on the CLI.",
@@ -18245,12 +18245,6 @@ var init_schemas = __esm({
18245
18245
  {
18246
18246
  debug: false
18247
18247
  },
18248
- {
18249
- debug: true
18250
- },
18251
- {
18252
- debug: "stepThrough"
18253
- },
18254
18248
  {
18255
18249
  integrations: {
18256
18250
  docDetectiveApi: {
@@ -31882,7 +31876,8 @@ var init_schemas = __esm({
31882
31876
  title: "Environment details"
31883
31877
  },
31884
31878
  debug: {
31885
- description: "Enable debugging mode. `true` allows pausing on breakpoints, waiting for user input before continuing. `stepThrough` pauses at every step, waiting for user input before continuing. `false` disables all debugging.",
31879
+ description: "Deprecated and ignored. Previously reserved for an interactive step-through debugger that was never implemented. Retained so existing configs continue to validate. For diagnostics, run `doc-detective debug` or set the `DOC_DETECTIVE_DEBUG` environment variable.",
31880
+ deprecated: true,
31886
31881
  anyOf: [
31887
31882
  {
31888
31883
  type: "boolean"
@@ -31893,8 +31888,7 @@ var init_schemas = __esm({
31893
31888
  "stepThrough"
31894
31889
  ]
31895
31890
  }
31896
- ],
31897
- default: false
31891
+ ]
31898
31892
  },
31899
31893
  dryRun: {
31900
31894
  description: "If `true`, fully resolve tests (file/env config merge, schema validation, file detection, inline-test extraction) and emit the resolved test plan as JSON, but do not execute any steps. Equivalent to `--dry-run` on the CLI.",
@@ -40204,12 +40198,6 @@ var init_schemas = __esm({
40204
40198
  {
40205
40199
  debug: false
40206
40200
  },
40207
- {
40208
- debug: true
40209
- },
40210
- {
40211
- debug: "stepThrough"
40212
- },
40213
40201
  {
40214
40202
  integrations: {
40215
40203
  docDetectiveApi: {
@@ -130740,7 +130728,9 @@ var loader_exports = {};
130740
130728
  __export(loader_exports, {
130741
130729
  ensureRuntimeInstalled: () => ensureRuntimeInstalled,
130742
130730
  loadHeavyDep: () => loadHeavyDep,
130743
- resolveHeavyDepPath: () => resolveHeavyDepPath
130731
+ resolveHeavyDepPath: () => resolveHeavyDepPath,
130732
+ resolveHeavyDepSource: () => resolveHeavyDepSource,
130733
+ resolveHeavyDepVersion: () => resolveHeavyDepVersion
130744
130734
  });
130745
130735
  function tryResolveFromShim(name) {
130746
130736
  try {
@@ -130764,6 +130754,36 @@ function tryResolveFromCache(name, ctx = {}) {
130764
130754
  function resolveHeavyDepPath(name, ctx = {}) {
130765
130755
  return tryResolveFromShim(name) ?? tryResolveFromCache(name, ctx);
130766
130756
  }
130757
+ function resolveHeavyDepSource(name, ctx = {}) {
130758
+ if (tryResolveFromShim(name))
130759
+ return "shim";
130760
+ if (tryResolveFromCache(name, ctx))
130761
+ return "cache";
130762
+ return null;
130763
+ }
130764
+ function resolveHeavyDepVersion(name, ctx = {}) {
130765
+ const entry = resolveHeavyDepPath(name, ctx);
130766
+ if (!entry)
130767
+ return null;
130768
+ let dir = import_node_path4.default.dirname(entry);
130769
+ for (let i = 0; i < 12; i++) {
130770
+ const pkgJsonPath = import_node_path4.default.join(dir, "package.json");
130771
+ try {
130772
+ if (import_node_fs4.default.existsSync(pkgJsonPath)) {
130773
+ const parsed = JSON.parse(import_node_fs4.default.readFileSync(pkgJsonPath, "utf8"));
130774
+ if (parsed?.name === name && typeof parsed.version === "string") {
130775
+ return parsed.version;
130776
+ }
130777
+ }
130778
+ } catch {
130779
+ }
130780
+ const parent = import_node_path4.default.dirname(dir);
130781
+ if (parent === dir)
130782
+ break;
130783
+ dir = parent;
130784
+ }
130785
+ return null;
130786
+ }
130767
130787
  async function loadHeavyDep(name, options = {}) {
130768
130788
  const { autoInstall = true, ctx = {}, deps = {} } = options;
130769
130789
  let resolved = tryResolveFromShim(name);
@@ -131426,6 +131446,7 @@ var config_exports = {};
131426
131446
  __export(config_exports, {
131427
131447
  clearAppCache: () => clearAppCache,
131428
131448
  getAvailableApps: () => getAvailableApps,
131449
+ getBrowserDiagnostics: () => getBrowserDiagnostics,
131429
131450
  getEnvironment: () => getEnvironment,
131430
131451
  resolveConcurrentRunners: () => resolveConcurrentRunners,
131431
131452
  setConfig: () => setConfig
@@ -131619,23 +131640,32 @@ function clearAppCache(config) {
131619
131640
  }
131620
131641
  cachedAppsByDir.delete(cacheKeyFor(config));
131621
131642
  }
131622
- async function getAvailableApps({ config }) {
131623
- const browsersDir = getBrowsersDir({ cacheDir: config?.cacheDir });
131624
- const key = browsersDir;
131625
- const hit = cachedAppsByDir.get(key);
131626
- if (hit)
131627
- return hit;
131643
+ async function probeBrowserEnvironment({ config, browsersDir }) {
131628
131644
  setAppiumHome({ cacheDir: config?.cacheDir });
131629
131645
  const cwd = process.cwd();
131630
131646
  process.chdir(import_node_path7.default.join(__dirname4, "../.."));
131631
- const apps = [];
131647
+ let installedBrowsers = [];
131648
+ let browserDetectionFailed = false;
131649
+ let appiumDriverOutput = "";
131632
131650
  try {
131633
- const browsers = await loadHeavyDep("@puppeteer/browsers", {
131634
- ctx: { cacheDir: config?.cacheDir }
131635
- });
131636
- const installedBrowsers = await browsers.getInstalledBrowsers({
131637
- cacheDir: browsersDir
131651
+ const browsersInstalled = resolveHeavyDepPath("@puppeteer/browsers", {
131652
+ cacheDir: config?.cacheDir
131638
131653
  });
131654
+ if (browsersInstalled) {
131655
+ try {
131656
+ const browsers = await loadHeavyDep("@puppeteer/browsers", {
131657
+ ctx: { cacheDir: config?.cacheDir },
131658
+ autoInstall: false
131659
+ });
131660
+ installedBrowsers = await browsers.getInstalledBrowsers({
131661
+ cacheDir: browsersDir
131662
+ });
131663
+ } catch (err) {
131664
+ browserDetectionFailed = true;
131665
+ log(config, "warning", `Browser detection failed; continuing without detected browsers: ${err?.message ?? err}`);
131666
+ installedBrowsers = [];
131667
+ }
131668
+ }
131639
131669
  const appiumEntry = resolveHeavyDepPath("appium", {
131640
131670
  cacheDir: config?.cacheDir
131641
131671
  });
@@ -131672,46 +131702,165 @@ async function getAvailableApps({ config }) {
131672
131702
  });
131673
131703
  });
131674
131704
  });
131675
- const appiumDriverOutput = installedAppiumDrivers.stdout + "\n" + installedAppiumDrivers.stderr;
131676
- const chrome = installedBrowsers.find((browser) => browser.browser === "chrome");
131677
- const chromeVersion = chrome?.buildId;
131678
- const chromedriver = installedBrowsers.find((browser) => browser.browser === "chromedriver");
131679
- const appiumChromium = appiumDriverOutput.match(/\n.*chromium.*installed \(npm\).*\n/);
131680
- if (chrome && chromedriver && appiumChromium) {
131681
- apps.push({
131682
- name: "chrome",
131683
- version: chromeVersion,
131684
- path: chrome.executablePath,
131685
- driver: chromedriver.executablePath
131686
- });
131705
+ appiumDriverOutput = installedAppiumDrivers.stdout + "\n" + installedAppiumDrivers.stderr;
131706
+ } finally {
131707
+ process.chdir(cwd);
131708
+ }
131709
+ return { installedBrowsers, appiumDriverOutput, browserDetectionFailed };
131710
+ }
131711
+ async function getAvailableApps({ config }) {
131712
+ const browsersDir = getBrowsersDir({ cacheDir: config?.cacheDir });
131713
+ const key = browsersDir;
131714
+ const hit = cachedAppsByDir.get(key);
131715
+ if (hit)
131716
+ return hit;
131717
+ const { installedBrowsers, appiumDriverOutput, browserDetectionFailed } = await probeBrowserEnvironment({ config, browsersDir });
131718
+ const apps = [];
131719
+ const chrome = installedBrowsers.find((browser) => browser.browser === "chrome");
131720
+ const chromeVersion = chrome?.buildId;
131721
+ const chromedriver = installedBrowsers.find((browser) => browser.browser === "chromedriver");
131722
+ const appiumChromium = appiumDriverOutput.match(/\n.*chromium.*installed \(npm\).*\n/);
131723
+ if (chrome && chromedriver && appiumChromium) {
131724
+ apps.push({
131725
+ name: "chrome",
131726
+ version: chromeVersion,
131727
+ path: chrome.executablePath,
131728
+ driver: chromedriver.executablePath
131729
+ });
131730
+ }
131731
+ const firefox = installedBrowsers.find((browser) => browser.browser === "firefox");
131732
+ const appiumFirefox = appiumDriverOutput.match(/\n.*gecko.*installed \(npm\).*\n/);
131733
+ if (firefox && appiumFirefox) {
131734
+ apps.push({
131735
+ name: "firefox",
131736
+ version: firefox.buildId,
131737
+ path: firefox.executablePath
131738
+ });
131739
+ }
131740
+ if (config.environment.platform === "mac") {
131741
+ const safariVersion = await spawnCommand("defaults read /Applications/Safari.app/Contents/Info.plist CFBundleShortVersionString");
131742
+ const appiumSafari = appiumDriverOutput.match(/\n.*safari.*installed \(npm\).*\n/);
131743
+ if (safariVersion.exitCode === 0 && appiumSafari) {
131744
+ apps.push({ name: "safari", version: safariVersion.stdout.trim(), path: "" });
131687
131745
  }
131688
- const firefox = installedBrowsers.find((browser) => browser.browser === "firefox");
131689
- const appiumFirefox = appiumDriverOutput.match(/\n.*gecko.*installed \(npm\).*\n/);
131690
- if (firefox && appiumFirefox) {
131691
- apps.push({
131692
- name: "firefox",
131693
- version: firefox.buildId,
131694
- path: firefox.executablePath
131695
- });
131746
+ }
131747
+ if (!browserDetectionFailed)
131748
+ cachedAppsByDir.set(key, apps);
131749
+ return apps;
131750
+ }
131751
+ function probeSafariVersion(timeoutMs) {
131752
+ return new Promise((resolve) => {
131753
+ let child;
131754
+ try {
131755
+ child = (0, import_node_child_process3.spawn)("defaults", [
131756
+ "read",
131757
+ "/Applications/Safari.app/Contents/Info.plist",
131758
+ "CFBundleShortVersionString"
131759
+ ], { stdio: ["ignore", "pipe", "ignore"] });
131760
+ } catch {
131761
+ resolve(null);
131762
+ return;
131696
131763
  }
131697
- if (config.environment.platform === "mac") {
131698
- const safariVersion = await spawnCommand("defaults read /Applications/Safari.app/Contents/Info.plist CFBundleShortVersionString");
131699
- const appiumSafari = appiumDriverOutput.match(/\n.*safari.*installed \(npm\).*\n/);
131700
- if (safariVersion.exitCode === 0 && appiumSafari) {
131701
- apps.push({ name: "safari", version: safariVersion.stdout.trim(), path: "" });
131764
+ let stdout = "";
131765
+ let settled = false;
131766
+ const finish = (value) => {
131767
+ if (settled)
131768
+ return;
131769
+ settled = true;
131770
+ clearTimeout(timer);
131771
+ try {
131772
+ child.kill();
131773
+ } catch {
131702
131774
  }
131775
+ resolve(value);
131776
+ };
131777
+ const timer = setTimeout(() => finish(null), timeoutMs);
131778
+ timer.unref?.();
131779
+ child.stdout?.on("data", (c) => {
131780
+ stdout += typeof c === "string" ? c : c.toString("utf8");
131781
+ });
131782
+ child.on("error", () => finish(null));
131783
+ child.on("close", (code) => finish(code === 0 ? stdout.trim() : null));
131784
+ });
131785
+ }
131786
+ async function getBrowserDiagnostics({ config }) {
131787
+ let detectionFailed = false;
131788
+ let record;
131789
+ try {
131790
+ record = readInstalledRecord({ cacheDir: config?.cacheDir });
131791
+ } catch {
131792
+ detectionFailed = true;
131793
+ record = { npmPackages: {}, browsers: {} };
131794
+ }
131795
+ const browsersRec = record.browsers || {};
131796
+ const platform = config?.environment?.platform;
131797
+ const brow = (name) => browsersRec[name];
131798
+ const npmInstalled = (name) => Boolean(resolveHeavyDepPath(name, { cacheDir: config?.cacheDir }));
131799
+ const browsers = [];
131800
+ const chrome = brow("chrome");
131801
+ const chromedriver = brow("chromedriver");
131802
+ const appiumChromium = npmInstalled("appium-chromium-driver");
131803
+ browsers.push({
131804
+ name: "chrome",
131805
+ supported: true,
131806
+ available: Boolean(chrome && chromedriver && appiumChromium),
131807
+ components: [
131808
+ { label: "chrome browser", installed: Boolean(chrome), detail: chrome?.installedVersion },
131809
+ { label: "chromedriver", installed: Boolean(chromedriver), detail: chromedriver?.installedVersion },
131810
+ { label: "appium-chromium-driver", installed: appiumChromium }
131811
+ ]
131812
+ });
131813
+ const firefox = brow("firefox");
131814
+ const geckodriver = brow("geckodriver");
131815
+ const appiumGecko = npmInstalled("appium-geckodriver");
131816
+ browsers.push({
131817
+ name: "firefox",
131818
+ supported: true,
131819
+ available: Boolean(firefox && geckodriver && appiumGecko),
131820
+ components: [
131821
+ { label: "firefox browser", installed: Boolean(firefox), detail: firefox?.installedVersion },
131822
+ { label: "geckodriver", installed: Boolean(geckodriver), detail: geckodriver?.installedVersion },
131823
+ { label: "appium-geckodriver", installed: appiumGecko }
131824
+ ]
131825
+ });
131826
+ const isMac = platform === "mac";
131827
+ const appiumSafari = npmInstalled("appium-safari-driver");
131828
+ let safariApp = false;
131829
+ let safariVersion;
131830
+ let safaridriver = false;
131831
+ if (isMac) {
131832
+ try {
131833
+ const version = await probeSafariVersion(4e3);
131834
+ safariApp = version !== null;
131835
+ safariVersion = version ?? void 0;
131836
+ safaridriver = import_node_fs7.default.existsSync("/usr/bin/safaridriver");
131837
+ } catch {
131838
+ detectionFailed = true;
131703
131839
  }
131704
- } finally {
131705
- process.chdir(cwd);
131706
131840
  }
131707
- cachedAppsByDir.set(key, apps);
131708
- return apps;
131841
+ browsers.push({
131842
+ name: "safari",
131843
+ supported: isMac,
131844
+ available: Boolean(isMac && safariApp && safaridriver && appiumSafari),
131845
+ components: [
131846
+ { label: "Safari app", installed: safariApp, detail: safariVersion },
131847
+ {
131848
+ label: "safaridriver",
131849
+ installed: safaridriver,
131850
+ detail: safaridriver ? "/usr/bin/safaridriver" : void 0
131851
+ },
131852
+ { label: "appium-safari-driver", installed: appiumSafari }
131853
+ ],
131854
+ note: isMac ? void 0 : "Safari is only available on macOS"
131855
+ });
131856
+ return { browsers, detectionFailed };
131709
131857
  }
131710
- var import_node_os3, import_node_path7, import_node_child_process3, import_node_url5, __dirname4, platformMap, defaultFileTypes, cachedAppsByDir;
131858
+ var import_node_os3, import_node_fs7, import_node_path7, import_node_child_process3, import_node_url5, __dirname4, platformMap, defaultFileTypes, cachedAppsByDir;
131711
131859
  var init_config = __esm({
131712
131860
  "dist/core/config.js"() {
131713
131861
  "use strict";
131714
131862
  import_node_os3 = __toESM(require("node:os"), 1);
131863
+ import_node_fs7 = __toESM(require("node:fs"), 1);
131715
131864
  init_validate();
131716
131865
  init_utils();
131717
131866
  import_node_path7 = __toESM(require("node:path"), 1);
@@ -132005,8 +132154,19 @@ var browsers_exports = {};
132005
132154
  __export(browsers_exports, {
132006
132155
  BROWSER_CHANNELS: () => BROWSER_CHANNELS,
132007
132156
  ensureBrowserInstalled: () => ensureBrowserInstalled,
132008
- getInstalledBrowsers: () => getInstalledBrowsers
132157
+ getInstalledBrowsers: () => getInstalledBrowsers,
132158
+ requiredBrowserAssets: () => requiredBrowserAssets
132009
132159
  });
132160
+ function requiredBrowserAssets(name) {
132161
+ switch ((name ?? "").toLowerCase()) {
132162
+ case "chrome":
132163
+ return ["chrome", "chromedriver"];
132164
+ case "firefox":
132165
+ return ["firefox", "geckodriver"];
132166
+ default:
132167
+ return [];
132168
+ }
132169
+ }
132010
132170
  async function loadPuppeteerBrowsers(deps, ctx) {
132011
132171
  if (deps.browsersModule)
132012
132172
  return deps.browsersModule;
@@ -132384,7 +132544,7 @@ module.exports = __toCommonJS(index_exports);
132384
132544
  init_config();
132385
132545
 
132386
132546
  // dist/core/detectTests.js
132387
- var import_node_fs8 = __toESM(require("node:fs"), 1);
132547
+ var import_node_fs9 = __toESM(require("node:fs"), 1);
132388
132548
  var import_node_path9 = __toESM(require("node:path"), 1);
132389
132549
  var import_node_os5 = __toESM(require("node:os"), 1);
132390
132550
  var import_node_crypto3 = __toESM(require("node:crypto"), 1);
@@ -133216,7 +133376,7 @@ init_files();
133216
133376
  init_utils();
133217
133377
 
133218
133378
  // dist/core/integrations/heretto.js
133219
- var import_node_fs7 = __toESM(require("node:fs"), 1);
133379
+ var import_node_fs8 = __toESM(require("node:fs"), 1);
133220
133380
  var import_node_path8 = __toESM(require("node:path"), 1);
133221
133381
  var import_node_https = __toESM(require("node:https"), 1);
133222
133382
  var import_node_http = __toESM(require("node:http"), 1);
@@ -133377,11 +133537,11 @@ var HerettoUploader = class {
133377
133537
  return result;
133378
133538
  }
133379
133539
  }
133380
- if (!import_node_fs7.default.existsSync(localFilePath)) {
133540
+ if (!import_node_fs8.default.existsSync(localFilePath)) {
133381
133541
  result.description = `Local file not found: ${localFilePath}`;
133382
133542
  return result;
133383
133543
  }
133384
- const fileContent = import_node_fs7.default.readFileSync(localFilePath);
133544
+ const fileContent = import_node_fs8.default.readFileSync(localFilePath);
133385
133545
  const contentType = this.getContentType(localFilePath);
133386
133546
  try {
133387
133547
  await this.uploadFile({
@@ -134155,7 +134315,7 @@ async function pollJobStatus(client, fileId, jobId, log3, config) {
134155
134315
  return null;
134156
134316
  }
134157
134317
  async function downloadAndExtractOutput(client, fileId, jobId, herettoName, log3, config, deps) {
134158
- const fsModule = deps?.fsModule || import_node_fs7.default;
134318
+ const fsModule = deps?.fsModule || import_node_fs8.default;
134159
134319
  const ZipClass = deps?.ZipClass || import_adm_zip.default;
134160
134320
  const tempDir = import_node_path8.default.join(import_node_os4.default.tmpdir(), "doc-detective");
134161
134321
  const hash = import_node_crypto2.default.createHash("md5").update(`${herettoName}_${jobId}`).digest("hex");
@@ -134393,7 +134553,7 @@ async function isValidSourceFile({ config, files, source }) {
134393
134553
  } else {
134394
134554
  beforePath = import_node_path9.default.resolve(test.before);
134395
134555
  }
134396
- if (!import_node_fs8.default.existsSync(beforePath)) {
134556
+ if (!import_node_fs9.default.existsSync(beforePath)) {
134397
134557
  log(config, "debug", `${beforePath} is specified to run before a test but isn't a valid file. Skipping ${source}.`);
134398
134558
  return false;
134399
134559
  }
@@ -134405,7 +134565,7 @@ async function isValidSourceFile({ config, files, source }) {
134405
134565
  } else {
134406
134566
  afterPath = import_node_path9.default.resolve(test.after);
134407
134567
  }
134408
- if (!import_node_fs8.default.existsSync(afterPath)) {
134568
+ if (!import_node_fs9.default.existsSync(afterPath)) {
134409
134569
  log(config, "debug", `${afterPath} is specified to run after a test but isn't a valid file. Skipping ${source}.`);
134410
134570
  return false;
134411
134571
  }
@@ -134423,9 +134583,9 @@ async function processDitaMap({ config, source }) {
134423
134583
  const hash = import_node_crypto3.default.createHash("md5").update(source).digest("hex");
134424
134584
  const tmpBase = import_node_path9.default.join(import_node_os5.default.tmpdir(), "doc-detective");
134425
134585
  const outputDir = import_node_path9.default.join(tmpBase, `ditamap_${hash}`);
134426
- if (!import_node_fs8.default.existsSync(tmpBase)) {
134586
+ if (!import_node_fs9.default.existsSync(tmpBase)) {
134427
134587
  log(config, "debug", `Creating temp directory: ${tmpBase}`);
134428
- import_node_fs8.default.mkdirSync(tmpBase, { recursive: true });
134588
+ import_node_fs9.default.mkdirSync(tmpBase, { recursive: true });
134429
134589
  }
134430
134590
  const ditaVersion = await spawnCommand("dita", ["--version"]);
134431
134591
  if (ditaVersion.exitCode !== 0) {
@@ -134527,8 +134687,8 @@ async function qualifyFiles({ config }) {
134527
134687
  let isFile = false;
134528
134688
  let isDir = false;
134529
134689
  try {
134530
- isFile = import_node_fs8.default.statSync(source).isFile();
134531
- isDir = import_node_fs8.default.statSync(source).isDirectory();
134690
+ isFile = import_node_fs9.default.statSync(source).isFile();
134691
+ isDir = import_node_fs9.default.statSync(source).isDirectory();
134532
134692
  } catch {
134533
134693
  log(config, "warning", `Cannot access path: ${source}. Skipping.`);
134534
134694
  continue;
@@ -134548,13 +134708,13 @@ async function qualifyFiles({ config }) {
134548
134708
  dirs = [];
134549
134709
  dirs[0] = source;
134550
134710
  for (const dir of dirs) {
134551
- const objects = import_node_fs8.default.readdirSync(dir);
134711
+ const objects = import_node_fs9.default.readdirSync(dir);
134552
134712
  for (const object of objects) {
134553
134713
  const content = import_node_path9.default.resolve(dir + "/" + object);
134554
134714
  if (content.includes("node_modules"))
134555
134715
  continue;
134556
- const isFile2 = import_node_fs8.default.statSync(content).isFile();
134557
- const isDir2 = import_node_fs8.default.statSync(content).isDirectory();
134716
+ const isFile2 = import_node_fs9.default.statSync(content).isFile();
134717
+ const isDir2 = import_node_fs9.default.statSync(content).isDirectory();
134558
134718
  if (isFile2 && await isValidSourceFile({ config, files, source: content })) {
134559
134719
  files.push(import_node_path9.default.resolve(content));
134560
134720
  } else if (isDir2 && config.recursive) {
@@ -134575,7 +134735,7 @@ async function parseTests({ config, files }) {
134575
134735
  let rawContent;
134576
134736
  if (extension === "json" || extension === "yaml" || extension === "yml") {
134577
134737
  try {
134578
- rawContent = await import_node_fs8.default.promises.readFile(file, "utf8");
134738
+ rawContent = await import_node_fs9.default.promises.readFile(file, "utf8");
134579
134739
  if (extension === "json") {
134580
134740
  content = JSON.parse(rawContent);
134581
134741
  } else {
@@ -134961,6 +135121,7 @@ init_utils();
134961
135121
  // dist/core/tests.js
134962
135122
  var import_tree_kill = __toESM(require("tree-kill"), 1);
134963
135123
  init_loader();
135124
+ init_browsers();
134964
135125
  var import_node_os8 = __toESM(require("node:os"), 1);
134965
135126
  init_utils();
134966
135127
  var import_axios6 = __toESM(require("axios"), 1);
@@ -136221,7 +136382,7 @@ async function waitForDOMStable(driver, idleTime, timeout) {
136221
136382
  // dist/core/tests/runShell.js
136222
136383
  init_validate();
136223
136384
  init_utils();
136224
- var import_node_fs9 = __toESM(require("node:fs"), 1);
136385
+ var import_node_fs10 = __toESM(require("node:fs"), 1);
136225
136386
  var import_node_path10 = __toESM(require("node:path"), 1);
136226
136387
  async function runShell({ config, step }) {
136227
136388
  const result = {
@@ -136296,23 +136457,23 @@ async function runShell({ config, step }) {
136296
136457
  }
136297
136458
  if (step.runShell.path) {
136298
136459
  const dir = import_node_path10.default.dirname(step.runShell.path);
136299
- if (!import_node_fs9.default.existsSync(dir)) {
136300
- import_node_fs9.default.mkdirSync(dir, { recursive: true });
136460
+ if (!import_node_fs10.default.existsSync(dir)) {
136461
+ import_node_fs10.default.mkdirSync(dir, { recursive: true });
136301
136462
  }
136302
136463
  let filePath = step.runShell.path;
136303
136464
  log(config, "debug", `Saving stdio to file: ${filePath}`);
136304
- if (!import_node_fs9.default.existsSync(filePath)) {
136305
- import_node_fs9.default.writeFileSync(filePath, result.outputs.stdio.stdout);
136465
+ if (!import_node_fs10.default.existsSync(filePath)) {
136466
+ import_node_fs10.default.writeFileSync(filePath, result.outputs.stdio.stdout);
136306
136467
  } else {
136307
136468
  if (step.runShell.overwrite == "false") {
136308
136469
  result.description = result.description + ` Didn't save output. File already exists.`;
136309
136470
  }
136310
- const existingFile = import_node_fs9.default.readFileSync(filePath, "utf8");
136471
+ const existingFile = import_node_fs10.default.readFileSync(filePath, "utf8");
136311
136472
  const fractionalDiff = calculateFractionalDifference(existingFile, result.outputs.stdio.stdout);
136312
136473
  log(config, "debug", `Fractional difference: ${fractionalDiff}`);
136313
136474
  if (fractionalDiff > step.runShell.maxVariation) {
136314
136475
  if (step.runShell.overwrite == "aboveVariation") {
136315
- import_node_fs9.default.writeFileSync(filePath, result.outputs.stdio.stdout);
136476
+ import_node_fs10.default.writeFileSync(filePath, result.outputs.stdio.stdout);
136316
136477
  result.description += ` Saved output to file.`;
136317
136478
  }
136318
136479
  result.status = "WARNING";
@@ -136320,7 +136481,7 @@ async function runShell({ config, step }) {
136320
136481
  return result;
136321
136482
  }
136322
136483
  if (step.runShell.overwrite == "true") {
136323
- import_node_fs9.default.writeFileSync(filePath, result.outputs.stdio.stdout);
136484
+ import_node_fs10.default.writeFileSync(filePath, result.outputs.stdio.stdout);
136324
136485
  result.description += ` Saved output to file.`;
136325
136486
  }
136326
136487
  }
@@ -136505,7 +136666,7 @@ async function checkLink({ config, step }) {
136505
136666
  init_validate();
136506
136667
  init_utils();
136507
136668
  var import_node_path11 = __toESM(require("node:path"), 1);
136508
- var import_node_fs10 = __toESM(require("node:fs"), 1);
136669
+ var import_node_fs11 = __toESM(require("node:fs"), 1);
136509
136670
  init_loader();
136510
136671
  var _pngjs = null;
136511
136672
  var _sharp = null;
@@ -136632,8 +136793,8 @@ async function saveScreenshot({ config, step, driver }) {
136632
136793
  const rawBase = import_node_path11.default.basename(urlPathname.split("?")[0].split("#")[0].replace(/\\/g, "/"));
136633
136794
  const safeBase = sanitizeFilesystemName(rawBase, `${step.stepId}.png`);
136634
136795
  dir = import_node_path11.default.join(process.cwd(), "doc-detective-runs", getOrInitRunTimestamp(config));
136635
- if (!import_node_fs10.default.existsSync(dir)) {
136636
- import_node_fs10.default.mkdirSync(dir, { recursive: true });
136796
+ if (!import_node_fs11.default.existsSync(dir)) {
136797
+ import_node_fs11.default.mkdirSync(dir, { recursive: true });
136637
136798
  }
136638
136799
  const captureId = `${step.stepId || "screenshot"}_${Date.now()}`;
136639
136800
  filePath = import_node_path11.default.join(dir, `${captureId}_${safeBase}`);
@@ -136649,10 +136810,10 @@ async function saveScreenshot({ config, step, driver }) {
136649
136810
  }
136650
136811
  } else {
136651
136812
  dir = import_node_path11.default.dirname(step.screenshot.path);
136652
- if (!import_node_fs10.default.existsSync(dir)) {
136653
- import_node_fs10.default.mkdirSync(dir, { recursive: true });
136813
+ if (!import_node_fs11.default.existsSync(dir)) {
136814
+ import_node_fs11.default.mkdirSync(dir, { recursive: true });
136654
136815
  }
136655
- if (import_node_fs10.default.existsSync(filePath)) {
136816
+ if (import_node_fs11.default.existsSync(filePath)) {
136656
136817
  if (step.screenshot.overwrite == "false") {
136657
136818
  result.status = "SKIPPED";
136658
136819
  result.description = `File already exists: ${filePath}`;
@@ -136748,8 +136909,8 @@ async function saveScreenshot({ config, step, driver }) {
136748
136909
  } catch (error) {
136749
136910
  result.status = "FAIL";
136750
136911
  result.description = `Couldn't save screenshot. ${error}`;
136751
- if (existFilePath && filePath !== existFilePath && import_node_fs10.default.existsSync(filePath)) {
136752
- import_node_fs10.default.unlinkSync(filePath);
136912
+ if (existFilePath && filePath !== existFilePath && import_node_fs11.default.existsSync(filePath)) {
136913
+ import_node_fs11.default.unlinkSync(filePath);
136753
136914
  }
136754
136915
  return result;
136755
136916
  }
@@ -136801,12 +136962,12 @@ async function saveScreenshot({ config, step, driver }) {
136801
136962
  width: rect.width,
136802
136963
  height: rect.height
136803
136964
  }).toFile(croppedPath);
136804
- import_node_fs10.default.renameSync(croppedPath, filePath);
136965
+ import_node_fs11.default.renameSync(croppedPath, filePath);
136805
136966
  } catch (error) {
136806
136967
  result.status = "FAIL";
136807
136968
  result.description = `Couldn't crop image. ${error}`;
136808
- if (existFilePath && filePath !== existFilePath && import_node_fs10.default.existsSync(filePath)) {
136809
- import_node_fs10.default.unlinkSync(filePath);
136969
+ if (existFilePath && filePath !== existFilePath && import_node_fs11.default.existsSync(filePath)) {
136970
+ import_node_fs11.default.unlinkSync(filePath);
136810
136971
  }
136811
136972
  return result;
136812
136973
  }
@@ -136814,7 +136975,7 @@ async function saveScreenshot({ config, step, driver }) {
136814
136975
  if (existFilePath) {
136815
136976
  if (step.screenshot.overwrite == "true" && !isUrlPath) {
136816
136977
  result.description += ` Overwrote existing file.`;
136817
- import_node_fs10.default.renameSync(filePath, existFilePath);
136978
+ import_node_fs11.default.renameSync(filePath, existFilePath);
136818
136979
  result.outputs.screenshotPath = existFilePath;
136819
136980
  result.outputs.changed = true;
136820
136981
  if (step.screenshot.sourceIntegration) {
@@ -136827,21 +136988,21 @@ async function saveScreenshot({ config, step, driver }) {
136827
136988
  let img1;
136828
136989
  let img2;
136829
136990
  try {
136830
- img1 = PNG.sync.read(import_node_fs10.default.readFileSync(existFilePath));
136831
- img2 = PNG.sync.read(import_node_fs10.default.readFileSync(filePath));
136991
+ img1 = PNG.sync.read(import_node_fs11.default.readFileSync(existFilePath));
136992
+ img2 = PNG.sync.read(import_node_fs11.default.readFileSync(filePath));
136832
136993
  } catch (error) {
136833
136994
  result.status = "FAIL";
136834
136995
  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}`;
136835
- if (!isUrlPath && filePath !== existFilePath && import_node_fs10.default.existsSync(filePath)) {
136836
- import_node_fs10.default.unlinkSync(filePath);
136996
+ if (!isUrlPath && filePath !== existFilePath && import_node_fs11.default.existsSync(filePath)) {
136997
+ import_node_fs11.default.unlinkSync(filePath);
136837
136998
  }
136838
136999
  return result;
136839
137000
  }
136840
137001
  if (!aspectRatiosMatch(img1, img2)) {
136841
137002
  result.status = "FAIL";
136842
137003
  result.description = `Couldn't compare images. Images have different aspect ratios.`;
136843
- if (!isUrlPath && filePath !== existFilePath && import_node_fs10.default.existsSync(filePath)) {
136844
- import_node_fs10.default.unlinkSync(filePath);
137004
+ if (!isUrlPath && filePath !== existFilePath && import_node_fs11.default.existsSync(filePath)) {
137005
+ import_node_fs11.default.unlinkSync(filePath);
136845
137006
  }
136846
137007
  return result;
136847
137008
  }
@@ -136868,8 +137029,8 @@ async function saveScreenshot({ config, step, driver }) {
136868
137029
  } catch (error) {
136869
137030
  result.status = "FAIL";
136870
137031
  result.description = `Couldn't load screenshot comparison dependency (pixelmatch). ${error?.message ?? error}`;
136871
- if (!isUrlPath && filePath !== existFilePath && import_node_fs10.default.existsSync(filePath)) {
136872
- import_node_fs10.default.unlinkSync(filePath);
137032
+ if (!isUrlPath && filePath !== existFilePath && import_node_fs11.default.existsSync(filePath)) {
137033
+ import_node_fs11.default.unlinkSync(filePath);
136873
137034
  }
136874
137035
  return result;
136875
137036
  }
@@ -136882,7 +137043,7 @@ async function saveScreenshot({ config, step, driver }) {
136882
137043
  });
136883
137044
  if (fractionalDiff > step.screenshot.maxVariation) {
136884
137045
  if (step.screenshot.overwrite == "aboveVariation" && !isUrlPath) {
136885
- import_node_fs10.default.renameSync(filePath, existFilePath);
137046
+ import_node_fs11.default.renameSync(filePath, existFilePath);
136886
137047
  }
136887
137048
  result.status = "WARNING";
136888
137049
  result.description += ` The difference between the existing screenshot and new screenshot (${fractionalDiff.toFixed(2)}) is greater than the max accepted variation (${step.screenshot.maxVariation}).`;
@@ -136908,7 +137069,7 @@ async function saveScreenshot({ config, step, driver }) {
136908
137069
  result.outputs.sourceIntegration = step.screenshot.sourceIntegration;
136909
137070
  }
136910
137071
  if (step.screenshot.overwrite != "true") {
136911
- import_node_fs10.default.unlinkSync(filePath);
137072
+ import_node_fs11.default.unlinkSync(filePath);
136912
137073
  }
136913
137074
  }
136914
137075
  }
@@ -136928,7 +137089,7 @@ async function saveScreenshot({ config, step, driver }) {
136928
137089
  init_validate();
136929
137090
  init_utils();
136930
137091
  var import_node_path12 = __toESM(require("node:path"), 1);
136931
- var import_node_fs11 = __toESM(require("node:fs"), 1);
137092
+ var import_node_fs12 = __toESM(require("node:fs"), 1);
136932
137093
  var import_node_os6 = __toESM(require("node:os"), 1);
136933
137094
  async function startRecording({ config, context, step, driver }) {
136934
137095
  let result = {
@@ -136966,10 +137127,10 @@ async function startRecording({ config, context, step, driver }) {
136966
137127
  let filePath = step.record.path;
136967
137128
  const baseName = import_node_path12.default.basename(filePath, import_node_path12.default.extname(filePath));
136968
137129
  const dir = import_node_path12.default.dirname(step.record.path);
136969
- if (!import_node_fs11.default.existsSync(dir)) {
136970
- import_node_fs11.default.mkdirSync(dir, { recursive: true });
137130
+ if (!import_node_fs12.default.existsSync(dir)) {
137131
+ import_node_fs12.default.mkdirSync(dir, { recursive: true });
136971
137132
  }
136972
- if (import_node_fs11.default.existsSync(filePath) && step.record.overwrite == "false") {
137133
+ if (import_node_fs12.default.existsSync(filePath) && step.record.overwrite == "false") {
136973
137134
  result.status = "SKIPPED";
136974
137135
  result.description = `File already exists: ${filePath}`;
136975
137136
  return result;
@@ -137093,7 +137254,7 @@ init_validate();
137093
137254
  init_utils();
137094
137255
  var import_node_child_process4 = require("node:child_process");
137095
137256
  var import_node_path13 = __toESM(require("node:path"), 1);
137096
- var import_node_fs12 = __toESM(require("node:fs"), 1);
137257
+ var import_node_fs13 = __toESM(require("node:fs"), 1);
137097
137258
  init_loader();
137098
137259
  async function getFfmpegPath(ctx = {}) {
137099
137260
  const mod = await loadHeavyDep("@ffmpeg-installer/ffmpeg", { ctx });
@@ -137142,11 +137303,11 @@ async function stopRecording({ config, step, driver }) {
137142
137303
  window.recorder.stop();
137143
137304
  });
137144
137305
  let waitCount = 0;
137145
- while (!import_node_fs12.default.existsSync(config.recording.downloadPath) && waitCount < 60) {
137306
+ while (!import_node_fs13.default.existsSync(config.recording.downloadPath) && waitCount < 60) {
137146
137307
  await new Promise((r) => setTimeout(r, 1e3));
137147
137308
  waitCount++;
137148
137309
  }
137149
- if (!import_node_fs12.default.existsSync(config.recording.downloadPath)) {
137310
+ if (!import_node_fs13.default.existsSync(config.recording.downloadPath)) {
137150
137311
  result.status = "FAIL";
137151
137312
  result.description = "Recording download timed out.";
137152
137313
  return result;
@@ -137171,7 +137332,7 @@ async function stopRecording({ config, step, driver }) {
137171
137332
  if (code === 0) {
137172
137333
  if (targetPath !== downloadPath) {
137173
137334
  try {
137174
- import_node_fs12.default.unlinkSync(downloadPath);
137335
+ import_node_fs13.default.unlinkSync(downloadPath);
137175
137336
  } catch {
137176
137337
  }
137177
137338
  }
@@ -137217,7 +137378,7 @@ async function loadVariables({ step }) {
137217
137378
  init_validate();
137218
137379
  init_utils();
137219
137380
  var import_node_path14 = __toESM(require("node:path"), 1);
137220
- var import_node_fs13 = __toESM(require("node:fs"), 1);
137381
+ var import_node_fs14 = __toESM(require("node:fs"), 1);
137221
137382
  async function saveCookie({ config, step, driver }) {
137222
137383
  let result = {
137223
137384
  status: "PASS",
@@ -137284,14 +137445,14 @@ async function saveCookie({ config, step, driver }) {
137284
137445
  if (filePath) {
137285
137446
  const outputDirectory = directory || config.output || process.cwd();
137286
137447
  const fullPath = import_node_path14.default.resolve(outputDirectory, filePath);
137287
- if (import_node_fs13.default.existsSync(fullPath) && !(overwrite === true || overwrite === "true")) {
137448
+ if (import_node_fs14.default.existsSync(fullPath) && !(overwrite === true || overwrite === "true")) {
137288
137449
  result.status = "FAIL";
137289
137450
  result.description = `File '${fullPath}' already exists and overwrite is not enabled.`;
137290
137451
  return result;
137291
137452
  }
137292
137453
  const dir = import_node_path14.default.dirname(fullPath);
137293
- if (!import_node_fs13.default.existsSync(dir)) {
137294
- import_node_fs13.default.mkdirSync(dir, { recursive: true });
137454
+ if (!import_node_fs14.default.existsSync(dir)) {
137455
+ import_node_fs14.default.mkdirSync(dir, { recursive: true });
137295
137456
  }
137296
137457
  if (targetCookie) {
137297
137458
  const netscapeCookie = formatCookieForNetscape(targetCookie);
@@ -137299,11 +137460,11 @@ async function saveCookie({ config, step, driver }) {
137299
137460
  # This is a cookie file saved by Doc Detective
137300
137461
  ${netscapeCookie}
137301
137462
  `;
137302
- import_node_fs13.default.writeFileSync(fullPath, content, "utf8");
137463
+ import_node_fs14.default.writeFileSync(fullPath, content, "utf8");
137303
137464
  result.description = `Saved cookie '${cookieName}' to '${fullPath}'.`;
137304
137465
  log(config, "debug", `Saved cookie '${cookieName}' to file: ${fullPath}`);
137305
137466
  } else {
137306
- import_node_fs13.default.writeFileSync(fullPath, "# No cookie data\n", "utf8");
137467
+ import_node_fs14.default.writeFileSync(fullPath, "# No cookie data\n", "utf8");
137307
137468
  result.description = `Created empty cookie file at '${fullPath}'.`;
137308
137469
  log(config, "debug", `Created empty cookie file: ${fullPath}`);
137309
137470
  }
@@ -137331,7 +137492,7 @@ function formatCookieForNetscape(cookie) {
137331
137492
  init_validate();
137332
137493
  init_utils();
137333
137494
  var import_node_path15 = __toESM(require("node:path"), 1);
137334
- var import_node_fs14 = __toESM(require("node:fs"), 1);
137495
+ var import_node_fs15 = __toESM(require("node:fs"), 1);
137335
137496
  async function loadCookie({ config, step, driver }) {
137336
137497
  let result = {
137337
137498
  status: "PASS",
@@ -137387,13 +137548,13 @@ async function loadCookie({ config, step, driver }) {
137387
137548
  } else if (filePath) {
137388
137549
  const inputDirectory = directory || config.output || process.cwd();
137389
137550
  const fullPath = import_node_path15.default.resolve(inputDirectory, filePath);
137390
- if (!import_node_fs14.default.existsSync(fullPath)) {
137551
+ if (!import_node_fs15.default.existsSync(fullPath)) {
137391
137552
  result.status = "FAIL";
137392
137553
  result.description = `Cookie file '${fullPath}' not found`;
137393
137554
  return result;
137394
137555
  }
137395
137556
  try {
137396
- const fileContent = import_node_fs14.default.readFileSync(fullPath, "utf8");
137557
+ const fileContent = import_node_fs15.default.readFileSync(fullPath, "utf8");
137397
137558
  const cookies = parseNetscapeCookieFile(fileContent);
137398
137559
  if (cookies.length === 0) {
137399
137560
  result.status = "FAIL";
@@ -137559,7 +137720,7 @@ function isDomainCompatible(currentDomain, cookieDomain) {
137559
137720
  // dist/core/tests/httpRequest.js
137560
137721
  init_validate();
137561
137722
  var import_axios5 = __toESM(require("axios"), 1);
137562
- var import_node_fs15 = __toESM(require("node:fs"), 1);
137723
+ var import_node_fs16 = __toESM(require("node:fs"), 1);
137563
137724
  var import_node_path16 = __toESM(require("node:path"), 1);
137564
137725
  var import_ajv2 = __toESM(require("ajv"), 1);
137565
137726
  init_openapi();
@@ -137866,24 +138027,24 @@ async function httpRequest({ config, step, openApiDefinitions = [] }) {
137866
138027
  }
137867
138028
  if (step.httpRequest.path) {
137868
138029
  const dir = import_node_path16.default.dirname(step.httpRequest.path);
137869
- if (!import_node_fs15.default.existsSync(dir)) {
137870
- import_node_fs15.default.mkdirSync(dir, { recursive: true });
138030
+ if (!import_node_fs16.default.existsSync(dir)) {
138031
+ import_node_fs16.default.mkdirSync(dir, { recursive: true });
137871
138032
  }
137872
138033
  let filePath = step.httpRequest.path;
137873
138034
  log(config, "debug", `Saving output to file: ${filePath}`);
137874
- if (!import_node_fs15.default.existsSync(filePath)) {
137875
- await import_node_fs15.default.promises.writeFile(filePath, JSON.stringify(response.data, null, 2));
138035
+ if (!import_node_fs16.default.existsSync(filePath)) {
138036
+ await import_node_fs16.default.promises.writeFile(filePath, JSON.stringify(response.data, null, 2));
137876
138037
  result.description += ` Saved output to file.`;
137877
138038
  } else {
137878
138039
  if (step.httpRequest.overwrite == "false") {
137879
138040
  result.description += ` Didn't save output. File already exists.`;
137880
138041
  }
137881
- const existingFile = import_node_fs15.default.readFileSync(filePath, "utf8");
138042
+ const existingFile = import_node_fs16.default.readFileSync(filePath, "utf8");
137882
138043
  const fractionalDiff = calculateFractionalDifference(existingFile, JSON.stringify(response.data, null, 2));
137883
138044
  log(config, "debug", `Fractional difference: ${fractionalDiff}`);
137884
138045
  if (fractionalDiff > step.httpRequest.maxVariation) {
137885
138046
  if (step.httpRequest.overwrite == "aboveVariation") {
137886
- await import_node_fs15.default.promises.writeFile(filePath, JSON.stringify(response.data, null, 2));
138047
+ await import_node_fs16.default.promises.writeFile(filePath, JSON.stringify(response.data, null, 2));
137887
138048
  result.description += ` Saved response to file.`;
137888
138049
  }
137889
138050
  result.status = "WARNING";
@@ -137891,7 +138052,7 @@ async function httpRequest({ config, step, openApiDefinitions = [] }) {
137891
138052
  return result;
137892
138053
  }
137893
138054
  if (step.httpRequest.overwrite == "true") {
137894
- import_node_fs15.default.writeFileSync(filePath, JSON.stringify(response.data, null, 2));
138055
+ import_node_fs16.default.writeFileSync(filePath, JSON.stringify(response.data, null, 2));
137895
138056
  result.description += ` Saved response to file.`;
137896
138057
  }
137897
138058
  }
@@ -138068,7 +138229,7 @@ async function clickElement({ config, step, driver }) {
138068
138229
  // dist/core/tests/runCode.js
138069
138230
  init_validate();
138070
138231
  init_utils();
138071
- var import_node_fs16 = __toESM(require("node:fs"), 1);
138232
+ var import_node_fs17 = __toESM(require("node:fs"), 1);
138072
138233
  var import_node_path17 = __toESM(require("node:path"), 1);
138073
138234
  var import_node_os7 = __toESM(require("node:os"), 1);
138074
138235
  function createTempScript(code, language) {
@@ -138092,7 +138253,7 @@ function createTempScript(code, language) {
138092
138253
  const tmpDir = import_node_os7.default.tmpdir();
138093
138254
  const tmpFile = import_node_path17.default.join(tmpDir, `doc-detective-${Date.now()}${extension}`);
138094
138255
  try {
138095
- import_node_fs16.default.writeFileSync(tmpFile, code);
138256
+ import_node_fs17.default.writeFileSync(tmpFile, code);
138096
138257
  } catch (error) {
138097
138258
  throw new Error(`Failed to create temporary script: ${error.message}`);
138098
138259
  }
@@ -138176,7 +138337,7 @@ async function runCode({ config, step }) {
138176
138337
  result.description = error.message;
138177
138338
  } finally {
138178
138339
  try {
138179
- import_node_fs16.default.unlinkSync(scriptPath);
138340
+ import_node_fs17.default.unlinkSync(scriptPath);
138180
138341
  log(config, "debug", `Removed temporary script: ${scriptPath}`);
138181
138342
  } catch (error) {
138182
138343
  log(config, "warn", `Failed to remove temporary script: ${scriptPath}`);
@@ -138724,6 +138885,14 @@ var driverActions2 = [
138724
138885
  "type"
138725
138886
  ];
138726
138887
  var KNOWN_BROWSERS = ["firefox", "chrome", "safari", "webkit"];
138888
+ function combinationKey(context) {
138889
+ const rawName = context?.browser?.name;
138890
+ const name = rawName === "webkit" ? "safari" : rawName || "<none>";
138891
+ return `${context?.platform}::${name}`;
138892
+ }
138893
+ function warmUpDecision(prev) {
138894
+ return prev === "failed" ? "skip" : "attempt";
138895
+ }
138727
138896
  function getDriverCapabilities({ runnerDetails, name, options }) {
138728
138897
  let capabilities = {};
138729
138898
  let args = [];
@@ -139002,8 +139171,10 @@ async function runSpecs({ resolvedTests }) {
139002
139171
  allowUnsafeSteps: await allowUnsafeSteps({ config })
139003
139172
  };
139004
139173
  const platform = runnerDetails.environment.platform;
139005
- const availableApps = runnerDetails.availableApps;
139174
+ let availableApps = runnerDetails.availableApps;
139006
139175
  const metaValues = { specs: {} };
139176
+ const installAttempts = /* @__PURE__ */ new Map();
139177
+ const warmUpResults = /* @__PURE__ */ new Map();
139007
139178
  let appium;
139008
139179
  const report = {
139009
139180
  summary: {
@@ -139118,14 +139289,46 @@ async function runSpecs({ resolvedTests }) {
139118
139289
  testReport.contexts.push(contextReport);
139119
139290
  continue;
139120
139291
  }
139121
- const supportedContext = isSupportedContext({
139292
+ let supportedContext = isSupportedContext({
139122
139293
  context,
139123
139294
  apps: availableApps,
139124
139295
  platform
139125
139296
  });
139297
+ let freshInstallRedetected = false;
139298
+ if (!supportedContext && context.platform === platform && // Mirror isSupportedContext's own guard: isDriverRequired iterates
139299
+ // context.steps, so a malformed context without a steps array would
139300
+ // otherwise crash the loop here instead of skipping cleanly.
139301
+ Array.isArray(context?.steps) && isDriverRequired({ test: context }) && requiredBrowserAssets(context.browser?.name).length > 0) {
139302
+ const firstAttempt = !installAttempts.has((context.browser?.name ?? "<none>").toLowerCase());
139303
+ const outcome = await ensureContextBrowserInstalled({
139304
+ browserName: context.browser?.name,
139305
+ config,
139306
+ installAttempts,
139307
+ deps: {
139308
+ ensureBrowser: (asset, options) => ensureBrowserInstalled(asset, options),
139309
+ log
139310
+ }
139311
+ });
139312
+ if (firstAttempt && (outcome === "installed" || outcome === "failed")) {
139313
+ freshInstallRedetected = true;
139314
+ clearAppCache(config);
139315
+ availableApps = await getAvailableApps({ config });
139316
+ runnerDetails.availableApps = availableApps;
139317
+ supportedContext = isSupportedContext({
139318
+ context,
139319
+ apps: availableApps,
139320
+ platform
139321
+ });
139322
+ }
139323
+ }
139126
139324
  if (!supportedContext) {
139127
- log(config, "info", `Skipping context. The current system doesn't support this context: {"platform": "${context.platform}", "apps": ${JSON.stringify(context.apps)}}`);
139128
- contextReport = { result: "SKIPPED", ...contextReport };
139325
+ const errorMessage = freshInstallRedetected ? `Skipping context '${context.browser?.name}' on '${context.platform}': the missing browser dependency was installed but still could not be detected.` : `Skipping context. The current system doesn't support this context: {"platform": "${context.platform}", "apps": ${JSON.stringify(context.apps)}}`;
139326
+ log(config, freshInstallRedetected ? "warning" : "info", errorMessage);
139327
+ contextReport = {
139328
+ ...contextReport,
139329
+ result: "SKIPPED",
139330
+ resultDescription: errorMessage
139331
+ };
139129
139332
  report.summary.contexts.skipped++;
139130
139333
  testReport.contexts.push(contextReport);
139131
139334
  continue;
@@ -139138,6 +139341,19 @@ ${JSON.stringify(context, null, 2)}`);
139138
139341
  }
139139
139342
  const driverRequired = isDriverRequired({ test: context });
139140
139343
  if (driverRequired) {
139344
+ const combo = combinationKey(context);
139345
+ if (warmUpDecision(warmUpResults.get(combo)) === "skip") {
139346
+ const errorMessage = `Skipping context '${context.browser?.name}' on '${context.platform}': this context combination could not start a driver earlier in this run.`;
139347
+ log(config, "warning", errorMessage);
139348
+ contextReport = {
139349
+ ...contextReport,
139350
+ result: "SKIPPED",
139351
+ resultDescription: errorMessage
139352
+ };
139353
+ report.summary.contexts.skipped++;
139354
+ testReport.contexts.push(contextReport);
139355
+ continue;
139356
+ }
139141
139357
  let caps = getDriverCapabilities({
139142
139358
  runnerDetails,
139143
139359
  name: context.browser.name,
@@ -139170,19 +139386,23 @@ ${JSON.stringify(context, null, 2)}`);
139170
139386
  driver = await driverStart(caps, appiumPort, 4, { cacheDir: config?.cacheDir });
139171
139387
  } catch (error2) {
139172
139388
  let errorMessage = `Failed to start context '${context.browser?.name}' on '${platform}'.`;
139173
- if (context.browser?.name === "safari")
139389
+ if (context.browser?.name === "safari" || context.browser?.name === "webkit")
139174
139390
  errorMessage = errorMessage + " Make sure you've run `safaridriver --enable` in a terminal and enabled 'Allow Remote Automation' in Safari's Develop menu.";
139175
139391
  log(config, "error", errorMessage);
139392
+ if (!warmUpResults.has(combo))
139393
+ warmUpResults.set(combo, "failed");
139176
139394
  contextReport = {
139395
+ ...contextReport,
139177
139396
  result: "SKIPPED",
139178
- resultDescription: errorMessage,
139179
- ...contextReport
139397
+ resultDescription: errorMessage
139180
139398
  };
139181
139399
  report.summary.contexts.skipped++;
139182
139400
  testReport.contexts.push(contextReport);
139183
139401
  continue;
139184
139402
  }
139185
139403
  }
139404
+ if (!warmUpResults.has(combo))
139405
+ warmUpResults.set(combo, "ok");
139186
139406
  if (context.browser?.viewport?.width || context.browser?.viewport?.height) {
139187
139407
  await setViewportSize(context, driver);
139188
139408
  } else if (context.browser?.window?.width || context.browser?.window?.height) {
@@ -139518,6 +139738,31 @@ async function ensureChromeAvailable(config, deps) {
139518
139738
  }
139519
139739
  return availableApps;
139520
139740
  }
139741
+ async function ensureContextBrowserInstalled({ browserName, config, installAttempts, deps }) {
139742
+ const key = (browserName ?? "<none>").toLowerCase();
139743
+ const cached = installAttempts.get(key);
139744
+ if (cached)
139745
+ return cached;
139746
+ const assets = requiredBrowserAssets(browserName);
139747
+ if (assets.length === 0) {
139748
+ installAttempts.set(key, "notInstallable");
139749
+ return "notInstallable";
139750
+ }
139751
+ const ctx = { cacheDir: config?.cacheDir };
139752
+ const logger = (msg, level = "info") => deps.log?.(config, level === "warn" ? "warning" : level, msg);
139753
+ try {
139754
+ deps.log?.(config, "info", `Browser '${browserName}' is not available; attempting on-demand install of: ${assets.join(", ")}.`);
139755
+ for (const asset of assets) {
139756
+ await deps.ensureBrowser(asset, { ctx, deps: { logger } });
139757
+ }
139758
+ installAttempts.set(key, "installed");
139759
+ return "installed";
139760
+ } catch (err) {
139761
+ deps.log?.(config, "warning", `On-demand install for '${browserName}' failed: ${err?.message ?? err}`);
139762
+ installAttempts.set(key, "failed");
139763
+ return "failed";
139764
+ }
139765
+ }
139521
139766
  async function getRunner(options = {}) {
139522
139767
  const environment = getEnvironment();
139523
139768
  const config = { ...options.config, environment };
@@ -139731,22 +139976,19 @@ async function runTests(config, options = {}) {
139731
139976
  if (needs.browsers.size > 0) {
139732
139977
  try {
139733
139978
  const { getAvailableApps: getAvailableApps2, clearAppCache: clearAppCache2 } = await Promise.resolve().then(() => (init_config(), config_exports));
139734
- const { ensureBrowserInstalled: ensureBrowserInstalled2 } = await Promise.resolve().then(() => (init_browsers(), browsers_exports));
139979
+ const { ensureBrowserInstalled: ensureBrowserInstalled2, requiredBrowserAssets: requiredBrowserAssets2 } = await Promise.resolve().then(() => (init_browsers(), browsers_exports));
139735
139980
  const available = await getAvailableApps2({ config });
139736
139981
  const availableNames = new Set(available.map((a) => a.name));
139737
139982
  let installedAnything = false;
139738
139983
  for (const browser of needs.browsers) {
139739
139984
  if (availableNames.has(browser))
139740
139985
  continue;
139741
- if (browser === "chrome") {
139742
- await ensureBrowserInstalled2("chrome", { ctx, deps: { logger: preflightLogger } });
139743
- await ensureBrowserInstalled2("chromedriver", { ctx, deps: { logger: preflightLogger } });
139744
- installedAnything = true;
139745
- } else if (browser === "firefox") {
139746
- await ensureBrowserInstalled2("firefox", { ctx, deps: { logger: preflightLogger } });
139747
- await ensureBrowserInstalled2("geckodriver", { ctx, deps: { logger: preflightLogger } });
139748
- installedAnything = true;
139986
+ const assets = requiredBrowserAssets2(browser);
139987
+ for (const asset of assets) {
139988
+ await ensureBrowserInstalled2(asset, { ctx, deps: { logger: preflightLogger } });
139749
139989
  }
139990
+ if (assets.length > 0)
139991
+ installedAnything = true;
139750
139992
  }
139751
139993
  if (installedAnything)
139752
139994
  clearAppCache2(config);