testchimp-runner-core 0.1.8 → 0.1.10

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.
@@ -1 +1 @@
1
- {"version":3,"file":"execution-service.d.ts","sourceRoot":"","sources":["../src/execution-service.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,0BAA0B,EAC1B,2BAA2B,EAE3B,sBAAsB,EACtB,uBAAuB,EAKvB,wBAAwB,EACxB,yBAAyB,EAG1B,MAAM,SAAS,CAAC;AAKjB,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAW3C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAylBvD;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAC5C,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,uBAAuB,CAAS;IACxC,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,MAAM,CAAC,CAA8D;IAC7E,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,cAAc,CAAkB;IAExC;;OAEG;IACH,aAAa;gBAKX,UAAU,CAAC,EAAE,UAAU,EACvB,UAAU,CAAC,EAAE,MAAM,EACnB,uBAAuB,GAAE,MAAW,EACpC,WAAW,CAAC,EAAE,WAAW,EACzB,gBAAgB,CAAC,EAAE,gBAAgB;IA8BrC;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,KAAK,IAAI,GAAG,IAAI;IAKpF;;OAEG;IACH,OAAO,CAAC,GAAG;IAOX,OAAO,CAAC,WAAW;IAanB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC;;;OAGG;IACH,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAQ3C;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAkBtF;;OAEG;YACW,qBAAqB;IAmBnC;;OAEG;IACG,gBAAgB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAgCjG;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAsC7B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;IACH,OAAO,IAAI,OAAO;IAIlB;;;;OAIG;YACW,+BAA+B;IAmkB7C;;;OAGG;YACW,iBAAiB;YAiGjB,UAAU;YAoLV,eAAe;YAqKf,oBAAoB;YAiBpB,eAAe;IAuC7B,OAAO,CAAC,qBAAqB;IAK7B;;OAEG;YACW,iBAAiB;IAI/B;;;;OAIG;IACG,WAAW,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAqrBxF;;;;;OAKG;YACW,WAAW;CAkI1B"}
1
+ {"version":3,"file":"execution-service.d.ts","sourceRoot":"","sources":["../src/execution-service.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,0BAA0B,EAC1B,2BAA2B,EAE3B,sBAAsB,EACtB,uBAAuB,EAKvB,wBAAwB,EACxB,yBAAyB,EAG1B,MAAM,SAAS,CAAC;AAKjB,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAW3C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAmbvD;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAC5C,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,uBAAuB,CAAS;IACxC,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,MAAM,CAAC,CAA8D;IAC7E,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,cAAc,CAAkB;IAExC;;OAEG;IACH,aAAa;gBAKX,UAAU,CAAC,EAAE,UAAU,EACvB,UAAU,CAAC,EAAE,MAAM,EACnB,uBAAuB,GAAE,MAAW,EACpC,WAAW,CAAC,EAAE,WAAW,EACzB,gBAAgB,CAAC,EAAE,gBAAgB;IA8BrC;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,KAAK,IAAI,GAAG,IAAI;IAKpF;;OAEG;IACH,OAAO,CAAC,GAAG;IAOX,OAAO,CAAC,WAAW;IAanB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC;;;OAGG;IACH,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAQ3C;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAkBtF;;OAEG;YACW,qBAAqB;IAmBnC;;OAEG;IACG,gBAAgB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAgCjG;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAsC7B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;IACH,OAAO,IAAI,OAAO;IAIlB;;;;OAIG;YACW,+BAA+B;IA4lB7C;;;OAGG;YACW,iBAAiB;YAiGjB,UAAU;YAkLV,eAAe;YAoKf,oBAAoB;YAiBpB,eAAe;IAuC7B,OAAO,CAAC,qBAAqB;IAK7B;;OAEG;YACW,iBAAiB;IAI/B;;;;OAIG;IACG,WAAW,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAorBxF;;;;;OAKG;YACW,WAAW;CAgI1B"}
@@ -56,13 +56,6 @@ const build_info_1 = require("./build-info");
56
56
  const orchestrator_1 = require("./orchestrator");
57
57
  const test_file_parser_1 = require("./utils/test-file-parser");
58
58
  class ModuleResolutionManager {
59
- /**
60
- * Get the current module resolution context from AsyncLocalStorage
61
- * Public method to allow requireWrapper to access the context
62
- */
63
- static getContext() {
64
- return this.asyncLocalStorage.getStore();
65
- }
66
59
  /**
67
60
  * Install the global hook (only once, shared by all instances)
68
61
  */
@@ -84,7 +77,7 @@ class ModuleResolutionManager {
84
77
  const nodeModulesDir = path.dirname(runnerCoreDir);
85
78
  if (fs.existsSync(nodeModulesDir) && path.basename(nodeModulesDir) === 'node_modules') {
86
79
  this.scriptserviceNodeModules = nodeModulesDir;
87
- // Note: This runs at module load time, before we have a logger, so we can't log here
80
+ console.log(`[ModuleResolution] Cached scriptservice node_modules: ${this.scriptserviceNodeModules}`);
88
81
  }
89
82
  }
90
83
  catch (e) {
@@ -94,14 +87,12 @@ class ModuleResolutionManager {
94
87
  Module._resolveFilename = function (request, parent, isMain, options) {
95
88
  // Get resolution context for current execution
96
89
  const context = ModuleResolutionManager.asyncLocalStorage.getStore();
97
- const log = context?.log || (() => { }); // No-op if no logger available
98
- // Log all module resolution attempts (even without context, for debugging)
99
- log(`[ModuleResolution] Hook intercepted: "${request}" (parent: ${parent?.filename || 'none'}, hasContext: ${!!context})`);
100
90
  // If no context, use original resolution (for requires outside our execution context)
101
91
  if (!context) {
102
- log(`[ModuleResolution] No context available, using original resolution`);
103
92
  return ModuleResolutionManager.originalResolveFilename.call(this, request, parent, isMain, options);
104
93
  }
94
+ // Log all module resolution attempts when we have context
95
+ console.log(`[ModuleResolution] Hook intercepted: "${request}" (parent: ${parent?.filename || 'none'})`);
105
96
  // Handle relative imports
106
97
  if (request.startsWith('.')) {
107
98
  // Use standard Node.js relative path resolution
@@ -109,27 +100,27 @@ class ModuleResolutionManager {
109
100
  // Resolve relative to testFileDir (the directory where the test file is located)
110
101
  const resolveDir = context.testFileDir || (parent?.filename ? path.dirname(parent.filename) : context.tempDir);
111
102
  // Log module resolution attempt
112
- log(`[ModuleResolution] Resolving relative import: "${request}"`);
113
- log(`[ModuleResolution] resolveDir: ${resolveDir}`);
103
+ console.log(`[ModuleResolution] Resolving relative import: "${request}"`);
104
+ console.log(`[ModuleResolution] resolveDir: ${resolveDir}`);
114
105
  // Resolve the relative path using standard Node.js resolution
115
106
  // This will correctly resolve based on the actual folder structure in tempDir
116
107
  const resolvedPath = path.resolve(resolveDir, request);
117
- log(`[ModuleResolution] resolvedPath: ${resolvedPath}`);
108
+ console.log(`[ModuleResolution] resolvedPath: ${resolvedPath}`);
118
109
  // Try extensions in order: .page.ts, .page.js (POM files)
119
110
  const extensions = ['.page.ts', '.page.js'];
120
111
  for (const ext of extensions) {
121
112
  const testPath = resolvedPath + ext;
122
113
  try {
123
114
  if (fs.existsSync(testPath)) {
124
- log(`[ModuleResolution] ✓ Found file: ${testPath}`);
115
+ console.log(`[ModuleResolution] ✓ Found file: ${testPath}`);
125
116
  return testPath;
126
117
  }
127
118
  else {
128
- log(`[ModuleResolution] ✗ File does not exist: ${testPath}`);
119
+ console.log(`[ModuleResolution] ✗ File does not exist: ${testPath}`);
129
120
  }
130
121
  }
131
122
  catch (e) {
132
- log(`[ModuleResolution] ✗ Error checking file: ${testPath} - ${e}`, 'error');
123
+ console.log(`[ModuleResolution] ✗ Error checking file: ${testPath} - ${e}`);
133
124
  }
134
125
  }
135
126
  // If no extension worked, try original resolution
@@ -153,95 +144,45 @@ class ModuleResolutionManager {
153
144
  }
154
145
  }
155
146
  // For non-relative imports (like 'ai-wright', '@playwright/test'), use standard Node.js resolution
156
- // These should resolve from node_modules in the workspace root (where the test files are)
157
- log(`[ModuleResolution] Resolving non-relative import: "${request}"`);
158
- log(`[ModuleResolution] tempDir: ${context.tempDir}, testFileDir: ${context.testFileDir || 'undefined'}`);
159
- // Find workspace root by looking for node_modules or package.json going up from tempDir
160
- // tempDir is the tests folder (e.g., /workspace/tests), so we need to find /workspace/node_modules
161
- let workspaceRoot = null;
162
- if (context.tempDir) {
163
- let currentDir = context.tempDir;
164
- const maxDepth = 10; // Prevent infinite loops
165
- let depth = 0;
166
- log(`[ModuleResolution] Searching for workspace root starting from: ${currentDir}`);
167
- while (depth < maxDepth && currentDir !== path.dirname(currentDir)) {
168
- const nodeModulesPath = path.join(currentDir, 'node_modules');
169
- const packageJsonPath = path.join(currentDir, 'package.json');
170
- log(`[ModuleResolution] Checking: ${currentDir} (depth: ${depth})`);
171
- if (fs.existsSync(nodeModulesPath) || fs.existsSync(packageJsonPath)) {
172
- workspaceRoot = currentDir;
173
- log(`[ModuleResolution] ✓ Found workspace root: ${workspaceRoot}`);
174
- break;
175
- }
176
- currentDir = path.dirname(currentDir);
177
- depth++;
178
- }
179
- if (!workspaceRoot) {
180
- log(`[ModuleResolution] ✗ Workspace root not found after ${depth} levels`, 'warn');
181
- }
182
- }
183
- else {
184
- log(`[ModuleResolution] ✗ No tempDir in context`, 'warn');
185
- }
186
- // Temporarily modify Module._nodeModulePaths to include workspace root's node_modules
187
- // This ensures Node.js searches the workspace root for modules like @playwright/test and ai-wright
188
- const originalNodeModulePaths = Module._nodeModulePaths;
189
- let nodeModulePathsModified = false;
190
- if (workspaceRoot) {
191
- const workspaceNodeModules = path.join(workspaceRoot, 'node_modules');
192
- log(`[ModuleResolution] Workspace node_modules: ${workspaceNodeModules}`);
193
- Module._nodeModulePaths = function (from) {
194
- const paths = originalNodeModulePaths.call(this, from);
195
- // Prepend workspace root's node_modules to the search paths (highest priority)
196
- if (!paths.includes(workspaceNodeModules)) {
197
- paths.unshift(workspaceNodeModules);
198
- log(`[ModuleResolution] Modified _nodeModulePaths: prepended ${workspaceNodeModules}`);
199
- }
200
- return paths;
201
- };
202
- nodeModulePathsModified = true;
203
- log(`[ModuleResolution] Modified _nodeModulePaths to include workspace root: ${workspaceNodeModules}`);
204
- }
147
+ // These should resolve from node_modules where runner-core is installed (scriptservice's node_modules)
148
+ console.log(`[ModuleResolution] Resolving non-relative import: "${request}"`);
205
149
  try {
206
- // Use original Node.js resolution (will now search workspace root's node_modules first)
207
- log(`[ModuleResolution] Attempting resolution with modified paths...`);
150
+ // Use original Node.js resolution (will find from node_modules)
208
151
  const resolved = ModuleResolutionManager.originalResolveFilename.call(this, request, parent, isMain, options);
209
- log(`[ModuleResolution] ✓ Resolved to: ${resolved}`);
210
- // Restore original _nodeModulePaths
211
- if (nodeModulePathsModified) {
212
- Module._nodeModulePaths = originalNodeModulePaths;
213
- }
152
+ console.log(`[ModuleResolution] ✓ Resolved to: ${resolved}`);
214
153
  return resolved;
215
154
  }
216
155
  catch (err) {
217
- // Restore original _nodeModulePaths before handling error
218
- if (nodeModulePathsModified) {
219
- Module._nodeModulePaths = originalNodeModulePaths;
220
- }
221
- log(`[ModuleResolution] ✗ Failed to resolve: ${err.message}`, 'error');
222
- // Fallback: try scriptservice's node_modules (for server-side usage)
156
+ console.log(`[ModuleResolution] ✗ Failed to resolve: ${err.message}`);
157
+ // Build enhanced paths that include scriptservice's node_modules
158
+ // Find node_modules by looking up from the execution-service.js location
159
+ const enhancedPaths = [...(options?.paths || [])];
160
+ // Add tempDir paths
161
+ if (context.tempDir) {
162
+ enhancedPaths.push(context.tempDir);
163
+ enhancedPaths.push(path.join(context.tempDir, 'node_modules'));
164
+ }
165
+ // Add scriptservice's node_modules (where runner-core is installed)
166
+ // Use the cached path from execution-service.js location
223
167
  if (ModuleResolutionManager.scriptserviceNodeModules) {
224
- log(`[ModuleResolution] Trying scriptservice node_modules fallback...`);
225
- try {
226
- // Manually check if module exists in scriptservice's node_modules
227
- const scriptserviceModulePath = path.join(ModuleResolutionManager.scriptserviceNodeModules, request);
228
- // Check for both directory and file modules
229
- if (fs.existsSync(scriptserviceModulePath) ||
230
- fs.existsSync(scriptserviceModulePath + '.js') ||
231
- fs.existsSync(scriptserviceModulePath + '.json')) {
232
- log(`[ModuleResolution] ✓ Found in scriptservice node_modules: ${scriptserviceModulePath}`);
233
- return scriptserviceModulePath;
234
- }
235
- else {
236
- log(`[ModuleResolution] ✗ Not found in scriptservice node_modules: ${scriptserviceModulePath}`);
237
- }
238
- }
239
- catch (fallbackErr) {
240
- log(`[ModuleResolution] ✗ Fallback error: ${fallbackErr.message}`, 'error');
241
- // Continue to throw original error
242
- }
168
+ enhancedPaths.push(ModuleResolutionManager.scriptserviceNodeModules);
169
+ console.log(`[ModuleResolution] Added scriptservice node_modules to paths: ${ModuleResolutionManager.scriptserviceNodeModules}`);
170
+ }
171
+ // Try with enhanced paths
172
+ try {
173
+ const enhancedOptions = {
174
+ ...options,
175
+ paths: enhancedPaths
176
+ };
177
+ const resolved = ModuleResolutionManager.originalResolveFilename.call(this, request, parent, isMain, enhancedOptions);
178
+ console.log(`[ModuleResolution] ✓ Resolved (with enhanced paths): ${resolved}`);
179
+ return resolved;
180
+ }
181
+ catch (resolveErr) {
182
+ console.log(`[ModuleResolution] ✗ Failed with enhanced paths: ${resolveErr}`);
183
+ // Fall back to original error
184
+ throw err;
243
185
  }
244
- throw err;
245
186
  }
246
187
  };
247
188
  this.hookInstalled = true;
@@ -253,9 +194,6 @@ class ModuleResolutionManager {
253
194
  return this.asyncLocalStorage.run({ tempDir }, fn);
254
195
  }
255
196
  static runWithContext(context, fn) {
256
- // Log when context is set
257
- const log = context.log || (() => { });
258
- log(`[ModuleResolutionManager] Setting context: tempDir=${context.tempDir}, testFileDir=${context.testFileDir || 'undefined'}`);
259
197
  return this.asyncLocalStorage.run(context, fn);
260
198
  }
261
199
  }
@@ -268,97 +206,13 @@ ModuleResolutionManager.scriptserviceNodeModules = null;
268
206
  * Variables defined in earlier steps remain available in later steps without re-evaluation
269
207
  */
270
208
  class PersistentExecutionContext {
271
- constructor(page, expect, test, ai, browser, browserContext, tempDir, log) {
209
+ constructor(page, expect, test, ai, browser, browserContext, tempDir) {
272
210
  this.tempDir = tempDir;
273
- this.log = log;
274
- // Find workspace root by looking for node_modules or package.json going up from tempDir
275
- // tempDir is the tests folder (e.g., /workspace/tests), so we need to find /workspace/node_modules
276
- const path = require('path');
277
- const Module = require('module');
278
- let workspaceRoot = null;
279
- if (tempDir) {
280
- const fs = require('fs');
281
- let currentDir = tempDir;
282
- const maxDepth = 10; // Prevent infinite loops
283
- let depth = 0;
284
- while (depth < maxDepth && currentDir !== path.dirname(currentDir)) {
285
- const nodeModulesPath = path.join(currentDir, 'node_modules');
286
- const packageJsonPath = path.join(currentDir, 'package.json');
287
- if (fs.existsSync(nodeModulesPath) || fs.existsSync(packageJsonPath)) {
288
- workspaceRoot = currentDir;
289
- this.log?.(`[PersistentExecutionContext] Found workspace root: ${workspaceRoot}`);
290
- break;
291
- }
292
- currentDir = path.dirname(currentDir);
293
- depth++;
294
- }
295
- if (!workspaceRoot) {
296
- this.log?.(`[PersistentExecutionContext] Workspace root not found, tempDir: ${tempDir}`);
297
- }
298
- }
299
- this.workspaceRoot = workspaceRoot || undefined;
300
211
  // Install global hook if not already installed (only happens once)
301
212
  if (tempDir) {
302
213
  ModuleResolutionManager.installHook();
303
214
  this.setupTypeScriptSupport(tempDir);
304
215
  }
305
- // Create a require wrapper that ensures the hook is triggered
306
- // When require is passed directly to vm.createContext(), it may not use the hooked Module._resolveFilename
307
- // We manually invoke Module._resolveFilename (our hook) when context is available
308
- const parentRequire = require;
309
- const requireWrapper = (function (parentReq, logFn) {
310
- return function (id) {
311
- // Log when wrapper is called
312
- logFn?.(`[RequireWrapper] Wrapper called for: "${id}"`);
313
- // Get the current AsyncLocalStorage context
314
- const context = ModuleResolutionManager.getContext();
315
- if (context) {
316
- // We have context, so manually trigger our hook logic by calling Module._resolveFilename
317
- logFn?.(`[RequireWrapper] Context available, manually invoking Module._resolveFilename for: "${id}"`);
318
- try {
319
- // Create a fake parent module object for resolution
320
- // Use testFileDir or tempDir as the parent's filename to ensure relative paths resolve correctly
321
- const parentFilename = context.testFileDir
322
- ? path.join(context.testFileDir, 'dummy.js')
323
- : (context.tempDir ? path.join(context.tempDir, 'dummy.js') : __filename);
324
- // Manually call the hooked Module._resolveFilename to get the resolved path
325
- // This will trigger our custom resolution logic (e.g., .page.ts extensions)
326
- const resolvedPath = Module._resolveFilename(id, { filename: parentFilename }, false);
327
- logFn?.(`[RequireWrapper] Resolved path: ${resolvedPath}`);
328
- // Now load the module using Module._load with the resolved path
329
- const module = Module._load(resolvedPath, { filename: parentFilename }, false);
330
- logFn?.(`[RequireWrapper] Successfully loaded: "${id}"`);
331
- return module;
332
- }
333
- catch (err) {
334
- logFn?.(`[RequireWrapper] Manual resolution failed for: "${id}" - ${err.message}`, 'error');
335
- // Fall back to standard require
336
- try {
337
- const result = parentReq(id);
338
- logFn?.(`[RequireWrapper] Fallback to standard require succeeded for: "${id}"`);
339
- return result;
340
- }
341
- catch (fallbackErr) {
342
- logFn?.(`[RequireWrapper] Fallback also failed for: "${id}" - ${fallbackErr.message}`, 'error');
343
- throw err; // Throw original error from our hook
344
- }
345
- }
346
- }
347
- else {
348
- // No context, use standard require (for requires outside our execution context)
349
- logFn?.(`[RequireWrapper] No context available, using standard require for: "${id}"`);
350
- try {
351
- const result = parentReq(id);
352
- logFn?.(`[RequireWrapper] Successfully resolved: "${id}"`);
353
- return result;
354
- }
355
- catch (err) {
356
- logFn?.(`[RequireWrapper] Failed to resolve: "${id}" - ${err.message}`, 'error');
357
- throw err;
358
- }
359
- }
360
- };
361
- }(parentRequire, this.log));
362
216
  // Create V8 context with Playwright globals
363
217
  // ai-wright will handle timeout extension internally (test.setTimeout or page.setDefaultTimeout)
364
218
  const contextGlobals = {
@@ -371,7 +225,7 @@ class PersistentExecutionContext {
371
225
  context: browserContext,
372
226
  // Node.js globals
373
227
  console,
374
- require: requireWrapper, // Wrapper that ensures ModuleResolutionManager hook is triggered
228
+ require, // Normal require - hook will intercept resolution
375
229
  setTimeout,
376
230
  setInterval,
377
231
  clearTimeout,
@@ -470,13 +324,13 @@ class PersistentExecutionContext {
470
324
  // If tempDir is set, run in isolated context with module resolution
471
325
  if (this.tempDir) {
472
326
  // Execute within AsyncLocalStorage context so module resolution hook can access tempDir
473
- // Use the stored testFileDir if available, otherwise use tempDir directly (it's already the tests folder)
474
- const testFileDir = this.testFileDir || this.tempDir;
327
+ // Use the stored testFileDir if available, otherwise use tempDir/tests as default
328
+ const testFileDir = this.testFileDir || (this.tempDir ? require('path').join(this.tempDir, 'tests') : undefined);
475
329
  const context = {
476
330
  tempDir: this.tempDir,
477
331
  testFileDir: testFileDir
478
332
  };
479
- await ModuleResolutionManager.runWithContext({ ...context, log: this.log }, async () => {
333
+ await ModuleResolutionManager.runWithContext(context, async () => {
480
334
  if (!this.context) {
481
335
  throw new Error('Context has been disposed');
482
336
  }
@@ -540,12 +394,11 @@ function resolveFilePaths(tempDir, files) {
540
394
  * Overrides setInputFiles to resolve relative paths using the test file's directory
541
395
  */
542
396
  function applyFileUploadOverrides(page, tempDir, testFolderPath, log) {
543
- // tempDir is already the tests folder path (workspace root + tests folder setting)
544
- // Use testFolderPath if provided (e.g., ["e2e"]) to construct test file directory within tests folder
545
- // Otherwise use tempDir directly (don't append '/tests' again since tempDir is already the tests folder)
397
+ // Use test folder path from DB (e.g., ["tests", "e2e"]) to construct test file directory
398
+ // This ensures paths like "../fixtures/file.pdf" resolve correctly regardless of test nesting
546
399
  const testFileDir = testFolderPath && testFolderPath.length > 0
547
400
  ? path.join(tempDir, ...testFolderPath)
548
- : tempDir; // Use tempDir directly since it's already the tests folder path
401
+ : path.join(tempDir, 'tests'); // Fallback if not provided
549
402
  log(`Using test file directory for file path resolution: ${testFileDir}`);
550
403
  const originalSetInputFiles = page.setInputFiles.bind(page);
551
404
  const originalLocator = page.locator.bind(page);
@@ -771,7 +624,7 @@ class ExecutionService {
771
624
  const { expect, test } = require('@playwright/test');
772
625
  const { ai } = require('ai-wright');
773
626
  // Use shared context if provided, otherwise create new context
774
- const persistentContext = sharedContext || new PersistentExecutionContext(page, expect, test, ai, browser, browserContext, options.tempDir, this.log.bind(this));
627
+ const persistentContext = sharedContext || new PersistentExecutionContext(page, expect, test, ai, browser, browserContext, options.tempDir);
775
628
  const isSharedContext = !!sharedContext;
776
629
  // Extract and execute imports from original script if provided
777
630
  this.log(`[executeStepsInPersistentContext] Checking imports: originalScript=${!!options.originalScript}, tempDir=${!!options.tempDir}`);
@@ -783,22 +636,62 @@ class ExecutionService {
783
636
  this.log(`Executing ${importStatements.length} import statement(s) before steps`);
784
637
  // Convert ES6 imports to CommonJS requires and execute
785
638
  const requireStatements = import_utils_1.ImportUtils.convertImportsToRequires(importStatements, (msg) => this.log(msg));
786
- // Determine test file directory for module resolution
787
- // tempDir is already the tests folder path (workspace root + tests folder setting)
788
- // If scriptFilePath is provided, use its directory; otherwise use tempDir as fallback
789
- let testFileDir;
790
- if (options.scriptFilePath) {
791
- // Use the directory of the actual script file for module resolution
792
- testFileDir = path.dirname(options.scriptFilePath);
793
- this.log(`Using script file directory for module resolution: ${testFileDir}`);
794
- }
795
- else if (options.tempDir) {
796
- // Fallback: use tempDir directly (it's already the tests folder path)
797
- testFileDir = options.tempDir;
798
- this.log(`Using tempDir for module resolution (script file path not available): ${testFileDir}`);
639
+ // Execute imports with proper context - find the actual test file path
640
+ // All tests are within the tests folder
641
+ // We search for test files in tempDir/tests directory tree
642
+ let testFileDir = path.join(options.tempDir, 'tests'); // Default to tests directory
643
+ try {
644
+ const fs = require('fs');
645
+ const testsDir = path.join(options.tempDir, 'tests');
646
+ // Check if tests directory exists
647
+ if (!fs.existsSync(testsDir)) {
648
+ this.log(`Tests directory ${testsDir} does not exist, using default for module resolution`, 'warn');
649
+ }
650
+ else {
651
+ // Recursive search with depth limit to prevent infinite loops
652
+ const findTestFile = (dir, depth = 0, maxDepth = 10) => {
653
+ if (depth > maxDepth) {
654
+ return null; // Prevent infinite recursion
655
+ }
656
+ try {
657
+ const files = fs.readdirSync(dir);
658
+ for (const file of files) {
659
+ const fullPath = path.join(dir, file);
660
+ try {
661
+ const stat = fs.statSync(fullPath);
662
+ if (stat.isDirectory()) {
663
+ const found = findTestFile(fullPath, depth + 1, maxDepth);
664
+ if (found)
665
+ return found;
666
+ }
667
+ else if (file.endsWith('.spec.ts') || file.endsWith('.spec.js') ||
668
+ file.endsWith('.test.ts') || file.endsWith('.test.js')) {
669
+ return fullPath; // Found a test file
670
+ }
671
+ }
672
+ catch (statError) {
673
+ // Skip files we can't stat
674
+ continue;
675
+ }
676
+ }
677
+ }
678
+ catch (e) {
679
+ // Continue searching in other directories
680
+ }
681
+ return null;
682
+ };
683
+ const testFilePath = findTestFile(testsDir);
684
+ if (testFilePath) {
685
+ testFileDir = path.dirname(testFilePath);
686
+ this.log(`Found test file at ${testFilePath}, using directory ${testFileDir} for module resolution`);
687
+ }
688
+ else {
689
+ this.log(`No test file found in ${testsDir}, using tests directory for module resolution`, 'warn');
690
+ }
691
+ }
799
692
  }
800
- else {
801
- throw new Error('Either scriptFilePath or tempDir must be provided for module resolution');
693
+ catch (searchError) {
694
+ this.log(`Could not search for test file: ${searchError.message}, using tests directory for module resolution`, 'warn');
802
695
  }
803
696
  // Store test file directory in context for module resolution
804
697
  persistentContext.setTestFileDir(testFileDir);
@@ -874,8 +767,7 @@ class ExecutionService {
874
767
  let importResults = {};
875
768
  try {
876
769
  if (options.tempDir) {
877
- importResults = await ModuleResolutionManager.runWithContext({ tempDir: options.tempDir, testFileDir: context.__testFileDir, log: this.log.bind(this) }, async () => {
878
- this.log(`[executeStepsInPersistentContext] Executing import capture code`);
770
+ importResults = await ModuleResolutionManager.runWithContext({ tempDir: options.tempDir, testFileDir: context.__testFileDir }, async () => {
879
771
  const script = new vm.Script(captureCode);
880
772
  const result = script.runInContext(context);
881
773
  return await result;
@@ -908,36 +800,25 @@ class ExecutionService {
908
800
  let moduleResult;
909
801
  if (modulePath && options.tempDir) {
910
802
  // Execute require for the specific module
911
- this.log(`[executeStepsInPersistentContext] About to call runWithContext for module: ${modulePath}`);
912
- moduleResult = await ModuleResolutionManager.runWithContext({ tempDir: options.tempDir, testFileDir: context.__testFileDir, log: this.log.bind(this) }, async () => {
913
- this.log(`[executeStepsInPersistentContext] Inside runWithContext, executing require for module: ${modulePath}`);
914
- this.log(`[DEBUG] About to execute require('${modulePath}') in VM context`);
803
+ moduleResult = await ModuleResolutionManager.runWithContext({ tempDir: options.tempDir, testFileDir: context.__testFileDir }, async () => {
915
804
  const script = new vm.Script(`(() => require('${modulePath}'))()`);
916
- const result = script.runInContext(context);
917
- this.log(`[DEBUG] require('${modulePath}') completed in VM context`);
918
- return result;
805
+ return script.runInContext(context);
919
806
  });
920
807
  }
921
808
  else {
922
809
  // Execute the full require statement as-is
923
810
  if (options.tempDir) {
924
- this.log(`[executeStepsInPersistentContext] About to call runWithContext for require statement: ${requireStmt}`);
925
- moduleResult = await ModuleResolutionManager.runWithContext({ tempDir: options.tempDir, testFileDir: context.__testFileDir, log: this.log.bind(this) }, async () => {
926
- this.log(`[executeStepsInPersistentContext] Inside runWithContext, executing require statement: ${requireStmt}`);
927
- this.log(`[DEBUG] About to execute require statement in VM context: ${requireStmt}`);
811
+ moduleResult = await ModuleResolutionManager.runWithContext({ tempDir: options.tempDir, testFileDir: context.__testFileDir }, async () => {
928
812
  // Try to extract variable name, but execute the require
929
813
  const varNameMatch = requireStmt.match(/const\s+(\w+)\s*=/);
930
814
  if (varNameMatch) {
931
815
  const script = new vm.Script(`(() => { ${requireStmt}; return ${varNameMatch[1]}; })()`);
932
- const result = script.runInContext(context);
933
- this.log(`[DEBUG] require statement completed in VM context`);
934
- return result;
816
+ return script.runInContext(context);
935
817
  }
936
818
  else {
937
819
  // Side-effect import - just execute
938
820
  const script = new vm.Script(`(() => { ${requireStmt}; return null; })()`);
939
821
  script.runInContext(context);
940
- this.log(`[DEBUG] Side-effect require statement completed in VM context`);
941
822
  return null;
942
823
  }
943
824
  });
@@ -1371,8 +1252,7 @@ class ExecutionService {
1371
1252
  mode: 'RUN_EXACTLY',
1372
1253
  jobId: request.jobId,
1373
1254
  tempDir: request.tempDir,
1374
- originalScript: request.script, // AST will extract statements from this
1375
- scriptFilePath: request.scriptFilePath // Pass script file path for module resolution
1255
+ originalScript: request.script // AST will extract statements from this
1376
1256
  });
1377
1257
  // LIFECYCLE: afterEndTest
1378
1258
  if (this.progressReporter?.afterEndTest) {
@@ -1425,8 +1305,7 @@ class ExecutionService {
1425
1305
  mode: 'RUN_EXACTLY',
1426
1306
  jobId: request.jobId,
1427
1307
  tempDir: request.tempDir,
1428
- originalScript: request.script, // AST will extract statements from this
1429
- scriptFilePath: request.scriptFilePath // Pass script file path for module resolution
1308
+ originalScript: request.script // AST will extract statements from this
1430
1309
  });
1431
1310
  // LIFECYCLE: afterEndTest
1432
1311
  if (this.progressReporter?.afterEndTest) {
@@ -1542,8 +1421,7 @@ class ExecutionService {
1542
1421
  jobId: request.jobId,
1543
1422
  model,
1544
1423
  tempDir: request.tempDir,
1545
- originalScript: request.script, // Pass original script for import extraction
1546
- scriptFilePath: request.scriptFilePath // Pass script file path for module resolution
1424
+ originalScript: request.script // Pass original script for import extraction
1547
1425
  });
1548
1426
  const updatedSteps = result.updatedSteps || steps;
1549
1427
  const allStepsSuccessful = result.success;
@@ -2029,8 +1907,7 @@ class ExecutionService {
2029
1907
  jobId: jobId,
2030
1908
  tempDir: request.tempDir,
2031
1909
  originalScript: testScript, // For module resolution (imports)
2032
- model: request.model,
2033
- scriptFilePath: request.scriptFilePath // Pass script file path for module resolution
1910
+ model: request.model
2034
1911
  }, suiteContext // Pass shared context
2035
1912
  );
2036
1913
  if (!result.success) {
@@ -2299,13 +2176,11 @@ class ExecutionService {
2299
2176
  const hookContext = sharedContext || new PersistentExecutionContext(page, expect, test, ai, browser, context, tempDir);
2300
2177
  try {
2301
2178
  // Execute using step-wise execution infrastructure to trigger callbacks
2302
- // Note: Hooks don't have a scriptFilePath, so tempDir will be used as fallback
2303
2179
  const result = await this.executeStepsInPersistentContext(steps, page, browser, context, {
2304
2180
  mode: types_1.ExecutionMode.RUN_EXACTLY,
2305
2181
  jobId: jobId,
2306
2182
  tempDir: tempDir,
2307
2183
  originalScript: hookScript
2308
- // No scriptFilePath for hooks - will use tempDir as fallback
2309
2184
  }, hookContext // Pass shared context
2310
2185
  );
2311
2186
  if (!result.success) {