testchimp-runner-core 0.0.78 → 0.0.80
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 +0 -4
- package/dist/execution-service.d.ts.map +1 -1
- package/dist/execution-service.js +157 -366
- package/dist/execution-service.js.map +1 -1
- package/dist/orchestrator/page-som-handler.d.ts.map +1 -1
- package/dist/orchestrator/page-som-handler.js +2 -9
- package/dist/orchestrator/page-som-handler.js.map +1 -1
- package/dist/progress-reporter.d.ts +1 -0
- package/dist/progress-reporter.d.ts.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/initialization-code-utils.d.ts +11 -0
- package/dist/utils/initialization-code-utils.d.ts.map +1 -1
- package/dist/utils/initialization-code-utils.js +105 -0
- package/dist/utils/initialization-code-utils.js.map +1 -1
- package/package.json +1 -1
|
@@ -358,26 +358,33 @@ class PersistentExecutionContext {
|
|
|
358
358
|
/**
|
|
359
359
|
* Helper function to resolve file paths relative to tempDir
|
|
360
360
|
* Relative paths are resolved to tempDir, absolute paths are unchanged
|
|
361
|
+
* Uses path.resolve() to properly handle ../ and ./ in relative paths
|
|
361
362
|
*/
|
|
362
363
|
function resolveFilePaths(tempDir, files) {
|
|
363
364
|
if (typeof files === "string") {
|
|
364
|
-
return path.isAbsolute(files) ? files : path.
|
|
365
|
+
return path.isAbsolute(files) ? files : path.resolve(tempDir, files);
|
|
365
366
|
}
|
|
366
367
|
else if (Array.isArray(files)) {
|
|
367
|
-
return files.map(file => typeof file === "string" && !path.isAbsolute(file) ? path.
|
|
368
|
+
return files.map(file => typeof file === "string" && !path.isAbsolute(file) ? path.resolve(tempDir, file) : file);
|
|
368
369
|
}
|
|
369
370
|
return files;
|
|
370
371
|
}
|
|
371
372
|
/**
|
|
372
373
|
* Apply file upload overrides to a page object
|
|
373
|
-
* Overrides setInputFiles to resolve relative paths using
|
|
374
|
+
* Overrides setInputFiles to resolve relative paths using the test file's directory
|
|
374
375
|
*/
|
|
375
|
-
function applyFileUploadOverrides(page, tempDir) {
|
|
376
|
+
function applyFileUploadOverrides(page, tempDir, testFolderPath, log) {
|
|
377
|
+
// Use test folder path from DB (e.g., ["tests", "e2e"]) to construct test file directory
|
|
378
|
+
// This ensures paths like "../fixtures/file.pdf" resolve correctly regardless of test nesting
|
|
379
|
+
const testFileDir = testFolderPath && testFolderPath.length > 0
|
|
380
|
+
? path.join(tempDir, ...testFolderPath)
|
|
381
|
+
: path.join(tempDir, 'tests'); // Fallback if not provided
|
|
382
|
+
log(`Using test file directory for file path resolution: ${testFileDir}`);
|
|
376
383
|
const originalSetInputFiles = page.setInputFiles.bind(page);
|
|
377
384
|
const originalLocator = page.locator.bind(page);
|
|
378
385
|
// Override page.setInputFiles
|
|
379
386
|
page.setInputFiles = async (selector, files, options) => {
|
|
380
|
-
const resolvedFiles = resolveFilePaths(
|
|
387
|
+
const resolvedFiles = resolveFilePaths(testFileDir, files);
|
|
381
388
|
return originalSetInputFiles(selector, resolvedFiles, options);
|
|
382
389
|
};
|
|
383
390
|
// Override page.locator to modify only setInputFiles
|
|
@@ -386,7 +393,7 @@ function applyFileUploadOverrides(page, tempDir) {
|
|
|
386
393
|
const originalLocatorSetInputFiles = locator.setInputFiles.bind(locator);
|
|
387
394
|
// Override only setInputFiles
|
|
388
395
|
locator.setInputFiles = async (files, options) => {
|
|
389
|
-
const resolvedFiles = resolveFilePaths(
|
|
396
|
+
const resolvedFiles = resolveFilePaths(testFileDir, files);
|
|
390
397
|
return originalLocatorSetInputFiles(resolvedFiles, options);
|
|
391
398
|
};
|
|
392
399
|
return locator;
|
|
@@ -865,41 +872,39 @@ class ExecutionService {
|
|
|
865
872
|
this.log(`Warning: Failed to execute imports: ${importError.message}`, 'warn');
|
|
866
873
|
// Continue execution - some imports might not be needed
|
|
867
874
|
}
|
|
868
|
-
// Extract and execute initialization code from the test script
|
|
869
|
-
// This includes code like: const signInPage = new SignInPage(page);
|
|
870
|
-
// We execute this using the same mechanism as steps (persistentContext.executeCode)
|
|
871
|
-
// But we need to transform const/let to assignments so variables are available in context
|
|
872
|
-
if (options.originalScript) {
|
|
873
|
-
try {
|
|
874
|
-
this.log('Extracting initialization code from test script...');
|
|
875
|
-
const initializationCode = initialization_code_utils_1.InitializationCodeUtils.extractInitializationCode(options.originalScript, (msg, level) => this.log(msg, level));
|
|
876
|
-
if (initializationCode) {
|
|
877
|
-
this.log(`Executing initialization code: ${initializationCode.substring(0, 100)}...`);
|
|
878
|
-
// Transform const/let declarations to assignments so they create properties on context
|
|
879
|
-
// This is necessary because const/let create block-scoped variables, not context properties
|
|
880
|
-
const { transformedCode } = initialization_code_utils_1.InitializationCodeUtils.transformInitializationCode(initializationCode, (msg, level) => this.log(msg, level));
|
|
881
|
-
// Execute using the same mechanism as steps
|
|
882
|
-
// This ensures consistent behavior and module resolution
|
|
883
|
-
await persistentContext.executeCode(transformedCode);
|
|
884
|
-
this.log('Initialization code executed successfully');
|
|
885
|
-
}
|
|
886
|
-
else {
|
|
887
|
-
this.log('No initialization code found in test script');
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
catch (initError) {
|
|
891
|
-
this.log(`Warning: Failed to execute initialization code: ${initError.message}`, 'warn');
|
|
892
|
-
// Continue execution - initialization might not be needed
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
875
|
}
|
|
896
|
-
|
|
897
|
-
//
|
|
876
|
+
// Extract ALL statements from test body using AST
|
|
877
|
+
// This replaces separate initialization extraction and LLM-based step parsing
|
|
878
|
+
let allStatements = [];
|
|
879
|
+
// Option 1: Use pre-parsed steps (for backward compatibility with consumers like journey-runner)
|
|
880
|
+
if (steps && steps.length > 0) {
|
|
881
|
+
this.log(`Using ${steps.length} pre-parsed steps from consumer`);
|
|
882
|
+
allStatements = steps.map(step => ({
|
|
883
|
+
code: step.code,
|
|
884
|
+
isVariableDeclaration: false, // Pre-parsed steps are assumed to be already filtered
|
|
885
|
+
intentComment: step.description !== step.code ? step.description : undefined,
|
|
886
|
+
screenStateAnnotation: undefined // Will be in description for pre-parsed steps
|
|
887
|
+
}));
|
|
888
|
+
}
|
|
889
|
+
// Option 2: Extract from script using AST
|
|
890
|
+
else if (options.originalScript) {
|
|
891
|
+
this.log('Extracting all statements from test script using AST...');
|
|
892
|
+
allStatements = initialization_code_utils_1.InitializationCodeUtils.extractAllStatements(options.originalScript, (msg, level) => this.log(msg, level));
|
|
893
|
+
this.log(`Extracted ${allStatements.length} statements from test body`);
|
|
894
|
+
}
|
|
895
|
+
// Option 3: No input provided
|
|
896
|
+
else {
|
|
897
|
+
this.log('No script or steps provided, cannot execute', 'error');
|
|
898
|
+
persistentContext.dispose();
|
|
899
|
+
return { success: false, error: 'No script or steps provided for execution' };
|
|
900
|
+
}
|
|
901
|
+
// Execute all statements sequentially
|
|
902
|
+
// Variable declarations execute silently, other statements trigger callbacks
|
|
903
|
+
let stepNumber = 0; // Track step number (excludes variables)
|
|
898
904
|
const executedStepDescriptions = [];
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
const step = updatedSteps[i];
|
|
905
|
+
for (let i = 0; i < allStatements.length; i++) {
|
|
906
|
+
const stmt = allStatements[i];
|
|
907
|
+
const isVariable = stmt.isVariableDeclaration;
|
|
903
908
|
// Check cancellation
|
|
904
909
|
if (this.progressReporter?.shouldContinue && options.jobId) {
|
|
905
910
|
const shouldContinue = await this.progressReporter.shouldContinue(options.jobId);
|
|
@@ -909,101 +914,129 @@ class ExecutionService {
|
|
|
909
914
|
return { success: false, failedStepIndex: i, error: 'Cancelled by user' };
|
|
910
915
|
}
|
|
911
916
|
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
if (
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
917
|
+
// Transform const/let to assignments for variables (so they persist in context)
|
|
918
|
+
let codeToExecute = stmt.code;
|
|
919
|
+
if (isVariable) {
|
|
920
|
+
// Use AST transformation (no regex)
|
|
921
|
+
const transformed = initialization_code_utils_1.InitializationCodeUtils.transformInitializationCode(stmt.code, (msg, level) => this.log(msg, level));
|
|
922
|
+
codeToExecute = transformed.transformedCode;
|
|
923
|
+
}
|
|
924
|
+
// Increment step number for non-variables (before execution)
|
|
925
|
+
if (!isVariable) {
|
|
926
|
+
stepNumber++;
|
|
927
|
+
// LIFECYCLE: beforeStepStart (call before execution)
|
|
928
|
+
if (this.progressReporter?.beforeStepStart && options.jobId) {
|
|
929
|
+
const description = stmt.intentComment || stmt.code;
|
|
930
|
+
await this.progressReporter.beforeStepStart({
|
|
931
|
+
stepId: `step-${stepNumber}`,
|
|
932
|
+
stepNumber,
|
|
933
|
+
description,
|
|
934
|
+
code: stmt.code
|
|
935
|
+
}, page);
|
|
936
|
+
}
|
|
921
937
|
}
|
|
922
938
|
try {
|
|
923
|
-
// Execute
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
939
|
+
// Execute ALL statements (variables + actions)
|
|
940
|
+
await persistentContext.executeCode(codeToExecute);
|
|
941
|
+
// Only report non-variable statements as steps
|
|
942
|
+
if (!isVariable) {
|
|
943
|
+
const description = stmt.intentComment || stmt.code;
|
|
944
|
+
this.log(` ✓ Step ${stepNumber} succeeded: ${description}`);
|
|
945
|
+
executedStepDescriptions.push(description);
|
|
946
|
+
// LIFECYCLE: onStepProgress (success)
|
|
947
|
+
if (this.progressReporter?.onStepProgress && options.jobId) {
|
|
948
|
+
await this.progressReporter.onStepProgress({
|
|
949
|
+
jobId: options.jobId,
|
|
950
|
+
stepId: `step-${stepNumber}`,
|
|
951
|
+
stepNumber,
|
|
952
|
+
description,
|
|
953
|
+
code: stmt.code,
|
|
954
|
+
screenStateAnnotation: stmt.screenStateAnnotation,
|
|
955
|
+
status: 'SUCCESS_STEP_EXECUTION',
|
|
956
|
+
wasRepaired: false
|
|
957
|
+
});
|
|
958
|
+
}
|
|
933
959
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
// LIFECYCLE: onStepComplete (success)
|
|
937
|
-
if (this.progressReporter?.onStepProgress && options.jobId) {
|
|
938
|
-
await this.progressReporter.onStepProgress({
|
|
939
|
-
jobId: options.jobId,
|
|
940
|
-
stepId: step.id,
|
|
941
|
-
stepNumber: i + 1,
|
|
942
|
-
description: step.description,
|
|
943
|
-
code: step.code,
|
|
944
|
-
status: 'SUCCESS_STEP_EXECUTION',
|
|
945
|
-
wasRepaired: false
|
|
946
|
-
});
|
|
960
|
+
else {
|
|
961
|
+
this.log(` Executed variable declaration (no callback): ${stmt.code}`);
|
|
947
962
|
}
|
|
948
|
-
i++; // Move to next step
|
|
949
963
|
}
|
|
950
964
|
catch (error) {
|
|
951
965
|
const errorMsg = error.message || String(error);
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
966
|
+
// Report failure
|
|
967
|
+
if (!isVariable) {
|
|
968
|
+
// stepNumber already incremented in beforeStepStart
|
|
969
|
+
const description = stmt.intentComment || stmt.code;
|
|
970
|
+
this.log(` ✗ Step ${stepNumber} failed: ${errorMsg}`);
|
|
971
|
+
// LIFECYCLE: onStepProgress (failure)
|
|
972
|
+
if (this.progressReporter?.onStepProgress && options.jobId) {
|
|
973
|
+
await this.progressReporter.onStepProgress({
|
|
974
|
+
jobId: options.jobId,
|
|
975
|
+
stepId: `step-${stepNumber}`,
|
|
976
|
+
stepNumber,
|
|
977
|
+
description,
|
|
978
|
+
code: stmt.code,
|
|
979
|
+
screenStateAnnotation: stmt.screenStateAnnotation,
|
|
980
|
+
status: 'FAILURE_STEP_EXECUTION',
|
|
981
|
+
error: errorMsg,
|
|
982
|
+
wasRepaired: false
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
else {
|
|
987
|
+
this.log(` ✗ Variable declaration failed: ${stmt.code} - ${errorMsg}`);
|
|
965
988
|
}
|
|
966
989
|
// Mode-specific error handling
|
|
967
990
|
if (options.mode === 'RUN_EXACTLY') {
|
|
968
|
-
// Stop execution
|
|
991
|
+
// Stop execution on first failure
|
|
969
992
|
persistentContext.dispose();
|
|
970
993
|
return { success: false, failedStepIndex: i, error: errorMsg };
|
|
971
994
|
}
|
|
972
995
|
else {
|
|
973
|
-
// RUN_WITH_AI_REPAIR: Attempt repair
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
996
|
+
// RUN_WITH_AI_REPAIR: Attempt repair (only for non-variable statements)
|
|
997
|
+
if (!isVariable) {
|
|
998
|
+
// Convert statement to step format for repair
|
|
999
|
+
const stepForRepair = {
|
|
1000
|
+
id: `step-${stepNumber}`,
|
|
1001
|
+
description: stmt.intentComment || stmt.code,
|
|
1002
|
+
code: stmt.code
|
|
1003
|
+
};
|
|
1004
|
+
// Create temporary steps array for repair context
|
|
1005
|
+
const stepsForRepair = allStatements
|
|
1006
|
+
.filter(s => !s.isVariableDeclaration)
|
|
1007
|
+
.map((s, idx) => ({
|
|
1008
|
+
id: `step-${idx + 1}`,
|
|
1009
|
+
description: s.intentComment || s.code,
|
|
1010
|
+
code: s.code,
|
|
1011
|
+
success: idx < stepNumber - 1 // Previous steps succeeded
|
|
1012
|
+
}));
|
|
1013
|
+
const repairSuccess = await this.attemptStepRepair(stepNumber - 1, // 0-based index
|
|
1014
|
+
stepForRepair, stepsForRepair, executedStepDescriptions, page, persistentContext, options.jobId || '', options.model || model_constants_1.DEFAULT_MODEL);
|
|
1015
|
+
if (repairSuccess) {
|
|
1016
|
+
// Repair succeeded, continue to next statement
|
|
1017
|
+
this.log(` ✓ Step ${stepNumber} repaired successfully`);
|
|
1018
|
+
// Don't increment i - the repaired statement was already executed by orchestrator
|
|
983
1019
|
}
|
|
984
1020
|
else {
|
|
985
|
-
//
|
|
986
|
-
this.log(`
|
|
987
|
-
|
|
1021
|
+
// Repair failed, stop execution
|
|
1022
|
+
this.log(` ✗ Repair failed for step ${stepNumber}, stopping execution`);
|
|
1023
|
+
persistentContext.dispose();
|
|
1024
|
+
return { success: false, failedStepIndex: i, error: errorMsg };
|
|
988
1025
|
}
|
|
989
1026
|
}
|
|
990
1027
|
else {
|
|
991
|
-
//
|
|
1028
|
+
// Variable declaration failed - stop (can't repair variable declarations)
|
|
1029
|
+
this.log(` ✗ Variable declaration failed, cannot repair`);
|
|
992
1030
|
persistentContext.dispose();
|
|
993
|
-
return {
|
|
994
|
-
success: false,
|
|
995
|
-
failedStepIndex: i,
|
|
996
|
-
error: errorMsg,
|
|
997
|
-
updatedSteps
|
|
998
|
-
};
|
|
1031
|
+
return { success: false, failedStepIndex: i, error: `Variable declaration failed: ${errorMsg}` };
|
|
999
1032
|
}
|
|
1000
1033
|
}
|
|
1001
1034
|
}
|
|
1002
1035
|
}
|
|
1003
|
-
// All
|
|
1004
|
-
this.log(`All ${
|
|
1036
|
+
// All statements executed successfully
|
|
1037
|
+
this.log(`All ${allStatements.length} statements executed successfully (${stepNumber} steps with callbacks)`);
|
|
1005
1038
|
persistentContext.dispose();
|
|
1006
|
-
return { success: true, updatedSteps };
|
|
1039
|
+
return { success: true, updatedSteps: [] };
|
|
1007
1040
|
}
|
|
1008
1041
|
/**
|
|
1009
1042
|
* Attempt to repair a failed step using orchestrator
|
|
@@ -1095,7 +1128,7 @@ class ExecutionService {
|
|
|
1095
1128
|
const page = request.existingPage;
|
|
1096
1129
|
// Apply file upload overrides if tempDir is provided
|
|
1097
1130
|
if (request.tempDir) {
|
|
1098
|
-
applyFileUploadOverrides(page, request.tempDir);
|
|
1131
|
+
applyFileUploadOverrides(page, request.tempDir, request.testFolderPath, (msg, level) => this.log(msg, level));
|
|
1099
1132
|
this.log(`Applied file upload overrides with tempDir: ${request.tempDir}`);
|
|
1100
1133
|
}
|
|
1101
1134
|
try {
|
|
@@ -1103,18 +1136,14 @@ class ExecutionService {
|
|
|
1103
1136
|
if (this.progressReporter?.beforeStartTest) {
|
|
1104
1137
|
await this.progressReporter.beforeStartTest(page, browser, context);
|
|
1105
1138
|
}
|
|
1106
|
-
//
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
this.log(`Parsed script into ${steps.length} steps`);
|
|
1111
|
-
}
|
|
1112
|
-
// Execute steps in persistent context (STOP on first error)
|
|
1113
|
-
const result = await this.executeStepsInPersistentContext(steps, page, browser, context, {
|
|
1139
|
+
// Execute script in persistent context (STOP on first error)
|
|
1140
|
+
// Pre-parsed steps from consumer (if any) will be ignored - AST extracts from script
|
|
1141
|
+
const result = await this.executeStepsInPersistentContext(request.steps || [], // Pre-parsed steps (optional, for backward compatibility)
|
|
1142
|
+
page, browser, context, {
|
|
1114
1143
|
mode: 'RUN_EXACTLY',
|
|
1115
1144
|
jobId: request.jobId,
|
|
1116
1145
|
tempDir: request.tempDir,
|
|
1117
|
-
originalScript: request.script //
|
|
1146
|
+
originalScript: request.script // AST will extract statements from this
|
|
1118
1147
|
});
|
|
1119
1148
|
// LIFECYCLE: afterEndTest
|
|
1120
1149
|
if (this.progressReporter?.afterEndTest) {
|
|
@@ -1152,7 +1181,7 @@ class ExecutionService {
|
|
|
1152
1181
|
const { browser, context, page } = await this.initializeBrowser(request.playwrightConfig, request.headless, request.playwrightConfigFilePath);
|
|
1153
1182
|
// Apply file upload overrides if tempDir is provided
|
|
1154
1183
|
if (request.tempDir) {
|
|
1155
|
-
applyFileUploadOverrides(page, request.tempDir);
|
|
1184
|
+
applyFileUploadOverrides(page, request.tempDir, request.testFolderPath, (msg, level) => this.log(msg, level));
|
|
1156
1185
|
this.log(`Applied file upload overrides with tempDir: ${request.tempDir}`);
|
|
1157
1186
|
}
|
|
1158
1187
|
try {
|
|
@@ -1160,18 +1189,14 @@ class ExecutionService {
|
|
|
1160
1189
|
if (this.progressReporter?.beforeStartTest) {
|
|
1161
1190
|
await this.progressReporter.beforeStartTest(page, browser, context);
|
|
1162
1191
|
}
|
|
1163
|
-
//
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
this.log(`Parsed script into ${steps.length} steps`);
|
|
1168
|
-
}
|
|
1169
|
-
// Execute steps in persistent context (STOP on first error)
|
|
1170
|
-
const result = await this.executeStepsInPersistentContext(steps, page, browser, context, {
|
|
1192
|
+
// Execute script in persistent context (STOP on first error)
|
|
1193
|
+
// Pre-parsed steps from consumer (if any) will be ignored - AST extracts from script
|
|
1194
|
+
const result = await this.executeStepsInPersistentContext(request.steps || [], // Pre-parsed steps (optional, for backward compatibility)
|
|
1195
|
+
page, browser, context, {
|
|
1171
1196
|
mode: 'RUN_EXACTLY',
|
|
1172
1197
|
jobId: request.jobId,
|
|
1173
1198
|
tempDir: request.tempDir,
|
|
1174
|
-
originalScript: request.script //
|
|
1199
|
+
originalScript: request.script // AST will extract statements from this
|
|
1175
1200
|
});
|
|
1176
1201
|
// LIFECYCLE: afterEndTest
|
|
1177
1202
|
if (this.progressReporter?.afterEndTest) {
|
|
@@ -1252,7 +1277,7 @@ class ExecutionService {
|
|
|
1252
1277
|
repairPage = request.existingPage;
|
|
1253
1278
|
// Apply file upload overrides if tempDir is provided
|
|
1254
1279
|
if (request.tempDir) {
|
|
1255
|
-
applyFileUploadOverrides(repairPage, request.tempDir);
|
|
1280
|
+
applyFileUploadOverrides(repairPage, request.tempDir, request.testFolderPath, (msg, level) => this.log(msg, level));
|
|
1256
1281
|
this.log(`Applied file upload overrides with tempDir: ${request.tempDir}`);
|
|
1257
1282
|
}
|
|
1258
1283
|
}
|
|
@@ -1264,7 +1289,7 @@ class ExecutionService {
|
|
|
1264
1289
|
repairPage = browserInstance.page;
|
|
1265
1290
|
// Apply file upload overrides if tempDir is provided
|
|
1266
1291
|
if (request.tempDir) {
|
|
1267
|
-
applyFileUploadOverrides(repairPage, request.tempDir);
|
|
1292
|
+
applyFileUploadOverrides(repairPage, request.tempDir, request.testFolderPath, (msg, level) => this.log(msg, level));
|
|
1268
1293
|
this.log(`Applied file upload overrides with tempDir: ${request.tempDir}`);
|
|
1269
1294
|
}
|
|
1270
1295
|
}
|
|
@@ -1361,235 +1386,7 @@ class ExecutionService {
|
|
|
1361
1386
|
return fallbackResult;
|
|
1362
1387
|
}
|
|
1363
1388
|
}
|
|
1364
|
-
|
|
1365
|
-
let updatedSteps = [...steps];
|
|
1366
|
-
const maxTries = 3;
|
|
1367
|
-
const recentRepairs = [];
|
|
1368
|
-
// Track actual executed steps (including agent repairs) for proper history
|
|
1369
|
-
const executedStepDescriptions = [];
|
|
1370
|
-
// Create a shared execution context that accumulates ONLY variable declarations (not action commands)
|
|
1371
|
-
// This allows variables to be shared across steps without re-executing commands
|
|
1372
|
-
let executionContext = '';
|
|
1373
|
-
const contextVariables = new Map();
|
|
1374
|
-
let i = 0;
|
|
1375
|
-
while (i < updatedSteps.length) {
|
|
1376
|
-
// Check if job was cancelled by user
|
|
1377
|
-
if (this.progressReporter?.shouldContinue && jobId) {
|
|
1378
|
-
const shouldContinue = await this.progressReporter.shouldContinue(jobId);
|
|
1379
|
-
if (!shouldContinue) {
|
|
1380
|
-
this.log(`🛑 Job ${jobId} cancelled by user - aborting repair`);
|
|
1381
|
-
// Mark remaining steps as failed
|
|
1382
|
-
for (let j = i; j < updatedSteps.length; j++) {
|
|
1383
|
-
updatedSteps[j].success = false;
|
|
1384
|
-
updatedSteps[j].error = 'Cancelled by user';
|
|
1385
|
-
}
|
|
1386
|
-
break;
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
const step = updatedSteps[i];
|
|
1390
|
-
this.log(`Loop iteration: i=${i}, step description="${step.description}", total steps=${updatedSteps.length}`);
|
|
1391
|
-
// Declare variables that will be used in repair section
|
|
1392
|
-
let successfulCommands = [];
|
|
1393
|
-
let failingCommand;
|
|
1394
|
-
let remainingCommands = [];
|
|
1395
|
-
let stepWasAttempted = false; // Track if we actually tried this step
|
|
1396
|
-
try {
|
|
1397
|
-
// LIFECYCLE: Call beforeStepStart if provided
|
|
1398
|
-
if (this.progressReporter?.beforeStepStart) {
|
|
1399
|
-
await this.progressReporter.beforeStepStart({
|
|
1400
|
-
stepId: step.id, // Preserve original step ID if provided
|
|
1401
|
-
stepNumber: i + 1,
|
|
1402
|
-
description: step.description,
|
|
1403
|
-
code: step.code
|
|
1404
|
-
}, page);
|
|
1405
|
-
}
|
|
1406
|
-
// Execute step commands one-by-one to track which succeed
|
|
1407
|
-
this.log(`Attempting Step ${i + 1}: ${step.description}`);
|
|
1408
|
-
this.log(` Code: ${step.code}`);
|
|
1409
|
-
stepWasAttempted = true; // Mark that we attempted this step
|
|
1410
|
-
const result = await this.executeStepWithTracking(step.code, page, executionContext);
|
|
1411
|
-
successfulCommands = result.successfulCommands;
|
|
1412
|
-
failingCommand = result.failingCommand;
|
|
1413
|
-
remainingCommands = result.remainingCommands;
|
|
1414
|
-
const execError = result.error;
|
|
1415
|
-
if (!failingCommand) {
|
|
1416
|
-
// All commands succeeded
|
|
1417
|
-
step.success = true;
|
|
1418
|
-
this.log(`Step ${i + 1} executed successfully: ${step.description}`);
|
|
1419
|
-
this.log(`Step ${i + 1} success status set to: ${step.success}`);
|
|
1420
|
-
// Track executed step description for agent context
|
|
1421
|
-
executedStepDescriptions.push(step.description);
|
|
1422
|
-
// Report successful step execution
|
|
1423
|
-
this.log(`DEBUG: About to check callback - progressReporter=${!!this.progressReporter}, onStepProgress=${!!this.progressReporter?.onStepProgress}, jobId=${jobId}`);
|
|
1424
|
-
if (this.progressReporter?.onStepProgress && jobId) {
|
|
1425
|
-
this.log(`DEBUG: Firing onStepProgress callback for step ${i + 1}, stepId=${step.id}`);
|
|
1426
|
-
await this.progressReporter.onStepProgress({
|
|
1427
|
-
jobId,
|
|
1428
|
-
stepId: step.id, // Preserve original step ID if provided
|
|
1429
|
-
stepNumber: i + 1,
|
|
1430
|
-
description: step.description,
|
|
1431
|
-
code: step.code,
|
|
1432
|
-
status: 'SUCCESS_STEP_EXECUTION',
|
|
1433
|
-
wasRepaired: false
|
|
1434
|
-
});
|
|
1435
|
-
this.log(`DEBUG: onStepProgress callback completed for step ${i + 1}`);
|
|
1436
|
-
}
|
|
1437
|
-
else {
|
|
1438
|
-
this.log(`DEBUG: Skipping callback - conditions not met`);
|
|
1439
|
-
}
|
|
1440
|
-
// Add only variable declarations to execution context (not action commands)
|
|
1441
|
-
// This prevents re-executing commands while preserving variable scope
|
|
1442
|
-
const declarations = successfulCommands.filter(cmd => cmd.startsWith('const ') || cmd.startsWith('let ') || cmd.startsWith('var '));
|
|
1443
|
-
if (declarations.length > 0) {
|
|
1444
|
-
executionContext += declarations.join('\n') + '\n';
|
|
1445
|
-
}
|
|
1446
|
-
i++; // Move to next step
|
|
1447
|
-
continue; // Skip to next iteration
|
|
1448
|
-
}
|
|
1449
|
-
// Step failed - we have tracked which commands succeeded
|
|
1450
|
-
this.log(`Step ${i + 1} failed: ${step.description}`);
|
|
1451
|
-
this.log(` Error: ${execError}`);
|
|
1452
|
-
step.success = false;
|
|
1453
|
-
step.error = execError;
|
|
1454
|
-
this.log(` Identified ${successfulCommands.length} successful commands before failure`);
|
|
1455
|
-
if (successfulCommands.length > 0) {
|
|
1456
|
-
this.log(` Successful commands:\n ${successfulCommands.join('\n ')}`);
|
|
1457
|
-
}
|
|
1458
|
-
if (failingCommand) {
|
|
1459
|
-
this.log(` Failing command: ${failingCommand}`);
|
|
1460
|
-
}
|
|
1461
|
-
if (remainingCommands.length > 0) {
|
|
1462
|
-
this.log(` Remaining commands (${remainingCommands.length}):\n ${remainingCommands.join('\n ')}`);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
catch (error) {
|
|
1466
|
-
// Unexpected error in the tracking logic itself
|
|
1467
|
-
this.log(`Unexpected error in step execution tracking: ${error}`);
|
|
1468
|
-
step.success = false;
|
|
1469
|
-
step.error = step_execution_utils_1.StepExecutionUtils.safeSerializeError(error);
|
|
1470
|
-
stepWasAttempted = true; // Mark as attempted (failed)
|
|
1471
|
-
}
|
|
1472
|
-
// Only attempt repair if step was actually tried and failed
|
|
1473
|
-
if (stepWasAttempted && !step.success) {
|
|
1474
|
-
const aiCommandDetected = (0, ai_command_utils_1.containsAiCommand)(failingCommand) ||
|
|
1475
|
-
(0, ai_command_utils_1.anyContainsAiCommand)(remainingCommands) ||
|
|
1476
|
-
(0, ai_command_utils_1.containsAiCommand)(step.code);
|
|
1477
|
-
if (aiCommandDetected) {
|
|
1478
|
-
this.log(`Skipping AI repair for step ${i + 1} because it already relies on ai.act/ai.verify and failed during execution.`);
|
|
1479
|
-
break;
|
|
1480
|
-
}
|
|
1481
|
-
// Use orchestrator for repair (reuses all SoM infrastructure)
|
|
1482
|
-
this.log(`Calling orchestrator in REPAIR mode for step ${i + 1}`);
|
|
1483
|
-
// Prepare repair context - use executedStepDescriptions (includes agent repairs)
|
|
1484
|
-
const priorSteps = executedStepDescriptions; // What was ACTUALLY executed (scripted + agent)
|
|
1485
|
-
const nextSteps = updatedSteps.slice(i + 1).map(s => s.description);
|
|
1486
|
-
this.log(` Prior steps executed: ${priorSteps.length}, Next steps: ${nextSteps.length}`);
|
|
1487
|
-
this.log(` Prior steps context:\n ${priorSteps.map((s, idx) => `${idx + 1}. ${s}`).join('\n ')}`);
|
|
1488
|
-
// Create minimal memory for repair
|
|
1489
|
-
const memory = {
|
|
1490
|
-
extractedData: {},
|
|
1491
|
-
history: []
|
|
1492
|
-
};
|
|
1493
|
-
let repairSuccess = false;
|
|
1494
|
-
try {
|
|
1495
|
-
// Call orchestrator with repair context (page object persisted)
|
|
1496
|
-
const repairResult = await this.orchestratorAgent.executeStep(page, // Same page object (persisted state)
|
|
1497
|
-
step.description, // Goal with testdata embedded
|
|
1498
|
-
i + 1, // Current step number
|
|
1499
|
-
updatedSteps.length, // Total steps
|
|
1500
|
-
updatedSteps.map(s => s.description), // All step descriptions
|
|
1501
|
-
memory, // Memory (empty for repair)
|
|
1502
|
-
jobId || 'repair', priorSteps, // What was already completed (prior steps)
|
|
1503
|
-
nextSteps, // What comes after this (next steps)
|
|
1504
|
-
successfulCommands, // Commands that succeeded within THIS step before failure
|
|
1505
|
-
failingCommand, // The specific command that failed
|
|
1506
|
-
remainingCommands // Commands after the failing one (not yet executed)
|
|
1507
|
-
);
|
|
1508
|
-
if (repairResult.success && repairResult.commands.length > 0) {
|
|
1509
|
-
// MODIFY: Merge successful + fixed + remaining commands
|
|
1510
|
-
const allCommands = [...successfulCommands, ...repairResult.commands, ...remainingCommands];
|
|
1511
|
-
const repairedCode = allCommands.join('\n');
|
|
1512
|
-
updatedSteps[i] = {
|
|
1513
|
-
...step,
|
|
1514
|
-
code: repairedCode,
|
|
1515
|
-
success: true,
|
|
1516
|
-
error: undefined
|
|
1517
|
-
};
|
|
1518
|
-
this.log(`✓ Step ${i + 1} MODIFIED by orchestrator (repair successful)`);
|
|
1519
|
-
this.log(` Original code: ${step.code}`);
|
|
1520
|
-
if (successfulCommands.length > 0) {
|
|
1521
|
-
this.log(` Preserved ${successfulCommands.length} successful commands`);
|
|
1522
|
-
}
|
|
1523
|
-
this.log(` Added ${repairResult.commands.length} repaired commands`);
|
|
1524
|
-
if (remainingCommands.length > 0) {
|
|
1525
|
-
this.log(` Appended ${remainingCommands.length} remaining commands from original step`);
|
|
1526
|
-
}
|
|
1527
|
-
this.log(` Complete code (${allCommands.length} total):\n ${allCommands.join('\n ')}`);
|
|
1528
|
-
// Track what agent actually did in history (for future repair context)
|
|
1529
|
-
const agentActionSummary = `${step.description} [AI-repaired: ${successfulCommands.length} preserved + ${repairResult.commands.length} fixed + ${remainingCommands.length} remaining]`;
|
|
1530
|
-
executedStepDescriptions.push(agentActionSummary);
|
|
1531
|
-
// Update execution context with ONLY variable declarations from repaired step
|
|
1532
|
-
const repairedDeclarations = allCommands.filter(cmd => cmd.startsWith('const ') || cmd.startsWith('let ') || cmd.startsWith('var '));
|
|
1533
|
-
if (repairedDeclarations.length > 0) {
|
|
1534
|
-
executionContext += repairedDeclarations.join('\n') + '\n';
|
|
1535
|
-
}
|
|
1536
|
-
// Report repaired step
|
|
1537
|
-
if (this.progressReporter?.onStepProgress && jobId) {
|
|
1538
|
-
await this.progressReporter.onStepProgress({
|
|
1539
|
-
jobId,
|
|
1540
|
-
stepId: step.id,
|
|
1541
|
-
stepNumber: i + 1,
|
|
1542
|
-
description: updatedSteps[i].description,
|
|
1543
|
-
code: updatedSteps[i].code,
|
|
1544
|
-
status: 'SUCCESS_STEP_EXECUTION',
|
|
1545
|
-
wasRepaired: true
|
|
1546
|
-
});
|
|
1547
|
-
}
|
|
1548
|
-
// Ensure page is stable after agent repairs before returning control to script
|
|
1549
|
-
this.log(`Waiting for page stability after agent repair...`);
|
|
1550
|
-
try {
|
|
1551
|
-
await page.waitForLoadState('networkidle', { timeout: 5000 });
|
|
1552
|
-
this.log(`Page stabilized (networkidle) after agent repair`);
|
|
1553
|
-
}
|
|
1554
|
-
catch (stabilityError) {
|
|
1555
|
-
try {
|
|
1556
|
-
await page.waitForLoadState('domcontentloaded', { timeout: 3000 });
|
|
1557
|
-
this.log(`Page loaded (domcontentloaded) after agent repair`);
|
|
1558
|
-
}
|
|
1559
|
-
catch (fallbackError) {
|
|
1560
|
-
this.log(`Page stability wait timed out (continuing anyway)`, 'warn');
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
repairSuccess = true;
|
|
1564
|
-
i++; // Continue to NEXT step (hand control back to script)
|
|
1565
|
-
}
|
|
1566
|
-
else if (repairResult.success && repairResult.commands.length === 0) {
|
|
1567
|
-
// DELETE: Step goal already achieved or no longer needed (e.g., modal already dismissed)
|
|
1568
|
-
this.log(`✓ Step ${i + 1} DELETED by orchestrator (goal already achieved, step obsolete)`);
|
|
1569
|
-
this.log(` Reason: Orchestrator completed with 0 commands - step no longer needed`);
|
|
1570
|
-
// Track deletion in history (helps agent understand what was skipped)
|
|
1571
|
-
executedStepDescriptions.push(`${step.description} [AI-deleted: step obsolete/already done]`);
|
|
1572
|
-
// Remove the step from array
|
|
1573
|
-
updatedSteps.splice(i, 1);
|
|
1574
|
-
repairSuccess = true;
|
|
1575
|
-
// Don't increment i - next step moved to current position
|
|
1576
|
-
}
|
|
1577
|
-
else {
|
|
1578
|
-
this.log(`✗ Step ${i + 1} could not be repaired by orchestrator (reason: ${repairResult.terminationReason})`);
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
catch (repairError) {
|
|
1582
|
-
this.log(`✗ Orchestrator repair failed: ${repairError.message}`);
|
|
1583
|
-
}
|
|
1584
|
-
// Legacy repair code removed - now using orchestrator
|
|
1585
|
-
if (!repairSuccess) {
|
|
1586
|
-
this.log(`Step ${i + 1} could not be repaired - stopping execution`);
|
|
1587
|
-
break;
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
return updatedSteps;
|
|
1592
|
-
}
|
|
1389
|
+
// Dead code removed: repairStepsWithAI() - 283 lines of unused legacy repair logic
|
|
1593
1390
|
async executeStepCode(code, page) {
|
|
1594
1391
|
// Set timeout based on whether code contains AI commands (which need more time for LLM calls)
|
|
1595
1392
|
const hasAiCommands = (0, ai_command_utils_1.containsAiCommand)(code);
|
|
@@ -1622,12 +1419,6 @@ class ExecutionService {
|
|
|
1622
1419
|
page.setDefaultNavigationTimeout(30000);
|
|
1623
1420
|
}
|
|
1624
1421
|
}
|
|
1625
|
-
// Legacy repair helper methods (now unused but kept for compilation)
|
|
1626
|
-
buildFailureHistory() { return ''; }
|
|
1627
|
-
buildRecentRepairsContext() { return ''; }
|
|
1628
|
-
async applyRepairActionInContext() {
|
|
1629
|
-
return { success: false };
|
|
1630
|
-
}
|
|
1631
1422
|
generateUpdatedScript(steps, repairAdvice, originalScript) {
|
|
1632
1423
|
return script_generator_utils_1.ScriptGeneratorUtils.generateUpdatedScript(steps, repairAdvice, originalScript);
|
|
1633
1424
|
}
|