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.
- package/dist/{chunk-C46Q6B6I.cjs → chunk-MZ7OCAGA.cjs} +386 -264
- package/dist/chunk-MZ7OCAGA.cjs.map +1 -0
- package/dist/{chunk-ETAMKABR.js → chunk-PBSXURXK.js} +369 -262
- package/dist/chunk-PBSXURXK.js.map +1 -0
- package/dist/cli/index.cjs +15 -16
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +4 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +11 -19
- package/dist/index.d.cts +7 -8
- package/dist/index.d.ts +7 -8
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-C46Q6B6I.cjs.map +0 -1
- package/dist/chunk-ETAMKABR.js.map +0 -1
|
@@ -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
|
|
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
|
|
24
|
-
var
|
|
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 ?
|
|
885
|
-
var getActiveTestsPath = (cwd, trackDir) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
952
|
-
const activeFiles = new Set(Object.values(state.sessions).map((s) =>
|
|
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 =
|
|
974
|
+
const filePath = path4__namespace.default.join(dir, file);
|
|
957
975
|
try {
|
|
958
|
-
const
|
|
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() -
|
|
961
|
-
const isEmpty =
|
|
978
|
+
const isOld = Date.now() - stat2.mtimeMs > staleMs;
|
|
979
|
+
const isEmpty = stat2.size === 0;
|
|
962
980
|
if (isEmpty || isOld) {
|
|
963
|
-
await
|
|
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
|
|
980
|
-
isEmpty =
|
|
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
|
|
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
|
|
1019
|
+
await fs__namespace.default.mkdir(getTrackDir(cwd, trackDir), { recursive: true });
|
|
1002
1020
|
await pruneStaleTests(cwd, options.cleanupConfig, trackDir);
|
|
1003
|
-
const trackFile =
|
|
1004
|
-
await
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
1834
|
-
const workflowDir =
|
|
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 =
|
|
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
|
-
|
|
2078
|
+
await webServerManager.start(webServerConfig);
|
|
1955
2079
|
}
|
|
1956
2080
|
const cleanup = () => {
|
|
1957
2081
|
trackingServer.stop();
|
|
1958
|
-
|
|
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
|
-
|
|
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 =
|
|
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,
|
|
2289
|
-
if (
|
|
2290
|
-
const [testId, _varName] =
|
|
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 {{${
|
|
2416
|
+
console.warn(`Cross-test variable interpolation {{${path5}}} not yet fully implemented`);
|
|
2293
2417
|
return match;
|
|
2294
2418
|
}
|
|
2295
|
-
const result = interpolateVariables(`{{${
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
3376
|
+
await webServerManager.start({
|
|
3254
3377
|
...effectiveWebServerConfig,
|
|
3255
|
-
workdir:
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3434
|
-
//# sourceMappingURL=chunk-
|
|
3435
|
-
//# sourceMappingURL=chunk-
|
|
3555
|
+
exports.webServerManager = webServerManager;
|
|
3556
|
+
//# sourceMappingURL=chunk-MZ7OCAGA.cjs.map
|
|
3557
|
+
//# sourceMappingURL=chunk-MZ7OCAGA.cjs.map
|