testchimp-runner-core 0.1.5 → 0.1.7

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;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"}
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"}
@@ -56,6 +56,13 @@ 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
+ }
59
66
  /**
60
67
  * Install the global hook (only once, shared by all instances)
61
68
  */
@@ -77,7 +84,7 @@ class ModuleResolutionManager {
77
84
  const nodeModulesDir = path.dirname(runnerCoreDir);
78
85
  if (fs.existsSync(nodeModulesDir) && path.basename(nodeModulesDir) === 'node_modules') {
79
86
  this.scriptserviceNodeModules = nodeModulesDir;
80
- console.log(`[ModuleResolution] Cached scriptservice node_modules: ${this.scriptserviceNodeModules}`);
87
+ // Note: This runs at module load time, before we have a logger, so we can't log here
81
88
  }
82
89
  }
83
90
  catch (e) {
@@ -87,12 +94,14 @@ class ModuleResolutionManager {
87
94
  Module._resolveFilename = function (request, parent, isMain, options) {
88
95
  // Get resolution context for current execution
89
96
  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})`);
90
100
  // If no context, use original resolution (for requires outside our execution context)
91
101
  if (!context) {
102
+ log(`[ModuleResolution] No context available, using original resolution`);
92
103
  return ModuleResolutionManager.originalResolveFilename.call(this, request, parent, isMain, options);
93
104
  }
94
- // Log all module resolution attempts when we have context
95
- console.log(`[ModuleResolution] Hook intercepted: "${request}" (parent: ${parent?.filename || 'none'})`);
96
105
  // Handle relative imports
97
106
  if (request.startsWith('.')) {
98
107
  // Use standard Node.js relative path resolution
@@ -100,27 +109,27 @@ class ModuleResolutionManager {
100
109
  // Resolve relative to testFileDir (the directory where the test file is located)
101
110
  const resolveDir = context.testFileDir || (parent?.filename ? path.dirname(parent.filename) : context.tempDir);
102
111
  // Log module resolution attempt
103
- console.log(`[ModuleResolution] Resolving relative import: "${request}"`);
104
- console.log(`[ModuleResolution] resolveDir: ${resolveDir}`);
112
+ log(`[ModuleResolution] Resolving relative import: "${request}"`);
113
+ log(`[ModuleResolution] resolveDir: ${resolveDir}`);
105
114
  // Resolve the relative path using standard Node.js resolution
106
115
  // This will correctly resolve based on the actual folder structure in tempDir
107
116
  const resolvedPath = path.resolve(resolveDir, request);
108
- console.log(`[ModuleResolution] resolvedPath: ${resolvedPath}`);
117
+ log(`[ModuleResolution] resolvedPath: ${resolvedPath}`);
109
118
  // Try extensions in order: .page.ts, .page.js (POM files)
110
119
  const extensions = ['.page.ts', '.page.js'];
111
120
  for (const ext of extensions) {
112
121
  const testPath = resolvedPath + ext;
113
122
  try {
114
123
  if (fs.existsSync(testPath)) {
115
- console.log(`[ModuleResolution] ✓ Found file: ${testPath}`);
124
+ log(`[ModuleResolution] ✓ Found file: ${testPath}`);
116
125
  return testPath;
117
126
  }
118
127
  else {
119
- console.log(`[ModuleResolution] ✗ File does not exist: ${testPath}`);
128
+ log(`[ModuleResolution] ✗ File does not exist: ${testPath}`);
120
129
  }
121
130
  }
122
131
  catch (e) {
123
- console.log(`[ModuleResolution] ✗ Error checking file: ${testPath} - ${e}`);
132
+ log(`[ModuleResolution] ✗ Error checking file: ${testPath} - ${e}`, 'error');
124
133
  }
125
134
  }
126
135
  // If no extension worked, try original resolution
@@ -144,45 +153,95 @@ class ModuleResolutionManager {
144
153
  }
145
154
  }
146
155
  // For non-relative imports (like 'ai-wright', '@playwright/test'), use standard Node.js resolution
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}"`);
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
+ }
149
205
  try {
150
- // Use original Node.js resolution (will find from node_modules)
206
+ // Use original Node.js resolution (will now search workspace root's node_modules first)
207
+ log(`[ModuleResolution] Attempting resolution with modified paths...`);
151
208
  const resolved = ModuleResolutionManager.originalResolveFilename.call(this, request, parent, isMain, options);
152
- console.log(`[ModuleResolution] ✓ Resolved to: ${resolved}`);
209
+ log(`[ModuleResolution] ✓ Resolved to: ${resolved}`);
210
+ // Restore original _nodeModulePaths
211
+ if (nodeModulePathsModified) {
212
+ Module._nodeModulePaths = originalNodeModulePaths;
213
+ }
153
214
  return resolved;
154
215
  }
155
216
  catch (err) {
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
167
- if (ModuleResolutionManager.scriptserviceNodeModules) {
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;
217
+ // Restore original _nodeModulePaths before handling error
218
+ if (nodeModulePathsModified) {
219
+ Module._nodeModulePaths = originalNodeModulePaths;
180
220
  }
181
- catch (resolveErr) {
182
- console.log(`[ModuleResolution] ✗ Failed with enhanced paths: ${resolveErr}`);
183
- // Fall back to original error
184
- throw err;
221
+ log(`[ModuleResolution] ✗ Failed to resolve: ${err.message}`, 'error');
222
+ // Fallback: try scriptservice's node_modules (for server-side usage)
223
+ 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
+ }
185
243
  }
244
+ throw err;
186
245
  }
187
246
  };
188
247
  this.hookInstalled = true;
@@ -194,6 +253,9 @@ class ModuleResolutionManager {
194
253
  return this.asyncLocalStorage.run({ tempDir }, fn);
195
254
  }
196
255
  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'}`);
197
259
  return this.asyncLocalStorage.run(context, fn);
198
260
  }
199
261
  }
@@ -206,13 +268,97 @@ ModuleResolutionManager.scriptserviceNodeModules = null;
206
268
  * Variables defined in earlier steps remain available in later steps without re-evaluation
207
269
  */
208
270
  class PersistentExecutionContext {
209
- constructor(page, expect, test, ai, browser, browserContext, tempDir) {
271
+ constructor(page, expect, test, ai, browser, browserContext, tempDir, log) {
210
272
  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;
211
300
  // Install global hook if not already installed (only happens once)
212
301
  if (tempDir) {
213
302
  ModuleResolutionManager.installHook();
214
303
  this.setupTypeScriptSupport(tempDir);
215
304
  }
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));
216
362
  // Create V8 context with Playwright globals
217
363
  // ai-wright will handle timeout extension internally (test.setTimeout or page.setDefaultTimeout)
218
364
  const contextGlobals = {
@@ -225,7 +371,7 @@ class PersistentExecutionContext {
225
371
  context: browserContext,
226
372
  // Node.js globals
227
373
  console,
228
- require, // Normal require - hook will intercept resolution
374
+ require: requireWrapper, // Wrapper that ensures ModuleResolutionManager hook is triggered
229
375
  setTimeout,
230
376
  setInterval,
231
377
  clearTimeout,
@@ -324,13 +470,13 @@ class PersistentExecutionContext {
324
470
  // If tempDir is set, run in isolated context with module resolution
325
471
  if (this.tempDir) {
326
472
  // Execute within AsyncLocalStorage context so module resolution hook can access 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);
473
+ // Use the stored testFileDir if available, otherwise use tempDir directly (it's already the tests folder)
474
+ const testFileDir = this.testFileDir || this.tempDir;
329
475
  const context = {
330
476
  tempDir: this.tempDir,
331
477
  testFileDir: testFileDir
332
478
  };
333
- await ModuleResolutionManager.runWithContext(context, async () => {
479
+ await ModuleResolutionManager.runWithContext({ ...context, log: this.log }, async () => {
334
480
  if (!this.context) {
335
481
  throw new Error('Context has been disposed');
336
482
  }
@@ -394,11 +540,12 @@ function resolveFilePaths(tempDir, files) {
394
540
  * Overrides setInputFiles to resolve relative paths using the test file's directory
395
541
  */
396
542
  function applyFileUploadOverrides(page, tempDir, testFolderPath, log) {
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
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)
399
546
  const testFileDir = testFolderPath && testFolderPath.length > 0
400
547
  ? path.join(tempDir, ...testFolderPath)
401
- : path.join(tempDir, 'tests'); // Fallback if not provided
548
+ : tempDir; // Use tempDir directly since it's already the tests folder path
402
549
  log(`Using test file directory for file path resolution: ${testFileDir}`);
403
550
  const originalSetInputFiles = page.setInputFiles.bind(page);
404
551
  const originalLocator = page.locator.bind(page);
@@ -624,7 +771,7 @@ class ExecutionService {
624
771
  const { expect, test } = require('@playwright/test');
625
772
  const { ai } = require('ai-wright');
626
773
  // Use shared context if provided, otherwise create new context
627
- const persistentContext = sharedContext || new PersistentExecutionContext(page, expect, test, ai, browser, browserContext, options.tempDir);
774
+ const persistentContext = sharedContext || new PersistentExecutionContext(page, expect, test, ai, browser, browserContext, options.tempDir, this.log.bind(this));
628
775
  const isSharedContext = !!sharedContext;
629
776
  // Extract and execute imports from original script if provided
630
777
  this.log(`[executeStepsInPersistentContext] Checking imports: originalScript=${!!options.originalScript}, tempDir=${!!options.tempDir}`);
@@ -636,62 +783,22 @@ class ExecutionService {
636
783
  this.log(`Executing ${importStatements.length} import statement(s) before steps`);
637
784
  // Convert ES6 imports to CommonJS requires and execute
638
785
  const requireStatements = import_utils_1.ImportUtils.convertImportsToRequires(importStatements, (msg) => this.log(msg));
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
- }
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}`);
692
794
  }
693
- catch (searchError) {
694
- this.log(`Could not search for test file: ${searchError.message}, using tests directory for module resolution`, 'warn');
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}`);
799
+ }
800
+ else {
801
+ throw new Error('Either scriptFilePath or tempDir must be provided for module resolution');
695
802
  }
696
803
  // Store test file directory in context for module resolution
697
804
  persistentContext.setTestFileDir(testFileDir);
@@ -767,7 +874,8 @@ class ExecutionService {
767
874
  let importResults = {};
768
875
  try {
769
876
  if (options.tempDir) {
770
- importResults = await ModuleResolutionManager.runWithContext({ tempDir: options.tempDir, testFileDir: context.__testFileDir }, async () => {
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`);
771
879
  const script = new vm.Script(captureCode);
772
880
  const result = script.runInContext(context);
773
881
  return await result;
@@ -800,25 +908,36 @@ class ExecutionService {
800
908
  let moduleResult;
801
909
  if (modulePath && options.tempDir) {
802
910
  // Execute require for the specific module
803
- moduleResult = await ModuleResolutionManager.runWithContext({ tempDir: options.tempDir, testFileDir: context.__testFileDir }, async () => {
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`);
804
915
  const script = new vm.Script(`(() => require('${modulePath}'))()`);
805
- return script.runInContext(context);
916
+ const result = script.runInContext(context);
917
+ this.log(`[DEBUG] require('${modulePath}') completed in VM context`);
918
+ return result;
806
919
  });
807
920
  }
808
921
  else {
809
922
  // Execute the full require statement as-is
810
923
  if (options.tempDir) {
811
- moduleResult = await ModuleResolutionManager.runWithContext({ tempDir: options.tempDir, testFileDir: context.__testFileDir }, async () => {
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}`);
812
928
  // Try to extract variable name, but execute the require
813
929
  const varNameMatch = requireStmt.match(/const\s+(\w+)\s*=/);
814
930
  if (varNameMatch) {
815
931
  const script = new vm.Script(`(() => { ${requireStmt}; return ${varNameMatch[1]}; })()`);
816
- return script.runInContext(context);
932
+ const result = script.runInContext(context);
933
+ this.log(`[DEBUG] require statement completed in VM context`);
934
+ return result;
817
935
  }
818
936
  else {
819
937
  // Side-effect import - just execute
820
938
  const script = new vm.Script(`(() => { ${requireStmt}; return null; })()`);
821
939
  script.runInContext(context);
940
+ this.log(`[DEBUG] Side-effect require statement completed in VM context`);
822
941
  return null;
823
942
  }
824
943
  });
@@ -1252,7 +1371,8 @@ class ExecutionService {
1252
1371
  mode: 'RUN_EXACTLY',
1253
1372
  jobId: request.jobId,
1254
1373
  tempDir: request.tempDir,
1255
- originalScript: request.script // AST will extract statements from this
1374
+ originalScript: request.script, // AST will extract statements from this
1375
+ scriptFilePath: request.scriptFilePath // Pass script file path for module resolution
1256
1376
  });
1257
1377
  // LIFECYCLE: afterEndTest
1258
1378
  if (this.progressReporter?.afterEndTest) {
@@ -1305,7 +1425,8 @@ class ExecutionService {
1305
1425
  mode: 'RUN_EXACTLY',
1306
1426
  jobId: request.jobId,
1307
1427
  tempDir: request.tempDir,
1308
- originalScript: request.script // AST will extract statements from this
1428
+ originalScript: request.script, // AST will extract statements from this
1429
+ scriptFilePath: request.scriptFilePath // Pass script file path for module resolution
1309
1430
  });
1310
1431
  // LIFECYCLE: afterEndTest
1311
1432
  if (this.progressReporter?.afterEndTest) {
@@ -1421,7 +1542,8 @@ class ExecutionService {
1421
1542
  jobId: request.jobId,
1422
1543
  model,
1423
1544
  tempDir: request.tempDir,
1424
- originalScript: request.script // Pass original script for import extraction
1545
+ originalScript: request.script, // Pass original script for import extraction
1546
+ scriptFilePath: request.scriptFilePath // Pass script file path for module resolution
1425
1547
  });
1426
1548
  const updatedSteps = result.updatedSteps || steps;
1427
1549
  const allStepsSuccessful = result.success;
@@ -1907,7 +2029,8 @@ class ExecutionService {
1907
2029
  jobId: jobId,
1908
2030
  tempDir: request.tempDir,
1909
2031
  originalScript: testScript, // For module resolution (imports)
1910
- model: request.model
2032
+ model: request.model,
2033
+ scriptFilePath: request.scriptFilePath // Pass script file path for module resolution
1911
2034
  }, suiteContext // Pass shared context
1912
2035
  );
1913
2036
  if (!result.success) {
@@ -2176,11 +2299,13 @@ class ExecutionService {
2176
2299
  const hookContext = sharedContext || new PersistentExecutionContext(page, expect, test, ai, browser, context, tempDir);
2177
2300
  try {
2178
2301
  // Execute using step-wise execution infrastructure to trigger callbacks
2302
+ // Note: Hooks don't have a scriptFilePath, so tempDir will be used as fallback
2179
2303
  const result = await this.executeStepsInPersistentContext(steps, page, browser, context, {
2180
2304
  mode: types_1.ExecutionMode.RUN_EXACTLY,
2181
2305
  jobId: jobId,
2182
2306
  tempDir: tempDir,
2183
2307
  originalScript: hookScript
2308
+ // No scriptFilePath for hooks - will use tempDir as fallback
2184
2309
  }, hookContext // Pass shared context
2185
2310
  );
2186
2311
  if (!result.success) {