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.
- package/dist/execution-service.d.ts +1 -4
- package/dist/execution-service.d.ts.map +1 -1
- package/dist/execution-service.js +155 -468
- package/dist/execution-service.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/llm-facade.d.ts.map +1 -1
- package/dist/llm-facade.js +7 -7
- package/dist/llm-facade.js.map +1 -1
- package/dist/llm-provider.d.ts +9 -0
- package/dist/llm-provider.d.ts.map +1 -1
- package/dist/model-constants.d.ts +16 -5
- package/dist/model-constants.d.ts.map +1 -1
- package/dist/model-constants.js +17 -6
- package/dist/model-constants.js.map +1 -1
- package/dist/orchestrator/decision-parser.d.ts +18 -0
- package/dist/orchestrator/decision-parser.d.ts.map +1 -0
- package/dist/orchestrator/decision-parser.js +127 -0
- package/dist/orchestrator/decision-parser.js.map +1 -0
- package/dist/orchestrator/index.d.ts +4 -2
- package/dist/orchestrator/index.d.ts.map +1 -1
- package/dist/orchestrator/index.js +15 -2
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/orchestrator-agent.d.ts +17 -22
- package/dist/orchestrator/orchestrator-agent.d.ts.map +1 -1
- package/dist/orchestrator/orchestrator-agent.js +708 -577
- package/dist/orchestrator/orchestrator-agent.js.map +1 -1
- package/dist/orchestrator/orchestrator-prompts.d.ts +32 -0
- package/dist/orchestrator/orchestrator-prompts.d.ts.map +1 -0
- package/dist/orchestrator/orchestrator-prompts.js +737 -0
- package/dist/orchestrator/orchestrator-prompts.js.map +1 -0
- package/dist/orchestrator/page-som-handler.d.ts +106 -0
- package/dist/orchestrator/page-som-handler.d.ts.map +1 -0
- package/dist/orchestrator/page-som-handler.js +1353 -0
- package/dist/orchestrator/page-som-handler.js.map +1 -0
- package/dist/orchestrator/som-types.d.ts +149 -0
- package/dist/orchestrator/som-types.d.ts.map +1 -0
- package/dist/orchestrator/som-types.js +87 -0
- package/dist/orchestrator/som-types.js.map +1 -0
- package/dist/orchestrator/tool-registry.d.ts +2 -0
- package/dist/orchestrator/tool-registry.d.ts.map +1 -1
- package/dist/orchestrator/tool-registry.js.map +1 -1
- package/dist/orchestrator/tools/index.d.ts +5 -1
- package/dist/orchestrator/tools/index.d.ts.map +1 -1
- package/dist/orchestrator/tools/index.js +9 -2
- package/dist/orchestrator/tools/index.js.map +1 -1
- package/dist/orchestrator/tools/refresh-som-markers.d.ts +12 -0
- package/dist/orchestrator/tools/refresh-som-markers.d.ts.map +1 -0
- package/dist/orchestrator/tools/refresh-som-markers.js +64 -0
- package/dist/orchestrator/tools/refresh-som-markers.js.map +1 -0
- package/dist/orchestrator/tools/verify-action-result.d.ts +17 -0
- package/dist/orchestrator/tools/verify-action-result.d.ts.map +1 -0
- package/dist/orchestrator/tools/verify-action-result.js +140 -0
- package/dist/orchestrator/tools/verify-action-result.js.map +1 -0
- package/dist/orchestrator/tools/view-previous-screenshot.d.ts +15 -0
- package/dist/orchestrator/tools/view-previous-screenshot.d.ts.map +1 -0
- package/dist/orchestrator/tools/view-previous-screenshot.js +92 -0
- package/dist/orchestrator/tools/view-previous-screenshot.js.map +1 -0
- package/dist/orchestrator/types.d.ts +49 -1
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/orchestrator/types.js +11 -1
- package/dist/orchestrator/types.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +40 -34
- package/dist/prompts.js.map +1 -1
- package/dist/scenario-service.d.ts +5 -0
- package/dist/scenario-service.d.ts.map +1 -1
- package/dist/scenario-service.js +17 -0
- package/dist/scenario-service.js.map +1 -1
- package/dist/scenario-worker-class.d.ts +4 -0
- package/dist/scenario-worker-class.d.ts.map +1 -1
- package/dist/scenario-worker-class.js +21 -3
- package/dist/scenario-worker-class.js.map +1 -1
- package/dist/testing/agent-tester.d.ts +35 -0
- package/dist/testing/agent-tester.d.ts.map +1 -0
- package/dist/testing/agent-tester.js +84 -0
- package/dist/testing/agent-tester.js.map +1 -0
- package/dist/testing/ref-translator-tester.d.ts +44 -0
- package/dist/testing/ref-translator-tester.d.ts.map +1 -0
- package/dist/testing/ref-translator-tester.js +104 -0
- package/dist/testing/ref-translator-tester.js.map +1 -0
- package/dist/utils/coordinate-converter.d.ts +32 -0
- package/dist/utils/coordinate-converter.d.ts.map +1 -0
- package/dist/utils/coordinate-converter.js +130 -0
- package/dist/utils/coordinate-converter.js.map +1 -0
- package/dist/utils/hierarchical-selector.d.ts +47 -0
- package/dist/utils/hierarchical-selector.d.ts.map +1 -0
- package/dist/utils/hierarchical-selector.js +212 -0
- package/dist/utils/hierarchical-selector.js.map +1 -0
- package/dist/utils/page-info-retry.d.ts +14 -0
- package/dist/utils/page-info-retry.d.ts.map +1 -0
- package/dist/utils/page-info-retry.js +60 -0
- package/dist/utils/page-info-retry.js.map +1 -0
- package/dist/utils/page-info-utils.d.ts +1 -0
- package/dist/utils/page-info-utils.d.ts.map +1 -1
- package/dist/utils/page-info-utils.js +46 -18
- package/dist/utils/page-info-utils.js.map +1 -1
- package/dist/utils/ref-attacher.d.ts +21 -0
- package/dist/utils/ref-attacher.d.ts.map +1 -0
- package/dist/utils/ref-attacher.js +149 -0
- package/dist/utils/ref-attacher.js.map +1 -0
- package/dist/utils/ref-translator.d.ts +49 -0
- package/dist/utils/ref-translator.d.ts.map +1 -0
- package/dist/utils/ref-translator.js +276 -0
- package/dist/utils/ref-translator.js.map +1 -0
- package/package.json +1 -1
- package/plandocs/BEFORE_AFTER_VERIFICATION.md +148 -0
- package/plandocs/COORDINATE_MODE_DIAGNOSIS.md +144 -0
- package/plandocs/IMPLEMENTATION_STATUS.md +108 -0
- package/plandocs/PHASE_1_COMPLETE.md +165 -0
- package/plandocs/PHASE_1_SUMMARY.md +184 -0
- package/plandocs/PROMPT_OPTIMIZATION_ANALYSIS.md +120 -0
- package/plandocs/PROMPT_SANITY_CHECK.md +120 -0
- package/plandocs/SESSION_SUMMARY_v0.0.33.md +151 -0
- package/plandocs/TROUBLESHOOTING_SESSION.md +72 -0
- package/plandocs/VISUAL_AGENT_EVOLUTION_PLAN.md +396 -0
- package/plandocs/WHATS_NEW_v0.0.33.md +183 -0
- package/plandocs/exploratory-mode-support-v2.plan.md +953 -0
- package/plandocs/exploratory-mode-support.plan.md +928 -0
- package/plandocs/journey-id-tracking-addendum.md +227 -0
- package/src/execution-service.ts +179 -596
- package/src/index.ts +10 -0
- package/src/llm-facade.ts +8 -8
- package/src/llm-provider.ts +11 -1
- package/src/model-constants.ts +17 -5
- package/src/orchestrator/decision-parser.ts +139 -0
- package/src/orchestrator/index.ts +27 -2
- package/src/orchestrator/orchestrator-agent.ts +868 -623
- package/src/orchestrator/orchestrator-prompts.ts +786 -0
- package/src/orchestrator/page-som-handler.ts +1565 -0
- package/src/orchestrator/som-types.ts +188 -0
- package/src/orchestrator/tool-registry.ts +2 -0
- package/src/orchestrator/tools/index.ts +5 -1
- package/src/orchestrator/tools/refresh-som-markers.ts +69 -0
- package/src/orchestrator/tools/verify-action-result.ts +159 -0
- package/src/orchestrator/tools/view-previous-screenshot.ts +103 -0
- package/src/orchestrator/types.ts +95 -4
- package/src/prompts.ts +40 -34
- package/src/scenario-service.ts +20 -0
- package/src/scenario-worker-class.ts +30 -4
- package/src/utils/coordinate-converter.ts +162 -0
- package/src/utils/page-info-retry.ts +65 -0
- package/src/utils/page-info-utils.ts +53 -18
- package/testchimp-runner-core-0.0.35.tgz +0 -0
- /package/{CREDIT_CALLBACK_ARCHITECTURE.md → plandocs/CREDIT_CALLBACK_ARCHITECTURE.md} +0 -0
- /package/{INTEGRATION_COMPLETE.md → plandocs/INTEGRATION_COMPLETE.md} +0 -0
- /package/{VISION_DIAGNOSTICS_IMPROVEMENTS.md → plandocs/VISION_DIAGNOSTICS_IMPROVEMENTS.md} +0 -0
- /package/{RELEASE_0.0.26.md → releasenotes/RELEASE_0.0.26.md} +0 -0
- /package/{RELEASE_0.0.27.md → releasenotes/RELEASE_0.0.27.md} +0 -0
- /package/{RELEASE_0.0.28.md → releasenotes/RELEASE_0.0.28.md} +0 -0
package/src/execution-service.ts
CHANGED
|
@@ -21,6 +21,8 @@ import { DEFAULT_MODEL, VISION_MODEL } from './model-constants';
|
|
|
21
21
|
import { LLMProvider } from './llm-provider';
|
|
22
22
|
import { ProgressReporter } from './progress-reporter';
|
|
23
23
|
import { BackendProxyLLMProvider } from './providers/backend-proxy-llm-provider';
|
|
24
|
+
import { OrchestratorAgent, ToolRegistry, DEFAULT_AGENT_CONFIG } from './orchestrator';
|
|
25
|
+
import type { AgentConfig, JourneyMemory } from './orchestrator';
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Service for orchestrating Playwright script execution
|
|
@@ -34,6 +36,7 @@ export class ExecutionService {
|
|
|
34
36
|
private maxConcurrentExecutions: number;
|
|
35
37
|
private activeExecutions: Set<Promise<any>> = new Set();
|
|
36
38
|
private logger?: (message: string, level?: 'log' | 'error' | 'warn') => void;
|
|
39
|
+
private orchestratorAgent: OrchestratorAgent;
|
|
37
40
|
|
|
38
41
|
constructor(
|
|
39
42
|
authConfig?: AuthConfig,
|
|
@@ -51,6 +54,21 @@ export class ExecutionService {
|
|
|
51
54
|
this.progressReporter = progressReporter;
|
|
52
55
|
this.creditUsageService = new CreditUsageService(authConfig, backendUrl);
|
|
53
56
|
this.maxConcurrentExecutions = maxConcurrentExecutions;
|
|
57
|
+
|
|
58
|
+
// Initialize orchestrator for repair mode (reuses all SoM infrastructure)
|
|
59
|
+
const toolRegistry = new ToolRegistry();
|
|
60
|
+
const repairConfig: Partial<AgentConfig> = {
|
|
61
|
+
useSoM: true,
|
|
62
|
+
somRestrictCoordinates: true // Prefer SoM markers for repairs
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
this.orchestratorAgent = new OrchestratorAgent(
|
|
66
|
+
this.llmFacade,
|
|
67
|
+
toolRegistry,
|
|
68
|
+
repairConfig,
|
|
69
|
+
progressReporter,
|
|
70
|
+
(msg, level) => this.log(msg)
|
|
71
|
+
);
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
/**
|
|
@@ -253,7 +271,7 @@ export class ExecutionService {
|
|
|
253
271
|
}
|
|
254
272
|
|
|
255
273
|
// Execute the script as-is
|
|
256
|
-
await this.
|
|
274
|
+
await this.executeStepCode(request.script, page);
|
|
257
275
|
|
|
258
276
|
// LIFECYCLE: Call afterEndTest on success
|
|
259
277
|
if (this.progressReporter?.afterEndTest) {
|
|
@@ -301,7 +319,7 @@ export class ExecutionService {
|
|
|
301
319
|
}
|
|
302
320
|
|
|
303
321
|
// Execute the script as-is
|
|
304
|
-
await this.
|
|
322
|
+
await this.executeStepCode(request.script, page);
|
|
305
323
|
|
|
306
324
|
// LIFECYCLE: Call afterEndTest on success
|
|
307
325
|
if (this.progressReporter?.afterEndTest) {
|
|
@@ -385,8 +403,12 @@ export class ExecutionService {
|
|
|
385
403
|
// Start AI repair process
|
|
386
404
|
this.log('Starting AI repair process...');
|
|
387
405
|
|
|
406
|
+
let repairBrowser: any = null;
|
|
407
|
+
let repairContext: any = null;
|
|
408
|
+
let repairPage: any = null;
|
|
409
|
+
|
|
388
410
|
try {
|
|
389
|
-
let
|
|
411
|
+
let steps, updatedSteps;
|
|
390
412
|
|
|
391
413
|
if (useExistingBrowser) {
|
|
392
414
|
// Use existing browser
|
|
@@ -451,8 +473,8 @@ export class ExecutionService {
|
|
|
451
473
|
updatedSteps = await this.repairStepsWithAI(steps, repairPage, repairFlexibility, model, request.jobId);
|
|
452
474
|
}
|
|
453
475
|
|
|
454
|
-
// Always generate the updated script
|
|
455
|
-
const updatedScript = this.generateUpdatedScript(updatedSteps);
|
|
476
|
+
// Always generate the updated script (preserve original test name)
|
|
477
|
+
const updatedScript = this.generateUpdatedScript(updatedSteps, undefined, request.script);
|
|
456
478
|
|
|
457
479
|
// Check if repair was successful by seeing if we completed all steps
|
|
458
480
|
const allStepsSuccessful = updatedSteps.length > 0 && updatedSteps.every(step => step.success);
|
|
@@ -472,11 +494,32 @@ export class ExecutionService {
|
|
|
472
494
|
|
|
473
495
|
// Update file if we have any successful repairs (partial or complete)
|
|
474
496
|
if (hasSuccessfulRepairs) {
|
|
497
|
+
// IMPORTANT: Use the orchestrator-generated script directly (already has proper Playwright commands)
|
|
498
|
+
// Don't regenerate via LLM as it loses the actual repairs
|
|
499
|
+
this.log('Using orchestrator-generated script (skipping LLM regeneration to preserve repairs)');
|
|
500
|
+
|
|
501
|
+
// For repair advice, compare original vs repaired
|
|
475
502
|
const confidenceResponse = await this.llmFacade.assessRepairConfidence(request.script!, updatedScript, model);
|
|
476
|
-
const finalScript = await this.llmFacade.generateFinalScript(request.script!, updatedScript, confidenceResponse.advice, model);
|
|
477
503
|
|
|
478
|
-
//
|
|
479
|
-
const
|
|
504
|
+
// Add TestChimp comment with repair advice
|
|
505
|
+
const scriptWithAdvice = addTestChimpComment(updatedScript, confidenceResponse.advice);
|
|
506
|
+
|
|
507
|
+
// Polish the script with minor LLM cleanup (removes redundancies, fixes formatting)
|
|
508
|
+
this.log('Applying final LLM polish to repaired script (minor cleanup only)...');
|
|
509
|
+
const cleanupResult = await this.llmFacade.cleanupScript(scriptWithAdvice, model);
|
|
510
|
+
|
|
511
|
+
if (cleanupResult.changes.length > 0) {
|
|
512
|
+
this.log(`Script cleanup made ${cleanupResult.changes.length} minor improvements:`);
|
|
513
|
+
cleanupResult.changes.forEach((change, i) => {
|
|
514
|
+
this.log(` ${i + 1}. ${change}`);
|
|
515
|
+
});
|
|
516
|
+
} else if (cleanupResult.skipped) {
|
|
517
|
+
this.log(`Script cleanup skipped: ${cleanupResult.skipped}`);
|
|
518
|
+
} else {
|
|
519
|
+
this.log('Script cleanup: no changes needed');
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const scriptWithRepairAdvice = cleanupResult.script;
|
|
480
523
|
|
|
481
524
|
// Report credit usage for successful AI repair
|
|
482
525
|
this.creditUsageService.reportAIRepairCredit().catch(error => {
|
|
@@ -496,11 +539,6 @@ export class ExecutionService {
|
|
|
496
539
|
}
|
|
497
540
|
}
|
|
498
541
|
|
|
499
|
-
// Only close browser if we created it (not provided by caller)
|
|
500
|
-
if (!useExistingBrowser) {
|
|
501
|
-
await repairBrowser.close();
|
|
502
|
-
}
|
|
503
|
-
|
|
504
542
|
return {
|
|
505
543
|
runStatus: 'failed', // Original script failed
|
|
506
544
|
repairStatus: allStepsSuccessful ? 'success' : 'partial', // Complete or partial repair success
|
|
@@ -522,11 +560,6 @@ export class ExecutionService {
|
|
|
522
560
|
}
|
|
523
561
|
}
|
|
524
562
|
|
|
525
|
-
// Only close browser if we created it (not provided by caller)
|
|
526
|
-
if (!useExistingBrowser) {
|
|
527
|
-
await repairBrowser.close();
|
|
528
|
-
}
|
|
529
|
-
|
|
530
563
|
return {
|
|
531
564
|
runStatus: 'failed', // Original script failed
|
|
532
565
|
repairStatus: 'failed',
|
|
@@ -546,6 +579,16 @@ export class ExecutionService {
|
|
|
546
579
|
executionTime: Date.now() - startTime,
|
|
547
580
|
error: error instanceof Error ? error.message : 'Script execution failed'
|
|
548
581
|
};
|
|
582
|
+
} finally {
|
|
583
|
+
// Clean up browser resources if we created them (not provided by caller)
|
|
584
|
+
if (!useExistingBrowser && repairBrowser) {
|
|
585
|
+
try {
|
|
586
|
+
await repairBrowser.close();
|
|
587
|
+
this.log('AI repair browser closed');
|
|
588
|
+
} catch (closeError) {
|
|
589
|
+
this.log(`Error closing AI repair browser: ${closeError}`, 'warn');
|
|
590
|
+
}
|
|
591
|
+
}
|
|
549
592
|
}
|
|
550
593
|
}
|
|
551
594
|
|
|
@@ -629,6 +672,9 @@ export class ExecutionService {
|
|
|
629
672
|
newCode?: string;
|
|
630
673
|
}> = [];
|
|
631
674
|
|
|
675
|
+
// Track actual executed steps (including agent repairs) for proper history
|
|
676
|
+
const executedStepDescriptions: string[] = [];
|
|
677
|
+
|
|
632
678
|
// Create a shared execution context that accumulates all executed code for variable tracking
|
|
633
679
|
let executionContext = '';
|
|
634
680
|
const contextVariables = new Map<string, any>();
|
|
@@ -660,6 +706,9 @@ export class ExecutionService {
|
|
|
660
706
|
this.log(`Step ${i + 1} executed successfully: ${step.description}`);
|
|
661
707
|
this.log(`Step ${i + 1} success status set to: ${step.success}`);
|
|
662
708
|
|
|
709
|
+
// Track executed step description for agent context
|
|
710
|
+
executedStepDescriptions.push(step.description);
|
|
711
|
+
|
|
663
712
|
// Report successful step execution
|
|
664
713
|
this.log(`DEBUG: About to check callback - progressReporter=${!!this.progressReporter}, onStepProgress=${!!this.progressReporter?.onStepProgress}, jobId=${jobId}`);
|
|
665
714
|
if (this.progressReporter?.onStepProgress && jobId) {
|
|
@@ -691,304 +740,114 @@ export class ExecutionService {
|
|
|
691
740
|
step.success = false;
|
|
692
741
|
step.error = this.safeSerializeError(error);
|
|
693
742
|
|
|
694
|
-
//
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
743
|
+
// Use orchestrator for repair (reuses all SoM infrastructure)
|
|
744
|
+
this.log(`Calling orchestrator in REPAIR mode for step ${i + 1}`);
|
|
745
|
+
|
|
746
|
+
// Prepare repair context - use executedStepDescriptions (includes agent repairs)
|
|
747
|
+
const priorSteps = executedStepDescriptions; // What was ACTUALLY executed (scripted + agent)
|
|
748
|
+
const nextSteps = updatedSteps.slice(i + 1).map(s => s.description);
|
|
749
|
+
|
|
750
|
+
this.log(` Prior steps executed: ${priorSteps.length}, Next steps: ${nextSteps.length}`);
|
|
751
|
+
this.log(` Prior steps context:\n ${priorSteps.map((s, idx) => `${idx + 1}. ${s}`).join('\n ')}`);
|
|
752
|
+
|
|
753
|
+
// Create minimal memory for repair
|
|
754
|
+
const memory: JourneyMemory = {
|
|
755
|
+
experiences: [],
|
|
756
|
+
extractedData: {},
|
|
757
|
+
history: [],
|
|
758
|
+
latestNote: undefined
|
|
759
|
+
};
|
|
760
|
+
|
|
702
761
|
let repairSuccess = false;
|
|
703
|
-
const originalDescription = step.description;
|
|
704
|
-
const originalCode = step.code;
|
|
705
|
-
let usedVisionMode = false;
|
|
706
762
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
763
|
+
try {
|
|
764
|
+
// Call orchestrator with repair context (page object persisted)
|
|
765
|
+
const repairResult = await this.orchestratorAgent.executeStep(
|
|
766
|
+
page, // Same page object (persisted state)
|
|
767
|
+
step.description, // Goal with testdata embedded
|
|
768
|
+
i + 1, // Current step number
|
|
769
|
+
updatedSteps.length, // Total steps
|
|
770
|
+
updatedSteps.map(s => s.description), // All step descriptions
|
|
771
|
+
memory, // Memory (empty for repair)
|
|
772
|
+
jobId || 'repair',
|
|
773
|
+
priorSteps, // NEW: What was already completed
|
|
774
|
+
nextSteps // NEW: What comes after this
|
|
775
|
+
);
|
|
720
776
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
this.log(` 🤔 After ${repairHistory.length} failed repairs: Asking LLM if screenshot would help (last resort)...`);
|
|
725
|
-
|
|
726
|
-
const screenshotNeed = await this.llmFacade.assessScreenshotNeed(
|
|
727
|
-
step.description,
|
|
728
|
-
step.error || 'Unknown error',
|
|
729
|
-
repairHistory.length + 1,
|
|
730
|
-
pageInfo,
|
|
731
|
-
model
|
|
732
|
-
);
|
|
777
|
+
if (repairResult.success && repairResult.commands.length > 0) {
|
|
778
|
+
// MODIFY: Orchestrator fixed the step - replace with new code
|
|
779
|
+
const repairedCode = repairResult.commands.join('\n');
|
|
733
780
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
// 2. Get repair suggestion with enhanced context from vision analysis
|
|
740
|
-
|
|
741
|
-
this.log(` 📸 Taking screenshot for supervisor analysis...`);
|
|
742
|
-
|
|
743
|
-
// Capture optimized screenshot using utility method
|
|
744
|
-
const imageDataUrl = await captureOptimizedScreenshot(
|
|
745
|
-
page,
|
|
746
|
-
{ timeout: 10000 }, // Uses default quality 60
|
|
747
|
-
(msg) => this.log(msg)
|
|
748
|
-
);
|
|
749
|
-
|
|
750
|
-
this.log(` 👔 STEP 1: Supervisor analyzing screenshot (${VISION_MODEL})...`);
|
|
751
|
-
const supervisorDiagnostics = await this.llmFacade.getVisionDiagnostics(
|
|
752
|
-
step.description,
|
|
753
|
-
pageInfo,
|
|
754
|
-
[], // No previous steps context for repair
|
|
755
|
-
step.error,
|
|
756
|
-
imageDataUrl,
|
|
757
|
-
VISION_MODEL
|
|
758
|
-
);
|
|
759
|
-
|
|
760
|
-
// DEBUG: Log vision diagnostics
|
|
761
|
-
this.log(` 📸 Visual insights: ${supervisorDiagnostics.visualAnalysis}`);
|
|
762
|
-
this.log(` 🔍 Root cause: ${supervisorDiagnostics.rootCause}`);
|
|
763
|
-
this.log(` 💡 Recommended approach: ${supervisorDiagnostics.recommendedApproach}`);
|
|
764
|
-
if (supervisorDiagnostics.elementsFound.length > 0) {
|
|
765
|
-
this.log(` ✅ Elements found: ${supervisorDiagnostics.elementsFound.join(', ')}`);
|
|
766
|
-
}
|
|
767
|
-
if (supervisorDiagnostics.elementsNotFound.length > 0) {
|
|
768
|
-
this.log(` ❌ Elements not found: ${supervisorDiagnostics.elementsNotFound.join(', ')}`);
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// Get repair suggestion with vision-enhanced context
|
|
772
|
-
this.log(` 🔨 STEP 2: Getting repair suggestion with vision insights...`);
|
|
773
|
-
const visionEnhancedFailureHistory = `${failureHistory}
|
|
774
|
-
|
|
775
|
-
VISION-BASED DIAGNOSTIC INSIGHTS:
|
|
776
|
-
Visual Analysis: ${supervisorDiagnostics.visualAnalysis}
|
|
777
|
-
Root Cause: ${supervisorDiagnostics.rootCause}
|
|
778
|
-
Recommended Approach: ${supervisorDiagnostics.recommendedApproach}
|
|
779
|
-
Elements Found: ${supervisorDiagnostics.elementsFound.join(', ') || 'None'}
|
|
780
|
-
Elements Not Found: ${supervisorDiagnostics.elementsNotFound.join(', ') || 'None'}
|
|
781
|
-
|
|
782
|
-
Use these vision insights to inform your repair strategy.`;
|
|
783
|
-
|
|
784
|
-
repairSuggestion = await this.llmFacade.getRepairSuggestion(
|
|
785
|
-
step.description,
|
|
786
|
-
step.code,
|
|
787
|
-
step.error || 'Unknown error',
|
|
788
|
-
pageInfo,
|
|
789
|
-
visionEnhancedFailureHistory,
|
|
790
|
-
recentRepairsContext,
|
|
791
|
-
model
|
|
792
|
-
);
|
|
793
|
-
|
|
794
|
-
usedVisionMode = true;
|
|
795
|
-
} else {
|
|
796
|
-
// Regular repair without vision
|
|
797
|
-
if (screenshotNeed.alternativeApproach) {
|
|
798
|
-
this.log(` 💡 Alternative approach: ${screenshotNeed.alternativeApproach}`);
|
|
799
|
-
}
|
|
800
|
-
repairSuggestion = await this.llmFacade.getRepairSuggestion(
|
|
801
|
-
step.description,
|
|
802
|
-
step.code,
|
|
803
|
-
step.error || 'Unknown error',
|
|
804
|
-
pageInfo,
|
|
805
|
-
failureHistory,
|
|
806
|
-
recentRepairsContext,
|
|
807
|
-
model
|
|
808
|
-
);
|
|
809
|
-
}
|
|
810
|
-
} else {
|
|
811
|
-
// Regular repair attempt (first 2 attempts or already used vision)
|
|
812
|
-
repairSuggestion = await this.llmFacade.getRepairSuggestion(
|
|
813
|
-
step.description,
|
|
814
|
-
step.code,
|
|
815
|
-
step.error || 'Unknown error',
|
|
816
|
-
pageInfo,
|
|
817
|
-
failureHistory,
|
|
818
|
-
recentRepairsContext,
|
|
819
|
-
model
|
|
820
|
-
);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
if (!repairSuggestion.shouldContinue) {
|
|
824
|
-
this.log(`AI decided to stop repair at attempt ${attempt}: ${repairSuggestion.reason}`);
|
|
825
|
-
break;
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
// Apply the repair action
|
|
829
|
-
try {
|
|
830
|
-
// Set the step index and insertAfterIndex on the client side based on current step being processed
|
|
831
|
-
const repairAction = {
|
|
832
|
-
...repairSuggestion.action,
|
|
833
|
-
stepIndex: i, // Client-side step index management
|
|
834
|
-
insertAfterIndex: repairSuggestion.action.operation === StepOperation.INSERT ? i - 1 : undefined // For INSERT, insert before current step
|
|
781
|
+
updatedSteps[i] = {
|
|
782
|
+
...step,
|
|
783
|
+
code: repairedCode,
|
|
784
|
+
success: true,
|
|
785
|
+
error: undefined
|
|
835
786
|
};
|
|
836
787
|
|
|
837
|
-
this.log(
|
|
838
|
-
this.log(
|
|
788
|
+
this.log(`✓ Step ${i + 1} MODIFIED by orchestrator (repair successful)`);
|
|
789
|
+
this.log(` Original code: ${step.code}`);
|
|
790
|
+
this.log(` New code (${repairResult.commands.length} commands):\n ${repairResult.commands.join('\n ')}`);
|
|
839
791
|
|
|
840
|
-
|
|
792
|
+
// Track what agent actually did in history (for future repair context)
|
|
793
|
+
const agentActionSummary = `${step.description} [AI-repaired: ${repairResult.commands.length} commands]`;
|
|
794
|
+
executedStepDescriptions.push(agentActionSummary);
|
|
841
795
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
this.
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
if (repairAction.operation === StepOperation.MODIFY) {
|
|
848
|
-
// For MODIFY: mark the modified step as successful
|
|
849
|
-
step.success = true;
|
|
850
|
-
step.error = undefined;
|
|
851
|
-
updatedSteps[i].success = true;
|
|
852
|
-
updatedSteps[i].error = undefined;
|
|
853
|
-
this.log(`Step ${i + 1} marked as successful after MODIFY repair`);
|
|
854
|
-
|
|
855
|
-
// Report repaired step
|
|
856
|
-
if (this.progressReporter?.onStepProgress && jobId) {
|
|
857
|
-
this.log(`DEBUG: Reporting repaired step ${i + 1}:`);
|
|
858
|
-
this.log(` description: ${updatedSteps[i].description}`);
|
|
859
|
-
this.log(` code: ${updatedSteps[i].code}`);
|
|
860
|
-
await this.progressReporter.onStepProgress({
|
|
861
|
-
jobId,
|
|
862
|
-
stepId: updatedSteps[i].id, // Preserve original step ID if provided
|
|
863
|
-
stepNumber: i + 1,
|
|
864
|
-
description: updatedSteps[i].description,
|
|
865
|
-
code: updatedSteps[i].code,
|
|
866
|
-
status: 'SUCCESS_STEP_EXECUTION' as any,
|
|
867
|
-
wasRepaired: true
|
|
868
|
-
});
|
|
869
|
-
}
|
|
870
|
-
} else if (repairAction.operation === StepOperation.INSERT) {
|
|
871
|
-
// For INSERT: mark the newly inserted step as successful
|
|
872
|
-
const insertIndex = repairAction.insertAfterIndex !== undefined ? repairAction.insertAfterIndex + 1 : i + 1;
|
|
873
|
-
if (updatedSteps[insertIndex]) {
|
|
874
|
-
updatedSteps[insertIndex].success = true;
|
|
875
|
-
updatedSteps[insertIndex].error = undefined;
|
|
876
|
-
|
|
877
|
-
// Report inserted step
|
|
878
|
-
if (this.progressReporter?.onStepProgress && jobId) {
|
|
879
|
-
await this.progressReporter.onStepProgress({
|
|
880
|
-
jobId,
|
|
881
|
-
stepId: updatedSteps[insertIndex].id, // Preserve original step ID if provided
|
|
882
|
-
stepNumber: insertIndex + 1,
|
|
883
|
-
description: updatedSteps[insertIndex].description,
|
|
884
|
-
code: updatedSteps[insertIndex].code,
|
|
885
|
-
status: 'SUCCESS_STEP_EXECUTION' as any,
|
|
886
|
-
wasRepaired: true
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
} else if (repairAction.operation === StepOperation.REMOVE) {
|
|
891
|
-
// For REMOVE: no step to mark as successful since we removed it
|
|
892
|
-
// The step is already removed from the array
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const commandInfo = repairAction.operation === StepOperation.MODIFY ?
|
|
896
|
-
`MODIFY: "${repairAction.newStep?.code || 'N/A'}"` :
|
|
897
|
-
repairAction.operation === StepOperation.INSERT ?
|
|
898
|
-
`INSERT: "${repairAction.newStep?.code || 'N/A'}"` :
|
|
899
|
-
repairAction.operation === StepOperation.REMOVE ?
|
|
900
|
-
`REMOVE: step at index ${repairAction.stepIndex}` :
|
|
901
|
-
repairAction.operation;
|
|
902
|
-
this.log(`Step ${i + 1} repair action ${commandInfo} executed successfully on attempt ${attempt}${usedVisionMode ? ' (vision-aided)' : ''}`);
|
|
903
|
-
|
|
904
|
-
// Update execution context based on the repair action
|
|
905
|
-
if (repairAction.operation === StepOperation.MODIFY && repairAction.newStep) {
|
|
906
|
-
// Update the step in the execution context for variable tracking
|
|
907
|
-
executionContext = executionContext.replace(originalCode, repairAction.newStep.code);
|
|
908
|
-
} else if (repairAction.operation === StepOperation.INSERT && repairAction.newStep) {
|
|
909
|
-
// Insert the new step code into execution context for variable tracking
|
|
910
|
-
executionContext += repairAction.newStep.code + '\n';
|
|
911
|
-
} else if (repairAction.operation === StepOperation.REMOVE) {
|
|
912
|
-
// Remove the step code from execution context for variable tracking
|
|
913
|
-
executionContext = executionContext.replace(originalCode, '');
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
// Record this successful repair
|
|
917
|
-
recentRepairs.push({
|
|
796
|
+
// Report repaired step
|
|
797
|
+
if (this.progressReporter?.onStepProgress && jobId) {
|
|
798
|
+
await this.progressReporter.onStepProgress({
|
|
799
|
+
jobId,
|
|
800
|
+
stepId: step.id,
|
|
918
801
|
stepNumber: i + 1,
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
newCode: repairAction.newStep?.code
|
|
802
|
+
description: updatedSteps[i].description,
|
|
803
|
+
code: updatedSteps[i].code,
|
|
804
|
+
status: 'SUCCESS_STEP_EXECUTION' as any,
|
|
805
|
+
wasRepaired: true
|
|
924
806
|
});
|
|
925
|
-
|
|
926
|
-
// Keep only the last 3 repairs for context
|
|
927
|
-
if (recentRepairs.length > 3) {
|
|
928
|
-
recentRepairs.shift();
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
// Update step index based on operation
|
|
932
|
-
if (repairAction.operation === StepOperation.INSERT) {
|
|
933
|
-
// For INSERT: inserted step is already executed
|
|
934
|
-
this.log(`INSERT operation: current i=${i}, insertAfterIndex=${repairAction.insertAfterIndex}`);
|
|
935
|
-
this.log(`INSERT: Steps array length before: ${updatedSteps.length}`);
|
|
936
|
-
this.log(`INSERT: Steps before operation: ${updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`).join(', ')}`);
|
|
937
|
-
|
|
938
|
-
if (repairAction.insertAfterIndex !== undefined && repairAction.insertAfterIndex < i) {
|
|
939
|
-
// If inserting before current position, current step moved down by 1
|
|
940
|
-
this.log(`INSERT before current position: incrementing i from ${i} to ${i + 1}`);
|
|
941
|
-
i++; // Move to the original step that was pushed to the next position
|
|
942
|
-
} else {
|
|
943
|
-
// If inserting at or after current position, stay at current step
|
|
944
|
-
this.log(`INSERT at/after current position: keeping i at ${i}`);
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
this.log(`INSERT: Steps array length after: ${updatedSteps.length}`);
|
|
948
|
-
this.log(`INSERT: Steps after operation: ${updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`).join(', ')}`);
|
|
949
|
-
} else if (repairAction.operation === StepOperation.REMOVE) {
|
|
950
|
-
// For REMOVE: stay at same index since the next step moved to current position
|
|
951
|
-
// Don't increment i because the array shifted left
|
|
952
|
-
} else {
|
|
953
|
-
// For MODIFY: move to next step since modified step was executed
|
|
954
|
-
i++; // Move to next step for MODIFY
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
// Add the repaired step's code to execution context for variable tracking
|
|
958
|
-
executionContext += step.code + '\n';
|
|
959
|
-
|
|
960
|
-
break;
|
|
961
|
-
} else {
|
|
962
|
-
throw new Error(result.error || 'Repair action failed');
|
|
963
807
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
`
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
808
|
+
|
|
809
|
+
// Ensure page is stable after agent repairs before returning control to script
|
|
810
|
+
this.log(`Waiting for page stability after agent repair...`);
|
|
811
|
+
try {
|
|
812
|
+
await page.waitForLoadState('networkidle', { timeout: 5000 });
|
|
813
|
+
this.log(`Page stabilized (networkidle) after agent repair`);
|
|
814
|
+
} catch (stabilityError) {
|
|
815
|
+
try {
|
|
816
|
+
await page.waitForLoadState('domcontentloaded', { timeout: 3000 });
|
|
817
|
+
this.log(`Page loaded (domcontentloaded) after agent repair`);
|
|
818
|
+
} catch (fallbackError) {
|
|
819
|
+
this.log(`Page stability wait timed out (continuing anyway)`, 'warn');
|
|
820
|
+
}
|
|
976
821
|
}
|
|
977
822
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
823
|
+
repairSuccess = true;
|
|
824
|
+
i++; // Continue to NEXT step (hand control back to script)
|
|
825
|
+
|
|
826
|
+
} else if (repairResult.success && repairResult.commands.length === 0) {
|
|
827
|
+
// DELETE: Step goal already achieved or no longer needed (e.g., modal already dismissed)
|
|
828
|
+
this.log(`✓ Step ${i + 1} DELETED by orchestrator (goal already achieved, step obsolete)`);
|
|
829
|
+
this.log(` Reason: Orchestrator completed with 0 commands - step no longer needed`);
|
|
830
|
+
|
|
831
|
+
// Track deletion in history (helps agent understand what was skipped)
|
|
832
|
+
executedStepDescriptions.push(`${step.description} [AI-deleted: step obsolete/already done]`);
|
|
833
|
+
|
|
834
|
+
// Remove the step from array
|
|
835
|
+
updatedSteps.splice(i, 1);
|
|
836
|
+
|
|
837
|
+
repairSuccess = true;
|
|
838
|
+
// Don't increment i - next step moved to current position
|
|
985
839
|
|
|
986
|
-
|
|
840
|
+
} else {
|
|
841
|
+
this.log(`✗ Step ${i + 1} could not be repaired by orchestrator (reason: ${repairResult.terminationReason})`);
|
|
987
842
|
}
|
|
843
|
+
} catch (repairError: any) {
|
|
844
|
+
this.log(`✗ Orchestrator repair failed: ${repairError.message}`);
|
|
988
845
|
}
|
|
989
|
-
|
|
846
|
+
|
|
847
|
+
// Legacy repair code removed - now using orchestrator
|
|
848
|
+
|
|
990
849
|
if (!repairSuccess) {
|
|
991
|
-
this.log(`Step ${i + 1}
|
|
850
|
+
this.log(`Step ${i + 1} could not be repaired - stopping execution`);
|
|
992
851
|
break;
|
|
993
852
|
}
|
|
994
853
|
}
|
|
@@ -1041,310 +900,34 @@ Use these vision insights to inform your repair strategy.`;
|
|
|
1041
900
|
return code; // Return the original code without removing comments
|
|
1042
901
|
}
|
|
1043
902
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
): Promise<void> {
|
|
1050
|
-
// Detect if code contains navigation or load state operations that need longer timeout
|
|
1051
|
-
const needsLongerTimeout = code.includes('waitForLoadState') ||
|
|
1052
|
-
code.includes('goto(') ||
|
|
1053
|
-
code.includes('waitForURL') ||
|
|
1054
|
-
code.includes('waitForNavigation');
|
|
1055
|
-
|
|
1056
|
-
// Use appropriate timeout based on operation type
|
|
1057
|
-
const timeout = needsLongerTimeout ? 30000 : 5000;
|
|
1058
|
-
page.setDefaultTimeout(timeout);
|
|
1059
|
-
|
|
1060
|
-
try {
|
|
1061
|
-
// Execute only the current step code, but make context variables available
|
|
1062
|
-
const fullCode = code;
|
|
1063
|
-
|
|
1064
|
-
// Dynamically import expect
|
|
1065
|
-
const { expect } = require('@playwright/test');
|
|
1066
|
-
|
|
1067
|
-
// Create a function that has access to page, expect, and the context variables
|
|
1068
|
-
const executeCode = new Function(
|
|
1069
|
-
'page',
|
|
1070
|
-
'expect',
|
|
1071
|
-
'contextVariables',
|
|
1072
|
-
`return (async () => {
|
|
1073
|
-
// Make context variables available in the execution scope
|
|
1074
|
-
for (const [key, value] of contextVariables) {
|
|
1075
|
-
globalThis[key] = value;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
${fullCode}
|
|
1079
|
-
|
|
1080
|
-
// Capture any new variables that might have been created
|
|
1081
|
-
const newVars = {};
|
|
1082
|
-
for (const key in globalThis) {
|
|
1083
|
-
if (!contextVariables.has(key) && typeof globalThis[key] !== 'function' && key !== 'page' && key !== 'expect') {
|
|
1084
|
-
newVars[key] = globalThis[key];
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
return newVars;
|
|
1088
|
-
})()`
|
|
1089
|
-
);
|
|
1090
|
-
|
|
1091
|
-
const newVars = await executeCode(page, expect, contextVariables);
|
|
1092
|
-
|
|
1093
|
-
// Update the context variables with any new variables created
|
|
1094
|
-
for (const [key, value] of Object.entries(newVars)) {
|
|
1095
|
-
contextVariables.set(key, value);
|
|
1096
|
-
}
|
|
1097
|
-
} finally {
|
|
1098
|
-
// Reset to default timeout for element operations
|
|
1099
|
-
page.setDefaultTimeout(5000);
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
private async executeScriptContent(script: string, page: any): Promise<void> {
|
|
1104
|
-
// Extract the test function content
|
|
1105
|
-
const testMatch = script.match(/test\([^,]+,\s*async\s*\(\s*\{\s*page[^}]*\}\s*\)\s*=>\s*\{([\s\S]*)\}\s*\);/);
|
|
1106
|
-
if (!testMatch) {
|
|
1107
|
-
throw new Error('Could not extract test function from script');
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
const testBody = testMatch[1];
|
|
1111
|
-
|
|
1112
|
-
// Dynamically import expect
|
|
1113
|
-
const { expect } = require('@playwright/test');
|
|
1114
|
-
|
|
1115
|
-
// Execute the entire test body as one async function
|
|
1116
|
-
const executeTest = new Function('page', 'expect', `return (async () => { ${testBody} })()`);
|
|
1117
|
-
await executeTest(page, expect);
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
private async getEnhancedPageInfo(page: any): Promise<PageInfo> {
|
|
1121
|
-
try {
|
|
1122
|
-
return await getEnhancedPageInfo(page);
|
|
1123
|
-
} catch (error) {
|
|
1124
|
-
return {
|
|
1125
|
-
url: page.url(),
|
|
1126
|
-
title: 'Unknown',
|
|
1127
|
-
ariaSnapshot: null,
|
|
1128
|
-
interactiveElements: [],
|
|
1129
|
-
formattedElements: 'Unable to extract'
|
|
1130
|
-
};
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
private buildFailureHistory(
|
|
1135
|
-
repairHistory: Array<{ attempt: number; action: StepRepairAction; error: string; pageInfo: PageInfo }>,
|
|
1136
|
-
originalStep: ScriptStep,
|
|
1137
|
-
originalError: any
|
|
1138
|
-
): string {
|
|
1139
|
-
if (repairHistory.length === 0) {
|
|
1140
|
-
return `Original failure: ${this.safeSerializeError(originalError)}`;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
let history = `Original failure: ${this.safeSerializeError(originalError)}\n\n`;
|
|
1144
|
-
history += `Previous repair attempts:\n`;
|
|
1145
|
-
|
|
1146
|
-
repairHistory.forEach((attempt, index) => {
|
|
1147
|
-
history += `Attempt ${attempt.attempt}:\n`;
|
|
1148
|
-
history += ` Operation: ${attempt.action.operation}\n`;
|
|
1149
|
-
if (attempt.action.newStep) {
|
|
1150
|
-
history += ` Description: ${attempt.action.newStep.description}\n`;
|
|
1151
|
-
history += ` Code: ${attempt.action.newStep.code}\n`;
|
|
1152
|
-
}
|
|
1153
|
-
history += ` Error: ${attempt.error}\n`;
|
|
1154
|
-
if (index < repairHistory.length - 1) {
|
|
1155
|
-
history += `\n`;
|
|
1156
|
-
}
|
|
1157
|
-
});
|
|
1158
|
-
|
|
1159
|
-
return history;
|
|
903
|
+
// Legacy repair helper methods (now unused but kept for compilation)
|
|
904
|
+
private buildFailureHistory(): string { return ''; }
|
|
905
|
+
private buildRecentRepairsContext(): string { return ''; }
|
|
906
|
+
private async applyRepairActionInContext(): Promise<{ success: boolean; error?: string }> {
|
|
907
|
+
return { success: false };
|
|
1160
908
|
}
|
|
1161
909
|
|
|
1162
|
-
private
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
originalDescription?: string;
|
|
1167
|
-
newDescription?: string;
|
|
1168
|
-
originalCode?: string;
|
|
1169
|
-
newCode?: string;
|
|
1170
|
-
}>
|
|
1171
|
-
): string {
|
|
1172
|
-
if (recentRepairs.length === 0) {
|
|
1173
|
-
return 'No recent repairs to consider.';
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
let context = 'Recent successful repairs that may affect this step:\n\n';
|
|
910
|
+
private generateUpdatedScript(steps: (ScriptStep & { success?: boolean; error?: string })[], repairAdvice?: string, originalScript?: string): string {
|
|
911
|
+
// Extract test name and hashtags from original script if provided
|
|
912
|
+
let testName = 'repairedTest';
|
|
913
|
+
let hashtags: string[] = [];
|
|
1177
914
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
if (repair.operation === 'REMOVE') {
|
|
1183
|
-
context += ` Removed: "${repair.originalDescription}"\n`;
|
|
1184
|
-
context += ` Code removed:\n ${repair.originalCode?.replace(/\n/g, '\n ')}\n`;
|
|
1185
|
-
} else if (repair.operation === 'INSERT') {
|
|
1186
|
-
context += ` Inserted: "${repair.newDescription}"\n`;
|
|
1187
|
-
context += ` Code inserted:\n ${repair.newCode?.replace(/\n/g, '\n ')}\n`;
|
|
1188
|
-
} else {
|
|
1189
|
-
context += ` Original: "${repair.originalDescription}"\n`;
|
|
1190
|
-
context += ` Repaired: "${repair.newDescription}"\n`;
|
|
1191
|
-
context += ` Code changed from:\n ${repair.originalCode?.replace(/\n/g, '\n ')}\n`;
|
|
1192
|
-
context += ` To:\n ${repair.newCode?.replace(/\n/g, '\n ')}\n`;
|
|
915
|
+
if (originalScript) {
|
|
916
|
+
const testNameMatch = originalScript.match(/test\(['"]([^'"]+)['"]/);
|
|
917
|
+
if (testNameMatch) {
|
|
918
|
+
testName = testNameMatch[1];
|
|
1193
919
|
}
|
|
1194
920
|
|
|
1195
|
-
|
|
1196
|
-
|
|
921
|
+
// Extract hashtags from TestChimp comment
|
|
922
|
+
const hashtagMatch = originalScript.match(/#\w+(?:\s+#\w+)*/);
|
|
923
|
+
if (hashtagMatch) {
|
|
924
|
+
hashtags = hashtagMatch[0].split(/\s+/).filter(tag => tag.startsWith('#'));
|
|
1197
925
|
}
|
|
1198
|
-
});
|
|
1199
|
-
|
|
1200
|
-
context += '\nConsider how these changes might affect the current step and adjust accordingly.';
|
|
1201
|
-
return context;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
private async applyRepairActionInContext(
|
|
1205
|
-
action: StepRepairAction,
|
|
1206
|
-
steps: (ScriptStep & { success?: boolean; error?: string })[],
|
|
1207
|
-
currentIndex: number,
|
|
1208
|
-
page: any,
|
|
1209
|
-
executionContext: string,
|
|
1210
|
-
contextVariables: Map<string, any>
|
|
1211
|
-
): Promise<{ success: boolean; error?: string; updatedContext?: string }> {
|
|
1212
|
-
try {
|
|
1213
|
-
switch (action.operation) {
|
|
1214
|
-
case StepOperation.MODIFY:
|
|
1215
|
-
if (action.newStep && action.stepIndex !== undefined) {
|
|
1216
|
-
// Modify existing step
|
|
1217
|
-
steps[action.stepIndex] = {
|
|
1218
|
-
...action.newStep,
|
|
1219
|
-
success: false,
|
|
1220
|
-
error: undefined
|
|
1221
|
-
};
|
|
1222
|
-
// Test the modified step with current page state and variables
|
|
1223
|
-
await this.executeStepCode(action.newStep.code, page);
|
|
1224
|
-
return { success: true, updatedContext: executionContext + action.newStep.code };
|
|
1225
|
-
}
|
|
1226
|
-
break;
|
|
1227
|
-
|
|
1228
|
-
case StepOperation.INSERT:
|
|
1229
|
-
if (action.newStep && action.insertAfterIndex !== undefined) {
|
|
1230
|
-
// Insert new step after specified index
|
|
1231
|
-
const insertIndex = action.insertAfterIndex + 1;
|
|
1232
|
-
const newStep = {
|
|
1233
|
-
...action.newStep,
|
|
1234
|
-
success: false,
|
|
1235
|
-
error: undefined
|
|
1236
|
-
};
|
|
1237
|
-
this.log(`INSERT: Inserting step at index ${insertIndex} with description "${newStep.description}"`);
|
|
1238
|
-
this.log(`INSERT: Steps before insertion: ${steps.map((s, i) => `${i}: "${s.description}" (success: ${s.success})`).join(', ')}`);
|
|
1239
|
-
|
|
1240
|
-
// Preserve success status of existing steps before insertion
|
|
1241
|
-
const successStatusMap = new Map(steps.map((step, index) => [index, { success: step.success, error: step.error }]));
|
|
1242
|
-
|
|
1243
|
-
steps.splice(insertIndex, 0, newStep);
|
|
1244
|
-
|
|
1245
|
-
// Restore success status for steps that were shifted by the insertion
|
|
1246
|
-
// Steps at insertIndex and before keep their original status
|
|
1247
|
-
// Steps after insertIndex need to be shifted to their new positions
|
|
1248
|
-
for (let i = insertIndex + 1; i < steps.length; i++) {
|
|
1249
|
-
const originalIndex = i - 1; // The step that was originally at this position
|
|
1250
|
-
if (successStatusMap.has(originalIndex)) {
|
|
1251
|
-
const status = successStatusMap.get(originalIndex)!;
|
|
1252
|
-
steps[i].success = status.success;
|
|
1253
|
-
steps[i].error = status.error;
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
// CRITICAL FIX: Ensure the inserted step doesn't overwrite existing step data
|
|
1258
|
-
// The new step should only have its own description, not inherit from existing steps
|
|
1259
|
-
this.log(`INSERT: Final step array after restoration: ${steps.map((s, i) => `${i}: "${s.description}" (success: ${s.success})`).join(', ')}`);
|
|
1260
|
-
|
|
1261
|
-
this.log(`INSERT: Steps after insertion: ${steps.map((s, i) => `${i}: "${s.description}" (success: ${s.success})`).join(', ')}`);
|
|
1262
|
-
// Test the new step with current page state
|
|
1263
|
-
await this.executeStepCode(action.newStep.code, page);
|
|
1264
|
-
return { success: true, updatedContext: executionContext + action.newStep.code };
|
|
1265
|
-
}
|
|
1266
|
-
break;
|
|
1267
|
-
|
|
1268
|
-
case StepOperation.REMOVE:
|
|
1269
|
-
if (action.stepIndex !== undefined) {
|
|
1270
|
-
// Remove step
|
|
1271
|
-
steps.splice(action.stepIndex, 1);
|
|
1272
|
-
return { success: true, updatedContext: executionContext };
|
|
1273
|
-
}
|
|
1274
|
-
break;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
return { success: false, error: 'Invalid repair action' };
|
|
1278
|
-
} catch (error) {
|
|
1279
|
-
return {
|
|
1280
|
-
success: false,
|
|
1281
|
-
error: error instanceof Error ? error.message : 'Unknown error during repair action'
|
|
1282
|
-
};
|
|
1283
926
|
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
private async applyRepairAction(
|
|
1287
|
-
action: StepRepairAction,
|
|
1288
|
-
steps: (ScriptStep & { success?: boolean; error?: string })[],
|
|
1289
|
-
currentIndex: number,
|
|
1290
|
-
page: any
|
|
1291
|
-
): Promise<{ success: boolean; error?: string }> {
|
|
1292
|
-
try {
|
|
1293
|
-
switch (action.operation) {
|
|
1294
|
-
case StepOperation.MODIFY:
|
|
1295
|
-
if (action.newStep && action.stepIndex !== undefined) {
|
|
1296
|
-
// Modify existing step
|
|
1297
|
-
steps[action.stepIndex] = {
|
|
1298
|
-
...action.newStep,
|
|
1299
|
-
success: false,
|
|
1300
|
-
error: undefined
|
|
1301
|
-
};
|
|
1302
|
-
// Test the modified step
|
|
1303
|
-
await this.executeStepCode(action.newStep.code, page);
|
|
1304
|
-
return { success: true };
|
|
1305
|
-
}
|
|
1306
|
-
break;
|
|
1307
|
-
|
|
1308
|
-
case StepOperation.INSERT:
|
|
1309
|
-
if (action.newStep && action.insertAfterIndex !== undefined) {
|
|
1310
|
-
// Insert new step after specified index
|
|
1311
|
-
const insertIndex = action.insertAfterIndex + 1;
|
|
1312
|
-
const newStep = {
|
|
1313
|
-
...action.newStep,
|
|
1314
|
-
success: false,
|
|
1315
|
-
error: undefined
|
|
1316
|
-
};
|
|
1317
|
-
steps.splice(insertIndex, 0, newStep);
|
|
1318
|
-
// Test the inserted step
|
|
1319
|
-
await this.executeStepCode(action.newStep.code, page);
|
|
1320
|
-
return { success: true };
|
|
1321
|
-
}
|
|
1322
|
-
break;
|
|
1323
|
-
|
|
1324
|
-
case StepOperation.REMOVE:
|
|
1325
|
-
if (action.stepIndex !== undefined) {
|
|
1326
|
-
// Remove the step
|
|
1327
|
-
steps.splice(action.stepIndex, 1);
|
|
1328
|
-
return { success: true };
|
|
1329
|
-
}
|
|
1330
|
-
break;
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
return { success: false, error: 'Invalid repair action' };
|
|
1334
|
-
} catch (error) {
|
|
1335
|
-
return {
|
|
1336
|
-
success: false,
|
|
1337
|
-
error: error instanceof Error ? error.message : 'Repair action execution failed'
|
|
1338
|
-
};
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
private generateUpdatedScript(steps: (ScriptStep & { success?: boolean; error?: string })[], repairAdvice?: string): string {
|
|
927
|
+
|
|
1345
928
|
const scriptLines = [
|
|
1346
929
|
"import { test, expect } from '@playwright/test';",
|
|
1347
|
-
`test('
|
|
930
|
+
`test('${testName}', async ({ page, browser, context }) => {`
|
|
1348
931
|
];
|
|
1349
932
|
|
|
1350
933
|
steps.forEach((step, index) => {
|
|
@@ -1361,8 +944,8 @@ Use these vision insights to inform your repair strategy.`;
|
|
|
1361
944
|
scriptLines.push('});');
|
|
1362
945
|
const script = scriptLines.join('\n');
|
|
1363
946
|
|
|
1364
|
-
// Add TestChimp comment
|
|
1365
|
-
return addTestChimpComment(script, repairAdvice);
|
|
947
|
+
// Add TestChimp comment with hashtags and repair advice
|
|
948
|
+
return addTestChimpComment(script, repairAdvice, hashtags);
|
|
1366
949
|
}
|
|
1367
950
|
|
|
1368
951
|
|