doc-detective 4.4.0 → 4.5.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.
package/dist/index.cjs CHANGED
@@ -130043,6 +130043,21 @@ var import_promises = __toESM(require("node:dns/promises"), 1);
130043
130043
  var import_node_net = __toESM(require("node:net"), 1);
130044
130044
  var import_axios = __toESM(require("axios"), 1);
130045
130045
  var import_node_child_process = require("node:child_process");
130046
+ async function findFreePort() {
130047
+ return new Promise((resolve, reject) => {
130048
+ const server = import_node_net.default.createServer();
130049
+ server.once("error", reject);
130050
+ server.listen(0, "127.0.0.1", () => {
130051
+ const addr = server.address();
130052
+ if (!addr || typeof addr === "string") {
130053
+ server.close(() => reject(new Error("Failed to obtain ephemeral port")));
130054
+ return;
130055
+ }
130056
+ const port = addr.port;
130057
+ server.close(() => resolve(port));
130058
+ });
130059
+ });
130060
+ }
130046
130061
  function compileFilter(patterns) {
130047
130062
  if (!Array.isArray(patterns) || patterns.length === 0)
130048
130063
  return [];
@@ -137558,7 +137573,6 @@ function getIntegrationConfig(config, sourceIntegration) {
137558
137573
 
137559
137574
  // dist/core/tests.js
137560
137575
  var import_node_http2 = __toESM(require("node:http"), 1);
137561
- var import_node_net2 = __toESM(require("node:net"), 1);
137562
137576
  var import_node_https2 = __toESM(require("node:https"), 1);
137563
137577
  var import_node_url3 = require("node:url");
137564
137578
  var __dirname3 = import_node_path15.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
@@ -137879,10 +137893,12 @@ async function runSpecs({ resolvedTests }) {
137879
137893
  specs: []
137880
137894
  };
137881
137895
  const appiumRequired = isAppiumRequired(specs);
137896
+ let appiumPort;
137882
137897
  if (appiumRequired) {
137883
137898
  setAppiumHome();
137884
- await waitForPortFree(4723);
137885
- appium = (0, import_node_child_process3.spawn)("npx", ["appium"], {
137899
+ appiumPort = await findFreePort();
137900
+ log(config, "debug", `Starting Appium on port ${appiumPort}`);
137901
+ appium = (0, import_node_child_process3.spawn)("npx", ["appium", "-a", "127.0.0.1", "-p", String(appiumPort)], {
137886
137902
  shell: true,
137887
137903
  windowsHide: true,
137888
137904
  cwd: import_node_path15.default.join(__dirname3, "../..")
@@ -137894,7 +137910,7 @@ async function runSpecs({ resolvedTests }) {
137894
137910
  });
137895
137911
  appium.stderr.on("data", (data) => {
137896
137912
  });
137897
- await appiumIsReady();
137913
+ await appiumIsReady(appiumPort);
137898
137914
  log(config, "debug", "Appium is ready.");
137899
137915
  }
137900
137916
  log(config, "info", "Running test specs.");
@@ -137967,8 +137983,11 @@ ${JSON.stringify(context, null, 2)}`);
137967
137983
  });
137968
137984
  log(config, "debug", "CAPABILITIES:");
137969
137985
  log(config, "debug", caps);
137986
+ if (appiumPort === void 0) {
137987
+ throw new Error("Driver requested but Appium was not started. isAppiumRequired(specs) and isDriverRequired(context) disagreed; this is a bug.");
137988
+ }
137970
137989
  try {
137971
- driver = await driverStart(caps);
137990
+ driver = await driverStart(caps, appiumPort);
137972
137991
  } catch (error) {
137973
137992
  try {
137974
137993
  log(config, "warning", `Failed to start context '${context.browser?.name}' on '${platform}'. Retrying as headless.`);
@@ -137982,7 +138001,7 @@ ${JSON.stringify(context, null, 2)}`);
137982
138001
  headless: context.browser?.headless !== false
137983
138002
  }
137984
138003
  });
137985
- driver = await driverStart(caps);
138004
+ driver = await driverStart(caps, appiumPort);
137986
138005
  } catch (error2) {
137987
138006
  let errorMessage = `Failed to start context '${context.browser?.name}' on '${platform}'.`;
137988
138007
  if (context.browser?.name === "safari")
@@ -138258,40 +138277,16 @@ async function runStep({ config = {}, context = {}, step, driver, metaValues = {
138258
138277
  }
138259
138278
  return actionResult;
138260
138279
  }
138261
- async function waitForPortFree(port, timeoutMs = 3e4) {
138262
- const start = Date.now();
138263
- while (Date.now() - start < timeoutMs) {
138264
- const result = await new Promise((resolve) => {
138265
- const server = import_node_net2.default.createServer();
138266
- server.once("error", (err) => {
138267
- if (err && err.code === "EADDRINUSE")
138268
- resolve("busy");
138269
- else
138270
- resolve(err instanceof Error ? err : new Error(String(err)));
138271
- });
138272
- server.once("listening", () => {
138273
- server.close(() => resolve("free"));
138274
- });
138275
- server.listen(port, "127.0.0.1");
138276
- });
138277
- if (result === "free")
138278
- return;
138279
- if (result instanceof Error)
138280
- throw result;
138281
- await new Promise((r) => setTimeout(r, 250));
138282
- }
138283
- throw new Error(`Timed out after ${timeoutMs}ms waiting for 127.0.0.1:${port} to become free. A prior Appium instance is still bound to the port; the next session create would race the teardown and fail with an opaque ECONNREFUSED.`);
138284
- }
138285
- async function appiumIsReady(timeoutMs = 12e4) {
138280
+ async function appiumIsReady(port, timeoutMs = 12e4) {
138286
138281
  let isReady = false;
138287
138282
  const start = Date.now();
138288
138283
  while (!isReady) {
138289
138284
  if (Date.now() - start > timeoutMs) {
138290
- throw new Error(`Appium server failed to start within ${timeoutMs / 1e3} seconds`);
138285
+ throw new Error(`Appium server on port ${port} failed to start within ${timeoutMs / 1e3} seconds`);
138291
138286
  }
138292
138287
  await new Promise((resolve) => setTimeout(resolve, 1e3));
138293
138288
  try {
138294
- let resp = await import_axios6.default.get("http://127.0.0.1:4723/status");
138289
+ let resp = await import_axios6.default.get(`http://127.0.0.1:${port}/status`);
138295
138290
  if (resp.status === 200)
138296
138291
  isReady = true;
138297
138292
  } catch {
@@ -138299,14 +138294,14 @@ async function appiumIsReady(timeoutMs = 12e4) {
138299
138294
  }
138300
138295
  return isReady;
138301
138296
  }
138302
- async function driverStart(capabilities, maxAttempts = 4) {
138297
+ async function driverStart(capabilities, port, maxAttempts = 4) {
138303
138298
  let lastError;
138304
138299
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
138305
138300
  try {
138306
138301
  const driver = await wdio.remote({
138307
138302
  protocol: "http",
138308
138303
  hostname: "127.0.0.1",
138309
- port: 4723,
138304
+ port,
138310
138305
  path: "/",
138311
138306
  logLevel: "error",
138312
138307
  capabilities,
@@ -138343,13 +138338,17 @@ async function getRunner(options = {}) {
138343
138338
  throw new Error("Chrome browser is not available. Please ensure Chrome is installed and accessible.");
138344
138339
  }
138345
138340
  setAppiumHome();
138346
- const appium = (0, import_node_child_process3.spawn)("npx", ["appium"], {
138341
+ const appiumPort = await findFreePort();
138342
+ const appium = (0, import_node_child_process3.spawn)("npx", ["appium", "-a", "127.0.0.1", "-p", String(appiumPort)], {
138347
138343
  shell: true,
138348
138344
  windowsHide: true,
138349
138345
  cwd: import_node_path15.default.join(__dirname3, "../..")
138350
138346
  });
138351
- await appiumIsReady();
138352
- log(config, "debug", "Appium is ready for external driver.");
138347
+ appium.on("error", (err) => {
138348
+ log(config, "warning", `Appium process error: ${err?.stack ?? err?.message ?? String(err)}`);
138349
+ });
138350
+ await appiumIsReady(appiumPort);
138351
+ log(config, "debug", `Appium is ready for external driver on port ${appiumPort}.`);
138353
138352
  const caps = getDriverCapabilities({
138354
138353
  runnerDetails,
138355
138354
  name: "chrome",
@@ -138361,12 +138360,12 @@ async function getRunner(options = {}) {
138361
138360
  });
138362
138361
  let runner;
138363
138362
  try {
138364
- runner = await driverStart(caps);
138363
+ runner = await driverStart(caps, appiumPort);
138365
138364
  } catch (error) {
138366
138365
  try {
138367
138366
  log(config, "warning", "Failed to start Chrome runner. Retrying as headless.");
138368
138367
  caps["goog:chromeOptions"].args.push("--headless", "--disable-gpu");
138369
- runner = await driverStart(caps);
138368
+ runner = await driverStart(caps, appiumPort);
138370
138369
  } catch (error2) {
138371
138370
  (0, import_tree_kill.default)(appium.pid);
138372
138371
  throw new Error(`Failed to start Chrome runner: ${error2.message}`);
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "doc-detective",
3
- "version": "4.4.0",
3
+ "version": "4.5.0",
4
4
  "description": "Treat doc content as testable assertions to validate doc accuracy and product UX.",
5
5
  "bin": {
6
- "doc-detective": "bin/doc-detective.js"
6
+ "doc-detective": "bin/doc-detective.js",
7
+ "doc-detective-runner": "bin/runner-entrypoint.js"
7
8
  },
8
9
  "type": "module",
9
10
  "files": [