intellitester 0.5.2 → 0.5.3

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.
@@ -8,13 +8,13 @@ import { parsePhoneNumber, isValidPhoneNumber } from 'libphonenumber-js';
8
8
  import { createServer } from 'http';
9
9
  import * as fs3 from 'fs/promises';
10
10
  import fs3__default from 'fs/promises';
11
- import * as path5 from 'path';
12
- import path5__default from 'path';
11
+ import * as path4 from 'path';
12
+ import path4__default from 'path';
13
13
  import { chromium, webkit, firefox } from 'playwright';
14
14
  import prompts from 'prompts';
15
15
  import { Client, Users, TablesDB, Storage, Teams } from 'node-appwrite';
16
16
  import os from 'os';
17
- import { CompletionModel, Workflow, ChatMessage, runAgent } from 'blazen';
17
+ import { CompletionModel, Workflow, runAgent, ChatMessage } from 'blazen';
18
18
  import { z } from 'zod';
19
19
  import { spawn } from 'child_process';
20
20
  import { existsSync, readFileSync, rmSync } from 'fs';
@@ -441,8 +441,8 @@ var TRACK_DIR = ".intellitester/track";
441
441
  var ACTIVE_TESTS_FILE = "ACTIVE_TESTS.json";
442
442
  var DEFAULT_STALE_MS = 2 * 60 * 60 * 1e3;
443
443
  var HEARTBEAT_MS = 15e3;
444
- var getTrackDir = (cwd, trackDir) => trackDir ? path5__default.resolve(cwd, trackDir) : path5__default.join(cwd, TRACK_DIR);
445
- var getActiveTestsPath = (cwd, trackDir) => path5__default.join(getTrackDir(cwd, trackDir), ACTIVE_TESTS_FILE);
444
+ var getTrackDir = (cwd, trackDir) => trackDir ? path4__default.resolve(cwd, trackDir) : path4__default.join(cwd, TRACK_DIR);
445
+ var getActiveTestsPath = (cwd, trackDir) => path4__default.join(getTrackDir(cwd, trackDir), ACTIVE_TESTS_FILE);
446
446
  var loadActiveTests = async (cwd, trackDir) => {
447
447
  const filePath = getActiveTestsPath(cwd, trackDir);
448
448
  try {
@@ -509,11 +509,11 @@ var cleanupOrphanedTrackFiles = async (cwd, state, trackDir) => {
509
509
  const dir = getTrackDir(cwd, trackDir);
510
510
  try {
511
511
  const files = await fs3__default.readdir(dir);
512
- const activeFiles = new Set(Object.values(state.sessions).map((s) => path5__default.basename(s.trackFile)));
512
+ const activeFiles = new Set(Object.values(state.sessions).map((s) => path4__default.basename(s.trackFile)));
513
513
  for (const file of files) {
514
514
  if (!file.endsWith(".jsonl")) continue;
515
515
  if (activeFiles.has(file)) continue;
516
- const filePath = path5__default.join(dir, file);
516
+ const filePath = path4__default.join(dir, file);
517
517
  try {
518
518
  const stat2 = await fs3__default.stat(filePath);
519
519
  const staleMs = Number(process.env.INTELLITESTER_STALE_TEST_MS ?? DEFAULT_STALE_MS);
@@ -560,7 +560,7 @@ async function initFileTracking(options) {
560
560
  const trackDir = options.trackDir;
561
561
  await fs3__default.mkdir(getTrackDir(cwd, trackDir), { recursive: true });
562
562
  await pruneStaleTests(cwd, options.cleanupConfig, trackDir);
563
- const trackFile = path5__default.join(getTrackDir(cwd, trackDir), `TEST_SESSION_${options.sessionId}.jsonl`);
563
+ const trackFile = path4__default.join(getTrackDir(cwd, trackDir), `TEST_SESSION_${options.sessionId}.jsonl`);
564
564
  await fs3__default.writeFile(trackFile, "", "utf8");
565
565
  const state = await loadActiveTests(cwd, trackDir);
566
566
  state.sessions[options.sessionId] = {
@@ -1080,7 +1080,7 @@ var MIME_BY_EXTENSION = {
1080
1080
  ".webm": "video/webm"
1081
1081
  };
1082
1082
  function mimeFromName(name) {
1083
- const ext = path5__default.extname(name).toLowerCase();
1083
+ const ext = path4__default.extname(name).toLowerCase();
1084
1084
  return MIME_BY_EXTENSION[ext] ?? "application/octet-stream";
1085
1085
  }
1086
1086
  function deriveFilenameFromUrl(url) {
@@ -1100,8 +1100,8 @@ async function downloadUrl(url) {
1100
1100
  const arrayBuffer = await response.arrayBuffer();
1101
1101
  const buffer = Buffer.from(arrayBuffer);
1102
1102
  const name = deriveFilenameFromUrl(url);
1103
- const tempDir = await fs3__default.mkdtemp(path5__default.join(os.tmpdir(), "intellitester-upload-"));
1104
- const tempPath = path5__default.join(tempDir, name);
1103
+ const tempDir = await fs3__default.mkdtemp(path4__default.join(os.tmpdir(), "intellitester-upload-"));
1104
+ const tempPath = path4__default.join(tempDir, name);
1105
1105
  await fs3__default.writeFile(tempPath, buffer);
1106
1106
  return { path: tempPath, tempDir };
1107
1107
  }
@@ -1112,8 +1112,8 @@ async function resolveOne(entry, context) {
1112
1112
  const downloaded = await downloadUrl(interpolated);
1113
1113
  return { kind: "path", path: downloaded.path, tempDir: downloaded.tempDir };
1114
1114
  }
1115
- const absolute = path5__default.isAbsolute(interpolated) ? interpolated : path5__default.resolve(
1116
- context.testFilePath ? path5__default.dirname(context.testFilePath) : process.cwd(),
1115
+ const absolute = path4__default.isAbsolute(interpolated) ? interpolated : path4__default.resolve(
1116
+ context.testFilePath ? path4__default.dirname(context.testFilePath) : process.cwd(),
1117
1117
  interpolated
1118
1118
  );
1119
1119
  try {
@@ -1156,7 +1156,7 @@ async function toPlaywrightFiles(resolved) {
1156
1156
  }
1157
1157
  const buffer = await fs3__default.readFile(r.path);
1158
1158
  return {
1159
- name: path5__default.basename(r.path),
1159
+ name: path4__default.basename(r.path),
1160
1160
  mimeType: mimeFromName(r.path),
1161
1161
  buffer
1162
1162
  };
@@ -1635,9 +1635,9 @@ init_esm_shims();
1635
1635
  var INTELLITESTER_DIR = ".intellitester";
1636
1636
  var SERVERS_MARKER_FILE = "servers.json";
1637
1637
  var LEGACY_SINGLE_MARKER_FILE = "server.json";
1638
- var getMarkerDir = (cwd) => path5.join(cwd, INTELLITESTER_DIR);
1639
- var getMarkerPath = (cwd) => path5.join(getMarkerDir(cwd), SERVERS_MARKER_FILE);
1640
- var getLegacyMarkerPath = (cwd) => path5.join(getMarkerDir(cwd), LEGACY_SINGLE_MARKER_FILE);
1638
+ var getMarkerDir = (cwd) => path4.join(cwd, INTELLITESTER_DIR);
1639
+ var getMarkerPath = (cwd) => path4.join(getMarkerDir(cwd), SERVERS_MARKER_FILE);
1640
+ var getLegacyMarkerPath = (cwd) => path4.join(getMarkerDir(cwd), LEGACY_SINGLE_MARKER_FILE);
1641
1641
  async function readMarkers(cwd) {
1642
1642
  try {
1643
1643
  const content = await fs3.readFile(getMarkerPath(cwd), "utf-8");
@@ -1729,7 +1729,7 @@ async function isServerRunning(url) {
1729
1729
  }
1730
1730
  async function readPackageJson(cwd) {
1731
1731
  try {
1732
- const packagePath = path5.join(cwd, "package.json");
1732
+ const packagePath = path4.join(cwd, "package.json");
1733
1733
  const content = await fs3.readFile(packagePath, "utf-8");
1734
1734
  return JSON.parse(content);
1735
1735
  } catch {
@@ -1754,11 +1754,11 @@ function detectFramework(pkg) {
1754
1754
  return null;
1755
1755
  }
1756
1756
  async function detectPackageManager(cwd) {
1757
- const hasDenoLock = await fs3.stat(path5.join(cwd, "deno.lock")).catch(() => null);
1758
- const hasBunLockb = await fs3.stat(path5.join(cwd, "bun.lockb")).catch(() => null);
1759
- const hasBunLock = await fs3.stat(path5.join(cwd, "bun.lock")).catch(() => null);
1760
- const hasPnpmLock = await fs3.stat(path5.join(cwd, "pnpm-lock.yaml")).catch(() => null);
1761
- const hasYarnLock = await fs3.stat(path5.join(cwd, "yarn.lock")).catch(() => null);
1757
+ const hasDenoLock = await fs3.stat(path4.join(cwd, "deno.lock")).catch(() => null);
1758
+ const hasBunLockb = await fs3.stat(path4.join(cwd, "bun.lockb")).catch(() => null);
1759
+ const hasBunLock = await fs3.stat(path4.join(cwd, "bun.lock")).catch(() => null);
1760
+ const hasPnpmLock = await fs3.stat(path4.join(cwd, "pnpm-lock.yaml")).catch(() => null);
1761
+ const hasYarnLock = await fs3.stat(path4.join(cwd, "yarn.lock")).catch(() => null);
1762
1762
  if (hasDenoLock) return "deno";
1763
1763
  if (hasBunLockb || hasBunLock) return "bun";
1764
1764
  if (hasPnpmLock) return "pnpm";
@@ -1782,7 +1782,7 @@ function getDevCommand(pm, script) {
1782
1782
  async function detectBuildDirectory(cwd) {
1783
1783
  const commonDirs = [".next", ".output", ".svelte-kit", "dist", "build", "out"];
1784
1784
  for (const dir of commonDirs) {
1785
- const fullPath = path5.join(cwd, dir);
1785
+ const fullPath = path4.join(cwd, dir);
1786
1786
  try {
1787
1787
  const stat2 = await fs3.stat(fullPath);
1788
1788
  if (stat2.isDirectory()) return dir;
@@ -2330,10 +2330,10 @@ function safeCallback(label, fn) {
2330
2330
  console.error(`[intellitester] ${label} callback threw:`, e);
2331
2331
  }
2332
2332
  }
2333
- var defaultScreenshotDir = path5__default.join(process.cwd(), "artifacts", "screenshots");
2333
+ var defaultScreenshotDir = path4__default.join(process.cwd(), "artifacts", "screenshots");
2334
2334
  var resolveStorageStatePath = (value, baseDir) => {
2335
2335
  if (typeof value !== "string") return value;
2336
- return path5__default.isAbsolute(value) ? value : path5__default.resolve(baseDir, value);
2336
+ return path4__default.isAbsolute(value) ? value : path4__default.resolve(baseDir, value);
2337
2337
  };
2338
2338
  var interpolateTrackMetadata = (value, variables) => {
2339
2339
  if (typeof value === "string") {
@@ -2531,7 +2531,7 @@ var runScreenshot = async (page, name, screenshotDir, stepIndex, browserName) =>
2531
2531
  const timing = getBrowserTimingConfig(browserName ?? "chromium");
2532
2532
  await waitForPageStable(page, timing.screenshotNetworkIdleTimeout);
2533
2533
  const filename = name ?? `step-${stepIndex + 1}.png`;
2534
- const filePath = path5__default.join(screenshotDir, filename);
2534
+ const filePath = path4__default.join(screenshotDir, filename);
2535
2535
  await page.screenshot({ path: filePath, fullPage: true });
2536
2536
  return filePath;
2537
2537
  };
@@ -2551,7 +2551,7 @@ async function handleInteractiveError(page, action, error, screenshotDir, stepIn
2551
2551
  console.error(` Error: ${error.message}
2552
2552
  `);
2553
2553
  await ensureScreenshotDir(screenshotDir);
2554
- const screenshotPath = path5__default.join(screenshotDir, `error-step-${stepIndex + 1}.png`);
2554
+ const screenshotPath = path4__default.join(screenshotDir, `error-step-${stepIndex + 1}.png`);
2555
2555
  await page.screenshot({ path: screenshotPath, fullPage: true });
2556
2556
  const pageContent = await page.content();
2557
2557
  if (aiConfig) {
@@ -2600,7 +2600,7 @@ async function handleInteractiveError(page, action, error, screenshotDir, stepIn
2600
2600
  return response.action || "abort";
2601
2601
  }
2602
2602
  async function executeActionWithRetry(page, action, index, options) {
2603
- const { baseUrl, context, screenshotDir, debugMode, interactive, aiConfig, browserName, healing, testFilePath } = options;
2603
+ const { baseUrl, context, screenshotDir, debugMode, interactive, aiConfig, browserName, healing, testFilePath, responseLog, stepStartTs } = options;
2604
2604
  const extras = {};
2605
2605
  const buildTrackPayload = (stepExtras) => {
2606
2606
  if (!("track" in action)) return null;
@@ -3053,15 +3053,7 @@ async function executeActionWithRetry(page, action, index, options) {
3053
3053
  if (debugMode) {
3054
3054
  console.log(`[DEBUG] Executing nested step ${nestedIdx + 1}: ${nestedAction.type}`);
3055
3055
  }
3056
- await executeActionWithRetry(page, nestedAction, index, {
3057
- baseUrl,
3058
- context,
3059
- screenshotDir,
3060
- debugMode,
3061
- interactive,
3062
- aiConfig,
3063
- browserName
3064
- });
3056
+ await executeActionWithRetry(page, nestedAction, index, options);
3065
3057
  }
3066
3058
  break;
3067
3059
  }
@@ -3163,20 +3155,12 @@ async function executeActionWithRetry(page, action, index, options) {
3163
3155
  if (debugMode) {
3164
3156
  console.log(`[DEBUG] Executing branch step ${nestedIdx + 1}: ${nestedAction.type}`);
3165
3157
  }
3166
- await executeActionWithRetry(page, nestedAction, index, {
3167
- baseUrl,
3168
- context,
3169
- screenshotDir,
3170
- debugMode,
3171
- interactive,
3172
- aiConfig,
3173
- browserName
3174
- });
3158
+ await executeActionWithRetry(page, nestedAction, index, options);
3175
3159
  }
3176
3160
  } else {
3177
3161
  const { loadWorkflowDefinition, loadTestDefinition: loadTestDefinition2 } = await import('./loader-2CW6OEXJ.js');
3178
- const workflowPath = path5__default.resolve(process.cwd(), branchToExecute.workflow);
3179
- const workflowDir = path5__default.dirname(workflowPath);
3162
+ const workflowPath = path4__default.resolve(process.cwd(), branchToExecute.workflow);
3163
+ const workflowDir = path4__default.dirname(workflowPath);
3180
3164
  if (debugMode) {
3181
3165
  console.log(`[DEBUG] Executing workflow: ${workflowPath}`);
3182
3166
  }
@@ -3188,7 +3172,7 @@ async function executeActionWithRetry(page, action, index, options) {
3188
3172
  }
3189
3173
  }
3190
3174
  for (const testRef of workflow.tests) {
3191
- const testFilePath2 = path5__default.resolve(workflowDir, testRef.file);
3175
+ const testFilePath2 = path4__default.resolve(workflowDir, testRef.file);
3192
3176
  if (debugMode) {
3193
3177
  console.log(`[DEBUG] Loading test from workflow: ${testFilePath2}`);
3194
3178
  }
@@ -3200,18 +3184,148 @@ async function executeActionWithRetry(page, action, index, options) {
3200
3184
  }
3201
3185
  }
3202
3186
  for (const [testStepIdx, testAction] of test.steps.entries()) {
3203
- await executeActionWithRetry(page, testAction, testStepIdx, {
3204
- baseUrl,
3205
- context,
3206
- screenshotDir,
3207
- debugMode,
3208
- interactive,
3209
- aiConfig,
3210
- browserName
3211
- });
3187
+ await executeActionWithRetry(page, testAction, testStepIdx, options);
3188
+ }
3189
+ }
3190
+ }
3191
+ break;
3192
+ }
3193
+ case "saveStorageState": {
3194
+ const saveAction = action;
3195
+ if (saveAction.path) {
3196
+ const resolvedPath = interpolateVariables(saveAction.path, context.variables);
3197
+ const baseDir = testFilePath ? path4__default.dirname(testFilePath) : process.cwd();
3198
+ const absPath = path4__default.isAbsolute(resolvedPath) ? resolvedPath : path4__default.resolve(baseDir, resolvedPath);
3199
+ await page.context().storageState({ path: absPath });
3200
+ if (debugMode) {
3201
+ console.log(`[DEBUG] Saved storage state to ${absPath}`);
3202
+ }
3203
+ } else if (saveAction.handler) {
3204
+ const resolvedHandler = interpolateVariables(saveAction.handler, context.variables);
3205
+ const baseDir = testFilePath ? path4__default.dirname(testFilePath) : process.cwd();
3206
+ const absPath = path4__default.isAbsolute(resolvedHandler) ? resolvedHandler : path4__default.resolve(baseDir, resolvedHandler);
3207
+ let loadPath = absPath;
3208
+ if (absPath.endsWith(".ts")) {
3209
+ const jsPath = absPath.replace(/\.ts$/, ".js");
3210
+ try {
3211
+ await fs3__default.access(jsPath);
3212
+ loadPath = jsPath;
3213
+ } catch {
3214
+ }
3215
+ }
3216
+ const mod = await import(`${loadPath}?t=${Date.now()}`);
3217
+ const fn = mod.default ?? mod;
3218
+ if (typeof fn !== "function") {
3219
+ throw new Error(`saveStorageState handler at ${resolvedHandler} did not export a default function`);
3220
+ }
3221
+ await fn({
3222
+ page,
3223
+ context: page.context(),
3224
+ variables: context.variables
3225
+ });
3226
+ if (debugMode) {
3227
+ console.log(`[DEBUG] Ran custom saveStorageState handler: ${resolvedHandler}`);
3228
+ }
3229
+ } else {
3230
+ throw new Error("saveStorageState requires either `path` or `handler` (schema should have caught this)");
3231
+ }
3232
+ break;
3233
+ }
3234
+ case "assertCookies": {
3235
+ const cookieAction = action;
3236
+ const filterUrl = cookieAction.url ? interpolateVariables(cookieAction.url, context.variables) : void 0;
3237
+ const jar = await page.context().cookies(filterUrl);
3238
+ const names = new Set(jar.map((c) => c.name));
3239
+ const problems = [];
3240
+ if (cookieAction.has) {
3241
+ for (const name of cookieAction.has) {
3242
+ if (!names.has(name)) problems.push(`expected cookie "${name}" to be present`);
3243
+ }
3244
+ }
3245
+ if (cookieAction.not) {
3246
+ for (const name of cookieAction.not) {
3247
+ if (names.has(name)) problems.push(`expected cookie "${name}" to be absent`);
3248
+ }
3249
+ }
3250
+ if (cookieAction.match) {
3251
+ for (const [name, pattern] of Object.entries(cookieAction.match)) {
3252
+ const c = jar.find((entry) => entry.name === name);
3253
+ if (!c) {
3254
+ problems.push(`expected cookie "${name}" to be present (for value match)`);
3255
+ continue;
3256
+ }
3257
+ const matcher = compileMatcher(interpolateVariables(pattern, context.variables), "substr");
3258
+ if (!matcher(c.value)) {
3259
+ problems.push(`cookie "${name}" value "${c.value}" did not match pattern "${pattern}"`);
3212
3260
  }
3213
3261
  }
3214
3262
  }
3263
+ if (problems.length > 0) {
3264
+ throw new Error(`assertCookies failed:
3265
+ - ${problems.join("\n - ")}
3266
+ (cookies seen: ${[...names].join(", ") || "<none>"})`);
3267
+ }
3268
+ break;
3269
+ }
3270
+ case "expectResponse": {
3271
+ if (!responseLog) {
3272
+ throw new Error("expectResponse requires a responseLog to be attached (executor wiring issue)");
3273
+ }
3274
+ const respAction = action;
3275
+ const urlPattern = interpolateVariables(respAction.url, context.variables);
3276
+ const urlMatch = compileMatcher(urlPattern, "url");
3277
+ const headerMatchers = respAction.headers ? Object.entries(respAction.headers).map(([name, pattern]) => ({
3278
+ name: name.toLowerCase(),
3279
+ test: compileMatcher(interpolateVariables(pattern, context.variables), "substr"),
3280
+ pattern
3281
+ })) : [];
3282
+ const expectedStatus = respAction.status;
3283
+ let sinceTs;
3284
+ if (respAction.since === "testStart") {
3285
+ sinceTs = 0;
3286
+ } else if (typeof respAction.since === "number") {
3287
+ sinceTs = respAction.since;
3288
+ } else {
3289
+ sinceTs = stepStartTs ?? 0;
3290
+ }
3291
+ const timeout = respAction.timeout ?? 5e3;
3292
+ const deadline = Date.now() + timeout;
3293
+ const findMatch = () => {
3294
+ for (const entry of responseLog.snapshot()) {
3295
+ if (entry.ts < sinceTs) continue;
3296
+ if (!urlMatch(entry.url)) continue;
3297
+ if (expectedStatus !== void 0 && entry.status !== expectedStatus) continue;
3298
+ let headersOk = true;
3299
+ for (const h of headerMatchers) {
3300
+ const value = entry.headers[h.name];
3301
+ if (value === void 0 || !h.test(value)) {
3302
+ headersOk = false;
3303
+ break;
3304
+ }
3305
+ }
3306
+ if (headersOk) return entry;
3307
+ }
3308
+ return null;
3309
+ };
3310
+ let match = findMatch();
3311
+ while (!match && Date.now() < deadline) {
3312
+ await new Promise((resolve) => setTimeout(resolve, 50));
3313
+ match = findMatch();
3314
+ }
3315
+ if (!match) {
3316
+ const recent = responseLog.snapshot().slice(-5).map((e) => `${e.status} ${e.url}`).join("\n ");
3317
+ throw new Error(
3318
+ `expectResponse timed out after ${timeout}ms.
3319
+ url pattern: ${urlPattern}
3320
+ ` + (expectedStatus !== void 0 ? ` expected status: ${expectedStatus}
3321
+ ` : "") + (headerMatchers.length > 0 ? ` expected headers: ${headerMatchers.map((h) => `${h.name}=${h.pattern}`).join(", ")}
3322
+ ` : "") + ` recent responses:
3323
+ ${recent || "<none>"}`
3324
+ );
3325
+ }
3326
+ if (debugMode) {
3327
+ console.log(`[DEBUG] expectResponse matched: ${match.status} ${match.url}`);
3328
+ }
3215
3329
  break;
3216
3330
  }
3217
3331
  default:
@@ -3663,7 +3777,7 @@ var runWebTest = async (test, options = {}) => {
3663
3777
  const timing = getBrowserTimingConfig(browserName ?? "chromium");
3664
3778
  await waitForPageStable(page, timing.screenshotNetworkIdleTimeout);
3665
3779
  await ensureScreenshotDir(screenshotDir);
3666
- const evalScreenshotPath = path5__default.join(screenshotDir, `evaluate-step-${index + 1}.png`);
3780
+ const evalScreenshotPath = path4__default.join(screenshotDir, `evaluate-step-${index + 1}.png`);
3667
3781
  const screenshotBuffer = await page.screenshot({
3668
3782
  path: evalScreenshotPath,
3669
3783
  fullPage: evalAction.fullPage ?? true
@@ -3699,172 +3813,7 @@ var runWebTest = async (test, options = {}) => {
3699
3813
  safeCallback("onStepComplete", options.onStepComplete ? () => options.onStepComplete(sizeResults[sizeResults.length - 1], index, test.steps.length) : void 0);
3700
3814
  continue;
3701
3815
  }
3702
- if (action.type === "saveStorageState") {
3703
- const saveAction = action;
3704
- try {
3705
- if (saveAction.path) {
3706
- const resolvedPath = interpolateVariables(saveAction.path, executionContext.variables);
3707
- const baseDir = options.testFilePath ? path5__default.dirname(options.testFilePath) : process.cwd();
3708
- const absPath = path5__default.isAbsolute(resolvedPath) ? resolvedPath : path5__default.resolve(baseDir, resolvedPath);
3709
- await page.context().storageState({ path: absPath });
3710
- if (debugMode) {
3711
- console.log(`[DEBUG] Saved storage state to ${absPath}`);
3712
- }
3713
- } else if (saveAction.handler) {
3714
- const resolvedHandler = interpolateVariables(saveAction.handler, executionContext.variables);
3715
- const baseDir = options.testFilePath ? path5__default.dirname(options.testFilePath) : process.cwd();
3716
- const absPath = path5__default.isAbsolute(resolvedHandler) ? resolvedHandler : path5__default.resolve(baseDir, resolvedHandler);
3717
- let loadPath = absPath;
3718
- if (absPath.endsWith(".ts")) {
3719
- const jsPath = absPath.replace(/\.ts$/, ".js");
3720
- try {
3721
- await fs3__default.access(jsPath);
3722
- loadPath = jsPath;
3723
- } catch {
3724
- }
3725
- }
3726
- const mod = await import(`${loadPath}?t=${Date.now()}`);
3727
- const fn = mod.default ?? mod;
3728
- if (typeof fn !== "function") {
3729
- throw new Error(`saveStorageState handler at ${resolvedHandler} did not export a default function`);
3730
- }
3731
- await fn({
3732
- page,
3733
- context: page.context(),
3734
- variables: executionContext.variables
3735
- });
3736
- if (debugMode) {
3737
- console.log(`[DEBUG] Ran custom saveStorageState handler: ${resolvedHandler}`);
3738
- }
3739
- } else {
3740
- throw new Error("saveStorageState requires either `path` or `handler` (schema should have caught this)");
3741
- }
3742
- sizeResults.push({ action, status: "passed" });
3743
- safeCallback("onStepComplete", options.onStepComplete ? () => options.onStepComplete(sizeResults[sizeResults.length - 1], index, test.steps.length) : void 0);
3744
- const trackedPayload = buildTrackPayload(action, index);
3745
- if (trackedPayload) {
3746
- await track(trackedPayload);
3747
- }
3748
- } catch (e) {
3749
- const errMsg = e instanceof Error ? e.message : String(e);
3750
- sizeResults.push({ action, status: "failed", error: errMsg });
3751
- safeCallback("onStepComplete", options.onStepComplete ? () => options.onStepComplete(sizeResults[sizeResults.length - 1], index, test.steps.length) : void 0);
3752
- throw e;
3753
- }
3754
- continue;
3755
- }
3756
- if (action.type === "assertCookies") {
3757
- const cookieAction = action;
3758
- try {
3759
- const filterUrl = cookieAction.url ? interpolateVariables(cookieAction.url, executionContext.variables) : void 0;
3760
- const jar = await page.context().cookies(filterUrl);
3761
- const names = new Set(jar.map((c) => c.name));
3762
- const problems = [];
3763
- if (cookieAction.has) {
3764
- for (const name of cookieAction.has) {
3765
- if (!names.has(name)) problems.push(`expected cookie "${name}" to be present`);
3766
- }
3767
- }
3768
- if (cookieAction.not) {
3769
- for (const name of cookieAction.not) {
3770
- if (names.has(name)) problems.push(`expected cookie "${name}" to be absent`);
3771
- }
3772
- }
3773
- if (cookieAction.match) {
3774
- for (const [name, pattern] of Object.entries(cookieAction.match)) {
3775
- const c = jar.find((entry) => entry.name === name);
3776
- if (!c) {
3777
- problems.push(`expected cookie "${name}" to be present (for value match)`);
3778
- continue;
3779
- }
3780
- const matcher = compileMatcher(interpolateVariables(pattern, executionContext.variables), "substr");
3781
- if (!matcher(c.value)) {
3782
- problems.push(`cookie "${name}" value "${c.value}" did not match pattern "${pattern}"`);
3783
- }
3784
- }
3785
- }
3786
- if (problems.length > 0) {
3787
- throw new Error(`assertCookies failed:
3788
- - ${problems.join("\n - ")}
3789
- (cookies seen: ${[...names].join(", ") || "<none>"})`);
3790
- }
3791
- sizeResults.push({ action, status: "passed" });
3792
- safeCallback("onStepComplete", options.onStepComplete ? () => options.onStepComplete(sizeResults[sizeResults.length - 1], index, test.steps.length) : void 0);
3793
- } catch (e) {
3794
- const errMsg = e instanceof Error ? e.message : String(e);
3795
- sizeResults.push({ action, status: "failed", error: errMsg });
3796
- safeCallback("onStepComplete", options.onStepComplete ? () => options.onStepComplete(sizeResults[sizeResults.length - 1], index, test.steps.length) : void 0);
3797
- throw e;
3798
- }
3799
- continue;
3800
- }
3801
- if (action.type === "expectResponse") {
3802
- const respAction = action;
3803
- try {
3804
- const urlPattern = interpolateVariables(respAction.url, executionContext.variables);
3805
- const urlMatch = compileMatcher(urlPattern, "url");
3806
- const headerMatchers = respAction.headers ? Object.entries(respAction.headers).map(([name, pattern]) => ({
3807
- name: name.toLowerCase(),
3808
- test: compileMatcher(interpolateVariables(pattern, executionContext.variables), "substr"),
3809
- pattern
3810
- })) : [];
3811
- const expectedStatus = respAction.status;
3812
- let sinceTs;
3813
- if (respAction.since === "testStart") {
3814
- sinceTs = 0;
3815
- } else if (typeof respAction.since === "number") {
3816
- sinceTs = respAction.since;
3817
- } else {
3818
- sinceTs = lastStepEndTs;
3819
- }
3820
- const timeout = respAction.timeout ?? 5e3;
3821
- const deadline = Date.now() + timeout;
3822
- const findMatch = () => {
3823
- for (const entry of responseLog.snapshot()) {
3824
- if (entry.ts < sinceTs) continue;
3825
- if (!urlMatch(entry.url)) continue;
3826
- if (expectedStatus !== void 0 && entry.status !== expectedStatus) continue;
3827
- let headersOk = true;
3828
- for (const h of headerMatchers) {
3829
- const value = entry.headers[h.name];
3830
- if (value === void 0 || !h.test(value)) {
3831
- headersOk = false;
3832
- break;
3833
- }
3834
- }
3835
- if (headersOk) return entry;
3836
- }
3837
- return null;
3838
- };
3839
- let match = findMatch();
3840
- while (!match && Date.now() < deadline) {
3841
- await new Promise((resolve) => setTimeout(resolve, 50));
3842
- match = findMatch();
3843
- }
3844
- if (!match) {
3845
- const recent = responseLog.snapshot().slice(-5).map((e) => `${e.status} ${e.url}`).join("\n ");
3846
- throw new Error(
3847
- `expectResponse timed out after ${timeout}ms.
3848
- url pattern: ${urlPattern}
3849
- ` + (expectedStatus !== void 0 ? ` expected status: ${expectedStatus}
3850
- ` : "") + (headerMatchers.length > 0 ? ` expected headers: ${headerMatchers.map((h) => `${h.name}=${h.pattern}`).join(", ")}
3851
- ` : "") + ` recent responses:
3852
- ${recent || "<none>"}`
3853
- );
3854
- }
3855
- if (debugMode) {
3856
- console.log(`[DEBUG] expectResponse matched: ${match.status} ${match.url}`);
3857
- }
3858
- sizeResults.push({ action, status: "passed" });
3859
- safeCallback("onStepComplete", options.onStepComplete ? () => options.onStepComplete(sizeResults[sizeResults.length - 1], index, test.steps.length) : void 0);
3860
- } catch (e) {
3861
- const errMsg = e instanceof Error ? e.message : String(e);
3862
- sizeResults.push({ action, status: "failed", error: errMsg });
3863
- safeCallback("onStepComplete", options.onStepComplete ? () => options.onStepComplete(sizeResults[sizeResults.length - 1], index, test.steps.length) : void 0);
3864
- throw e;
3865
- }
3866
- continue;
3867
- }
3816
+ const stepStartTs = lastStepEndTs;
3868
3817
  const actionExtras = await executeActionWithRetry(page, action, index, {
3869
3818
  baseUrl: options.baseUrl ?? test.config?.web?.baseUrl,
3870
3819
  context: executionContext,
@@ -3874,7 +3823,9 @@ var runWebTest = async (test, options = {}) => {
3874
3823
  aiConfig: options.aiConfig,
3875
3824
  browserName,
3876
3825
  healing: options.healing,
3877
- testFilePath: options.testFilePath
3826
+ testFilePath: options.testFilePath,
3827
+ responseLog,
3828
+ stepStartTs
3878
3829
  });
3879
3830
  sizeResults.push({ action, status: "passed", logOutput: actionExtras.logOutput });
3880
3831
  safeCallback("onStepComplete", options.onStepComplete ? () => options.onStepComplete(sizeResults[sizeResults.length - 1], index, test.steps.length) : void 0);
@@ -3951,24 +3902,7 @@ var runWebTest = async (test, options = {}) => {
3951
3902
 
3952
3903
  // src/executors/web/workflowExecutor.ts
3953
3904
  init_esm_shims();
3954
- var defaultScreenshotDir2 = path5__default.join(process.cwd(), "artifacts", "screenshots");
3955
- var interpolateTrackMetadata2 = (value, variables) => {
3956
- if (typeof value === "string") {
3957
- return interpolateVariables(value, variables);
3958
- }
3959
- if (Array.isArray(value)) {
3960
- return value.map((entry) => interpolateTrackMetadata2(entry, variables));
3961
- }
3962
- if (value && typeof value === "object") {
3963
- return Object.fromEntries(
3964
- Object.entries(value).map(([key, entry]) => [
3965
- key,
3966
- interpolateTrackMetadata2(entry, variables)
3967
- ])
3968
- );
3969
- }
3970
- return value;
3971
- };
3905
+ var defaultScreenshotDir2 = path4__default.join(process.cwd(), "artifacts", "screenshots");
3972
3906
  var getBrowser2 = (browser) => {
3973
3907
  switch (browser) {
3974
3908
  case "firefox":
@@ -3991,761 +3925,34 @@ function interpolateWorkflowVariables(value, currentVariables, testResults) {
3991
3925
  return result;
3992
3926
  });
3993
3927
  }
3994
- async function runTestInWorkflow(test, page, context, options, workflowDir, testFilePath, workflowBaseUrl) {
3928
+ async function runTestInWorkflow(test, page, context, options, _workflowDir, testFilePath, workflowBaseUrl) {
3995
3929
  const results = [];
3996
3930
  const debugMode = options.debug ?? false;
3997
3931
  const screenshotDir = defaultScreenshotDir2;
3998
- const resolveUrl2 = (value, baseUrl) => {
3999
- if (!baseUrl) return value;
4000
- try {
4001
- const url = new URL(value, baseUrl);
4002
- return url.toString();
4003
- } catch {
4004
- return value;
4005
- }
4006
- };
4007
- const interpolate = (value) => {
4008
- return interpolateVariables(value, context.variables);
4009
- };
3932
+ const browserName = options.browser ?? "chromium";
3933
+ const baseUrl = test.config?.web?.baseUrl || workflowBaseUrl;
4010
3934
  const responseLog = new ResponseLog();
4011
3935
  responseLog.attach(page);
4012
- let lastStepEndTs = Date.now();
4013
- const resolveLocator2 = (locator) => {
4014
- if (locator.testId) return page.getByTestId(locator.testId);
4015
- if (locator.text) return page.getByText(locator.text);
4016
- if (locator.css) return page.locator(locator.css);
4017
- if (locator.xpath) return page.locator(`xpath=${locator.xpath}`);
4018
- if (locator.role) {
4019
- const options2 = {};
4020
- if (locator.name) options2.name = locator.name;
4021
- return page.getByRole(locator.role, options2);
4022
- }
4023
- if (locator.description) return page.getByText(locator.description);
4024
- throw new Error("No usable selector found for locator");
4025
- };
4026
- const buildTrackPayload = (action, index, stepExtras) => {
4027
- if (!("track" in action)) return null;
4028
- const rawTrack = action.track;
4029
- if (!rawTrack || typeof rawTrack !== "object") return null;
4030
- const track2 = interpolateTrackMetadata2(rawTrack, context.variables);
4031
- if (typeof track2.type !== "string" || typeof track2.id !== "string") return null;
4032
- const { includeStepContext, ...rest } = track2;
4033
- const payload = {
4034
- type: track2.type,
4035
- id: track2.id,
4036
- ...rest
4037
- };
4038
- if (includeStepContext) {
4039
- payload.step = { index, ...action, ...stepExtras };
4040
- }
4041
- return payload;
4042
- };
4043
3936
  try {
4044
3937
  for (const [index, action] of test.steps.entries()) {
4045
- lastStepEndTs = Date.now();
3938
+ const stepStartTs = Date.now();
4046
3939
  if (debugMode) {
4047
3940
  console.log(` [DEBUG] Step ${index + 1}: ${action.type}`);
4048
3941
  }
4049
3942
  try {
4050
- switch (action.type) {
4051
- case "navigate": {
4052
- const interpolated = interpolate(action.value);
4053
- const baseUrl = test.config?.web?.baseUrl || workflowBaseUrl;
4054
- const target = resolveUrl2(interpolated, baseUrl);
4055
- if (debugMode) {
4056
- console.log(` [DEBUG] Navigate step:`);
4057
- console.log(` [DEBUG] - action.value: ${action.value}`);
4058
- console.log(` [DEBUG] - interpolated: ${interpolated}`);
4059
- console.log(` [DEBUG] - test.config?.web?.baseUrl: ${test.config?.web?.baseUrl ?? "(undefined)"}`);
4060
- console.log(` [DEBUG] - workflowBaseUrl: ${workflowBaseUrl ?? "(undefined)"}`);
4061
- console.log(` [DEBUG] - effective baseUrl: ${baseUrl ?? "(undefined)"}`);
4062
- console.log(` [DEBUG] - target: ${target}`);
4063
- }
4064
- await page.goto(target);
4065
- break;
4066
- }
4067
- case "tap": {
4068
- if (debugMode) console.log(` [DEBUG] Tapping element:`, action.target);
4069
- const handle = resolveLocator2(action.target);
4070
- await handle.click();
4071
- await page.waitForLoadState("domcontentloaded").catch(() => {
4072
- });
4073
- await page.waitForLoadState("networkidle", { timeout: 1e4 }).catch(() => {
4074
- });
4075
- break;
4076
- }
4077
- case "input": {
4078
- const interpolated = interpolate(action.value);
4079
- if (debugMode) console.log(` [DEBUG] Input: ${interpolated}`);
4080
- const handle = resolveLocator2(action.target);
4081
- await handle.fill(interpolated);
4082
- break;
4083
- }
4084
- case "clear": {
4085
- if (debugMode) console.log(` [DEBUG] Clearing element:`, action.target);
4086
- const handle = resolveLocator2(action.target);
4087
- await handle.clear();
4088
- break;
4089
- }
4090
- case "hover": {
4091
- if (debugMode) console.log(` [DEBUG] Hovering element:`, action.target);
4092
- const handle = resolveLocator2(action.target);
4093
- await handle.hover();
4094
- break;
4095
- }
4096
- case "select": {
4097
- const interpolated = interpolate(action.value);
4098
- if (debugMode) console.log(` [DEBUG] Selecting: ${interpolated}`);
4099
- const handle = resolveLocator2(action.target);
4100
- await handle.selectOption(interpolated);
4101
- break;
4102
- }
4103
- case "check": {
4104
- if (debugMode) console.log(` [DEBUG] Checking:`, action.target);
4105
- const handle = resolveLocator2(action.target);
4106
- await handle.check();
4107
- break;
4108
- }
4109
- case "uncheck": {
4110
- if (debugMode) console.log(` [DEBUG] Unchecking:`, action.target);
4111
- const handle = resolveLocator2(action.target);
4112
- await handle.uncheck();
4113
- break;
4114
- }
4115
- case "press": {
4116
- if (debugMode) console.log(` [DEBUG] Pressing key: ${action.key}`);
4117
- if (action.target) {
4118
- const handle = resolveLocator2(action.target);
4119
- await handle.press(action.key);
4120
- } else {
4121
- await page.keyboard.press(action.key);
4122
- }
4123
- break;
4124
- }
4125
- case "focus": {
4126
- if (debugMode) console.log(` [DEBUG] Focusing:`, action.target);
4127
- const handle = resolveLocator2(action.target);
4128
- await handle.focus();
4129
- break;
4130
- }
4131
- case "assert": {
4132
- if (debugMode) console.log(` [DEBUG] Assert:`, action.target);
4133
- const handle = resolveLocator2(action.target);
4134
- await handle.waitFor({ state: "visible" });
4135
- if (action.value) {
4136
- const interpolated = interpolate(action.value);
4137
- const text = (await handle.textContent())?.trim() ?? "";
4138
- if (!text.includes(interpolated)) {
4139
- throw new Error(
4140
- `Assertion failed: expected "${interpolated}", got "${text}"`
4141
- );
4142
- }
4143
- }
4144
- break;
4145
- }
4146
- case "wait": {
4147
- if (action.target) {
4148
- const handle = resolveLocator2(action.target);
4149
- await handle.waitFor({ state: "visible", timeout: action.timeout });
4150
- } else {
4151
- await page.waitForTimeout(action.timeout ?? 1e3);
4152
- }
4153
- break;
4154
- }
4155
- case "scroll": {
4156
- if (action.target) {
4157
- const handle = resolveLocator2(action.target);
4158
- await handle.scrollIntoViewIfNeeded();
4159
- } else {
4160
- const amount = action.amount ?? 500;
4161
- const direction = action.direction ?? "down";
4162
- const deltaY = direction === "up" ? -amount : amount;
4163
- await page.evaluate((value) => window.scrollBy(0, value), deltaY);
4164
- }
4165
- break;
4166
- }
4167
- case "screenshot": {
4168
- const ssAction = action;
4169
- await page.waitForLoadState("domcontentloaded").catch(() => {
4170
- });
4171
- await page.waitForLoadState("networkidle", { timeout: 5e3 }).catch(() => {
4172
- });
4173
- const waitBefore = ssAction.waitBefore ?? 500;
4174
- if (waitBefore > 0) {
4175
- await page.waitForTimeout(waitBefore);
4176
- }
4177
- const filename = ssAction.name ?? `step-${index + 1}.png`;
4178
- const filePath = path5__default.join(screenshotDir, filename);
4179
- await page.screenshot({ path: filePath, fullPage: true });
4180
- results.push({ action, status: "passed", screenshotPath: filePath });
4181
- const trackedPayload2 = buildTrackPayload(action, index, { screenshotPath: filePath });
4182
- if (trackedPayload2) {
4183
- await track(trackedPayload2);
4184
- }
4185
- continue;
4186
- }
4187
- case "saveStorageState": {
4188
- const saveAction = action;
4189
- if (saveAction.path) {
4190
- const resolvedPath = interpolate(saveAction.path);
4191
- const baseDir = path5__default.dirname(testFilePath);
4192
- const absPath = path5__default.isAbsolute(resolvedPath) ? resolvedPath : path5__default.resolve(baseDir, resolvedPath);
4193
- await page.context().storageState({ path: absPath });
4194
- if (debugMode) {
4195
- console.log(` [DEBUG] Saved storage state to ${absPath}`);
4196
- }
4197
- } else if (saveAction.handler) {
4198
- const resolvedHandler = interpolate(saveAction.handler);
4199
- const baseDir = path5__default.dirname(testFilePath);
4200
- const absPath = path5__default.isAbsolute(resolvedHandler) ? resolvedHandler : path5__default.resolve(baseDir, resolvedHandler);
4201
- let loadPath = absPath;
4202
- if (absPath.endsWith(".ts")) {
4203
- const jsPath = absPath.replace(/\.ts$/, ".js");
4204
- try {
4205
- await fs3__default.access(jsPath);
4206
- loadPath = jsPath;
4207
- } catch {
4208
- }
4209
- }
4210
- const mod = await import(`${loadPath}?t=${Date.now()}`);
4211
- const fn = mod.default ?? mod;
4212
- if (typeof fn !== "function") {
4213
- throw new Error(`saveStorageState handler at ${resolvedHandler} did not export a default function`);
4214
- }
4215
- await fn({
4216
- page,
4217
- context: page.context(),
4218
- variables: context.variables
4219
- });
4220
- if (debugMode) {
4221
- console.log(` [DEBUG] Ran custom saveStorageState handler: ${resolvedHandler}`);
4222
- }
4223
- } else {
4224
- throw new Error("saveStorageState requires either `path` or `handler` (schema should have caught this)");
4225
- }
4226
- results.push({ action, status: "passed" });
4227
- const trackedPayload2 = buildTrackPayload(action, index);
4228
- if (trackedPayload2) {
4229
- await track(trackedPayload2);
4230
- }
4231
- break;
4232
- }
4233
- case "assertCookies": {
4234
- const cookieAction = action;
4235
- const filterUrl = cookieAction.url ? interpolate(cookieAction.url) : void 0;
4236
- const jar = await page.context().cookies(filterUrl);
4237
- const names = new Set(jar.map((c) => c.name));
4238
- const problems = [];
4239
- if (cookieAction.has) {
4240
- for (const name of cookieAction.has) {
4241
- if (!names.has(name)) problems.push(`expected cookie "${name}" to be present`);
4242
- }
4243
- }
4244
- if (cookieAction.not) {
4245
- for (const name of cookieAction.not) {
4246
- if (names.has(name)) problems.push(`expected cookie "${name}" to be absent`);
4247
- }
4248
- }
4249
- if (cookieAction.match) {
4250
- for (const [name, pattern] of Object.entries(cookieAction.match)) {
4251
- const c = jar.find((entry) => entry.name === name);
4252
- if (!c) {
4253
- problems.push(`expected cookie "${name}" to be present (for value match)`);
4254
- continue;
4255
- }
4256
- const matcher = compileMatcher(interpolate(pattern), "substr");
4257
- if (!matcher(c.value)) {
4258
- problems.push(`cookie "${name}" value "${c.value}" did not match pattern "${pattern}"`);
4259
- }
4260
- }
4261
- }
4262
- if (problems.length > 0) {
4263
- throw new Error(`assertCookies failed:
4264
- - ${problems.join("\n - ")}
4265
- (cookies seen: ${[...names].join(", ") || "<none>"})`);
4266
- }
4267
- results.push({ action, status: "passed" });
4268
- break;
4269
- }
4270
- case "expectResponse": {
4271
- const respAction = action;
4272
- const urlPattern = interpolate(respAction.url);
4273
- const urlMatch = compileMatcher(urlPattern, "url");
4274
- const headerMatchers = respAction.headers ? Object.entries(respAction.headers).map(([name, pattern]) => ({
4275
- name: name.toLowerCase(),
4276
- test: compileMatcher(interpolate(pattern), "substr"),
4277
- pattern
4278
- })) : [];
4279
- const expectedStatus = respAction.status;
4280
- let sinceTs;
4281
- if (respAction.since === "testStart") {
4282
- sinceTs = 0;
4283
- } else if (typeof respAction.since === "number") {
4284
- sinceTs = respAction.since;
4285
- } else {
4286
- sinceTs = lastStepEndTs;
4287
- }
4288
- const timeout = respAction.timeout ?? 5e3;
4289
- const deadline = Date.now() + timeout;
4290
- const findMatch = () => {
4291
- for (const entry of responseLog.snapshot()) {
4292
- if (entry.ts < sinceTs) continue;
4293
- if (!urlMatch(entry.url)) continue;
4294
- if (expectedStatus !== void 0 && entry.status !== expectedStatus) continue;
4295
- let headersOk = true;
4296
- for (const h of headerMatchers) {
4297
- const value = entry.headers[h.name];
4298
- if (value === void 0 || !h.test(value)) {
4299
- headersOk = false;
4300
- break;
4301
- }
4302
- }
4303
- if (headersOk) return entry;
4304
- }
4305
- return null;
4306
- };
4307
- let match = findMatch();
4308
- while (!match && Date.now() < deadline) {
4309
- await new Promise((resolve) => setTimeout(resolve, 50));
4310
- match = findMatch();
4311
- }
4312
- if (!match) {
4313
- const recent = responseLog.snapshot().slice(-5).map((e) => `${e.status} ${e.url}`).join("\n ");
4314
- throw new Error(
4315
- `expectResponse timed out after ${timeout}ms.
4316
- url pattern: ${urlPattern}
4317
- ` + (expectedStatus !== void 0 ? ` expected status: ${expectedStatus}
4318
- ` : "") + (headerMatchers.length > 0 ? ` expected headers: ${headerMatchers.map((h) => `${h.name}=${h.pattern}`).join(", ")}
4319
- ` : "") + ` recent responses:
4320
- ${recent || "<none>"}`
4321
- );
4322
- }
4323
- if (debugMode) {
4324
- console.log(` [DEBUG] expectResponse matched: ${match.status} ${match.url}`);
4325
- }
4326
- results.push({ action, status: "passed" });
4327
- break;
4328
- }
4329
- case "setVar": {
4330
- let value;
4331
- if (action.value) {
4332
- value = interpolate(action.value);
4333
- } else if (action.from === "response") {
4334
- throw new Error("setVar from response not yet implemented");
4335
- } else if (action.from === "element") {
4336
- throw new Error("setVar from element not yet implemented");
4337
- } else if (action.from === "email") {
4338
- throw new Error("Use email.extractCode or email.extractLink instead");
4339
- } else {
4340
- throw new Error("setVar requires value or from");
4341
- }
4342
- context.variables.set(action.name, value);
4343
- if (debugMode) console.log(` [DEBUG] Set variable ${action.name} = ${value}`);
4344
- break;
4345
- }
4346
- case "email.waitFor": {
4347
- if (!context.emailClient) {
4348
- throw new Error("Email client not configured");
4349
- }
4350
- const mailbox = interpolate(action.mailbox);
4351
- context.lastEmail = await context.emailClient.waitForEmail(mailbox, {
4352
- timeout: action.timeout,
4353
- subjectContains: action.subjectContains
4354
- });
4355
- break;
4356
- }
4357
- case "email.extractCode": {
4358
- if (!context.emailClient) {
4359
- throw new Error("Email client not configured");
4360
- }
4361
- if (!context.lastEmail) {
4362
- throw new Error("No email loaded - call email.waitFor first");
4363
- }
4364
- const code = context.emailClient.extractCode(
4365
- context.lastEmail,
4366
- action.pattern ? new RegExp(action.pattern) : void 0
4367
- );
4368
- if (!code) {
4369
- throw new Error("No code found in email");
4370
- }
4371
- context.variables.set(action.saveTo, code);
4372
- break;
4373
- }
4374
- case "email.extractLink": {
4375
- if (!context.emailClient) {
4376
- throw new Error("Email client not configured");
4377
- }
4378
- if (!context.lastEmail) {
4379
- throw new Error("No email loaded - call email.waitFor first");
4380
- }
4381
- const link = context.emailClient.extractLink(
4382
- context.lastEmail,
4383
- action.pattern ? new RegExp(action.pattern) : void 0
4384
- );
4385
- if (!link) {
4386
- throw new Error("No link found in email");
4387
- }
4388
- context.variables.set(action.saveTo, link);
4389
- break;
4390
- }
4391
- case "email.clear": {
4392
- if (!context.emailClient) {
4393
- throw new Error("Email client not configured");
4394
- }
4395
- const mailbox = interpolate(action.mailbox);
4396
- await context.emailClient.clearMailbox(mailbox);
4397
- break;
4398
- }
4399
- case "appwrite.verifyEmail": {
4400
- if (!context.appwriteContext.userId) {
4401
- throw new Error("No user tracked. appwrite.verifyEmail requires a user signup first.");
4402
- }
4403
- if (!context.appwriteConfig?.apiKey) {
4404
- throw new Error("appwrite.verifyEmail requires appwrite.apiKey in config");
4405
- }
4406
- const { Client: Client2, Users: Users2 } = await import('node-appwrite');
4407
- const client = new Client2().setEndpoint(context.appwriteConfig.endpoint).setProject(context.appwriteConfig.projectId).setKey(context.appwriteConfig.apiKey);
4408
- const users = new Users2(client);
4409
- await users.updateEmailVerification(context.appwriteContext.userId, true);
4410
- if (debugMode) console.log(` [DEBUG] Verified email for user ${context.appwriteContext.userId}`);
4411
- break;
4412
- }
4413
- case "debug": {
4414
- console.log(" [DEBUG] Pausing execution - Playwright Inspector will open");
4415
- await page.pause();
4416
- break;
4417
- }
4418
- case "waitForSelector": {
4419
- const handle = resolveLocator2(action.target);
4420
- const timeout = action.timeout ?? 3e4;
4421
- if (debugMode) {
4422
- console.log(` [DEBUG] Waiting for element to be ${action.state}:`, action.target);
4423
- }
4424
- const waitForCondition2 = async (checkFn, timeoutMs, errorMessage) => {
4425
- const start = Date.now();
4426
- while (Date.now() - start < timeoutMs) {
4427
- if (await checkFn()) return;
4428
- await new Promise((r) => setTimeout(r, 100));
4429
- }
4430
- throw new Error(errorMessage);
4431
- };
4432
- switch (action.state) {
4433
- case "visible":
4434
- case "hidden":
4435
- case "attached":
4436
- case "detached":
4437
- await handle.waitFor({ state: action.state, timeout });
4438
- break;
4439
- case "enabled":
4440
- await waitForCondition2(
4441
- () => handle.isEnabled(),
4442
- timeout,
4443
- `Element did not become enabled within ${timeout}ms`
4444
- );
4445
- break;
4446
- case "disabled":
4447
- await waitForCondition2(
4448
- () => handle.isDisabled(),
4449
- timeout,
4450
- `Element did not become disabled within ${timeout}ms`
4451
- );
4452
- break;
4453
- }
4454
- break;
4455
- }
4456
- case "conditional": {
4457
- const handle = resolveLocator2(action.condition.target);
4458
- let conditionMet = false;
4459
- if (debugMode) {
4460
- console.log(` [DEBUG] Checking condition ${action.condition.type}:`, action.condition.target);
4461
- }
4462
- try {
4463
- switch (action.condition.type) {
4464
- case "exists":
4465
- await handle.waitFor({ state: "attached", timeout: 500 });
4466
- conditionMet = true;
4467
- break;
4468
- case "notExists":
4469
- try {
4470
- await handle.waitFor({ state: "detached", timeout: 500 });
4471
- conditionMet = true;
4472
- } catch {
4473
- conditionMet = false;
4474
- }
4475
- break;
4476
- case "visible":
4477
- conditionMet = await handle.isVisible();
4478
- break;
4479
- case "hidden":
4480
- conditionMet = !await handle.isVisible();
4481
- break;
4482
- }
4483
- } catch {
4484
- conditionMet = action.condition.type === "notExists";
4485
- }
4486
- if (debugMode) {
4487
- console.log(` [DEBUG] Condition result: ${conditionMet}`);
4488
- }
4489
- const stepsToRun = conditionMet ? action.then : action.else ?? [];
4490
- for (const nestedAction of stepsToRun) {
4491
- switch (nestedAction.type) {
4492
- case "screenshot": {
4493
- await page.waitForLoadState("domcontentloaded").catch(() => {
4494
- });
4495
- await page.waitForLoadState("networkidle", { timeout: 5e3 }).catch(() => {
4496
- });
4497
- const filename = nestedAction.name ?? `conditional-step.png`;
4498
- const filePath = path5__default.join(screenshotDir, filename);
4499
- await page.screenshot({ path: filePath, fullPage: true });
4500
- results.push({ action: nestedAction, status: "passed", screenshotPath: filePath });
4501
- const trackedPayload2 = buildTrackPayload(nestedAction, index, { screenshotPath: filePath });
4502
- if (trackedPayload2) {
4503
- await track(trackedPayload2);
4504
- }
4505
- break;
4506
- }
4507
- case "evaluate":
4508
- throw new Error("Evaluate action in nested context (conditional/waitForBranch) is not yet supported");
4509
- case "fail": {
4510
- throw new Error(nestedAction.message);
4511
- }
4512
- default:
4513
- throw new Error(`Nested action type ${nestedAction.type} in conditional not yet supported`);
4514
- }
4515
- }
4516
- break;
4517
- }
4518
- case "evaluate": {
4519
- const evalAction = action;
4520
- await page.waitForLoadState("domcontentloaded").catch(() => {
4521
- });
4522
- await page.waitForLoadState("networkidle", { timeout: 5e3 }).catch(() => {
4523
- });
4524
- const waitBefore = evalAction.waitBefore ?? 500;
4525
- if (waitBefore > 0) {
4526
- await page.waitForTimeout(waitBefore);
4527
- }
4528
- const evalScreenshotPath = path5__default.join(screenshotDir, `evaluate-step-${index + 1}.png`);
4529
- const screenshotBuffer = await page.screenshot({
4530
- path: evalScreenshotPath,
4531
- fullPage: evalAction.fullPage ?? true
4532
- });
4533
- const expectedRaw = evalAction.expected;
4534
- const expectedArray = Array.isArray(expectedRaw) ? expectedRaw.map((e) => interpolate(e)) : [interpolate(expectedRaw)];
4535
- const evalResult = await evaluate({
4536
- expected: expectedArray,
4537
- mode: evalAction.mode ?? "auto",
4538
- regex: evalAction.regex ?? false,
4539
- prompt: evalAction.prompt,
4540
- confidence: evalAction.confidence ?? 60,
4541
- screenshotBuffer,
4542
- screenshotPath: evalScreenshotPath,
4543
- aiConfig: options.aiConfig
4544
- });
4545
- if (debugMode) {
4546
- console.log(` [DEBUG] Evaluate result: ${evalResult.passed ? "PASSED" : "FAILED"} (${evalResult.mode})`);
4547
- console.log(` [DEBUG] Reason: ${evalResult.reason}`);
4548
- }
4549
- if (!evalResult.passed) {
4550
- throw new Error(`Evaluate failed (${evalResult.mode} mode): ${evalResult.reason}`);
4551
- }
4552
- results.push({
4553
- action,
4554
- status: "passed",
4555
- screenshotPath: evalScreenshotPath,
4556
- logOutput: `Evaluate passed (${evalResult.mode}): ${evalResult.reason}`
4557
- });
4558
- continue;
4559
- }
4560
- case "fail": {
4561
- throw new Error(action.message);
4562
- }
4563
- case "waitForBranch": {
4564
- const wfbAction = action;
4565
- const handle = resolveLocator2(wfbAction.target);
4566
- const timeout = wfbAction.timeout ?? 3e4;
4567
- const state = wfbAction.state ?? "visible";
4568
- const pollInterval = wfbAction.pollInterval ?? 100;
4569
- if (debugMode) {
4570
- console.log(` [DEBUG] waitForBranch: waiting for element to be ${state}:`, wfbAction.target);
4571
- }
4572
- const startTime = Date.now();
4573
- let elementAppeared = false;
4574
- while (Date.now() - startTime < timeout) {
4575
- try {
4576
- let conditionMet = false;
4577
- switch (state) {
4578
- case "visible":
4579
- conditionMet = await handle.isVisible();
4580
- break;
4581
- case "attached":
4582
- conditionMet = await handle.count() > 0;
4583
- break;
4584
- case "enabled":
4585
- conditionMet = await handle.isEnabled().catch(() => false);
4586
- break;
4587
- }
4588
- if (conditionMet) {
4589
- elementAppeared = true;
4590
- break;
4591
- }
4592
- } catch {
4593
- }
4594
- await new Promise((r) => setTimeout(r, pollInterval));
4595
- }
4596
- if (debugMode) {
4597
- console.log(` [DEBUG] waitForBranch: element ${elementAppeared ? "appeared" : "timed out"}`);
4598
- }
4599
- const branch = elementAppeared ? wfbAction.onAppear : wfbAction.onTimeout;
4600
- if (branch) {
4601
- if (Array.isArray(branch)) {
4602
- for (const nestedAction of branch) {
4603
- if (debugMode) {
4604
- console.log(` [DEBUG] waitForBranch: executing nested action ${nestedAction.type}`);
4605
- }
4606
- switch (nestedAction.type) {
4607
- case "navigate": {
4608
- const interpolated = interpolate(nestedAction.value);
4609
- const baseUrl = test.config?.web?.baseUrl || workflowBaseUrl;
4610
- const target = resolveUrl2(interpolated, baseUrl);
4611
- await page.goto(target);
4612
- break;
4613
- }
4614
- case "tap": {
4615
- const nestedHandle = resolveLocator2(nestedAction.target);
4616
- await nestedHandle.click();
4617
- await page.waitForLoadState("domcontentloaded").catch(() => {
4618
- });
4619
- await page.waitForLoadState("networkidle", { timeout: 1e4 }).catch(() => {
4620
- });
4621
- break;
4622
- }
4623
- case "input": {
4624
- const interpolated = interpolate(nestedAction.value);
4625
- const nestedHandle = resolveLocator2(nestedAction.target);
4626
- await nestedHandle.fill(interpolated);
4627
- break;
4628
- }
4629
- case "screenshot": {
4630
- await page.waitForLoadState("domcontentloaded").catch(() => {
4631
- });
4632
- await page.waitForLoadState("networkidle", { timeout: 5e3 }).catch(() => {
4633
- });
4634
- const nestedSsAction = nestedAction;
4635
- const nestedWaitBefore = nestedSsAction.waitBefore ?? 500;
4636
- if (nestedWaitBefore > 0) {
4637
- await page.waitForTimeout(nestedWaitBefore);
4638
- }
4639
- const filename = nestedSsAction.name ?? `waitForBranch-step.png`;
4640
- const filePath = path5__default.join(screenshotDir, filename);
4641
- await page.screenshot({ path: filePath, fullPage: true });
4642
- results.push({ action: nestedAction, status: "passed", screenshotPath: filePath });
4643
- const trackedPayload2 = buildTrackPayload(nestedAction, index, { screenshotPath: filePath });
4644
- if (trackedPayload2) {
4645
- await track(trackedPayload2);
4646
- }
4647
- break;
4648
- }
4649
- case "wait": {
4650
- if (nestedAction.target) {
4651
- const nestedHandle = resolveLocator2(nestedAction.target);
4652
- await nestedHandle.waitFor({ state: "visible", timeout: nestedAction.timeout });
4653
- } else {
4654
- await page.waitForTimeout(nestedAction.timeout ?? 1e3);
4655
- }
4656
- break;
4657
- }
4658
- case "evaluate":
4659
- throw new Error("Evaluate action in nested context (conditional/waitForBranch) is not yet supported");
4660
- case "fail": {
4661
- throw new Error(nestedAction.message);
4662
- }
4663
- case "setVar": {
4664
- let value;
4665
- if (nestedAction.value) {
4666
- value = interpolate(nestedAction.value);
4667
- } else {
4668
- throw new Error("setVar in waitForBranch requires value");
4669
- }
4670
- context.variables.set(nestedAction.name, value);
4671
- if (debugMode) console.log(` [DEBUG] Set variable ${nestedAction.name} = ${value}`);
4672
- break;
4673
- }
4674
- case "assert": {
4675
- const nestedHandle = resolveLocator2(nestedAction.target);
4676
- await nestedHandle.waitFor({ state: "visible" });
4677
- if (nestedAction.value) {
4678
- const interpolated = interpolate(nestedAction.value);
4679
- const text = (await nestedHandle.textContent())?.trim() ?? "";
4680
- if (!text.includes(interpolated)) {
4681
- throw new Error(
4682
- `Assertion failed: expected "${interpolated}", got "${text}"`
4683
- );
4684
- }
4685
- }
4686
- break;
4687
- }
4688
- default:
4689
- throw new Error(`Nested action type ${nestedAction.type} in waitForBranch not yet supported`);
4690
- }
4691
- if (nestedAction.type !== "screenshot") {
4692
- const trackedPayload2 = buildTrackPayload(nestedAction, index);
4693
- if (trackedPayload2) {
4694
- await track(trackedPayload2);
4695
- }
4696
- }
4697
- results.push({ action: nestedAction, status: "passed" });
4698
- }
4699
- } else if (typeof branch === "object" && "workflow" in branch) {
4700
- const workflowPath = path5__default.resolve(workflowDir, branch.workflow);
4701
- if (debugMode) {
4702
- console.log(` [DEBUG] waitForBranch: loading workflow from ${workflowPath}`);
4703
- }
4704
- const { loadWorkflowDefinition } = await import('./loader-2CW6OEXJ.js');
4705
- const nestedWorkflow = await loadWorkflowDefinition(workflowPath);
4706
- if (branch.variables) {
4707
- for (const [key, value] of Object.entries(branch.variables)) {
4708
- const interpolated = interpolate(value);
4709
- context.variables.set(key, interpolated);
4710
- }
4711
- }
4712
- for (const testRef of nestedWorkflow.tests) {
4713
- const testFilePath2 = path5__default.resolve(path5__default.dirname(workflowPath), testRef.file);
4714
- const nestedTest = await loadTestDefinition(testFilePath2);
4715
- if (nestedTest.variables) {
4716
- for (const [key, value] of Object.entries(nestedTest.variables)) {
4717
- const interpolated = interpolateVariables(value, context.variables);
4718
- context.variables.set(key, interpolated);
4719
- }
4720
- }
4721
- const nestedResult = await runTestInWorkflow(
4722
- nestedTest,
4723
- page,
4724
- context,
4725
- options,
4726
- path5__default.dirname(workflowPath),
4727
- testFilePath2,
4728
- nestedWorkflow.config?.web?.baseUrl ?? workflowBaseUrl
4729
- );
4730
- results.push(...nestedResult.steps);
4731
- if (nestedResult.status === "failed") {
4732
- throw new Error(`Nested workflow test failed in waitForBranch`);
4733
- }
4734
- }
4735
- }
4736
- } else if (!elementAppeared && debugMode) {
4737
- console.log(` [DEBUG] waitForBranch: timeout occurred but no onTimeout branch defined, continuing silently`);
4738
- }
4739
- break;
4740
- }
4741
- default:
4742
- throw new Error(`Unsupported action type: ${action.type}`);
4743
- }
4744
- const trackedPayload = buildTrackPayload(action, index);
4745
- if (trackedPayload) {
4746
- await track(trackedPayload);
4747
- }
4748
- results.push({ action, status: "passed" });
3943
+ const actionExtras = await executeActionWithRetry(page, action, index, {
3944
+ baseUrl,
3945
+ context,
3946
+ screenshotDir,
3947
+ debugMode,
3948
+ interactive: false,
3949
+ aiConfig: options.aiConfig,
3950
+ browserName,
3951
+ testFilePath,
3952
+ responseLog,
3953
+ stepStartTs
3954
+ });
3955
+ results.push({ action, status: "passed", logOutput: actionExtras.logOutput });
4749
3956
  } catch (error) {
4750
3957
  const message = error instanceof Error ? error.message : String(error);
4751
3958
  results.push({ action, status: "failed", error: message });
@@ -4948,20 +4155,21 @@ function inferCleanupConfig(config) {
4948
4155
  }
4949
4156
  async function runWorkflowWithContext(workflow, workflowFilePath, options) {
4950
4157
  const { page, executionContext, skipCleanup = false, sessionId: providedSessionId, testStartTime: providedTestStartTime } = options;
4951
- const workflowDir = path5__default.dirname(workflowFilePath);
4158
+ const workflowDir = path4__default.dirname(workflowFilePath);
4952
4159
  const sessionId = providedSessionId ?? crypto4.randomUUID();
4953
4160
  const testStartTime = providedTestStartTime ?? (/* @__PURE__ */ new Date()).toISOString();
4954
4161
  console.log(`
4955
4162
  Starting workflow: ${workflow.name}`);
4956
4163
  console.log(`Session ID: ${sessionId}
4957
4164
  `);
4958
- if (workflow.config?.appwrite) {
4165
+ const effectiveAppwriteConfig = workflow.config?.appwrite ? {
4166
+ endpoint: workflow.config.appwrite.endpoint,
4167
+ projectId: workflow.config.appwrite.projectId,
4168
+ apiKey: workflow.config.appwrite.apiKey
4169
+ } : options.appwriteConfig;
4170
+ if (effectiveAppwriteConfig) {
4959
4171
  if (!executionContext.appwriteConfig) {
4960
- executionContext.appwriteConfig = {
4961
- endpoint: workflow.config.appwrite.endpoint,
4962
- projectId: workflow.config.appwrite.projectId,
4963
- apiKey: workflow.config.appwrite.apiKey
4964
- };
4172
+ executionContext.appwriteConfig = effectiveAppwriteConfig;
4965
4173
  }
4966
4174
  setupAppwriteTracking(page, executionContext);
4967
4175
  }
@@ -4976,7 +4184,7 @@ Starting workflow: ${workflow.name}`);
4976
4184
  const testResults = [];
4977
4185
  let workflowFailed = false;
4978
4186
  for (const [index, testRef] of workflow.tests.entries()) {
4979
- const testFilePath = path5__default.resolve(workflowDir, testRef.file);
4187
+ const testFilePath = path4__default.resolve(workflowDir, testRef.file);
4980
4188
  console.log(`
4981
4189
  [${index + 1}/${workflow.tests.length}] Running: ${testRef.file}`);
4982
4190
  if (testRef.id) {
@@ -5143,7 +4351,7 @@ ${"=".repeat(60)}`);
5143
4351
  };
5144
4352
  }
5145
4353
  async function runWorkflow(workflow, workflowFilePath, options = {}) {
5146
- const workflowDir = path5__default.dirname(workflowFilePath);
4354
+ const workflowDir = path4__default.dirname(workflowFilePath);
5147
4355
  const sessionId = options.sessionId ?? crypto4.randomUUID();
5148
4356
  const testStartTime = (/* @__PURE__ */ new Date()).toISOString();
5149
4357
  const cleanupConfig = inferCleanupConfig(workflow.config);
@@ -5163,15 +4371,20 @@ async function runWorkflow(workflow, workflowFilePath, options = {}) {
5163
4371
  process.env.INTELLITESTER_SESSION_ID = sessionId;
5164
4372
  process.env.INTELLITESTER_TRACK_URL = `http://localhost:${trackingServer.port}`;
5165
4373
  }
4374
+ const trackingAppwrite = workflow.config?.appwrite ? {
4375
+ endpoint: workflow.config.appwrite.endpoint,
4376
+ projectId: workflow.config.appwrite.projectId,
4377
+ apiKey: workflow.config.appwrite.apiKey
4378
+ } : options.appwriteConfig;
5166
4379
  fileTracking = await initFileTracking({
5167
4380
  sessionId,
5168
4381
  cleanupConfig,
5169
4382
  trackDir: options.trackDir,
5170
- providerConfig: workflow.config?.appwrite ? {
4383
+ providerConfig: trackingAppwrite ? {
5171
4384
  provider: "appwrite",
5172
- endpoint: workflow.config.appwrite.endpoint,
5173
- projectId: workflow.config.appwrite.projectId,
5174
- apiKey: workflow.config.appwrite.apiKey
4385
+ endpoint: trackingAppwrite.endpoint,
4386
+ projectId: trackingAppwrite.projectId,
4387
+ apiKey: trackingAppwrite.apiKey
5175
4388
  } : void 0
5176
4389
  });
5177
4390
  process.env.INTELLITESTER_TRACK_FILE = fileTracking.trackFile;
@@ -5195,7 +4408,7 @@ async function runWorkflow(workflow, workflowFilePath, options = {}) {
5195
4408
  }
5196
4409
  const normalized = wsEntries.map((entry) => ({
5197
4410
  ...entry,
5198
- workdir: path5__default.resolve(serverCwd, entry.workdir ?? entry.cwd ?? "."),
4411
+ workdir: path4__default.resolve(serverCwd, entry.workdir ?? entry.cwd ?? "."),
5199
4412
  ...shouldForceNoReuse ? { reuseExistingServer: false } : {}
5200
4413
  }));
5201
4414
  await webServerManager.start(normalized.length === 1 ? normalized[0] : normalized);
@@ -5219,12 +4432,13 @@ async function runWorkflow(workflow, workflowFilePath, options = {}) {
5219
4432
  };
5220
4433
  process.on("SIGINT", signalCleanup);
5221
4434
  process.on("SIGTERM", signalCleanup);
5222
- const browserName = options.browser ?? workflow.config?.web?.browser ?? "chromium";
4435
+ const browserName = workflow.config?.web?.browser ?? options.browser ?? "chromium";
5223
4436
  const headless = options.headed === true ? false : workflow.config?.web?.headless ?? true;
5224
4437
  console.log(`Launching ${browserName}${headless ? " (headless)" : " (visible)"}...`);
5225
4438
  const browser = await getBrowser2(browserName).launch(getBrowserLaunchOptions({ headless, browser: browserName }));
5226
4439
  console.log(`Browser launched successfully`);
5227
- const testSizes = options.testSizes && options.testSizes.length > 0 ? options.testSizes : ["1920x1080"];
4440
+ const workflowTestSizes = workflow.config?.web?.testSizes;
4441
+ const testSizes = workflowTestSizes && workflowTestSizes.length > 0 ? workflowTestSizes : options.testSizes && options.testSizes.length > 0 ? options.testSizes : ["1920x1080"];
5228
4442
  const viewportSizes = [];
5229
4443
  for (const size of testSizes) {
5230
4444
  const viewport = parseViewportSize(size);
@@ -5237,11 +4451,12 @@ async function runWorkflow(workflow, workflowFilePath, options = {}) {
5237
4451
  }
5238
4452
  const allTestResults = [];
5239
4453
  let anyFailed = false;
5240
- const cliStorageState = typeof options.storageState === "string" ? path5__default.isAbsolute(options.storageState) ? options.storageState : path5__default.resolve(process.cwd(), options.storageState) : options.storageState;
5241
- const storageState = cliStorageState ?? resolveStorageStatePath(
4454
+ const optionsStorageState = typeof options.storageState === "string" ? path4__default.isAbsolute(options.storageState) ? options.storageState : path4__default.resolve(process.cwd(), options.storageState) : options.storageState;
4455
+ const workflowStorageState = resolveStorageStatePath(
5242
4456
  workflow.config?.web?.storageState,
5243
4457
  workflowDir
5244
4458
  );
4459
+ const storageState = workflowStorageState ?? optionsStorageState;
5245
4460
  let browserContext = await browser.newContext({
5246
4461
  viewport: viewportSizes[0].viewport,
5247
4462
  ...storageState ? { storageState } : {}
@@ -5257,7 +4472,7 @@ async function runWorkflow(workflow, workflowFilePath, options = {}) {
5257
4472
  endpoint: workflow.config.appwrite.endpoint,
5258
4473
  projectId: workflow.config.appwrite.projectId,
5259
4474
  apiKey: workflow.config.appwrite.apiKey
5260
- } : void 0
4475
+ } : options.appwriteConfig
5261
4476
  };
5262
4477
  if (workflow.variables) {
5263
4478
  for (const [key, value] of Object.entries(workflow.variables)) {
@@ -5430,6 +4645,6 @@ Collected ${serverResources.length} server-tracked resources`);
5430
4645
  // src/executors/web/index.ts
5431
4646
  init_esm_shims();
5432
4647
 
5433
- export { buildCompletionOptions, buildModel, createTestContext, generateFillerText, generateRandomEmail, generateRandomPhone, generateRandomPhoto, generateRandomUsername, getBrowserLaunchOptions, initFileTracking, interpolateVariables, mergeFileTrackedResources, parseViewportSize, resolveStorageStatePath, runWebTest, runWorkflow, runWorkflowWithContext, setupAppwriteTracking, startTrackingServer, webServerManager };
5434
- //# sourceMappingURL=chunk-VWXGXFIM.js.map
5435
- //# sourceMappingURL=chunk-VWXGXFIM.js.map
4648
+ export { buildCompletionOptions, buildModel, createTestContext, executeActionWithRetry, generateFillerText, generateRandomEmail, generateRandomPhone, generateRandomPhoto, generateRandomUsername, getBrowserLaunchOptions, initFileTracking, interpolateVariables, mergeFileTrackedResources, parseViewportSize, resolveStorageStatePath, runWebTest, runWorkflow, runWorkflowWithContext, setupAppwriteTracking, startTrackingServer, webServerManager };
4649
+ //# sourceMappingURL=chunk-7UYD5Q7D.js.map
4650
+ //# sourceMappingURL=chunk-7UYD5Q7D.js.map