intellitester 0.2.43 → 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,186 +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 waitForServer(url, timeout) {
1218
- const start = Date.now();
1219
- while (Date.now() - start < timeout) {
1220
- if (await isServerRunning(url)) return;
1221
- await new Promise((r) => setTimeout(r, 500));
1222
- }
1223
- throw new Error(`Server at ${url} not ready after ${timeout}ms`);
1224
- }
1225
- async function detectBuildDirectory(cwd) {
1226
- const commonDirs = [
1227
- ".next",
1228
- // Next.js
1229
- ".output",
1230
- // Nuxt 3
1231
- ".svelte-kit",
1232
- // SvelteKit
1233
- "dist",
1234
- // Vite, Astro, Rollup, generic
1235
- "build",
1236
- // CRA, Remix, generic
1237
- "out"
1238
- // Next.js static export
1239
- ];
1240
- for (const dir of commonDirs) {
1241
- const fullPath = path2__default.default.join(cwd, dir);
1242
- try {
1243
- const stat = await fs__default.default.stat(fullPath);
1244
- if (stat.isDirectory()) {
1245
- return dir;
1246
- }
1247
- } catch {
1248
- }
1249
- }
1250
- return null;
1251
- }
1252
- async function readPackageJson(cwd) {
1253
- try {
1254
- const packagePath = path2__default.default.join(cwd, "package.json");
1255
- const content = await fs__default.default.readFile(packagePath, "utf-8");
1256
- return JSON.parse(content);
1257
- } catch {
1258
- return null;
1259
- }
1260
- }
1261
- function detectFramework(pkg) {
1262
- if (!pkg) return null;
1263
- const deps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
1264
- if (deps["next"]) {
1265
- return { name: "next", buildCommand: "npx -y next start", devCommand: "next dev" };
1266
- }
1267
- if (deps["nuxt"]) {
1268
- return { name: "nuxt", buildCommand: "node .output/server/index.mjs", devCommand: "nuxi dev" };
1269
- }
1270
- if (deps["astro"]) {
1271
- return { name: "astro", buildCommand: "npx -y astro dev", devCommand: "astro dev" };
1272
- }
1273
- if (deps["@sveltejs/kit"]) {
1274
- return { name: "sveltekit", buildCommand: "npx -y vite preview", devCommand: "vite dev" };
1275
- }
1276
- if (deps["@remix-run/serve"] || deps["@remix-run/dev"]) {
1277
- return { name: "remix", buildCommand: "npx -y remix-serve build/server/index.js", devCommand: "remix vite:dev" };
1278
- }
1279
- if (deps["vite"]) {
1280
- return { name: "vite", buildCommand: "npx -y vite preview", devCommand: "vite dev" };
1281
- }
1282
- if (deps["react-scripts"]) {
1283
- return { name: "cra", buildCommand: "npx -y serve -s build", devCommand: "react-scripts start" };
1284
- }
1285
- return null;
1286
- }
1287
- async function detectPackageManager(cwd) {
1288
- const hasDenoLock = await fs__default.default.stat(path2__default.default.join(cwd, "deno.lock")).catch(() => null);
1289
- const hasBunLock = await fs__default.default.stat(path2__default.default.join(cwd, "bun.lockb")).catch(() => null);
1290
- const hasPnpmLock = await fs__default.default.stat(path2__default.default.join(cwd, "pnpm-lock.yaml")).catch(() => null);
1291
- const hasYarnLock = await fs__default.default.stat(path2__default.default.join(cwd, "yarn.lock")).catch(() => null);
1292
- if (hasDenoLock) return "deno";
1293
- if (hasBunLock) return "bun";
1294
- if (hasPnpmLock) return "pnpm";
1295
- if (hasYarnLock) return "yarn";
1296
- return "npm";
1297
- }
1298
- function getDevCommand(pm, script) {
1299
- switch (pm) {
1300
- case "deno":
1301
- return `deno task ${script}`;
1302
- case "bun":
1303
- return `bun run ${script}`;
1304
- case "pnpm":
1305
- return `pnpm ${script}`;
1306
- case "yarn":
1307
- return `yarn ${script}`;
1308
- case "npm":
1309
- return `npm run ${script}`;
1310
- }
1311
- }
1312
- async function detectServerCommand(cwd) {
1313
- const pkg = await readPackageJson(cwd);
1314
- const framework = detectFramework(pkg);
1315
- const pm = await detectPackageManager(cwd);
1316
- const buildDir = await detectBuildDirectory(cwd);
1317
- if (buildDir) {
1318
- if (framework) {
1319
- console.log(`Detected ${framework.name} project with build at ${buildDir}`);
1320
- return framework.buildCommand;
1321
- }
1322
- console.log(`Detected build directory at ${buildDir}, using static server`);
1323
- return `npx -y serve ${buildDir}`;
1324
- }
1325
- if (pkg?.scripts?.dev) {
1326
- if (framework) {
1327
- console.log(`Detected ${framework.name} project, running dev server`);
1328
- }
1329
- return getDevCommand(pm, "dev");
1330
- }
1331
- if (pkg?.scripts?.start) {
1332
- return getDevCommand(pm, "start");
1333
- }
1334
- throw new Error("Could not auto-detect server command. Please specify command explicitly.");
1335
- }
1336
- async function startWebServer(config) {
1337
- const { url, reuseExistingServer = true, timeout = 3e4 } = config;
1338
- const cwd = config.workdir ?? config.cwd ?? process.cwd();
1339
- if (reuseExistingServer && await isServerRunning(url)) {
1340
- console.log(`Server already running at ${url}`);
1341
- return null;
1342
- }
1343
- let command;
1344
- if (config.command) {
1345
- command = config.command;
1346
- } else if (config.static) {
1347
- const port = config.port ?? new URL(url).port ?? "3000";
1348
- command = `npx -y serve ${config.static} -l ${port}`;
1349
- } else if (config.auto) {
1350
- command = await detectServerCommand(cwd);
1351
- } else {
1352
- throw new Error("WebServerConfig requires command, auto: true, or static directory");
1353
- }
1354
- console.log(`Starting server: ${command}`);
1355
- const serverProcess = child_process.spawn(command, {
1356
- shell: true,
1357
- stdio: "pipe",
1358
- cwd,
1359
- detached: false
1360
- });
1361
- serverProcess.stdout?.on("data", (data) => {
1362
- process.stdout.write(`[server] ${data}`);
1363
- });
1364
- serverProcess.stderr?.on("data", (data) => {
1365
- process.stderr.write(`[server] ${data}`);
1366
- });
1367
- await waitForServer(url, timeout);
1368
- console.log(`Server ready at ${url}`);
1369
- return serverProcess;
1370
- }
1371
- function killServer(serverProcess) {
1372
- if (serverProcess && !serverProcess.killed) {
1373
- console.log("Stopping server...");
1374
- serverProcess.kill("SIGTERM");
1375
- }
1376
- }
1377
1539
  async function handleInteractiveError(page, action, error, screenshotDir, stepIndex, aiConfig) {
1378
1540
  console.error(`
1379
1541
  \u274C Action failed: ${action.type}`);
1380
1542
  console.error(` Error: ${error.message}
1381
1543
  `);
1382
1544
  await ensureScreenshotDir(screenshotDir);
1383
- 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`);
1384
1546
  await page.screenshot({ path: screenshotPath, fullPage: true });
1385
1547
  const pageContent = await page.content();
1386
1548
  if (aiConfig) {
1387
1549
  console.log("\u{1F916} Analyzing error with AI...\n");
1388
- const screenshot = await fs__default.default.readFile(screenshotPath);
1550
+ const screenshot = await fs__namespace.default.readFile(screenshotPath);
1389
1551
  const suggestion = await getAISuggestion(error.message, action, pageContent, screenshot, aiConfig);
1390
1552
  if (suggestion.hasSuggestion && suggestion.suggestedSelector) {
1391
1553
  console.log("\u{1F916} AI Suggestion:");
@@ -1793,8 +1955,8 @@ async function executeActionWithRetry(page, action, index, options) {
1793
1955
  }
1794
1956
  } else {
1795
1957
  const { loadWorkflowDefinition, loadTestDefinition: loadTestDefinition2 } = await import('./loader-QG5BFIY6.cjs');
1796
- const workflowPath = path2__default.default.resolve(process.cwd(), branchToExecute.workflow);
1797
- 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);
1798
1960
  if (debugMode) {
1799
1961
  console.log(`[DEBUG] Executing workflow: ${workflowPath}`);
1800
1962
  }
@@ -1806,7 +1968,7 @@ async function executeActionWithRetry(page, action, index, options) {
1806
1968
  }
1807
1969
  }
1808
1970
  for (const testRef of workflow.tests) {
1809
- const testFilePath = path2__default.default.resolve(workflowDir, testRef.file);
1971
+ const testFilePath = path4__namespace.default.resolve(workflowDir, testRef.file);
1810
1972
  if (debugMode) {
1811
1973
  console.log(`[DEBUG] Loading test from workflow: ${testFilePath}`);
1812
1974
  }
@@ -1905,7 +2067,6 @@ var runWebTest = async (test, options = {}) => {
1905
2067
  } : void 0
1906
2068
  });
1907
2069
  process.env.INTELLITESTER_TRACK_FILE = fileTracking.trackFile;
1908
- let serverProcess = null;
1909
2070
  if (options.webServer) {
1910
2071
  const requiresTrackingEnv = Boolean(
1911
2072
  test.config?.appwrite?.cleanup || test.config?.appwrite?.cleanupOnFailure
@@ -1914,11 +2075,11 @@ var runWebTest = async (test, options = {}) => {
1914
2075
  if (requiresTrackingEnv && options.webServer.reuseExistingServer !== false) {
1915
2076
  console.log("[Intellitester] Appwrite cleanup enabled; restarting server to inject tracking env.");
1916
2077
  }
1917
- serverProcess = await startWebServer(webServerConfig);
2078
+ await webServerManager.start(webServerConfig);
1918
2079
  }
1919
2080
  const cleanup = () => {
1920
2081
  trackingServer.stop();
1921
- killServer(serverProcess);
2082
+ webServerManager.kill();
1922
2083
  void fileTracking.stop();
1923
2084
  delete process.env.INTELLITESTER_TRACK_FILE;
1924
2085
  process.exit(1);
@@ -2228,7 +2389,7 @@ var runWebTest = async (test, options = {}) => {
2228
2389
  await browserContext.close();
2229
2390
  await browser.close();
2230
2391
  trackingServer.stop();
2231
- killServer(serverProcess);
2392
+ await webServerManager.stop();
2232
2393
  }
2233
2394
  return {
2234
2395
  status: results.every((step) => step.status === "passed") ? "passed" : "failed",
@@ -2236,7 +2397,7 @@ var runWebTest = async (test, options = {}) => {
2236
2397
  variables: executionContext.variables
2237
2398
  };
2238
2399
  };
2239
- var defaultScreenshotDir2 = path2__default.default.join(process.cwd(), "artifacts", "screenshots");
2400
+ var defaultScreenshotDir2 = path4__namespace.default.join(process.cwd(), "artifacts", "screenshots");
2240
2401
  var getBrowser2 = (browser) => {
2241
2402
  switch (browser) {
2242
2403
  case "firefox":
@@ -2248,14 +2409,14 @@ var getBrowser2 = (browser) => {
2248
2409
  }
2249
2410
  };
2250
2411
  function interpolateWorkflowVariables(value, currentVariables, testResults) {
2251
- return value.replace(/\{\{([^}]+)\}\}/g, (match, path4) => {
2252
- if (path4.includes(".") && !path4.includes(":")) {
2253
- 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);
2254
2415
  testResults.find((t) => t.id === testId);
2255
- console.warn(`Cross-test variable interpolation {{${path4}}} not yet fully implemented`);
2416
+ console.warn(`Cross-test variable interpolation {{${path5}}} not yet fully implemented`);
2256
2417
  return match;
2257
2418
  }
2258
- const result = interpolateVariables(`{{${path4}}}`, currentVariables);
2419
+ const result = interpolateVariables(`{{${path5}}}`, currentVariables);
2259
2420
  return result;
2260
2421
  });
2261
2422
  }
@@ -2422,7 +2583,7 @@ async function runTestInWorkflow(test, page, context, options, _workflowDir, wor
2422
2583
  await page.waitForTimeout(waitBefore);
2423
2584
  }
2424
2585
  const filename = ssAction.name ?? `step-${index + 1}.png`;
2425
- const filePath = path2__default.default.join(screenshotDir, filename);
2586
+ const filePath = path4__namespace.default.join(screenshotDir, filename);
2426
2587
  await page.screenshot({ path: filePath, fullPage: true });
2427
2588
  results.push({ action, status: "passed", screenshotPath: filePath });
2428
2589
  const trackedPayload2 = buildTrackPayload(action, index, { screenshotPath: filePath });
@@ -2596,7 +2757,7 @@ async function runTestInWorkflow(test, page, context, options, _workflowDir, wor
2596
2757
  switch (nestedAction.type) {
2597
2758
  case "screenshot": {
2598
2759
  const filename = nestedAction.name ?? `conditional-step.png`;
2599
- const filePath = path2__default.default.join(screenshotDir, filename);
2760
+ const filePath = path4__namespace.default.join(screenshotDir, filename);
2600
2761
  await page.screenshot({ path: filePath, fullPage: true });
2601
2762
  results.push({ action: nestedAction, status: "passed", screenshotPath: filePath });
2602
2763
  const trackedPayload2 = buildTrackPayload(nestedAction, index, { screenshotPath: filePath });
@@ -2686,7 +2847,7 @@ async function runTestInWorkflow(test, page, context, options, _workflowDir, wor
2686
2847
  await page.waitForTimeout(nestedWaitBefore);
2687
2848
  }
2688
2849
  const filename = nestedSsAction.name ?? `waitForBranch-step.png`;
2689
- const filePath = path2__default.default.join(screenshotDir, filename);
2850
+ const filePath = path4__namespace.default.join(screenshotDir, filename);
2690
2851
  await page.screenshot({ path: filePath, fullPage: true });
2691
2852
  results.push({ action: nestedAction, status: "passed", screenshotPath: filePath });
2692
2853
  const trackedPayload2 = buildTrackPayload(nestedAction, index, { screenshotPath: filePath });
@@ -2744,7 +2905,7 @@ async function runTestInWorkflow(test, page, context, options, _workflowDir, wor
2744
2905
  results.push({ action: nestedAction, status: "passed" });
2745
2906
  }
2746
2907
  } else if (typeof branch === "object" && "workflow" in branch) {
2747
- const workflowPath = path2__default.default.resolve(_workflowDir, branch.workflow);
2908
+ const workflowPath = path4__namespace.default.resolve(_workflowDir, branch.workflow);
2748
2909
  if (debugMode) {
2749
2910
  console.log(` [DEBUG] waitForBranch: loading workflow from ${workflowPath}`);
2750
2911
  }
@@ -2757,7 +2918,7 @@ async function runTestInWorkflow(test, page, context, options, _workflowDir, wor
2757
2918
  }
2758
2919
  }
2759
2920
  for (const testRef of nestedWorkflow.tests) {
2760
- 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);
2761
2922
  const nestedTest = await chunk2ZBKD7WW_cjs.loadTestDefinition(testFilePath);
2762
2923
  if (nestedTest.variables) {
2763
2924
  for (const [key, value] of Object.entries(nestedTest.variables)) {
@@ -2770,7 +2931,7 @@ async function runTestInWorkflow(test, page, context, options, _workflowDir, wor
2770
2931
  page,
2771
2932
  context,
2772
2933
  options,
2773
- path2__default.default.dirname(workflowPath),
2934
+ path4__namespace.default.dirname(workflowPath),
2774
2935
  nestedWorkflow.config?.web?.baseUrl ?? workflowBaseUrl
2775
2936
  );
2776
2937
  results.push(...nestedResult.steps);
@@ -2985,7 +3146,7 @@ function inferCleanupConfig(config) {
2985
3146
  }
2986
3147
  async function runWorkflowWithContext(workflow, workflowFilePath, options) {
2987
3148
  const { page, executionContext, skipCleanup = false, sessionId: providedSessionId, testStartTime: providedTestStartTime } = options;
2988
- const workflowDir = path2__default.default.dirname(workflowFilePath);
3149
+ const workflowDir = path4__namespace.default.dirname(workflowFilePath);
2989
3150
  const sessionId = providedSessionId ?? crypto4__default.default.randomUUID();
2990
3151
  const testStartTime = providedTestStartTime ?? (/* @__PURE__ */ new Date()).toISOString();
2991
3152
  console.log(`
@@ -3013,7 +3174,7 @@ Starting workflow: ${workflow.name}`);
3013
3174
  const testResults = [];
3014
3175
  let workflowFailed = false;
3015
3176
  for (const [index, testRef] of workflow.tests.entries()) {
3016
- const testFilePath = path2__default.default.resolve(workflowDir, testRef.file);
3177
+ const testFilePath = path4__namespace.default.resolve(workflowDir, testRef.file);
3017
3178
  console.log(`
3018
3179
  [${index + 1}/${workflow.tests.length}] Running: ${testRef.file}`);
3019
3180
  if (testRef.id) {
@@ -3174,7 +3335,7 @@ ${"=".repeat(60)}`);
3174
3335
  };
3175
3336
  }
3176
3337
  async function runWorkflow(workflow, workflowFilePath, options = {}) {
3177
- const workflowDir = path2__default.default.dirname(workflowFilePath);
3338
+ const workflowDir = path4__namespace.default.dirname(workflowFilePath);
3178
3339
  const sessionId = options.sessionId ?? crypto4__default.default.randomUUID();
3179
3340
  const testStartTime = (/* @__PURE__ */ new Date()).toISOString();
3180
3341
  const cleanupConfig = inferCleanupConfig(workflow.config);
@@ -3201,7 +3362,6 @@ async function runWorkflow(workflow, workflowFilePath, options = {}) {
3201
3362
  } : void 0
3202
3363
  });
3203
3364
  process.env.INTELLITESTER_TRACK_FILE = fileTracking.trackFile;
3204
- let serverProcess = null;
3205
3365
  const webServerConfig = workflow.config?.webServer ?? options.webServer;
3206
3366
  if (webServerConfig) {
3207
3367
  try {
@@ -3213,9 +3373,9 @@ async function runWorkflow(workflow, workflowFilePath, options = {}) {
3213
3373
  if (requiresTrackingEnv && webServerConfig.reuseExistingServer !== false) {
3214
3374
  console.log("[Intellitester] Appwrite cleanup enabled; restarting server to inject tracking env.");
3215
3375
  }
3216
- serverProcess = await startWebServer({
3376
+ await webServerManager.start({
3217
3377
  ...effectiveWebServerConfig,
3218
- workdir: path2__default.default.resolve(serverCwd, effectiveWebServerConfig.workdir ?? effectiveWebServerConfig.cwd ?? ".")
3378
+ workdir: path4__namespace.default.resolve(serverCwd, effectiveWebServerConfig.workdir ?? effectiveWebServerConfig.cwd ?? ".")
3219
3379
  });
3220
3380
  } catch (error) {
3221
3381
  console.error("Failed to start web server:", error);
@@ -3225,7 +3385,7 @@ async function runWorkflow(workflow, workflowFilePath, options = {}) {
3225
3385
  }
3226
3386
  const signalCleanup = async () => {
3227
3387
  console.log("\n\nInterrupted - cleaning up...");
3228
- killServer(serverProcess);
3388
+ webServerManager.kill();
3229
3389
  if (trackingServer) await trackingServer.stop();
3230
3390
  await fileTracking.stop();
3231
3391
  delete process.env.INTELLITESTER_TRACK_FILE;
@@ -3365,7 +3525,7 @@ Collected ${serverResources.length} server-tracked resources`);
3365
3525
  process.off("SIGTERM", signalCleanup);
3366
3526
  await browserContext.close();
3367
3527
  await browser.close();
3368
- killServer(serverProcess);
3528
+ await webServerManager.stop();
3369
3529
  if (trackingServer) {
3370
3530
  await trackingServer.stop();
3371
3531
  }
@@ -3386,13 +3546,12 @@ exports.generateRandomUsername = generateRandomUsername;
3386
3546
  exports.getBrowserLaunchOptions = getBrowserLaunchOptions;
3387
3547
  exports.initFileTracking = initFileTracking;
3388
3548
  exports.interpolateVariables = interpolateVariables;
3389
- exports.killServer = killServer;
3390
3549
  exports.mergeFileTrackedResources = mergeFileTrackedResources;
3391
3550
  exports.runWebTest = runWebTest;
3392
3551
  exports.runWorkflow = runWorkflow;
3393
3552
  exports.runWorkflowWithContext = runWorkflowWithContext;
3394
3553
  exports.setupAppwriteTracking = setupAppwriteTracking;
3395
3554
  exports.startTrackingServer = startTrackingServer;
3396
- exports.startWebServer = startWebServer;
3397
- //# sourceMappingURL=chunk-GDPJLAXU.cjs.map
3398
- //# sourceMappingURL=chunk-GDPJLAXU.cjs.map
3555
+ exports.webServerManager = webServerManager;
3556
+ //# sourceMappingURL=chunk-MZ7OCAGA.cjs.map
3557
+ //# sourceMappingURL=chunk-MZ7OCAGA.cjs.map