intellitester 0.2.44 → 0.2.45

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.
@@ -7,8 +7,7 @@ var uniqueNamesGenerator = require('unique-names-generator');
7
7
  var crypto4 = require('crypto');
8
8
  var libphonenumberJs = require('libphonenumber-js');
9
9
  var fs = require('fs/promises');
10
- var path2 = require('path');
11
- var child_process = require('child_process');
10
+ var path4 = require('path');
12
11
  var playwright = require('playwright');
13
12
  var prompts = require('prompts');
14
13
  var nodeAppwrite = require('node-appwrite');
@@ -16,12 +15,31 @@ var anthropic = require('@llamaindex/anthropic');
16
15
  var openai = require('@llamaindex/openai');
17
16
  var ollama = require('@llamaindex/ollama');
18
17
  var http = require('http');
18
+ var child_process = require('child_process');
19
19
 
20
20
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
21
 
22
+ function _interopNamespace(e) {
23
+ if (e && e.__esModule) return e;
24
+ var n = Object.create(null);
25
+ if (e) {
26
+ Object.keys(e).forEach(function (k) {
27
+ if (k !== 'default') {
28
+ var d = Object.getOwnPropertyDescriptor(e, k);
29
+ Object.defineProperty(n, k, d.get ? d : {
30
+ enumerable: true,
31
+ get: function () { return e[k]; }
32
+ });
33
+ }
34
+ });
35
+ }
36
+ n.default = e;
37
+ return Object.freeze(n);
38
+ }
39
+
22
40
  var crypto4__default = /*#__PURE__*/_interopDefault(crypto4);
23
- var fs__default = /*#__PURE__*/_interopDefault(fs);
24
- var path2__default = /*#__PURE__*/_interopDefault(path2);
41
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
42
+ var path4__namespace = /*#__PURE__*/_interopNamespace(path4);
25
43
  var prompts__default = /*#__PURE__*/_interopDefault(prompts);
26
44
 
27
45
  function generateRandomUsername() {
@@ -881,12 +899,12 @@ var TRACK_DIR = ".intellitester/track";
881
899
  var ACTIVE_TESTS_FILE = "ACTIVE_TESTS.json";
882
900
  var DEFAULT_STALE_MS = 2 * 60 * 60 * 1e3;
883
901
  var HEARTBEAT_MS = 15e3;
884
- var getTrackDir = (cwd, trackDir) => trackDir ? path2__default.default.resolve(cwd, trackDir) : path2__default.default.join(cwd, TRACK_DIR);
885
- var getActiveTestsPath = (cwd, trackDir) => path2__default.default.join(getTrackDir(cwd, trackDir), ACTIVE_TESTS_FILE);
902
+ var getTrackDir = (cwd, trackDir) => trackDir ? path4__namespace.default.resolve(cwd, trackDir) : path4__namespace.default.join(cwd, TRACK_DIR);
903
+ var getActiveTestsPath = (cwd, trackDir) => path4__namespace.default.join(getTrackDir(cwd, trackDir), ACTIVE_TESTS_FILE);
886
904
  var loadActiveTests = async (cwd, trackDir) => {
887
905
  const filePath = getActiveTestsPath(cwd, trackDir);
888
906
  try {
889
- const content = await fs__default.default.readFile(filePath, "utf8");
907
+ const content = await fs__namespace.default.readFile(filePath, "utf8");
890
908
  const parsed = JSON.parse(content);
891
909
  if (!parsed.sessions || typeof parsed.sessions !== "object") {
892
910
  throw new Error("Invalid ACTIVE_TESTS structure");
@@ -903,7 +921,7 @@ var loadActiveTests = async (cwd, trackDir) => {
903
921
  var saveActiveTests = async (cwd, state, trackDir) => {
904
922
  const filePath = getActiveTestsPath(cwd, trackDir);
905
923
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
906
- await fs__default.default.writeFile(filePath, JSON.stringify(state, null, 2), "utf8");
924
+ await fs__namespace.default.writeFile(filePath, JSON.stringify(state, null, 2), "utf8");
907
925
  };
908
926
  var isStale = (entry, staleMs) => {
909
927
  const last = new Date(entry.lastUpdated).getTime();
@@ -912,7 +930,7 @@ var isStale = (entry, staleMs) => {
912
930
  };
913
931
  var readTrackedResources = async (trackFile) => {
914
932
  try {
915
- const content = await fs__default.default.readFile(trackFile, "utf8");
933
+ const content = await fs__namespace.default.readFile(trackFile, "utf8");
916
934
  if (!content.trim()) return [];
917
935
  return content.split("\n").filter(Boolean).map((line) => {
918
936
  try {
@@ -948,19 +966,19 @@ var cleanupStaleSession = async (entry, cleanupConfig) => {
948
966
  var cleanupOrphanedTrackFiles = async (cwd, state, trackDir) => {
949
967
  const dir = getTrackDir(cwd, trackDir);
950
968
  try {
951
- const files = await fs__default.default.readdir(dir);
952
- const activeFiles = new Set(Object.values(state.sessions).map((s) => path2__default.default.basename(s.trackFile)));
969
+ const files = await fs__namespace.default.readdir(dir);
970
+ const activeFiles = new Set(Object.values(state.sessions).map((s) => path4__namespace.default.basename(s.trackFile)));
953
971
  for (const file of files) {
954
972
  if (!file.endsWith(".jsonl")) continue;
955
973
  if (activeFiles.has(file)) continue;
956
- const filePath = path2__default.default.join(dir, file);
974
+ const filePath = path4__namespace.default.join(dir, file);
957
975
  try {
958
- const stat = await fs__default.default.stat(filePath);
976
+ const stat2 = await fs__namespace.default.stat(filePath);
959
977
  const staleMs = Number(process.env.INTELLITESTER_STALE_TEST_MS ?? DEFAULT_STALE_MS);
960
- const isOld = Date.now() - stat.mtimeMs > staleMs;
961
- const isEmpty = stat.size === 0;
978
+ const isOld = Date.now() - stat2.mtimeMs > staleMs;
979
+ const isEmpty = stat2.size === 0;
962
980
  if (isEmpty || isOld) {
963
- await fs__default.default.rm(filePath, { force: true });
981
+ await fs__namespace.default.rm(filePath, { force: true });
964
982
  }
965
983
  } catch {
966
984
  }
@@ -976,8 +994,8 @@ var pruneStaleTests = async (cwd, cleanupConfig, trackDir) => {
976
994
  let missingFile = false;
977
995
  let isEmpty = false;
978
996
  try {
979
- const stat = await fs__default.default.stat(entry.trackFile);
980
- isEmpty = stat.size === 0;
997
+ const stat2 = await fs__namespace.default.stat(entry.trackFile);
998
+ isEmpty = stat2.size === 0;
981
999
  } catch {
982
1000
  missingFile = true;
983
1001
  }
@@ -985,7 +1003,7 @@ var pruneStaleTests = async (cwd, cleanupConfig, trackDir) => {
985
1003
  changed = true;
986
1004
  await cleanupStaleSession(entry, cleanupConfig);
987
1005
  try {
988
- await fs__default.default.rm(entry.trackFile, { force: true });
1006
+ await fs__namespace.default.rm(entry.trackFile, { force: true });
989
1007
  } catch {
990
1008
  }
991
1009
  delete state.sessions[sessionId];
@@ -998,10 +1016,10 @@ var pruneStaleTests = async (cwd, cleanupConfig, trackDir) => {
998
1016
  async function initFileTracking(options) {
999
1017
  const cwd = options.cwd ?? process.cwd();
1000
1018
  const trackDir = options.trackDir;
1001
- await fs__default.default.mkdir(getTrackDir(cwd, trackDir), { recursive: true });
1019
+ await fs__namespace.default.mkdir(getTrackDir(cwd, trackDir), { recursive: true });
1002
1020
  await pruneStaleTests(cwd, options.cleanupConfig, trackDir);
1003
- const trackFile = path2__default.default.join(getTrackDir(cwd, trackDir), `TEST_SESSION_${options.sessionId}.jsonl`);
1004
- await fs__default.default.writeFile(trackFile, "", "utf8");
1021
+ const trackFile = path4__namespace.default.join(getTrackDir(cwd, trackDir), `TEST_SESSION_${options.sessionId}.jsonl`);
1022
+ await fs__namespace.default.writeFile(trackFile, "", "utf8");
1005
1023
  const state = await loadActiveTests(cwd, trackDir);
1006
1024
  state.sessions[options.sessionId] = {
1007
1025
  sessionId: options.sessionId,
@@ -1029,7 +1047,7 @@ async function initFileTracking(options) {
1029
1047
  delete current.sessions[options.sessionId];
1030
1048
  await saveActiveTests(cwd, current, trackDir);
1031
1049
  try {
1032
- await fs__default.default.rm(trackFile, { force: true });
1050
+ await fs__namespace.default.rm(trackFile, { force: true });
1033
1051
  } catch {
1034
1052
  }
1035
1053
  };
@@ -1058,9 +1076,321 @@ async function mergeFileTrackedResources(trackFile, target, allowedTypes) {
1058
1076
  }
1059
1077
  }
1060
1078
  }
1079
+ async function isServerRunning(url) {
1080
+ try {
1081
+ const response = await fetch(url, { method: "HEAD" });
1082
+ return response.ok || response.status < 500;
1083
+ } catch {
1084
+ return false;
1085
+ }
1086
+ }
1087
+ async function readPackageJson(cwd) {
1088
+ try {
1089
+ const packagePath = path4__namespace.join(cwd, "package.json");
1090
+ const content = await fs__namespace.readFile(packagePath, "utf-8");
1091
+ return JSON.parse(content);
1092
+ } catch {
1093
+ return null;
1094
+ }
1095
+ }
1096
+ function detectFramework(pkg) {
1097
+ if (!pkg) return null;
1098
+ const deps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
1099
+ if (deps["next"]) {
1100
+ return { name: "next", buildCommand: "npx -y next start", devCommand: "next dev" };
1101
+ }
1102
+ if (deps["nuxt"]) {
1103
+ return { name: "nuxt", buildCommand: "node .output/server/index.mjs", devCommand: "nuxi dev" };
1104
+ }
1105
+ if (deps["astro"]) {
1106
+ return { name: "astro", buildCommand: "npx -y astro dev", devCommand: "astro dev" };
1107
+ }
1108
+ if (deps["@sveltejs/kit"]) {
1109
+ return { name: "sveltekit", buildCommand: "npx -y vite preview", devCommand: "vite dev" };
1110
+ }
1111
+ if (deps["@remix-run/serve"] || deps["@remix-run/dev"]) {
1112
+ return { name: "remix", buildCommand: "npx -y remix-serve build/server/index.js", devCommand: "remix vite:dev" };
1113
+ }
1114
+ if (deps["vite"]) {
1115
+ return { name: "vite", buildCommand: "npx -y vite preview", devCommand: "vite dev" };
1116
+ }
1117
+ if (deps["react-scripts"]) {
1118
+ return { name: "cra", buildCommand: "npx -y serve -s build", devCommand: "react-scripts start" };
1119
+ }
1120
+ return null;
1121
+ }
1122
+ async function detectPackageManager(cwd) {
1123
+ const hasDenoLock = await fs__namespace.stat(path4__namespace.join(cwd, "deno.lock")).catch(() => null);
1124
+ const hasBunLock = await fs__namespace.stat(path4__namespace.join(cwd, "bun.lockb")).catch(() => null);
1125
+ const hasPnpmLock = await fs__namespace.stat(path4__namespace.join(cwd, "pnpm-lock.yaml")).catch(() => null);
1126
+ const hasYarnLock = await fs__namespace.stat(path4__namespace.join(cwd, "yarn.lock")).catch(() => null);
1127
+ if (hasDenoLock) return "deno";
1128
+ if (hasBunLock) return "bun";
1129
+ if (hasPnpmLock) return "pnpm";
1130
+ if (hasYarnLock) return "yarn";
1131
+ return "npm";
1132
+ }
1133
+ function getDevCommand(pm, script) {
1134
+ switch (pm) {
1135
+ case "deno":
1136
+ return `deno task ${script}`;
1137
+ case "bun":
1138
+ return `bun run ${script}`;
1139
+ case "pnpm":
1140
+ return `pnpm ${script}`;
1141
+ case "yarn":
1142
+ return `yarn ${script}`;
1143
+ case "npm":
1144
+ return `npm run ${script}`;
1145
+ }
1146
+ }
1147
+ async function detectBuildDirectory(cwd) {
1148
+ const commonDirs = [
1149
+ ".next",
1150
+ ".output",
1151
+ ".svelte-kit",
1152
+ "dist",
1153
+ "build",
1154
+ "out"
1155
+ ];
1156
+ for (const dir of commonDirs) {
1157
+ const fullPath = path4__namespace.join(cwd, dir);
1158
+ try {
1159
+ const stat2 = await fs__namespace.stat(fullPath);
1160
+ if (stat2.isDirectory()) {
1161
+ return dir;
1162
+ }
1163
+ } catch {
1164
+ }
1165
+ }
1166
+ return null;
1167
+ }
1168
+ async function detectServerCommand(cwd) {
1169
+ const pkg = await readPackageJson(cwd);
1170
+ const framework = detectFramework(pkg);
1171
+ const pm = await detectPackageManager(cwd);
1172
+ const buildDir = await detectBuildDirectory(cwd);
1173
+ if (buildDir) {
1174
+ if (framework) {
1175
+ console.log(`Detected ${framework.name} project with build at ${buildDir}`);
1176
+ return framework.buildCommand;
1177
+ }
1178
+ console.log(`Detected build directory at ${buildDir}, using static server`);
1179
+ return `npx -y serve ${buildDir}`;
1180
+ }
1181
+ const scripts = pkg?.scripts;
1182
+ if (scripts?.dev) {
1183
+ if (framework) {
1184
+ console.log(`Detected ${framework.name} project, running dev server`);
1185
+ }
1186
+ return getDevCommand(pm, "dev");
1187
+ }
1188
+ if (scripts?.start) {
1189
+ return getDevCommand(pm, "start");
1190
+ }
1191
+ throw new Error("Could not auto-detect server command. Please specify command explicitly.");
1192
+ }
1193
+ var WebServerManager = class _WebServerManager {
1194
+ constructor() {
1195
+ this.serverProcess = null;
1196
+ this.currentUrl = null;
1197
+ this.currentCwd = null;
1198
+ this.stopping = false;
1199
+ }
1200
+ static getInstance() {
1201
+ if (!_WebServerManager.instance) {
1202
+ _WebServerManager.instance = new _WebServerManager();
1203
+ }
1204
+ return _WebServerManager.instance;
1205
+ }
1206
+ /**
1207
+ * Check if the managed server is currently running
1208
+ */
1209
+ isRunning() {
1210
+ return this.serverProcess !== null && !this.serverProcess.killed;
1211
+ }
1212
+ /**
1213
+ * Get the current server URL if running
1214
+ */
1215
+ getUrl() {
1216
+ return this.currentUrl;
1217
+ }
1218
+ /**
1219
+ * Start a web server with the given config.
1220
+ *
1221
+ * - If a server is already running at the same URL, reuses it (unless reuseExistingServer=false)
1222
+ * - If a server is running at a different URL, stops it first
1223
+ * - Properly waits for any stopping server to fully terminate
1224
+ */
1225
+ async start(config) {
1226
+ const { url, reuseExistingServer = true, timeout = 3e4, idleTimeout = 2e4 } = config;
1227
+ const cwd = config.workdir ?? config.cwd ?? process.cwd();
1228
+ while (this.stopping) {
1229
+ await new Promise((r) => setTimeout(r, 100));
1230
+ }
1231
+ if (this.serverProcess && !this.serverProcess.killed && this.currentUrl === url) {
1232
+ if (await isServerRunning(url)) {
1233
+ if (reuseExistingServer) {
1234
+ console.log(`Server already running at ${url}`);
1235
+ return this.serverProcess;
1236
+ } else {
1237
+ await this.stop();
1238
+ }
1239
+ } else {
1240
+ await this.stop();
1241
+ }
1242
+ }
1243
+ if (this.serverProcess && !this.serverProcess.killed && this.currentUrl !== url) {
1244
+ await this.stop();
1245
+ }
1246
+ if (reuseExistingServer && await isServerRunning(url)) {
1247
+ console.log(`Server already running at ${url}`);
1248
+ this.currentUrl = url;
1249
+ this.currentCwd = cwd;
1250
+ return null;
1251
+ }
1252
+ let command;
1253
+ if (config.command) {
1254
+ command = config.command;
1255
+ } else if (config.static) {
1256
+ const port = config.port ?? new URL(url).port ?? "3000";
1257
+ command = `npx -y serve ${config.static} -l ${port}`;
1258
+ } else if (config.auto) {
1259
+ command = await detectServerCommand(cwd);
1260
+ } else {
1261
+ throw new Error("WebServerConfig requires command, auto: true, or static directory");
1262
+ }
1263
+ console.log(`Starting server: ${command}`);
1264
+ this.serverProcess = child_process.spawn(command, {
1265
+ shell: true,
1266
+ stdio: "pipe",
1267
+ cwd,
1268
+ detached: false
1269
+ });
1270
+ this.currentUrl = url;
1271
+ this.currentCwd = cwd;
1272
+ let stderrOutput = "";
1273
+ let lastOutputTime = Date.now();
1274
+ this.serverProcess.stdout?.on("data", (data) => {
1275
+ lastOutputTime = Date.now();
1276
+ process.stdout.write(`[server] ${data}`);
1277
+ });
1278
+ this.serverProcess.stderr?.on("data", (data) => {
1279
+ lastOutputTime = Date.now();
1280
+ stderrOutput += data.toString();
1281
+ process.stderr.write(`[server] ${data}`);
1282
+ });
1283
+ await new Promise((resolve, reject) => {
1284
+ let resolved = false;
1285
+ const startTime = Date.now();
1286
+ const cleanup = () => {
1287
+ resolved = true;
1288
+ clearInterval(pollInterval);
1289
+ };
1290
+ this.serverProcess.on("close", (code) => {
1291
+ if (!resolved && code !== 0 && code !== null) {
1292
+ cleanup();
1293
+ this.serverProcess = null;
1294
+ this.currentUrl = null;
1295
+ reject(new Error(`Server exited with code ${code}
1296
+ ${stderrOutput}`));
1297
+ }
1298
+ });
1299
+ this.serverProcess.on("error", (err) => {
1300
+ if (!resolved) {
1301
+ cleanup();
1302
+ this.serverProcess = null;
1303
+ this.currentUrl = null;
1304
+ reject(err);
1305
+ }
1306
+ });
1307
+ const pollInterval = setInterval(async () => {
1308
+ if (resolved) return;
1309
+ if (await isServerRunning(url)) {
1310
+ cleanup();
1311
+ resolve();
1312
+ return;
1313
+ }
1314
+ if (Date.now() - startTime > timeout) {
1315
+ cleanup();
1316
+ reject(new Error(`Server at ${url} not ready after ${timeout}ms`));
1317
+ return;
1318
+ }
1319
+ if (Date.now() - lastOutputTime > idleTimeout) {
1320
+ cleanup();
1321
+ this.serverProcess?.kill("SIGTERM");
1322
+ this.serverProcess = null;
1323
+ this.currentUrl = null;
1324
+ const lastOutput = stderrOutput.slice(-500);
1325
+ reject(new Error(`Server stalled - no output for ${idleTimeout}ms. Last output:
1326
+ ${lastOutput}`));
1327
+ return;
1328
+ }
1329
+ }, 500);
1330
+ });
1331
+ console.log(`Server ready at ${url}`);
1332
+ return this.serverProcess;
1333
+ }
1334
+ /**
1335
+ * Stop the managed server and wait for it to fully terminate.
1336
+ * This prevents race conditions where a dying server still responds to health checks.
1337
+ */
1338
+ async stop() {
1339
+ if (!this.serverProcess || this.serverProcess.killed) {
1340
+ this.serverProcess = null;
1341
+ this.currentUrl = null;
1342
+ this.currentCwd = null;
1343
+ return;
1344
+ }
1345
+ this.stopping = true;
1346
+ console.log("Stopping server...");
1347
+ const process2 = this.serverProcess;
1348
+ const exitPromise = new Promise((resolve) => {
1349
+ const onExit = () => {
1350
+ process2.removeListener("close", onExit);
1351
+ process2.removeListener("exit", onExit);
1352
+ resolve();
1353
+ };
1354
+ process2.on("close", onExit);
1355
+ process2.on("exit", onExit);
1356
+ if (process2.killed || process2.exitCode !== null) {
1357
+ resolve();
1358
+ }
1359
+ });
1360
+ process2.kill("SIGTERM");
1361
+ const timeoutPromise = new Promise((resolve) => {
1362
+ setTimeout(() => {
1363
+ if (!process2.killed && process2.exitCode === null) {
1364
+ console.log("Server did not stop gracefully, sending SIGKILL...");
1365
+ process2.kill("SIGKILL");
1366
+ }
1367
+ resolve();
1368
+ }, 5e3);
1369
+ });
1370
+ await Promise.race([exitPromise, timeoutPromise]);
1371
+ await new Promise((r) => setTimeout(r, 200));
1372
+ this.serverProcess = null;
1373
+ this.currentUrl = null;
1374
+ this.currentCwd = null;
1375
+ this.stopping = false;
1376
+ }
1377
+ /**
1378
+ * Synchronous kill for signal handlers - doesn't wait for termination
1379
+ */
1380
+ kill() {
1381
+ if (this.serverProcess && !this.serverProcess.killed) {
1382
+ console.log("Stopping server...");
1383
+ this.serverProcess.kill("SIGTERM");
1384
+ }
1385
+ this.serverProcess = null;
1386
+ this.currentUrl = null;
1387
+ this.currentCwd = null;
1388
+ }
1389
+ };
1390
+ var webServerManager = WebServerManager.getInstance();
1061
1391
 
1062
1392
  // src/executors/web/playwrightExecutor.ts
1063
- var defaultScreenshotDir = path2__default.default.join(process.cwd(), "artifacts", "screenshots");
1393
+ var defaultScreenshotDir = path4__namespace.default.join(process.cwd(), "artifacts", "screenshots");
1064
1394
  var resolveUrl = (value, baseUrl) => {
1065
1395
  if (!baseUrl) return value;
1066
1396
  try {
@@ -1131,7 +1461,7 @@ var checkErrorIf = async (page, locator, errorIf) => {
1131
1461
  }
1132
1462
  };
1133
1463
  async function ensureScreenshotDir(dir) {
1134
- await fs__default.default.mkdir(dir, { recursive: true });
1464
+ await fs__namespace.default.mkdir(dir, { recursive: true });
1135
1465
  }
1136
1466
  var runNavigate = async (page, value, baseUrl, context) => {
1137
1467
  const interpolated = interpolateVariables(value, context.variables);
@@ -1192,7 +1522,7 @@ var runScreenshot = async (page, name, screenshotDir, stepIndex) => {
1192
1522
  await page.waitForLoadState("networkidle", { timeout: 5e3 }).catch(() => {
1193
1523
  });
1194
1524
  const filename = name ?? `step-${stepIndex + 1}.png`;
1195
- const filePath = path2__default.default.join(screenshotDir, filename);
1525
+ const filePath = path4__namespace.default.join(screenshotDir, filename);
1196
1526
  await page.screenshot({ path: filePath, fullPage: true });
1197
1527
  return filePath;
1198
1528
  };
@@ -1206,223 +1536,18 @@ var getBrowser = (browser) => {
1206
1536
  return playwright.chromium;
1207
1537
  }
1208
1538
  };
1209
- async function isServerRunning(url) {
1210
- try {
1211
- const response = await fetch(url, { method: "HEAD" });
1212
- return response.ok || response.status < 500;
1213
- } catch {
1214
- return false;
1215
- }
1216
- }
1217
- async function detectBuildDirectory(cwd) {
1218
- const commonDirs = [
1219
- ".next",
1220
- // Next.js
1221
- ".output",
1222
- // Nuxt 3
1223
- ".svelte-kit",
1224
- // SvelteKit
1225
- "dist",
1226
- // Vite, Astro, Rollup, generic
1227
- "build",
1228
- // CRA, Remix, generic
1229
- "out"
1230
- // Next.js static export
1231
- ];
1232
- for (const dir of commonDirs) {
1233
- const fullPath = path2__default.default.join(cwd, dir);
1234
- try {
1235
- const stat = await fs__default.default.stat(fullPath);
1236
- if (stat.isDirectory()) {
1237
- return dir;
1238
- }
1239
- } catch {
1240
- }
1241
- }
1242
- return null;
1243
- }
1244
- async function readPackageJson(cwd) {
1245
- try {
1246
- const packagePath = path2__default.default.join(cwd, "package.json");
1247
- const content = await fs__default.default.readFile(packagePath, "utf-8");
1248
- return JSON.parse(content);
1249
- } catch {
1250
- return null;
1251
- }
1252
- }
1253
- function detectFramework(pkg) {
1254
- if (!pkg) return null;
1255
- const deps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
1256
- if (deps["next"]) {
1257
- return { name: "next", buildCommand: "npx -y next start", devCommand: "next dev" };
1258
- }
1259
- if (deps["nuxt"]) {
1260
- return { name: "nuxt", buildCommand: "node .output/server/index.mjs", devCommand: "nuxi dev" };
1261
- }
1262
- if (deps["astro"]) {
1263
- return { name: "astro", buildCommand: "npx -y astro dev", devCommand: "astro dev" };
1264
- }
1265
- if (deps["@sveltejs/kit"]) {
1266
- return { name: "sveltekit", buildCommand: "npx -y vite preview", devCommand: "vite dev" };
1267
- }
1268
- if (deps["@remix-run/serve"] || deps["@remix-run/dev"]) {
1269
- return { name: "remix", buildCommand: "npx -y remix-serve build/server/index.js", devCommand: "remix vite:dev" };
1270
- }
1271
- if (deps["vite"]) {
1272
- return { name: "vite", buildCommand: "npx -y vite preview", devCommand: "vite dev" };
1273
- }
1274
- if (deps["react-scripts"]) {
1275
- return { name: "cra", buildCommand: "npx -y serve -s build", devCommand: "react-scripts start" };
1276
- }
1277
- return null;
1278
- }
1279
- async function detectPackageManager(cwd) {
1280
- const hasDenoLock = await fs__default.default.stat(path2__default.default.join(cwd, "deno.lock")).catch(() => null);
1281
- const hasBunLock = await fs__default.default.stat(path2__default.default.join(cwd, "bun.lockb")).catch(() => null);
1282
- const hasPnpmLock = await fs__default.default.stat(path2__default.default.join(cwd, "pnpm-lock.yaml")).catch(() => null);
1283
- const hasYarnLock = await fs__default.default.stat(path2__default.default.join(cwd, "yarn.lock")).catch(() => null);
1284
- if (hasDenoLock) return "deno";
1285
- if (hasBunLock) return "bun";
1286
- if (hasPnpmLock) return "pnpm";
1287
- if (hasYarnLock) return "yarn";
1288
- return "npm";
1289
- }
1290
- function getDevCommand(pm, script) {
1291
- switch (pm) {
1292
- case "deno":
1293
- return `deno task ${script}`;
1294
- case "bun":
1295
- return `bun run ${script}`;
1296
- case "pnpm":
1297
- return `pnpm ${script}`;
1298
- case "yarn":
1299
- return `yarn ${script}`;
1300
- case "npm":
1301
- return `npm run ${script}`;
1302
- }
1303
- }
1304
- async function detectServerCommand(cwd) {
1305
- const pkg = await readPackageJson(cwd);
1306
- const framework = detectFramework(pkg);
1307
- const pm = await detectPackageManager(cwd);
1308
- const buildDir = await detectBuildDirectory(cwd);
1309
- if (buildDir) {
1310
- if (framework) {
1311
- console.log(`Detected ${framework.name} project with build at ${buildDir}`);
1312
- return framework.buildCommand;
1313
- }
1314
- console.log(`Detected build directory at ${buildDir}, using static server`);
1315
- return `npx -y serve ${buildDir}`;
1316
- }
1317
- if (pkg?.scripts?.dev) {
1318
- if (framework) {
1319
- console.log(`Detected ${framework.name} project, running dev server`);
1320
- }
1321
- return getDevCommand(pm, "dev");
1322
- }
1323
- if (pkg?.scripts?.start) {
1324
- return getDevCommand(pm, "start");
1325
- }
1326
- throw new Error("Could not auto-detect server command. Please specify command explicitly.");
1327
- }
1328
- async function startWebServer(config) {
1329
- const { url, reuseExistingServer = true, timeout = 3e4, idleTimeout = 2e4 } = config;
1330
- const cwd = config.workdir ?? config.cwd ?? process.cwd();
1331
- if (reuseExistingServer && await isServerRunning(url)) {
1332
- console.log(`Server already running at ${url}`);
1333
- return null;
1334
- }
1335
- let command;
1336
- if (config.command) {
1337
- command = config.command;
1338
- } else if (config.static) {
1339
- const port = config.port ?? new URL(url).port ?? "3000";
1340
- command = `npx -y serve ${config.static} -l ${port}`;
1341
- } else if (config.auto) {
1342
- command = await detectServerCommand(cwd);
1343
- } else {
1344
- throw new Error("WebServerConfig requires command, auto: true, or static directory");
1345
- }
1346
- console.log(`Starting server: ${command}`);
1347
- const serverProcess = child_process.spawn(command, {
1348
- shell: true,
1349
- stdio: "pipe",
1350
- cwd,
1351
- detached: false
1352
- });
1353
- let stderrOutput = "";
1354
- let lastOutputTime = Date.now();
1355
- serverProcess.stdout?.on("data", (data) => {
1356
- lastOutputTime = Date.now();
1357
- process.stdout.write(`[server] ${data}`);
1358
- });
1359
- serverProcess.stderr?.on("data", (data) => {
1360
- lastOutputTime = Date.now();
1361
- stderrOutput += data.toString();
1362
- process.stderr.write(`[server] ${data}`);
1363
- });
1364
- await new Promise((resolve, reject) => {
1365
- let resolved = false;
1366
- const startTime = Date.now();
1367
- const cleanup = () => {
1368
- resolved = true;
1369
- clearInterval(pollInterval);
1370
- };
1371
- serverProcess.on("close", (code) => {
1372
- if (!resolved && code !== 0 && code !== null) {
1373
- cleanup();
1374
- reject(new Error(`Server exited with code ${code}
1375
- ${stderrOutput}`));
1376
- }
1377
- });
1378
- serverProcess.on("error", (err) => {
1379
- if (!resolved) {
1380
- cleanup();
1381
- reject(err);
1382
- }
1383
- });
1384
- const pollInterval = setInterval(async () => {
1385
- if (resolved) return;
1386
- if (await isServerRunning(url)) {
1387
- cleanup();
1388
- resolve();
1389
- return;
1390
- }
1391
- if (Date.now() - startTime > timeout) {
1392
- cleanup();
1393
- reject(new Error(`Server at ${url} not ready after ${timeout}ms`));
1394
- return;
1395
- }
1396
- if (Date.now() - lastOutputTime > idleTimeout) {
1397
- cleanup();
1398
- serverProcess.kill("SIGTERM");
1399
- reject(new Error(`Server stalled - no output for ${idleTimeout}ms. Last output:
1400
- ${stderrOutput.slice(-500)}`));
1401
- return;
1402
- }
1403
- }, 500);
1404
- });
1405
- console.log(`Server ready at ${url}`);
1406
- return serverProcess;
1407
- }
1408
- function killServer(serverProcess) {
1409
- if (serverProcess && !serverProcess.killed) {
1410
- console.log("Stopping server...");
1411
- serverProcess.kill("SIGTERM");
1412
- }
1413
- }
1414
1539
  async function handleInteractiveError(page, action, error, screenshotDir, stepIndex, aiConfig) {
1415
1540
  console.error(`
1416
1541
  \u274C Action failed: ${action.type}`);
1417
1542
  console.error(` Error: ${error.message}
1418
1543
  `);
1419
1544
  await ensureScreenshotDir(screenshotDir);
1420
- const screenshotPath = path2__default.default.join(screenshotDir, `error-step-${stepIndex + 1}.png`);
1545
+ const screenshotPath = path4__namespace.default.join(screenshotDir, `error-step-${stepIndex + 1}.png`);
1421
1546
  await page.screenshot({ path: screenshotPath, fullPage: true });
1422
1547
  const pageContent = await page.content();
1423
1548
  if (aiConfig) {
1424
1549
  console.log("\u{1F916} Analyzing error with AI...\n");
1425
- const screenshot = await fs__default.default.readFile(screenshotPath);
1550
+ const screenshot = await fs__namespace.default.readFile(screenshotPath);
1426
1551
  const suggestion = await getAISuggestion(error.message, action, pageContent, screenshot, aiConfig);
1427
1552
  if (suggestion.hasSuggestion && suggestion.suggestedSelector) {
1428
1553
  console.log("\u{1F916} AI Suggestion:");
@@ -1830,8 +1955,8 @@ async function executeActionWithRetry(page, action, index, options) {
1830
1955
  }
1831
1956
  } else {
1832
1957
  const { loadWorkflowDefinition, loadTestDefinition: loadTestDefinition2 } = await import('./loader-QG5BFIY6.cjs');
1833
- const workflowPath = path2__default.default.resolve(process.cwd(), branchToExecute.workflow);
1834
- const workflowDir = path2__default.default.dirname(workflowPath);
1958
+ const workflowPath = path4__namespace.default.resolve(process.cwd(), branchToExecute.workflow);
1959
+ const workflowDir = path4__namespace.default.dirname(workflowPath);
1835
1960
  if (debugMode) {
1836
1961
  console.log(`[DEBUG] Executing workflow: ${workflowPath}`);
1837
1962
  }
@@ -1843,7 +1968,7 @@ async function executeActionWithRetry(page, action, index, options) {
1843
1968
  }
1844
1969
  }
1845
1970
  for (const testRef of workflow.tests) {
1846
- const testFilePath = path2__default.default.resolve(workflowDir, testRef.file);
1971
+ const testFilePath = path4__namespace.default.resolve(workflowDir, testRef.file);
1847
1972
  if (debugMode) {
1848
1973
  console.log(`[DEBUG] Loading test from workflow: ${testFilePath}`);
1849
1974
  }
@@ -1942,7 +2067,6 @@ var runWebTest = async (test, options = {}) => {
1942
2067
  } : void 0
1943
2068
  });
1944
2069
  process.env.INTELLITESTER_TRACK_FILE = fileTracking.trackFile;
1945
- let serverProcess = null;
1946
2070
  if (options.webServer) {
1947
2071
  const requiresTrackingEnv = Boolean(
1948
2072
  test.config?.appwrite?.cleanup || test.config?.appwrite?.cleanupOnFailure
@@ -1951,11 +2075,11 @@ var runWebTest = async (test, options = {}) => {
1951
2075
  if (requiresTrackingEnv && options.webServer.reuseExistingServer !== false) {
1952
2076
  console.log("[Intellitester] Appwrite cleanup enabled; restarting server to inject tracking env.");
1953
2077
  }
1954
- serverProcess = await startWebServer(webServerConfig);
2078
+ await webServerManager.start(webServerConfig);
1955
2079
  }
1956
2080
  const cleanup = () => {
1957
2081
  trackingServer.stop();
1958
- killServer(serverProcess);
2082
+ webServerManager.kill();
1959
2083
  void fileTracking.stop();
1960
2084
  delete process.env.INTELLITESTER_TRACK_FILE;
1961
2085
  process.exit(1);
@@ -2265,7 +2389,7 @@ var runWebTest = async (test, options = {}) => {
2265
2389
  await browserContext.close();
2266
2390
  await browser.close();
2267
2391
  trackingServer.stop();
2268
- killServer(serverProcess);
2392
+ await webServerManager.stop();
2269
2393
  }
2270
2394
  return {
2271
2395
  status: results.every((step) => step.status === "passed") ? "passed" : "failed",
@@ -2273,7 +2397,7 @@ var runWebTest = async (test, options = {}) => {
2273
2397
  variables: executionContext.variables
2274
2398
  };
2275
2399
  };
2276
- var defaultScreenshotDir2 = path2__default.default.join(process.cwd(), "artifacts", "screenshots");
2400
+ var defaultScreenshotDir2 = path4__namespace.default.join(process.cwd(), "artifacts", "screenshots");
2277
2401
  var getBrowser2 = (browser) => {
2278
2402
  switch (browser) {
2279
2403
  case "firefox":
@@ -2285,14 +2409,14 @@ var getBrowser2 = (browser) => {
2285
2409
  }
2286
2410
  };
2287
2411
  function interpolateWorkflowVariables(value, currentVariables, testResults) {
2288
- return value.replace(/\{\{([^}]+)\}\}/g, (match, path4) => {
2289
- if (path4.includes(".") && !path4.includes(":")) {
2290
- const [testId, _varName] = path4.split(".", 2);
2412
+ return value.replace(/\{\{([^}]+)\}\}/g, (match, path5) => {
2413
+ if (path5.includes(".") && !path5.includes(":")) {
2414
+ const [testId, _varName] = path5.split(".", 2);
2291
2415
  testResults.find((t) => t.id === testId);
2292
- console.warn(`Cross-test variable interpolation {{${path4}}} not yet fully implemented`);
2416
+ console.warn(`Cross-test variable interpolation {{${path5}}} not yet fully implemented`);
2293
2417
  return match;
2294
2418
  }
2295
- const result = interpolateVariables(`{{${path4}}}`, currentVariables);
2419
+ const result = interpolateVariables(`{{${path5}}}`, currentVariables);
2296
2420
  return result;
2297
2421
  });
2298
2422
  }
@@ -2459,7 +2583,7 @@ async function runTestInWorkflow(test, page, context, options, _workflowDir, wor
2459
2583
  await page.waitForTimeout(waitBefore);
2460
2584
  }
2461
2585
  const filename = ssAction.name ?? `step-${index + 1}.png`;
2462
- const filePath = path2__default.default.join(screenshotDir, filename);
2586
+ const filePath = path4__namespace.default.join(screenshotDir, filename);
2463
2587
  await page.screenshot({ path: filePath, fullPage: true });
2464
2588
  results.push({ action, status: "passed", screenshotPath: filePath });
2465
2589
  const trackedPayload2 = buildTrackPayload(action, index, { screenshotPath: filePath });
@@ -2633,7 +2757,7 @@ async function runTestInWorkflow(test, page, context, options, _workflowDir, wor
2633
2757
  switch (nestedAction.type) {
2634
2758
  case "screenshot": {
2635
2759
  const filename = nestedAction.name ?? `conditional-step.png`;
2636
- const filePath = path2__default.default.join(screenshotDir, filename);
2760
+ const filePath = path4__namespace.default.join(screenshotDir, filename);
2637
2761
  await page.screenshot({ path: filePath, fullPage: true });
2638
2762
  results.push({ action: nestedAction, status: "passed", screenshotPath: filePath });
2639
2763
  const trackedPayload2 = buildTrackPayload(nestedAction, index, { screenshotPath: filePath });
@@ -2723,7 +2847,7 @@ async function runTestInWorkflow(test, page, context, options, _workflowDir, wor
2723
2847
  await page.waitForTimeout(nestedWaitBefore);
2724
2848
  }
2725
2849
  const filename = nestedSsAction.name ?? `waitForBranch-step.png`;
2726
- const filePath = path2__default.default.join(screenshotDir, filename);
2850
+ const filePath = path4__namespace.default.join(screenshotDir, filename);
2727
2851
  await page.screenshot({ path: filePath, fullPage: true });
2728
2852
  results.push({ action: nestedAction, status: "passed", screenshotPath: filePath });
2729
2853
  const trackedPayload2 = buildTrackPayload(nestedAction, index, { screenshotPath: filePath });
@@ -2781,7 +2905,7 @@ async function runTestInWorkflow(test, page, context, options, _workflowDir, wor
2781
2905
  results.push({ action: nestedAction, status: "passed" });
2782
2906
  }
2783
2907
  } else if (typeof branch === "object" && "workflow" in branch) {
2784
- const workflowPath = path2__default.default.resolve(_workflowDir, branch.workflow);
2908
+ const workflowPath = path4__namespace.default.resolve(_workflowDir, branch.workflow);
2785
2909
  if (debugMode) {
2786
2910
  console.log(` [DEBUG] waitForBranch: loading workflow from ${workflowPath}`);
2787
2911
  }
@@ -2794,7 +2918,7 @@ async function runTestInWorkflow(test, page, context, options, _workflowDir, wor
2794
2918
  }
2795
2919
  }
2796
2920
  for (const testRef of nestedWorkflow.tests) {
2797
- const testFilePath = path2__default.default.resolve(path2__default.default.dirname(workflowPath), testRef.file);
2921
+ const testFilePath = path4__namespace.default.resolve(path4__namespace.default.dirname(workflowPath), testRef.file);
2798
2922
  const nestedTest = await chunk2ZBKD7WW_cjs.loadTestDefinition(testFilePath);
2799
2923
  if (nestedTest.variables) {
2800
2924
  for (const [key, value] of Object.entries(nestedTest.variables)) {
@@ -2807,7 +2931,7 @@ async function runTestInWorkflow(test, page, context, options, _workflowDir, wor
2807
2931
  page,
2808
2932
  context,
2809
2933
  options,
2810
- path2__default.default.dirname(workflowPath),
2934
+ path4__namespace.default.dirname(workflowPath),
2811
2935
  nestedWorkflow.config?.web?.baseUrl ?? workflowBaseUrl
2812
2936
  );
2813
2937
  results.push(...nestedResult.steps);
@@ -3022,7 +3146,7 @@ function inferCleanupConfig(config) {
3022
3146
  }
3023
3147
  async function runWorkflowWithContext(workflow, workflowFilePath, options) {
3024
3148
  const { page, executionContext, skipCleanup = false, sessionId: providedSessionId, testStartTime: providedTestStartTime } = options;
3025
- const workflowDir = path2__default.default.dirname(workflowFilePath);
3149
+ const workflowDir = path4__namespace.default.dirname(workflowFilePath);
3026
3150
  const sessionId = providedSessionId ?? crypto4__default.default.randomUUID();
3027
3151
  const testStartTime = providedTestStartTime ?? (/* @__PURE__ */ new Date()).toISOString();
3028
3152
  console.log(`
@@ -3050,7 +3174,7 @@ Starting workflow: ${workflow.name}`);
3050
3174
  const testResults = [];
3051
3175
  let workflowFailed = false;
3052
3176
  for (const [index, testRef] of workflow.tests.entries()) {
3053
- const testFilePath = path2__default.default.resolve(workflowDir, testRef.file);
3177
+ const testFilePath = path4__namespace.default.resolve(workflowDir, testRef.file);
3054
3178
  console.log(`
3055
3179
  [${index + 1}/${workflow.tests.length}] Running: ${testRef.file}`);
3056
3180
  if (testRef.id) {
@@ -3211,7 +3335,7 @@ ${"=".repeat(60)}`);
3211
3335
  };
3212
3336
  }
3213
3337
  async function runWorkflow(workflow, workflowFilePath, options = {}) {
3214
- const workflowDir = path2__default.default.dirname(workflowFilePath);
3338
+ const workflowDir = path4__namespace.default.dirname(workflowFilePath);
3215
3339
  const sessionId = options.sessionId ?? crypto4__default.default.randomUUID();
3216
3340
  const testStartTime = (/* @__PURE__ */ new Date()).toISOString();
3217
3341
  const cleanupConfig = inferCleanupConfig(workflow.config);
@@ -3238,7 +3362,6 @@ async function runWorkflow(workflow, workflowFilePath, options = {}) {
3238
3362
  } : void 0
3239
3363
  });
3240
3364
  process.env.INTELLITESTER_TRACK_FILE = fileTracking.trackFile;
3241
- let serverProcess = null;
3242
3365
  const webServerConfig = workflow.config?.webServer ?? options.webServer;
3243
3366
  if (webServerConfig) {
3244
3367
  try {
@@ -3250,9 +3373,9 @@ async function runWorkflow(workflow, workflowFilePath, options = {}) {
3250
3373
  if (requiresTrackingEnv && webServerConfig.reuseExistingServer !== false) {
3251
3374
  console.log("[Intellitester] Appwrite cleanup enabled; restarting server to inject tracking env.");
3252
3375
  }
3253
- serverProcess = await startWebServer({
3376
+ await webServerManager.start({
3254
3377
  ...effectiveWebServerConfig,
3255
- workdir: path2__default.default.resolve(serverCwd, effectiveWebServerConfig.workdir ?? effectiveWebServerConfig.cwd ?? ".")
3378
+ workdir: path4__namespace.default.resolve(serverCwd, effectiveWebServerConfig.workdir ?? effectiveWebServerConfig.cwd ?? ".")
3256
3379
  });
3257
3380
  } catch (error) {
3258
3381
  console.error("Failed to start web server:", error);
@@ -3262,7 +3385,7 @@ async function runWorkflow(workflow, workflowFilePath, options = {}) {
3262
3385
  }
3263
3386
  const signalCleanup = async () => {
3264
3387
  console.log("\n\nInterrupted - cleaning up...");
3265
- killServer(serverProcess);
3388
+ webServerManager.kill();
3266
3389
  if (trackingServer) await trackingServer.stop();
3267
3390
  await fileTracking.stop();
3268
3391
  delete process.env.INTELLITESTER_TRACK_FILE;
@@ -3402,7 +3525,7 @@ Collected ${serverResources.length} server-tracked resources`);
3402
3525
  process.off("SIGTERM", signalCleanup);
3403
3526
  await browserContext.close();
3404
3527
  await browser.close();
3405
- killServer(serverProcess);
3528
+ await webServerManager.stop();
3406
3529
  if (trackingServer) {
3407
3530
  await trackingServer.stop();
3408
3531
  }
@@ -3423,13 +3546,12 @@ exports.generateRandomUsername = generateRandomUsername;
3423
3546
  exports.getBrowserLaunchOptions = getBrowserLaunchOptions;
3424
3547
  exports.initFileTracking = initFileTracking;
3425
3548
  exports.interpolateVariables = interpolateVariables;
3426
- exports.killServer = killServer;
3427
3549
  exports.mergeFileTrackedResources = mergeFileTrackedResources;
3428
3550
  exports.runWebTest = runWebTest;
3429
3551
  exports.runWorkflow = runWorkflow;
3430
3552
  exports.runWorkflowWithContext = runWorkflowWithContext;
3431
3553
  exports.setupAppwriteTracking = setupAppwriteTracking;
3432
3554
  exports.startTrackingServer = startTrackingServer;
3433
- exports.startWebServer = startWebServer;
3434
- //# sourceMappingURL=chunk-C46Q6B6I.cjs.map
3435
- //# sourceMappingURL=chunk-C46Q6B6I.cjs.map
3555
+ exports.webServerManager = webServerManager;
3556
+ //# sourceMappingURL=chunk-MZ7OCAGA.cjs.map
3557
+ //# sourceMappingURL=chunk-MZ7OCAGA.cjs.map