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.
- package/dist/execution-service.d.ts.map +1 -1
- package/dist/execution-service.js +239 -114
- package/dist/execution-service.js.map +1 -1
- package/dist/orchestrator/orchestrator-agent.d.ts.map +1 -1
- package/dist/orchestrator/orchestrator-agent.js +7 -2
- package/dist/orchestrator/orchestrator-agent.js.map +1 -1
- package/dist/orchestrator/orchestrator-prompts.d.ts +1 -1
- package/dist/orchestrator/orchestrator-prompts.d.ts.map +1 -1
- package/dist/orchestrator/orchestrator-prompts.js +14 -5
- package/dist/orchestrator/orchestrator-prompts.js.map +1 -1
- package/dist/orchestrator/page-som-handler.d.ts.map +1 -1
- package/dist/orchestrator/page-som-handler.js +6 -1
- package/dist/orchestrator/page-som-handler.js.map +1 -1
- package/dist/orchestrator/types.d.ts +1 -0
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/orchestrator/types.js +1 -0
- package/dist/orchestrator/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
+
log(`[ModuleResolution] ✓ Found file: ${testPath}`);
|
|
116
125
|
return testPath;
|
|
117
126
|
}
|
|
118
127
|
else {
|
|
119
|
-
|
|
128
|
+
log(`[ModuleResolution] ✗ File does not exist: ${testPath}`);
|
|
120
129
|
}
|
|
121
130
|
}
|
|
122
131
|
catch (e) {
|
|
123
|
-
|
|
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
|
|
148
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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, //
|
|
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
|
|
328
|
-
const testFileDir = this.testFileDir ||
|
|
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
|
-
//
|
|
398
|
-
//
|
|
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
|
-
:
|
|
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
|
-
//
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
let testFileDir
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
-
|
|
694
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|