testchimp-runner-core 0.0.33 → 0.0.35

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.
Files changed (152) hide show
  1. package/dist/execution-service.d.ts +1 -4
  2. package/dist/execution-service.d.ts.map +1 -1
  3. package/dist/execution-service.js +155 -468
  4. package/dist/execution-service.js.map +1 -1
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +11 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/llm-facade.d.ts.map +1 -1
  10. package/dist/llm-facade.js +7 -7
  11. package/dist/llm-facade.js.map +1 -1
  12. package/dist/llm-provider.d.ts +9 -0
  13. package/dist/llm-provider.d.ts.map +1 -1
  14. package/dist/model-constants.d.ts +16 -5
  15. package/dist/model-constants.d.ts.map +1 -1
  16. package/dist/model-constants.js +17 -6
  17. package/dist/model-constants.js.map +1 -1
  18. package/dist/orchestrator/decision-parser.d.ts +18 -0
  19. package/dist/orchestrator/decision-parser.d.ts.map +1 -0
  20. package/dist/orchestrator/decision-parser.js +127 -0
  21. package/dist/orchestrator/decision-parser.js.map +1 -0
  22. package/dist/orchestrator/index.d.ts +4 -2
  23. package/dist/orchestrator/index.d.ts.map +1 -1
  24. package/dist/orchestrator/index.js +15 -2
  25. package/dist/orchestrator/index.js.map +1 -1
  26. package/dist/orchestrator/orchestrator-agent.d.ts +17 -22
  27. package/dist/orchestrator/orchestrator-agent.d.ts.map +1 -1
  28. package/dist/orchestrator/orchestrator-agent.js +708 -577
  29. package/dist/orchestrator/orchestrator-agent.js.map +1 -1
  30. package/dist/orchestrator/orchestrator-prompts.d.ts +32 -0
  31. package/dist/orchestrator/orchestrator-prompts.d.ts.map +1 -0
  32. package/dist/orchestrator/orchestrator-prompts.js +737 -0
  33. package/dist/orchestrator/orchestrator-prompts.js.map +1 -0
  34. package/dist/orchestrator/page-som-handler.d.ts +106 -0
  35. package/dist/orchestrator/page-som-handler.d.ts.map +1 -0
  36. package/dist/orchestrator/page-som-handler.js +1353 -0
  37. package/dist/orchestrator/page-som-handler.js.map +1 -0
  38. package/dist/orchestrator/som-types.d.ts +149 -0
  39. package/dist/orchestrator/som-types.d.ts.map +1 -0
  40. package/dist/orchestrator/som-types.js +87 -0
  41. package/dist/orchestrator/som-types.js.map +1 -0
  42. package/dist/orchestrator/tool-registry.d.ts +2 -0
  43. package/dist/orchestrator/tool-registry.d.ts.map +1 -1
  44. package/dist/orchestrator/tool-registry.js.map +1 -1
  45. package/dist/orchestrator/tools/index.d.ts +5 -1
  46. package/dist/orchestrator/tools/index.d.ts.map +1 -1
  47. package/dist/orchestrator/tools/index.js +9 -2
  48. package/dist/orchestrator/tools/index.js.map +1 -1
  49. package/dist/orchestrator/tools/refresh-som-markers.d.ts +12 -0
  50. package/dist/orchestrator/tools/refresh-som-markers.d.ts.map +1 -0
  51. package/dist/orchestrator/tools/refresh-som-markers.js +64 -0
  52. package/dist/orchestrator/tools/refresh-som-markers.js.map +1 -0
  53. package/dist/orchestrator/tools/verify-action-result.d.ts +17 -0
  54. package/dist/orchestrator/tools/verify-action-result.d.ts.map +1 -0
  55. package/dist/orchestrator/tools/verify-action-result.js +140 -0
  56. package/dist/orchestrator/tools/verify-action-result.js.map +1 -0
  57. package/dist/orchestrator/tools/view-previous-screenshot.d.ts +15 -0
  58. package/dist/orchestrator/tools/view-previous-screenshot.d.ts.map +1 -0
  59. package/dist/orchestrator/tools/view-previous-screenshot.js +92 -0
  60. package/dist/orchestrator/tools/view-previous-screenshot.js.map +1 -0
  61. package/dist/orchestrator/types.d.ts +49 -1
  62. package/dist/orchestrator/types.d.ts.map +1 -1
  63. package/dist/orchestrator/types.js +11 -1
  64. package/dist/orchestrator/types.js.map +1 -1
  65. package/dist/prompts.d.ts.map +1 -1
  66. package/dist/prompts.js +40 -34
  67. package/dist/prompts.js.map +1 -1
  68. package/dist/scenario-service.d.ts +5 -0
  69. package/dist/scenario-service.d.ts.map +1 -1
  70. package/dist/scenario-service.js +17 -0
  71. package/dist/scenario-service.js.map +1 -1
  72. package/dist/scenario-worker-class.d.ts +4 -0
  73. package/dist/scenario-worker-class.d.ts.map +1 -1
  74. package/dist/scenario-worker-class.js +21 -3
  75. package/dist/scenario-worker-class.js.map +1 -1
  76. package/dist/testing/agent-tester.d.ts +35 -0
  77. package/dist/testing/agent-tester.d.ts.map +1 -0
  78. package/dist/testing/agent-tester.js +84 -0
  79. package/dist/testing/agent-tester.js.map +1 -0
  80. package/dist/testing/ref-translator-tester.d.ts +44 -0
  81. package/dist/testing/ref-translator-tester.d.ts.map +1 -0
  82. package/dist/testing/ref-translator-tester.js +104 -0
  83. package/dist/testing/ref-translator-tester.js.map +1 -0
  84. package/dist/utils/coordinate-converter.d.ts +32 -0
  85. package/dist/utils/coordinate-converter.d.ts.map +1 -0
  86. package/dist/utils/coordinate-converter.js +130 -0
  87. package/dist/utils/coordinate-converter.js.map +1 -0
  88. package/dist/utils/hierarchical-selector.d.ts +47 -0
  89. package/dist/utils/hierarchical-selector.d.ts.map +1 -0
  90. package/dist/utils/hierarchical-selector.js +212 -0
  91. package/dist/utils/hierarchical-selector.js.map +1 -0
  92. package/dist/utils/page-info-retry.d.ts +14 -0
  93. package/dist/utils/page-info-retry.d.ts.map +1 -0
  94. package/dist/utils/page-info-retry.js +60 -0
  95. package/dist/utils/page-info-retry.js.map +1 -0
  96. package/dist/utils/page-info-utils.d.ts +1 -0
  97. package/dist/utils/page-info-utils.d.ts.map +1 -1
  98. package/dist/utils/page-info-utils.js +46 -18
  99. package/dist/utils/page-info-utils.js.map +1 -1
  100. package/dist/utils/ref-attacher.d.ts +21 -0
  101. package/dist/utils/ref-attacher.d.ts.map +1 -0
  102. package/dist/utils/ref-attacher.js +149 -0
  103. package/dist/utils/ref-attacher.js.map +1 -0
  104. package/dist/utils/ref-translator.d.ts +49 -0
  105. package/dist/utils/ref-translator.d.ts.map +1 -0
  106. package/dist/utils/ref-translator.js +276 -0
  107. package/dist/utils/ref-translator.js.map +1 -0
  108. package/package.json +1 -1
  109. package/plandocs/BEFORE_AFTER_VERIFICATION.md +148 -0
  110. package/plandocs/COORDINATE_MODE_DIAGNOSIS.md +144 -0
  111. package/plandocs/IMPLEMENTATION_STATUS.md +108 -0
  112. package/plandocs/PHASE_1_COMPLETE.md +165 -0
  113. package/plandocs/PHASE_1_SUMMARY.md +184 -0
  114. package/plandocs/PROMPT_OPTIMIZATION_ANALYSIS.md +120 -0
  115. package/plandocs/PROMPT_SANITY_CHECK.md +120 -0
  116. package/plandocs/SESSION_SUMMARY_v0.0.33.md +151 -0
  117. package/plandocs/TROUBLESHOOTING_SESSION.md +72 -0
  118. package/plandocs/VISUAL_AGENT_EVOLUTION_PLAN.md +396 -0
  119. package/plandocs/WHATS_NEW_v0.0.33.md +183 -0
  120. package/plandocs/exploratory-mode-support-v2.plan.md +953 -0
  121. package/plandocs/exploratory-mode-support.plan.md +928 -0
  122. package/plandocs/journey-id-tracking-addendum.md +227 -0
  123. package/src/execution-service.ts +179 -596
  124. package/src/index.ts +10 -0
  125. package/src/llm-facade.ts +8 -8
  126. package/src/llm-provider.ts +11 -1
  127. package/src/model-constants.ts +17 -5
  128. package/src/orchestrator/decision-parser.ts +139 -0
  129. package/src/orchestrator/index.ts +27 -2
  130. package/src/orchestrator/orchestrator-agent.ts +868 -623
  131. package/src/orchestrator/orchestrator-prompts.ts +786 -0
  132. package/src/orchestrator/page-som-handler.ts +1565 -0
  133. package/src/orchestrator/som-types.ts +188 -0
  134. package/src/orchestrator/tool-registry.ts +2 -0
  135. package/src/orchestrator/tools/index.ts +5 -1
  136. package/src/orchestrator/tools/refresh-som-markers.ts +69 -0
  137. package/src/orchestrator/tools/verify-action-result.ts +159 -0
  138. package/src/orchestrator/tools/view-previous-screenshot.ts +103 -0
  139. package/src/orchestrator/types.ts +95 -4
  140. package/src/prompts.ts +40 -34
  141. package/src/scenario-service.ts +20 -0
  142. package/src/scenario-worker-class.ts +30 -4
  143. package/src/utils/coordinate-converter.ts +162 -0
  144. package/src/utils/page-info-retry.ts +65 -0
  145. package/src/utils/page-info-utils.ts +53 -18
  146. package/testchimp-runner-core-0.0.35.tgz +0 -0
  147. /package/{CREDIT_CALLBACK_ARCHITECTURE.md → plandocs/CREDIT_CALLBACK_ARCHITECTURE.md} +0 -0
  148. /package/{INTEGRATION_COMPLETE.md → plandocs/INTEGRATION_COMPLETE.md} +0 -0
  149. /package/{VISION_DIAGNOSTICS_IMPROVEMENTS.md → plandocs/VISION_DIAGNOSTICS_IMPROVEMENTS.md} +0 -0
  150. /package/{RELEASE_0.0.26.md → releasenotes/RELEASE_0.0.26.md} +0 -0
  151. /package/{RELEASE_0.0.27.md → releasenotes/RELEASE_0.0.27.md} +0 -0
  152. /package/{RELEASE_0.0.28.md → releasenotes/RELEASE_0.0.28.md} +0 -0
@@ -3,13 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ExecutionService = void 0;
4
4
  const playwright_mcp_service_1 = require("./playwright-mcp-service");
5
5
  const types_1 = require("./types");
6
- const page_info_utils_1 = require("./utils/page-info-utils");
7
6
  const browser_utils_1 = require("./utils/browser-utils");
8
7
  const llm_facade_1 = require("./llm-facade");
9
8
  const script_utils_1 = require("./script-utils");
10
9
  const credit_usage_service_1 = require("./credit-usage-service");
11
10
  const model_constants_1 = require("./model-constants");
12
11
  const backend_proxy_llm_provider_1 = require("./providers/backend-proxy-llm-provider");
12
+ const orchestrator_1 = require("./orchestrator");
13
13
  /**
14
14
  * Service for orchestrating Playwright script execution
15
15
  */
@@ -23,6 +23,13 @@ class ExecutionService {
23
23
  this.progressReporter = progressReporter;
24
24
  this.creditUsageService = new credit_usage_service_1.CreditUsageService(authConfig, backendUrl);
25
25
  this.maxConcurrentExecutions = maxConcurrentExecutions;
26
+ // Initialize orchestrator for repair mode (reuses all SoM infrastructure)
27
+ const toolRegistry = new orchestrator_1.ToolRegistry();
28
+ const repairConfig = {
29
+ useSoM: true,
30
+ somRestrictCoordinates: true // Prefer SoM markers for repairs
31
+ };
32
+ this.orchestratorAgent = new orchestrator_1.OrchestratorAgent(this.llmFacade, toolRegistry, repairConfig, progressReporter, (msg, level) => this.log(msg));
26
33
  }
27
34
  /**
28
35
  * Set a logger callback for capturing execution logs
@@ -201,7 +208,7 @@ class ExecutionService {
201
208
  await this.progressReporter.beforeStartTest(page, browser, context);
202
209
  }
203
210
  // Execute the script as-is
204
- await this.executeScriptContent(request.script, page);
211
+ await this.executeStepCode(request.script, page);
205
212
  // LIFECYCLE: Call afterEndTest on success
206
213
  if (this.progressReporter?.afterEndTest) {
207
214
  await this.progressReporter.afterEndTest('passed', undefined, page);
@@ -243,7 +250,7 @@ class ExecutionService {
243
250
  await this.progressReporter.beforeStartTest(page, browser, context);
244
251
  }
245
252
  // Execute the script as-is
246
- await this.executeScriptContent(request.script, page);
253
+ await this.executeStepCode(request.script, page);
247
254
  // LIFECYCLE: Call afterEndTest on success
248
255
  if (this.progressReporter?.afterEndTest) {
249
256
  await this.progressReporter.afterEndTest('passed', undefined, page);
@@ -315,8 +322,11 @@ class ExecutionService {
315
322
  }
316
323
  // Start AI repair process
317
324
  this.log('Starting AI repair process...');
325
+ let repairBrowser = null;
326
+ let repairContext = null;
327
+ let repairPage = null;
318
328
  try {
319
- let repairBrowser, repairContext, repairPage, steps, updatedSteps;
329
+ let steps, updatedSteps;
320
330
  if (useExistingBrowser) {
321
331
  // Use existing browser
322
332
  this.log('Using existing browser for AI repair...');
@@ -376,8 +386,8 @@ class ExecutionService {
376
386
  this.log('Starting AI repair with parsed steps...');
377
387
  updatedSteps = await this.repairStepsWithAI(steps, repairPage, repairFlexibility, model, request.jobId);
378
388
  }
379
- // Always generate the updated script
380
- const updatedScript = this.generateUpdatedScript(updatedSteps);
389
+ // Always generate the updated script (preserve original test name)
390
+ const updatedScript = this.generateUpdatedScript(updatedSteps, undefined, request.script);
381
391
  // Check if repair was successful by seeing if we completed all steps
382
392
  const allStepsSuccessful = updatedSteps.length > 0 && updatedSteps.every(step => step.success);
383
393
  // Check if we have any successful repairs (partial success)
@@ -392,10 +402,29 @@ class ExecutionService {
392
402
  });
393
403
  // Update file if we have any successful repairs (partial or complete)
394
404
  if (hasSuccessfulRepairs) {
405
+ // IMPORTANT: Use the orchestrator-generated script directly (already has proper Playwright commands)
406
+ // Don't regenerate via LLM as it loses the actual repairs
407
+ this.log('Using orchestrator-generated script (skipping LLM regeneration to preserve repairs)');
408
+ // For repair advice, compare original vs repaired
395
409
  const confidenceResponse = await this.llmFacade.assessRepairConfidence(request.script, updatedScript, model);
396
- const finalScript = await this.llmFacade.generateFinalScript(request.script, updatedScript, confidenceResponse.advice, model);
397
- // Ensure the final script has the correct TestChimp comment format with repair advice
398
- const scriptWithRepairAdvice = (0, script_utils_1.addTestChimpComment)(finalScript, confidenceResponse.advice);
410
+ // Add TestChimp comment with repair advice
411
+ const scriptWithAdvice = (0, script_utils_1.addTestChimpComment)(updatedScript, confidenceResponse.advice);
412
+ // Polish the script with minor LLM cleanup (removes redundancies, fixes formatting)
413
+ this.log('Applying final LLM polish to repaired script (minor cleanup only)...');
414
+ const cleanupResult = await this.llmFacade.cleanupScript(scriptWithAdvice, model);
415
+ if (cleanupResult.changes.length > 0) {
416
+ this.log(`Script cleanup made ${cleanupResult.changes.length} minor improvements:`);
417
+ cleanupResult.changes.forEach((change, i) => {
418
+ this.log(` ${i + 1}. ${change}`);
419
+ });
420
+ }
421
+ else if (cleanupResult.skipped) {
422
+ this.log(`Script cleanup skipped: ${cleanupResult.skipped}`);
423
+ }
424
+ else {
425
+ this.log('Script cleanup: no changes needed');
426
+ }
427
+ const scriptWithRepairAdvice = cleanupResult.script;
399
428
  // Report credit usage for successful AI repair
400
429
  this.creditUsageService.reportAIRepairCredit().catch(error => {
401
430
  this.log(`Failed to report credit usage for AI repair: ${error}`, 'warn');
@@ -409,10 +438,6 @@ class ExecutionService {
409
438
  this.log(`afterEndTest callback failed: ${callbackError}`, 'warn');
410
439
  }
411
440
  }
412
- // Only close browser if we created it (not provided by caller)
413
- if (!useExistingBrowser) {
414
- await repairBrowser.close();
415
- }
416
441
  return {
417
442
  runStatus: 'failed', // Original script failed
418
443
  repairStatus: allStepsSuccessful ? 'success' : 'partial', // Complete or partial repair success
@@ -434,10 +459,6 @@ class ExecutionService {
434
459
  this.log(`afterEndTest callback failed: ${callbackError}`, 'warn');
435
460
  }
436
461
  }
437
- // Only close browser if we created it (not provided by caller)
438
- if (!useExistingBrowser) {
439
- await repairBrowser.close();
440
- }
441
462
  return {
442
463
  runStatus: 'failed', // Original script failed
443
464
  repairStatus: 'failed',
@@ -459,6 +480,18 @@ class ExecutionService {
459
480
  error: error instanceof Error ? error.message : 'Script execution failed'
460
481
  };
461
482
  }
483
+ finally {
484
+ // Clean up browser resources if we created them (not provided by caller)
485
+ if (!useExistingBrowser && repairBrowser) {
486
+ try {
487
+ await repairBrowser.close();
488
+ this.log('AI repair browser closed');
489
+ }
490
+ catch (closeError) {
491
+ this.log(`Error closing AI repair browser: ${closeError}`, 'warn');
492
+ }
493
+ }
494
+ }
462
495
  }
463
496
  async parseScriptIntoSteps(script, model) {
464
497
  // First try LLM-based parsing
@@ -520,6 +553,8 @@ class ExecutionService {
520
553
  let updatedSteps = [...steps];
521
554
  const maxTries = 3;
522
555
  const recentRepairs = [];
556
+ // Track actual executed steps (including agent repairs) for proper history
557
+ const executedStepDescriptions = [];
523
558
  // Create a shared execution context that accumulates all executed code for variable tracking
524
559
  let executionContext = '';
525
560
  const contextVariables = new Map();
@@ -544,6 +579,8 @@ class ExecutionService {
544
579
  step.success = true;
545
580
  this.log(`Step ${i + 1} executed successfully: ${step.description}`);
546
581
  this.log(`Step ${i + 1} success status set to: ${step.success}`);
582
+ // Track executed step description for agent context
583
+ executedStepDescriptions.push(step.description);
547
584
  // Report successful step execution
548
585
  this.log(`DEBUG: About to check callback - progressReporter=${!!this.progressReporter}, onStepProgress=${!!this.progressReporter?.onStepProgress}, jobId=${jobId}`);
549
586
  if (this.progressReporter?.onStepProgress && jobId) {
@@ -575,234 +612,98 @@ class ExecutionService {
575
612
  }
576
613
  step.success = false;
577
614
  step.error = this.safeSerializeError(error);
578
- // Try multiple repair attempts
579
- const repairHistory = [];
615
+ // Use orchestrator for repair (reuses all SoM infrastructure)
616
+ this.log(`Calling orchestrator in REPAIR mode for step ${i + 1}`);
617
+ // Prepare repair context - use executedStepDescriptions (includes agent repairs)
618
+ const priorSteps = executedStepDescriptions; // What was ACTUALLY executed (scripted + agent)
619
+ const nextSteps = updatedSteps.slice(i + 1).map(s => s.description);
620
+ this.log(` Prior steps executed: ${priorSteps.length}, Next steps: ${nextSteps.length}`);
621
+ this.log(` Prior steps context:\n ${priorSteps.map((s, idx) => `${idx + 1}. ${s}`).join('\n ')}`);
622
+ // Create minimal memory for repair
623
+ const memory = {
624
+ experiences: [],
625
+ extractedData: {},
626
+ history: [],
627
+ latestNote: undefined
628
+ };
580
629
  let repairSuccess = false;
581
- const originalDescription = step.description;
582
- const originalCode = step.code;
583
- let usedVisionMode = false;
584
- for (let attempt = 1; attempt <= maxTries; attempt++) {
585
- this.log(`Step ${i + 1} repair attempt ${attempt}/${maxTries}`);
586
- // Get current page state for AI repair
587
- const pageInfo = await this.getEnhancedPageInfo(page);
588
- // Build failure history for LLM context
589
- const failureHistory = this.buildFailureHistory(repairHistory, step, error);
590
- // Build recent repairs context for LLM
591
- const recentRepairsContext = this.buildRecentRepairsContext(recentRepairs);
592
- let repairSuggestion;
593
- // VISION-BASED FALLBACK: After 2 regular repair attempts, consider vision diagnostics on final attempt
594
- if (attempt === maxTries - 1 && repairHistory.length >= 2 && !usedVisionMode) {
595
- // Ask LLM if screenshot would help for repair diagnostics
596
- this.log(` 🤔 After ${repairHistory.length} failed repairs: Asking LLM if screenshot would help (last resort)...`);
597
- const screenshotNeed = await this.llmFacade.assessScreenshotNeed(step.description, step.error || 'Unknown error', repairHistory.length + 1, pageInfo, model);
598
- this.log(` 💭 LLM assessment: ${screenshotNeed.needsScreenshot ? 'SCREENSHOT NEEDED' : 'NO SCREENSHOT'} - ${screenshotNeed.reason}`);
599
- if (screenshotNeed.needsScreenshot) {
600
- // Two-step supervisor pattern for vision-based repair:
601
- // 1. Supervisor analyzes screenshot and provides diagnostic insights
602
- // 2. Get repair suggestion with enhanced context from vision analysis
603
- this.log(` 📸 Taking screenshot for supervisor analysis...`);
604
- // Capture optimized screenshot using utility method
605
- const imageDataUrl = await (0, browser_utils_1.captureOptimizedScreenshot)(page, { timeout: 10000 }, // Uses default quality 60
606
- (msg) => this.log(msg));
607
- this.log(` 👔 STEP 1: Supervisor analyzing screenshot (${model_constants_1.VISION_MODEL})...`);
608
- const supervisorDiagnostics = await this.llmFacade.getVisionDiagnostics(step.description, pageInfo, [], // No previous steps context for repair
609
- step.error, imageDataUrl, model_constants_1.VISION_MODEL);
610
- // DEBUG: Log vision diagnostics
611
- this.log(` 📸 Visual insights: ${supervisorDiagnostics.visualAnalysis}`);
612
- this.log(` 🔍 Root cause: ${supervisorDiagnostics.rootCause}`);
613
- this.log(` 💡 Recommended approach: ${supervisorDiagnostics.recommendedApproach}`);
614
- if (supervisorDiagnostics.elementsFound.length > 0) {
615
- this.log(` ✅ Elements found: ${supervisorDiagnostics.elementsFound.join(', ')}`);
616
- }
617
- if (supervisorDiagnostics.elementsNotFound.length > 0) {
618
- this.log(` ❌ Elements not found: ${supervisorDiagnostics.elementsNotFound.join(', ')}`);
619
- }
620
- // Get repair suggestion with vision-enhanced context
621
- this.log(` 🔨 STEP 2: Getting repair suggestion with vision insights...`);
622
- const visionEnhancedFailureHistory = `${failureHistory}
623
-
624
- VISION-BASED DIAGNOSTIC INSIGHTS:
625
- Visual Analysis: ${supervisorDiagnostics.visualAnalysis}
626
- Root Cause: ${supervisorDiagnostics.rootCause}
627
- Recommended Approach: ${supervisorDiagnostics.recommendedApproach}
628
- Elements Found: ${supervisorDiagnostics.elementsFound.join(', ') || 'None'}
629
- Elements Not Found: ${supervisorDiagnostics.elementsNotFound.join(', ') || 'None'}
630
-
631
- Use these vision insights to inform your repair strategy.`;
632
- repairSuggestion = await this.llmFacade.getRepairSuggestion(step.description, step.code, step.error || 'Unknown error', pageInfo, visionEnhancedFailureHistory, recentRepairsContext, model);
633
- usedVisionMode = true;
634
- }
635
- else {
636
- // Regular repair without vision
637
- if (screenshotNeed.alternativeApproach) {
638
- this.log(` 💡 Alternative approach: ${screenshotNeed.alternativeApproach}`);
639
- }
640
- repairSuggestion = await this.llmFacade.getRepairSuggestion(step.description, step.code, step.error || 'Unknown error', pageInfo, failureHistory, recentRepairsContext, model);
641
- }
642
- }
643
- else {
644
- // Regular repair attempt (first 2 attempts or already used vision)
645
- repairSuggestion = await this.llmFacade.getRepairSuggestion(step.description, step.code, step.error || 'Unknown error', pageInfo, failureHistory, recentRepairsContext, model);
646
- }
647
- if (!repairSuggestion.shouldContinue) {
648
- this.log(`AI decided to stop repair at attempt ${attempt}: ${repairSuggestion.reason}`);
649
- break;
650
- }
651
- // Apply the repair action
652
- try {
653
- // Set the step index and insertAfterIndex on the client side based on current step being processed
654
- const repairAction = {
655
- ...repairSuggestion.action,
656
- stepIndex: i, // Client-side step index management
657
- insertAfterIndex: repairSuggestion.action.operation === types_1.StepOperation.INSERT ? i - 1 : undefined // For INSERT, insert before current step
630
+ try {
631
+ // Call orchestrator with repair context (page object persisted)
632
+ const repairResult = await this.orchestratorAgent.executeStep(page, // Same page object (persisted state)
633
+ step.description, // Goal with testdata embedded
634
+ i + 1, // Current step number
635
+ updatedSteps.length, // Total steps
636
+ updatedSteps.map(s => s.description), // All step descriptions
637
+ memory, // Memory (empty for repair)
638
+ jobId || 'repair', priorSteps, // NEW: What was already completed
639
+ nextSteps // NEW: What comes after this
640
+ );
641
+ if (repairResult.success && repairResult.commands.length > 0) {
642
+ // MODIFY: Orchestrator fixed the step - replace with new code
643
+ const repairedCode = repairResult.commands.join('\n');
644
+ updatedSteps[i] = {
645
+ ...step,
646
+ code: repairedCode,
647
+ success: true,
648
+ error: undefined
658
649
  };
659
- this.log(`🔧 Applying repair action: ${repairAction.operation} on step ${repairAction.stepIndex}`);
660
- this.log(`🔧 Steps array before repair: ${updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`).join(', ')}`);
661
- const result = await this.applyRepairActionInContext(repairAction, updatedSteps, i, page, executionContext, contextVariables);
662
- if (result.success) {
663
- repairSuccess = true;
664
- this.log(`🔧 Steps array after repair: ${updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`).join(', ')}`);
665
- // Mark the appropriate step(s) as successful based on operation type
666
- if (repairAction.operation === types_1.StepOperation.MODIFY) {
667
- // For MODIFY: mark the modified step as successful
668
- step.success = true;
669
- step.error = undefined;
670
- updatedSteps[i].success = true;
671
- updatedSteps[i].error = undefined;
672
- this.log(`Step ${i + 1} marked as successful after MODIFY repair`);
673
- // Report repaired step
674
- if (this.progressReporter?.onStepProgress && jobId) {
675
- this.log(`DEBUG: Reporting repaired step ${i + 1}:`);
676
- this.log(` description: ${updatedSteps[i].description}`);
677
- this.log(` code: ${updatedSteps[i].code}`);
678
- await this.progressReporter.onStepProgress({
679
- jobId,
680
- stepId: updatedSteps[i].id, // Preserve original step ID if provided
681
- stepNumber: i + 1,
682
- description: updatedSteps[i].description,
683
- code: updatedSteps[i].code,
684
- status: 'SUCCESS_STEP_EXECUTION',
685
- wasRepaired: true
686
- });
687
- }
688
- }
689
- else if (repairAction.operation === types_1.StepOperation.INSERT) {
690
- // For INSERT: mark the newly inserted step as successful
691
- const insertIndex = repairAction.insertAfterIndex !== undefined ? repairAction.insertAfterIndex + 1 : i + 1;
692
- if (updatedSteps[insertIndex]) {
693
- updatedSteps[insertIndex].success = true;
694
- updatedSteps[insertIndex].error = undefined;
695
- // Report inserted step
696
- if (this.progressReporter?.onStepProgress && jobId) {
697
- await this.progressReporter.onStepProgress({
698
- jobId,
699
- stepId: updatedSteps[insertIndex].id, // Preserve original step ID if provided
700
- stepNumber: insertIndex + 1,
701
- description: updatedSteps[insertIndex].description,
702
- code: updatedSteps[insertIndex].code,
703
- status: 'SUCCESS_STEP_EXECUTION',
704
- wasRepaired: true
705
- });
706
- }
707
- }
708
- }
709
- else if (repairAction.operation === types_1.StepOperation.REMOVE) {
710
- // For REMOVE: no step to mark as successful since we removed it
711
- // The step is already removed from the array
712
- }
713
- const commandInfo = repairAction.operation === types_1.StepOperation.MODIFY ?
714
- `MODIFY: "${repairAction.newStep?.code || 'N/A'}"` :
715
- repairAction.operation === types_1.StepOperation.INSERT ?
716
- `INSERT: "${repairAction.newStep?.code || 'N/A'}"` :
717
- repairAction.operation === types_1.StepOperation.REMOVE ?
718
- `REMOVE: step at index ${repairAction.stepIndex}` :
719
- repairAction.operation;
720
- this.log(`Step ${i + 1} repair action ${commandInfo} executed successfully on attempt ${attempt}${usedVisionMode ? ' (vision-aided)' : ''}`);
721
- // Update execution context based on the repair action
722
- if (repairAction.operation === types_1.StepOperation.MODIFY && repairAction.newStep) {
723
- // Update the step in the execution context for variable tracking
724
- executionContext = executionContext.replace(originalCode, repairAction.newStep.code);
725
- }
726
- else if (repairAction.operation === types_1.StepOperation.INSERT && repairAction.newStep) {
727
- // Insert the new step code into execution context for variable tracking
728
- executionContext += repairAction.newStep.code + '\n';
729
- }
730
- else if (repairAction.operation === types_1.StepOperation.REMOVE) {
731
- // Remove the step code from execution context for variable tracking
732
- executionContext = executionContext.replace(originalCode, '');
733
- }
734
- // Record this successful repair
735
- recentRepairs.push({
650
+ this.log(`✓ Step ${i + 1} MODIFIED by orchestrator (repair successful)`);
651
+ this.log(` Original code: ${step.code}`);
652
+ this.log(` New code (${repairResult.commands.length} commands):\n ${repairResult.commands.join('\n ')}`);
653
+ // Track what agent actually did in history (for future repair context)
654
+ const agentActionSummary = `${step.description} [AI-repaired: ${repairResult.commands.length} commands]`;
655
+ executedStepDescriptions.push(agentActionSummary);
656
+ // Report repaired step
657
+ if (this.progressReporter?.onStepProgress && jobId) {
658
+ await this.progressReporter.onStepProgress({
659
+ jobId,
660
+ stepId: step.id,
736
661
  stepNumber: i + 1,
737
- operation: repairAction.operation,
738
- originalDescription: repairAction.operation === types_1.StepOperation.REMOVE ? originalDescription : undefined,
739
- newDescription: repairAction.newStep?.description,
740
- originalCode: repairAction.operation === types_1.StepOperation.REMOVE ? originalCode : undefined,
741
- newCode: repairAction.newStep?.code
662
+ description: updatedSteps[i].description,
663
+ code: updatedSteps[i].code,
664
+ status: 'SUCCESS_STEP_EXECUTION',
665
+ wasRepaired: true
742
666
  });
743
- // Keep only the last 3 repairs for context
744
- if (recentRepairs.length > 3) {
745
- recentRepairs.shift();
746
- }
747
- // Update step index based on operation
748
- if (repairAction.operation === types_1.StepOperation.INSERT) {
749
- // For INSERT: inserted step is already executed
750
- this.log(`INSERT operation: current i=${i}, insertAfterIndex=${repairAction.insertAfterIndex}`);
751
- this.log(`INSERT: Steps array length before: ${updatedSteps.length}`);
752
- this.log(`INSERT: Steps before operation: ${updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`).join(', ')}`);
753
- if (repairAction.insertAfterIndex !== undefined && repairAction.insertAfterIndex < i) {
754
- // If inserting before current position, current step moved down by 1
755
- this.log(`INSERT before current position: incrementing i from ${i} to ${i + 1}`);
756
- i++; // Move to the original step that was pushed to the next position
757
- }
758
- else {
759
- // If inserting at or after current position, stay at current step
760
- this.log(`INSERT at/after current position: keeping i at ${i}`);
761
- }
762
- this.log(`INSERT: Steps array length after: ${updatedSteps.length}`);
763
- this.log(`INSERT: Steps after operation: ${updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`).join(', ')}`);
764
- }
765
- else if (repairAction.operation === types_1.StepOperation.REMOVE) {
766
- // For REMOVE: stay at same index since the next step moved to current position
767
- // Don't increment i because the array shifted left
667
+ }
668
+ // Ensure page is stable after agent repairs before returning control to script
669
+ this.log(`Waiting for page stability after agent repair...`);
670
+ try {
671
+ await page.waitForLoadState('networkidle', { timeout: 5000 });
672
+ this.log(`Page stabilized (networkidle) after agent repair`);
673
+ }
674
+ catch (stabilityError) {
675
+ try {
676
+ await page.waitForLoadState('domcontentloaded', { timeout: 3000 });
677
+ this.log(`Page loaded (domcontentloaded) after agent repair`);
768
678
  }
769
- else {
770
- // For MODIFY: move to next step since modified step was executed
771
- i++; // Move to next step for MODIFY
679
+ catch (fallbackError) {
680
+ this.log(`Page stability wait timed out (continuing anyway)`, 'warn');
772
681
  }
773
- // Add the repaired step's code to execution context for variable tracking
774
- executionContext += step.code + '\n';
775
- break;
776
- }
777
- else {
778
- throw new Error(result.error || 'Repair action failed');
779
682
  }
683
+ repairSuccess = true;
684
+ i++; // Continue to NEXT step (hand control back to script)
780
685
  }
781
- catch (repairError) {
782
- const repairErrorMessage = repairError instanceof Error ? repairError.message : 'Repair failed';
783
- const commandInfo = repairSuggestion.action.operation === types_1.StepOperation.MODIFY ?
784
- `MODIFY: "${repairSuggestion.action.newStep?.code || 'N/A'}"` :
785
- repairSuggestion.action.operation === types_1.StepOperation.INSERT ?
786
- `INSERT: "${repairSuggestion.action.newStep?.code || 'N/A'}"` :
787
- repairSuggestion.action.operation === types_1.StepOperation.REMOVE ?
788
- `REMOVE: step at index ${repairSuggestion.action.stepIndex}` :
789
- repairSuggestion.action.operation;
790
- this.log(`Step ${i + 1} repair attempt ${attempt} failed (${commandInfo}): ${repairErrorMessage}`);
791
- if (repairError instanceof Error && repairError.stack) {
792
- this.log(` Repair stack trace: ${repairError.stack}`);
793
- }
794
- // Record this attempt in history
795
- repairHistory.push({
796
- attempt,
797
- action: repairSuggestion.action,
798
- error: repairErrorMessage,
799
- pageInfo
800
- });
801
- step.error = repairErrorMessage;
686
+ else if (repairResult.success && repairResult.commands.length === 0) {
687
+ // DELETE: Step goal already achieved or no longer needed (e.g., modal already dismissed)
688
+ this.log(`✓ Step ${i + 1} DELETED by orchestrator (goal already achieved, step obsolete)`);
689
+ this.log(` Reason: Orchestrator completed with 0 commands - step no longer needed`);
690
+ // Track deletion in history (helps agent understand what was skipped)
691
+ executedStepDescriptions.push(`${step.description} [AI-deleted: step obsolete/already done]`);
692
+ // Remove the step from array
693
+ updatedSteps.splice(i, 1);
694
+ repairSuccess = true;
695
+ // Don't increment i - next step moved to current position
802
696
  }
697
+ else {
698
+ this.log(`✗ Step ${i + 1} could not be repaired by orchestrator (reason: ${repairResult.terminationReason})`);
699
+ }
700
+ }
701
+ catch (repairError) {
702
+ this.log(`✗ Orchestrator repair failed: ${repairError.message}`);
803
703
  }
704
+ // Legacy repair code removed - now using orchestrator
804
705
  if (!repairSuccess) {
805
- this.log(`Step ${i + 1} failed after ${maxTries} repair attempts`);
706
+ this.log(`Step ${i + 1} could not be repaired - stopping execution`);
806
707
  break;
807
708
  }
808
709
  }
@@ -845,244 +746,30 @@ Use these vision insights to inform your repair strategy.`;
845
746
  }
846
747
  return code; // Return the original code without removing comments
847
748
  }
848
- async executeStepInContext(code, page, executionContext, contextVariables) {
849
- // Detect if code contains navigation or load state operations that need longer timeout
850
- const needsLongerTimeout = code.includes('waitForLoadState') ||
851
- code.includes('goto(') ||
852
- code.includes('waitForURL') ||
853
- code.includes('waitForNavigation');
854
- // Use appropriate timeout based on operation type
855
- const timeout = needsLongerTimeout ? 30000 : 5000;
856
- page.setDefaultTimeout(timeout);
857
- try {
858
- // Execute only the current step code, but make context variables available
859
- const fullCode = code;
860
- // Dynamically import expect
861
- const { expect } = require('@playwright/test');
862
- // Create a function that has access to page, expect, and the context variables
863
- const executeCode = new Function('page', 'expect', 'contextVariables', `return (async () => {
864
- // Make context variables available in the execution scope
865
- for (const [key, value] of contextVariables) {
866
- globalThis[key] = value;
867
- }
868
-
869
- ${fullCode}
870
-
871
- // Capture any new variables that might have been created
872
- const newVars = {};
873
- for (const key in globalThis) {
874
- if (!contextVariables.has(key) && typeof globalThis[key] !== 'function' && key !== 'page' && key !== 'expect') {
875
- newVars[key] = globalThis[key];
876
- }
877
- }
878
- return newVars;
879
- })()`);
880
- const newVars = await executeCode(page, expect, contextVariables);
881
- // Update the context variables with any new variables created
882
- for (const [key, value] of Object.entries(newVars)) {
883
- contextVariables.set(key, value);
884
- }
885
- }
886
- finally {
887
- // Reset to default timeout for element operations
888
- page.setDefaultTimeout(5000);
889
- }
890
- }
891
- async executeScriptContent(script, page) {
892
- // Extract the test function content
893
- const testMatch = script.match(/test\([^,]+,\s*async\s*\(\s*\{\s*page[^}]*\}\s*\)\s*=>\s*\{([\s\S]*)\}\s*\);/);
894
- if (!testMatch) {
895
- throw new Error('Could not extract test function from script');
896
- }
897
- const testBody = testMatch[1];
898
- // Dynamically import expect
899
- const { expect } = require('@playwright/test');
900
- // Execute the entire test body as one async function
901
- const executeTest = new Function('page', 'expect', `return (async () => { ${testBody} })()`);
902
- await executeTest(page, expect);
749
+ // Legacy repair helper methods (now unused but kept for compilation)
750
+ buildFailureHistory() { return ''; }
751
+ buildRecentRepairsContext() { return ''; }
752
+ async applyRepairActionInContext() {
753
+ return { success: false };
903
754
  }
904
- async getEnhancedPageInfo(page) {
905
- try {
906
- return await (0, page_info_utils_1.getEnhancedPageInfo)(page);
907
- }
908
- catch (error) {
909
- return {
910
- url: page.url(),
911
- title: 'Unknown',
912
- ariaSnapshot: null,
913
- interactiveElements: [],
914
- formattedElements: 'Unable to extract'
915
- };
916
- }
917
- }
918
- buildFailureHistory(repairHistory, originalStep, originalError) {
919
- if (repairHistory.length === 0) {
920
- return `Original failure: ${this.safeSerializeError(originalError)}`;
921
- }
922
- let history = `Original failure: ${this.safeSerializeError(originalError)}\n\n`;
923
- history += `Previous repair attempts:\n`;
924
- repairHistory.forEach((attempt, index) => {
925
- history += `Attempt ${attempt.attempt}:\n`;
926
- history += ` Operation: ${attempt.action.operation}\n`;
927
- if (attempt.action.newStep) {
928
- history += ` Description: ${attempt.action.newStep.description}\n`;
929
- history += ` Code: ${attempt.action.newStep.code}\n`;
755
+ generateUpdatedScript(steps, repairAdvice, originalScript) {
756
+ // Extract test name and hashtags from original script if provided
757
+ let testName = 'repairedTest';
758
+ let hashtags = [];
759
+ if (originalScript) {
760
+ const testNameMatch = originalScript.match(/test\(['"]([^'"]+)['"]/);
761
+ if (testNameMatch) {
762
+ testName = testNameMatch[1];
930
763
  }
931
- history += ` Error: ${attempt.error}\n`;
932
- if (index < repairHistory.length - 1) {
933
- history += `\n`;
764
+ // Extract hashtags from TestChimp comment
765
+ const hashtagMatch = originalScript.match(/#\w+(?:\s+#\w+)*/);
766
+ if (hashtagMatch) {
767
+ hashtags = hashtagMatch[0].split(/\s+/).filter(tag => tag.startsWith('#'));
934
768
  }
935
- });
936
- return history;
937
- }
938
- buildRecentRepairsContext(recentRepairs) {
939
- if (recentRepairs.length === 0) {
940
- return 'No recent repairs to consider.';
941
769
  }
942
- let context = 'Recent successful repairs that may affect this step:\n\n';
943
- recentRepairs.forEach((repair, index) => {
944
- context += `Step ${repair.stepNumber} was successfully repaired:\n`;
945
- context += ` Operation: ${repair.operation}\n`;
946
- if (repair.operation === 'REMOVE') {
947
- context += ` Removed: "${repair.originalDescription}"\n`;
948
- context += ` Code removed:\n ${repair.originalCode?.replace(/\n/g, '\n ')}\n`;
949
- }
950
- else if (repair.operation === 'INSERT') {
951
- context += ` Inserted: "${repair.newDescription}"\n`;
952
- context += ` Code inserted:\n ${repair.newCode?.replace(/\n/g, '\n ')}\n`;
953
- }
954
- else {
955
- context += ` Original: "${repair.originalDescription}"\n`;
956
- context += ` Repaired: "${repair.newDescription}"\n`;
957
- context += ` Code changed from:\n ${repair.originalCode?.replace(/\n/g, '\n ')}\n`;
958
- context += ` To:\n ${repair.newCode?.replace(/\n/g, '\n ')}\n`;
959
- }
960
- if (index < recentRepairs.length - 1) {
961
- context += `\n`;
962
- }
963
- });
964
- context += '\nConsider how these changes might affect the current step and adjust accordingly.';
965
- return context;
966
- }
967
- async applyRepairActionInContext(action, steps, currentIndex, page, executionContext, contextVariables) {
968
- try {
969
- switch (action.operation) {
970
- case types_1.StepOperation.MODIFY:
971
- if (action.newStep && action.stepIndex !== undefined) {
972
- // Modify existing step
973
- steps[action.stepIndex] = {
974
- ...action.newStep,
975
- success: false,
976
- error: undefined
977
- };
978
- // Test the modified step with current page state and variables
979
- await this.executeStepCode(action.newStep.code, page);
980
- return { success: true, updatedContext: executionContext + action.newStep.code };
981
- }
982
- break;
983
- case types_1.StepOperation.INSERT:
984
- if (action.newStep && action.insertAfterIndex !== undefined) {
985
- // Insert new step after specified index
986
- const insertIndex = action.insertAfterIndex + 1;
987
- const newStep = {
988
- ...action.newStep,
989
- success: false,
990
- error: undefined
991
- };
992
- this.log(`INSERT: Inserting step at index ${insertIndex} with description "${newStep.description}"`);
993
- this.log(`INSERT: Steps before insertion: ${steps.map((s, i) => `${i}: "${s.description}" (success: ${s.success})`).join(', ')}`);
994
- // Preserve success status of existing steps before insertion
995
- const successStatusMap = new Map(steps.map((step, index) => [index, { success: step.success, error: step.error }]));
996
- steps.splice(insertIndex, 0, newStep);
997
- // Restore success status for steps that were shifted by the insertion
998
- // Steps at insertIndex and before keep their original status
999
- // Steps after insertIndex need to be shifted to their new positions
1000
- for (let i = insertIndex + 1; i < steps.length; i++) {
1001
- const originalIndex = i - 1; // The step that was originally at this position
1002
- if (successStatusMap.has(originalIndex)) {
1003
- const status = successStatusMap.get(originalIndex);
1004
- steps[i].success = status.success;
1005
- steps[i].error = status.error;
1006
- }
1007
- }
1008
- // CRITICAL FIX: Ensure the inserted step doesn't overwrite existing step data
1009
- // The new step should only have its own description, not inherit from existing steps
1010
- this.log(`INSERT: Final step array after restoration: ${steps.map((s, i) => `${i}: "${s.description}" (success: ${s.success})`).join(', ')}`);
1011
- this.log(`INSERT: Steps after insertion: ${steps.map((s, i) => `${i}: "${s.description}" (success: ${s.success})`).join(', ')}`);
1012
- // Test the new step with current page state
1013
- await this.executeStepCode(action.newStep.code, page);
1014
- return { success: true, updatedContext: executionContext + action.newStep.code };
1015
- }
1016
- break;
1017
- case types_1.StepOperation.REMOVE:
1018
- if (action.stepIndex !== undefined) {
1019
- // Remove step
1020
- steps.splice(action.stepIndex, 1);
1021
- return { success: true, updatedContext: executionContext };
1022
- }
1023
- break;
1024
- }
1025
- return { success: false, error: 'Invalid repair action' };
1026
- }
1027
- catch (error) {
1028
- return {
1029
- success: false,
1030
- error: error instanceof Error ? error.message : 'Unknown error during repair action'
1031
- };
1032
- }
1033
- }
1034
- async applyRepairAction(action, steps, currentIndex, page) {
1035
- try {
1036
- switch (action.operation) {
1037
- case types_1.StepOperation.MODIFY:
1038
- if (action.newStep && action.stepIndex !== undefined) {
1039
- // Modify existing step
1040
- steps[action.stepIndex] = {
1041
- ...action.newStep,
1042
- success: false,
1043
- error: undefined
1044
- };
1045
- // Test the modified step
1046
- await this.executeStepCode(action.newStep.code, page);
1047
- return { success: true };
1048
- }
1049
- break;
1050
- case types_1.StepOperation.INSERT:
1051
- if (action.newStep && action.insertAfterIndex !== undefined) {
1052
- // Insert new step after specified index
1053
- const insertIndex = action.insertAfterIndex + 1;
1054
- const newStep = {
1055
- ...action.newStep,
1056
- success: false,
1057
- error: undefined
1058
- };
1059
- steps.splice(insertIndex, 0, newStep);
1060
- // Test the inserted step
1061
- await this.executeStepCode(action.newStep.code, page);
1062
- return { success: true };
1063
- }
1064
- break;
1065
- case types_1.StepOperation.REMOVE:
1066
- if (action.stepIndex !== undefined) {
1067
- // Remove the step
1068
- steps.splice(action.stepIndex, 1);
1069
- return { success: true };
1070
- }
1071
- break;
1072
- }
1073
- return { success: false, error: 'Invalid repair action' };
1074
- }
1075
- catch (error) {
1076
- return {
1077
- success: false,
1078
- error: error instanceof Error ? error.message : 'Repair action execution failed'
1079
- };
1080
- }
1081
- }
1082
- generateUpdatedScript(steps, repairAdvice) {
1083
770
  const scriptLines = [
1084
771
  "import { test, expect } from '@playwright/test';",
1085
- `test('repairedTest', async ({ page, browser, context }) => {`
772
+ `test('${testName}', async ({ page, browser, context }) => {`
1086
773
  ];
1087
774
  steps.forEach((step, index) => {
1088
775
  // Only add step if it has code to execute
@@ -1096,8 +783,8 @@ Use these vision insights to inform your repair strategy.`;
1096
783
  });
1097
784
  scriptLines.push('});');
1098
785
  const script = scriptLines.join('\n');
1099
- // Add TestChimp comment to the repaired script with repair advice
1100
- return (0, script_utils_1.addTestChimpComment)(script, repairAdvice);
786
+ // Add TestChimp comment with hashtags and repair advice
787
+ return (0, script_utils_1.addTestChimpComment)(script, repairAdvice, hashtags);
1101
788
  }
1102
789
  /**
1103
790
  * Initialize browser with configuration (delegates to utility function)