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.
@@ -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.join(tempDir, files);
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.join(tempDir, file) : file);
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 tempDir
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(tempDir, files);
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(tempDir, files);
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
- this.log(`Executing ${steps.length} steps in persistent context (mode: ${options.mode})`);
897
- // Track executed step descriptions for repair context
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
- const updatedSteps = [...steps]; // Copy for modifications
900
- let i = 0;
901
- while (i < updatedSteps.length) {
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
- this.log(`Step ${i + 1}/${updatedSteps.length}: ${step.description}`);
913
- // LIFECYCLE: beforeStepStart
914
- if (this.progressReporter?.beforeStepStart) {
915
- await this.progressReporter.beforeStepStart({
916
- stepId: step.id,
917
- stepNumber: i + 1,
918
- description: step.description,
919
- code: step.code
920
- }, page);
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 step in persistent context
924
- // ai-wright will automatically extend timeout via test.setTimeout() when ai.act/verify/extract is called
925
- await persistentContext.executeCode(step.code);
926
- this.log(` ✓ Step ${i + 1} succeeded`);
927
- // Mark step as successful
928
- // Ensure we're updating the step in the array (important after repair when new object was created)
929
- updatedSteps[i].success = true;
930
- // Ensure code is preserved in array (defensive - step is a reference but be explicit after repair)
931
- if (step.code && step.code.trim().length > 0) {
932
- updatedSteps[i].code = step.code;
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
- // Track executed step for repair context
935
- executedStepDescriptions.push(step.description);
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
- this.log(` ✗ Step ${i + 1} failed: ${errorMsg}`);
953
- // LIFECYCLE: onStepComplete (failure)
954
- if (this.progressReporter?.onStepProgress && options.jobId) {
955
- await this.progressReporter.onStepProgress({
956
- jobId: options.jobId,
957
- stepId: step.id,
958
- stepNumber: i + 1,
959
- description: step.description,
960
- code: step.code,
961
- status: 'FAILURE_STEP_EXECUTION',
962
- error: errorMsg,
963
- wasRepaired: false
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
- const repairSuccess = await this.attemptStepRepair(i, step, updatedSteps, executedStepDescriptions, page, persistentContext, options.jobId || '', options.model || 'gpt-4o-mini');
975
- if (repairSuccess) {
976
- // Repair succeeded - orchestrator already executed the commands, so step is done
977
- // Check if step was marked as successful during repair (orchestrator executed it)
978
- if (updatedSteps[i] && updatedSteps[i].success) {
979
- // Step was already executed by orchestrator during repair, move to next step
980
- this.log(` ✓ Step ${i + 1} already executed during repair, moving to next step`);
981
- i++;
982
- continue;
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
- // Step was deleted or needs re-execution
986
- this.log(` Re-executing repaired step ${i + 1}`);
987
- continue;
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
- // Repair failed, stop execution
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 steps succeeded
1004
- this.log(`All ${updatedSteps.length} steps completed successfully`);
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
- // Parse script into steps if not already provided
1107
- let steps = request.steps;
1108
- if (!steps || steps.length === 0) {
1109
- steps = await this.parseScriptIntoSteps(request.script, model || model_constants_1.DEFAULT_MODEL);
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 // Pass original script for import extraction
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
- // Parse script into steps if not already provided
1164
- let steps = request.steps;
1165
- if (!steps || steps.length === 0) {
1166
- steps = await this.parseScriptIntoSteps(request.script, model || model_constants_1.DEFAULT_MODEL);
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 // Pass original script for import extraction
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
- async repairStepsWithAI(steps, page, repairFlexibility, model, jobId) {
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
  }