testchimp-runner-core 0.0.21 → 0.0.23
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/VISION_DIAGNOSTICS_IMPROVEMENTS.md +336 -0
- package/dist/credit-usage-service.d.ts +9 -0
- package/dist/credit-usage-service.d.ts.map +1 -1
- package/dist/credit-usage-service.js +20 -5
- package/dist/credit-usage-service.js.map +1 -1
- package/dist/execution-service.d.ts +7 -2
- package/dist/execution-service.d.ts.map +1 -1
- package/dist/execution-service.js +91 -36
- package/dist/execution-service.js.map +1 -1
- package/dist/index.d.ts +30 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +91 -26
- package/dist/index.js.map +1 -1
- package/dist/llm-facade.d.ts +64 -8
- package/dist/llm-facade.d.ts.map +1 -1
- package/dist/llm-facade.js +361 -109
- package/dist/llm-facade.js.map +1 -1
- package/dist/llm-provider.d.ts +39 -0
- package/dist/llm-provider.d.ts.map +1 -0
- package/dist/llm-provider.js +7 -0
- package/dist/llm-provider.js.map +1 -0
- package/dist/model-constants.d.ts +21 -0
- package/dist/model-constants.d.ts.map +1 -0
- package/dist/model-constants.js +24 -0
- package/dist/model-constants.js.map +1 -0
- package/dist/orchestrator/index.d.ts +8 -0
- package/dist/orchestrator/index.d.ts.map +1 -0
- package/dist/orchestrator/index.js +23 -0
- package/dist/orchestrator/index.js.map +1 -0
- package/dist/orchestrator/orchestrator-agent.d.ts +66 -0
- package/dist/orchestrator/orchestrator-agent.d.ts.map +1 -0
- package/dist/orchestrator/orchestrator-agent.js +855 -0
- package/dist/orchestrator/orchestrator-agent.js.map +1 -0
- package/dist/orchestrator/tool-registry.d.ts +74 -0
- package/dist/orchestrator/tool-registry.d.ts.map +1 -0
- package/dist/orchestrator/tool-registry.js +131 -0
- package/dist/orchestrator/tool-registry.js.map +1 -0
- package/dist/orchestrator/tools/check-page-ready.d.ts +13 -0
- package/dist/orchestrator/tools/check-page-ready.d.ts.map +1 -0
- package/dist/orchestrator/tools/check-page-ready.js +72 -0
- package/dist/orchestrator/tools/check-page-ready.js.map +1 -0
- package/dist/orchestrator/tools/extract-data.d.ts +13 -0
- package/dist/orchestrator/tools/extract-data.d.ts.map +1 -0
- package/dist/orchestrator/tools/extract-data.js +84 -0
- package/dist/orchestrator/tools/extract-data.js.map +1 -0
- package/dist/orchestrator/tools/index.d.ts +10 -0
- package/dist/orchestrator/tools/index.d.ts.map +1 -0
- package/dist/orchestrator/tools/index.js +18 -0
- package/dist/orchestrator/tools/index.js.map +1 -0
- package/dist/orchestrator/tools/inspect-page.d.ts +13 -0
- package/dist/orchestrator/tools/inspect-page.d.ts.map +1 -0
- package/dist/orchestrator/tools/inspect-page.js +39 -0
- package/dist/orchestrator/tools/inspect-page.js.map +1 -0
- package/dist/orchestrator/tools/recall-history.d.ts +13 -0
- package/dist/orchestrator/tools/recall-history.d.ts.map +1 -0
- package/dist/orchestrator/tools/recall-history.js +64 -0
- package/dist/orchestrator/tools/recall-history.js.map +1 -0
- package/dist/orchestrator/tools/take-screenshot.d.ts +15 -0
- package/dist/orchestrator/tools/take-screenshot.d.ts.map +1 -0
- package/dist/orchestrator/tools/take-screenshot.js +112 -0
- package/dist/orchestrator/tools/take-screenshot.js.map +1 -0
- package/dist/orchestrator/types.d.ts +133 -0
- package/dist/orchestrator/types.d.ts.map +1 -0
- package/dist/orchestrator/types.js +28 -0
- package/dist/orchestrator/types.js.map +1 -0
- package/dist/playwright-mcp-service.d.ts +9 -0
- package/dist/playwright-mcp-service.d.ts.map +1 -1
- package/dist/playwright-mcp-service.js +20 -5
- package/dist/playwright-mcp-service.js.map +1 -1
- package/dist/progress-reporter.d.ts +97 -0
- package/dist/progress-reporter.d.ts.map +1 -0
- package/dist/progress-reporter.js +18 -0
- package/dist/progress-reporter.js.map +1 -0
- package/dist/prompts.d.ts +24 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +593 -68
- package/dist/prompts.js.map +1 -1
- package/dist/providers/backend-proxy-llm-provider.d.ts +25 -0
- package/dist/providers/backend-proxy-llm-provider.d.ts.map +1 -0
- package/dist/providers/backend-proxy-llm-provider.js +76 -0
- package/dist/providers/backend-proxy-llm-provider.js.map +1 -0
- package/dist/providers/local-llm-provider.d.ts +21 -0
- package/dist/providers/local-llm-provider.d.ts.map +1 -0
- package/dist/providers/local-llm-provider.js +35 -0
- package/dist/providers/local-llm-provider.js.map +1 -0
- package/dist/scenario-service.d.ts +27 -1
- package/dist/scenario-service.d.ts.map +1 -1
- package/dist/scenario-service.js +48 -12
- package/dist/scenario-service.js.map +1 -1
- package/dist/scenario-worker-class.d.ts +39 -2
- package/dist/scenario-worker-class.d.ts.map +1 -1
- package/dist/scenario-worker-class.js +614 -86
- package/dist/scenario-worker-class.js.map +1 -1
- package/dist/script-utils.d.ts +2 -0
- package/dist/script-utils.d.ts.map +1 -1
- package/dist/script-utils.js +44 -4
- package/dist/script-utils.js.map +1 -1
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/browser-utils.d.ts +20 -1
- package/dist/utils/browser-utils.d.ts.map +1 -1
- package/dist/utils/browser-utils.js +102 -51
- package/dist/utils/browser-utils.js.map +1 -1
- package/dist/utils/page-info-utils.d.ts +23 -4
- package/dist/utils/page-info-utils.d.ts.map +1 -1
- package/dist/utils/page-info-utils.js +174 -43
- package/dist/utils/page-info-utils.js.map +1 -1
- package/package.json +1 -2
- package/plandocs/HUMAN_LIKE_IMPROVEMENTS.md +642 -0
- package/plandocs/MULTI_AGENT_ARCHITECTURE_REVIEW.md +844 -0
- package/plandocs/ORCHESTRATOR_MVP_SUMMARY.md +539 -0
- package/plandocs/PHASE1_ABSTRACTION_COMPLETE.md +241 -0
- package/plandocs/PHASE1_FINAL_STATUS.md +210 -0
- package/plandocs/PLANNING_SESSION_SUMMARY.md +372 -0
- package/plandocs/SCRIPT_CLEANUP_FEATURE.md +201 -0
- package/plandocs/SCRIPT_GENERATION_ARCHITECTURE.md +364 -0
- package/plandocs/SELECTOR_IMPROVEMENTS.md +139 -0
- package/src/credit-usage-service.ts +23 -5
- package/src/execution-service.ts +152 -42
- package/src/index.ts +169 -26
- package/src/llm-facade.ts +500 -126
- package/src/llm-provider.ts +43 -0
- package/src/model-constants.ts +23 -0
- package/src/orchestrator/index.ts +33 -0
- package/src/orchestrator/orchestrator-agent.ts +1037 -0
- package/src/orchestrator/tool-registry.ts +182 -0
- package/src/orchestrator/tools/check-page-ready.ts +75 -0
- package/src/orchestrator/tools/extract-data.ts +92 -0
- package/src/orchestrator/tools/index.ts +11 -0
- package/src/orchestrator/tools/inspect-page.ts +42 -0
- package/src/orchestrator/tools/recall-history.ts +72 -0
- package/src/orchestrator/tools/take-screenshot.ts +128 -0
- package/src/orchestrator/types.ts +200 -0
- package/src/playwright-mcp-service.ts +23 -5
- package/src/progress-reporter.ts +109 -0
- package/src/prompts.ts +606 -69
- package/src/providers/backend-proxy-llm-provider.ts +91 -0
- package/src/providers/local-llm-provider.ts +38 -0
- package/src/scenario-service.ts +83 -13
- package/src/scenario-worker-class.ts +740 -72
- package/src/script-utils.ts +50 -5
- package/src/types.ts +13 -1
- package/src/utils/browser-utils.ts +123 -51
- package/src/utils/page-info-utils.ts +210 -53
- package/testchimp-runner-core-0.0.22.tgz +0 -0
|
@@ -1,33 +1,169 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ScenarioWorker = void 0;
|
|
4
|
+
const events_1 = require("events");
|
|
4
5
|
const page_info_utils_1 = require("./utils/page-info-utils");
|
|
5
6
|
const browser_utils_1 = require("./utils/browser-utils");
|
|
6
7
|
const llm_facade_1 = require("./llm-facade");
|
|
7
8
|
const script_utils_1 = require("./script-utils");
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const model_constants_1 = require("./model-constants");
|
|
10
|
+
const progress_reporter_1 = require("./progress-reporter");
|
|
11
|
+
const backend_proxy_llm_provider_1 = require("./providers/backend-proxy-llm-provider");
|
|
12
|
+
const orchestrator_1 = require("./orchestrator");
|
|
13
|
+
const MAX_RETRIES_PER_STEP = 3; // 4 total attempts per sub-action: 3 DOM-only, then 1 potential vision attempt
|
|
14
|
+
const MAX_SUBACTIONS_PER_STEP = 5; // Maximum sub-actions to attempt for a single step (reduced from 10 to prevent excessive retries)
|
|
15
|
+
const MAX_FAILED_ATTEMPTS_PER_STEP = 12; // Hard limit on FAILED attempts per step across all sub-actions
|
|
16
|
+
class ScenarioWorker extends events_1.EventEmitter {
|
|
17
|
+
constructor(fileHandler, llmProvider, progressReporter, authConfig, backendUrl, options, outputChannel) {
|
|
18
|
+
super();
|
|
11
19
|
this.initialized = false;
|
|
12
20
|
this.sessionId = null;
|
|
13
|
-
|
|
21
|
+
// Orchestrator mode
|
|
22
|
+
this.useOrchestrator = false;
|
|
23
|
+
this.debugMode = false;
|
|
24
|
+
// Use provided LLM provider or default to backend proxy (backward compatible)
|
|
25
|
+
const actualLLMProvider = llmProvider || new backend_proxy_llm_provider_1.BackendProxyLLMProvider(authConfig, backendUrl);
|
|
26
|
+
this.llmFacade = new llm_facade_1.LLMFacade(actualLLMProvider);
|
|
14
27
|
this.fileHandler = fileHandler;
|
|
15
|
-
this.
|
|
28
|
+
this.progressReporter = progressReporter;
|
|
29
|
+
this.outputChannel = outputChannel; // Set outputChannel for log routing
|
|
30
|
+
// Orchestrator setup
|
|
31
|
+
this.useOrchestrator = options?.useOrchestrator || false;
|
|
32
|
+
this.orchestratorConfig = options?.orchestratorConfig;
|
|
33
|
+
this.debugMode = options?.debugMode || false;
|
|
34
|
+
if (this.useOrchestrator) {
|
|
35
|
+
this.initializeOrchestrator();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Initialize orchestrator mode with tools
|
|
40
|
+
*/
|
|
41
|
+
initializeOrchestrator() {
|
|
42
|
+
this.log('🤖 Initializing Orchestrator Mode');
|
|
43
|
+
// Create tool registry
|
|
44
|
+
this.toolRegistry = new orchestrator_1.ToolRegistry();
|
|
45
|
+
// Create tools (READ-ONLY information gathering only)
|
|
46
|
+
const takeScreenshotTool = new orchestrator_1.TakeScreenshotTool();
|
|
47
|
+
takeScreenshotTool.setLLMFacade(this.llmFacade); // Inject LLM for vision analysis
|
|
48
|
+
// Register 5 information-gathering tools (state changes via Playwright commands)
|
|
49
|
+
this.toolRegistry.register(takeScreenshotTool);
|
|
50
|
+
this.toolRegistry.register(new orchestrator_1.RecallHistoryTool());
|
|
51
|
+
this.toolRegistry.register(new orchestrator_1.InspectPageTool());
|
|
52
|
+
this.toolRegistry.register(new orchestrator_1.CheckPageReadyTool());
|
|
53
|
+
this.toolRegistry.register(new orchestrator_1.ExtractDataTool());
|
|
54
|
+
// Create orchestrator agent
|
|
55
|
+
this.orchestratorAgent = new orchestrator_1.OrchestratorAgent(this.llmFacade, this.toolRegistry, this.orchestratorConfig, this.progressReporter, (message, level) => {
|
|
56
|
+
if (level === 'error') {
|
|
57
|
+
this.logError(message);
|
|
58
|
+
}
|
|
59
|
+
else if (level === 'warn') {
|
|
60
|
+
this.log(`⚠️ ${message}`);
|
|
61
|
+
}
|
|
62
|
+
else if (level === 'debug') {
|
|
63
|
+
if (this.debugMode) {
|
|
64
|
+
this.log(`🐛 ${message}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
this.log(message);
|
|
69
|
+
}
|
|
70
|
+
}, this.debugMode // Pass debug mode
|
|
71
|
+
);
|
|
72
|
+
this.log(`✓ Orchestrator initialized with 5 tools${this.debugMode ? ' (DEBUG MODE)' : ''} (information-gathering only)`);
|
|
16
73
|
}
|
|
17
74
|
log(message) {
|
|
18
|
-
|
|
75
|
+
const timestamp = new Date().toISOString().substring(11, 23); // HH:MM:SS.mmm
|
|
76
|
+
const formattedMessage = `[${timestamp}] [ScenarioWorker] ${message}`;
|
|
77
|
+
// Always log to console for debug visibility
|
|
78
|
+
console.log(formattedMessage);
|
|
79
|
+
// Also route to outputChannel if provided
|
|
19
80
|
if (this.outputChannel) {
|
|
20
|
-
this.outputChannel.appendLine(
|
|
81
|
+
this.outputChannel.appendLine(formattedMessage);
|
|
21
82
|
}
|
|
22
83
|
}
|
|
23
84
|
logError(message) {
|
|
24
|
-
|
|
85
|
+
const timestamp = new Date().toISOString().substring(11, 23); // HH:MM:SS.mmm
|
|
86
|
+
const formattedMessage = `[${timestamp}] [ScenarioWorker] ERROR: ${message}`;
|
|
87
|
+
// Always log to console for debug visibility
|
|
88
|
+
console.error(formattedMessage);
|
|
89
|
+
// Also route to outputChannel if provided
|
|
25
90
|
if (this.outputChannel) {
|
|
26
|
-
this.outputChannel.appendLine(
|
|
91
|
+
this.outputChannel.appendLine(formattedMessage);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Capture screenshot as data URL
|
|
96
|
+
* Returns data:image/png;base64,... format
|
|
97
|
+
*/
|
|
98
|
+
async captureStepScreenshot(page) {
|
|
99
|
+
try {
|
|
100
|
+
const screenshot = await page.screenshot({ type: 'png' });
|
|
101
|
+
const base64 = screenshot.toString('base64');
|
|
102
|
+
return `data:image/png;base64,${base64}`;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
this.log(`Failed to capture screenshot: ${error}`);
|
|
106
|
+
return undefined;
|
|
27
107
|
}
|
|
28
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Report step progress to progress reporter
|
|
111
|
+
*/
|
|
112
|
+
async reportStepProgress(progress) {
|
|
113
|
+
// Report to progress reporter if available
|
|
114
|
+
await this.progressReporter?.onStepProgress?.(progress);
|
|
115
|
+
// Still emit events for backward compatibility
|
|
116
|
+
this.emit('stepProgress', progress);
|
|
117
|
+
// Also log for visibility
|
|
118
|
+
this.progressReporter?.log?.(`Step ${progress.stepNumber} [${progress.status}]: ${progress.description}`, progress.status === progress_reporter_1.StepExecutionStatus.FAILURE ? 'error' : 'log');
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Report job progress to progress reporter
|
|
122
|
+
*/
|
|
123
|
+
async reportJobProgress(progress) {
|
|
124
|
+
// Report to progress reporter if available
|
|
125
|
+
await this.progressReporter?.onJobProgress?.(progress);
|
|
126
|
+
// Still emit events for backward compatibility
|
|
127
|
+
this.emit('jobProgress', progress);
|
|
128
|
+
// Also log for visibility
|
|
129
|
+
this.progressReporter?.log?.(`Job ${progress.jobId}: ${progress.status}`);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Detect if a step is complex and benefits from proactive vision usage
|
|
133
|
+
* Complex steps: form filling, verification, navigation after actions
|
|
134
|
+
*/
|
|
135
|
+
isComplexStep(stepDescription) {
|
|
136
|
+
const description = stepDescription.toLowerCase();
|
|
137
|
+
// Verification steps - often need visual confirmation
|
|
138
|
+
if (description.includes('verify') || description.includes('check') ||
|
|
139
|
+
description.includes('confirm') || description.includes('ensure')) {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
// Form-related steps - multiple fields, complex interactions
|
|
143
|
+
if (description.includes('fill') && (description.includes('form') || description.includes('field'))) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
if (description.includes('enter') && description.includes('information')) {
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
// Steps that typically follow navigation (page may still be loading)
|
|
150
|
+
if (description.includes('click') && (description.includes('menu') ||
|
|
151
|
+
description.includes('tab') ||
|
|
152
|
+
description.includes('link'))) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
// Multi-step actions indicated by "and" or commas
|
|
156
|
+
if (description.includes(' and ') || description.split(',').length > 1) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
29
161
|
async initialize() {
|
|
30
162
|
try {
|
|
163
|
+
const RUNNER_CORE_VERSION = "v1.5.0-vision-preserve-values";
|
|
164
|
+
this.log('═══════════════════════════════════════════════════════');
|
|
165
|
+
this.log(`🚀 RUNNER-CORE VERSION: ${RUNNER_CORE_VERSION}`);
|
|
166
|
+
this.log('═══════════════════════════════════════════════════════');
|
|
31
167
|
this.log('Initializing Scenario worker...');
|
|
32
168
|
this.sessionId = `scenario_worker_${Date.now()}`;
|
|
33
169
|
this.initialized = true;
|
|
@@ -42,6 +178,12 @@ class ScenarioWorker {
|
|
|
42
178
|
if (!this.initialized) {
|
|
43
179
|
throw new Error('Scenario worker not initialized');
|
|
44
180
|
}
|
|
181
|
+
// Set current job ID for progress reporting
|
|
182
|
+
this.currentJobId = job.id;
|
|
183
|
+
// VERSION MARKER - increment this number with each significant change
|
|
184
|
+
const RUNNER_CORE_VERSION = "v1.5.0-vision-preserve-values";
|
|
185
|
+
this.log(`🚀 RUNNER-CORE VERSION: ${RUNNER_CORE_VERSION}`);
|
|
186
|
+
this.log(`📋 Processing scenario: ${job.scenario}`);
|
|
45
187
|
const startTime = Date.now();
|
|
46
188
|
const steps = [];
|
|
47
189
|
let generatedScript = '';
|
|
@@ -51,94 +193,457 @@ class ScenarioWorker {
|
|
|
51
193
|
let page;
|
|
52
194
|
let overallSuccess = true;
|
|
53
195
|
try {
|
|
196
|
+
// Report job started
|
|
197
|
+
await this.reportJobProgress({
|
|
198
|
+
jobId: job.id,
|
|
199
|
+
status: 'started',
|
|
200
|
+
testName: job.testName
|
|
201
|
+
});
|
|
54
202
|
// 1. Break down scenario into steps using LLM
|
|
55
203
|
const scenarioSteps = await this.llmFacade.breakdownScenario(job.scenario, job.model);
|
|
56
204
|
steps.push(...scenarioSteps);
|
|
205
|
+
// Report total steps
|
|
206
|
+
await this.reportJobProgress({
|
|
207
|
+
jobId: job.id,
|
|
208
|
+
status: 'in_progress',
|
|
209
|
+
testName: job.testName,
|
|
210
|
+
totalSteps: steps.length,
|
|
211
|
+
currentStep: 0
|
|
212
|
+
});
|
|
213
|
+
// Emit log events for steps breakdown
|
|
214
|
+
this.emit('log', job.id, `\n## Steps Identified (${steps.length} total)\n`);
|
|
215
|
+
for (const step of steps) {
|
|
216
|
+
this.emit('log', job.id, `${step.stepNumber}. ${step.description}\n`);
|
|
217
|
+
}
|
|
218
|
+
this.emit('log', job.id, `\n## Execution Progress\n\n`);
|
|
57
219
|
// 2. Start a new browser session using centralized utility
|
|
58
220
|
// Default to headed mode (headless: false) for better debugging
|
|
59
|
-
|
|
221
|
+
// Create logger function from outputChannel for browser initialization
|
|
222
|
+
const logger = this.outputChannel ? (message, level) => {
|
|
223
|
+
this.outputChannel.appendLine(`[Browser] ${message}`);
|
|
224
|
+
} : undefined;
|
|
225
|
+
const browserInstance = await (0, browser_utils_1.initializeBrowser)(job.playwrightConfig, false, undefined, logger);
|
|
60
226
|
browser = browserInstance.browser;
|
|
61
227
|
context = browserInstance.context;
|
|
62
228
|
page = browserInstance.page;
|
|
63
|
-
// Set reasonable timeout for
|
|
64
|
-
|
|
229
|
+
// Set reasonable timeout for most operations
|
|
230
|
+
// 5 seconds for element interactions (fast feedback on wrong selectors)
|
|
231
|
+
// Navigation operations should use explicit longer timeouts
|
|
232
|
+
page.setDefaultTimeout(5000);
|
|
65
233
|
let previousSteps = [];
|
|
66
234
|
let lastError;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
235
|
+
let consecutiveFailures = 0;
|
|
236
|
+
const MAX_CONSECUTIVE_FAILURES = this.orchestratorConfig?.maxConsecutiveStepFailures || 3;
|
|
237
|
+
const CONTINUE_ON_FAILURE = this.orchestratorConfig?.continueOnStepFailure !== false; // Default true
|
|
238
|
+
// 3a. ORCHESTRATOR MODE - Use orchestrator agent for execution
|
|
239
|
+
if (this.useOrchestrator && this.orchestratorAgent) {
|
|
240
|
+
this.log(`🤖 Using Orchestrator Mode (continueOnFailure: ${CONTINUE_ON_FAILURE})`);
|
|
241
|
+
// Initialize journey memory
|
|
242
|
+
const memory = {
|
|
243
|
+
history: [],
|
|
244
|
+
experiences: [],
|
|
245
|
+
extractedData: {}
|
|
246
|
+
};
|
|
247
|
+
// Execute steps using orchestrator
|
|
248
|
+
for (let i = 0; i < steps.length; i++) {
|
|
249
|
+
// Only stop if consecutive failures exceed limit AND continueOnFailure is false
|
|
250
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES && !CONTINUE_ON_FAILURE) {
|
|
251
|
+
this.log(`🛑 Stopping execution: ${consecutiveFailures} consecutive failures`);
|
|
252
|
+
// Mark remaining steps as skipped
|
|
253
|
+
for (let j = i; j < steps.length; j++) {
|
|
254
|
+
steps[j].success = false;
|
|
255
|
+
steps[j].error = `Skipped due to ${consecutiveFailures} consecutive failures`;
|
|
256
|
+
steps[j].playwrightCommands = [];
|
|
257
|
+
}
|
|
258
|
+
overallSuccess = false;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
// Warn if approaching limit (even with continueOnFailure)
|
|
262
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES && CONTINUE_ON_FAILURE) {
|
|
263
|
+
this.log(`⚠️ ${consecutiveFailures} consecutive failures - continuing but scenario may have issues`);
|
|
264
|
+
}
|
|
265
|
+
const step = steps[i];
|
|
266
|
+
step.stepNumber = i + 1;
|
|
80
267
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
268
|
+
// Use orchestrator to execute this step
|
|
269
|
+
const result = await this.orchestratorAgent.executeStep(page, step.description, step.stepNumber, steps.length, steps.map(s => s.description), memory, job.id);
|
|
270
|
+
// Update step with result
|
|
271
|
+
step.success = result.success;
|
|
272
|
+
step.playwrightCommands = result.commands;
|
|
273
|
+
step.error = result.error;
|
|
274
|
+
if (result.success) {
|
|
275
|
+
this.log(`✓ Step ${step.stepNumber} completed via orchestrator (${result.iterations} iterations)`);
|
|
276
|
+
consecutiveFailures = 0;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
this.log(`✗ Step ${step.stepNumber} failed via orchestrator: ${result.terminationReason}`);
|
|
280
|
+
consecutiveFailures++;
|
|
281
|
+
overallSuccess = false;
|
|
282
|
+
// CRITICAL: Stop on agent_stuck or infeasible (explicit agent decision)
|
|
283
|
+
// continueOnStepFailure only applies to command failures, not agent decisions
|
|
284
|
+
if (result.terminationReason === 'agent_stuck' || result.terminationReason === 'infeasible') {
|
|
285
|
+
this.log(`🛑 Stopping: Agent declared step ${result.terminationReason} - cannot continue`);
|
|
286
|
+
// Mark remaining steps as skipped
|
|
287
|
+
for (let j = i + 1; j < steps.length; j++) {
|
|
288
|
+
steps[j].success = false;
|
|
289
|
+
steps[j].error = `Skipped: Previous step was ${result.terminationReason}`;
|
|
290
|
+
steps[j].playwrightCommands = [];
|
|
291
|
+
}
|
|
292
|
+
break; // Exit loop
|
|
293
|
+
}
|
|
93
294
|
}
|
|
94
|
-
step.playwrightCommand = command;
|
|
95
|
-
currentAttemptCommand = command;
|
|
96
|
-
this.log(` Command: ${command}`);
|
|
97
|
-
// Execute the command
|
|
98
|
-
await this.executePlaywrightCommand(page, browser, context, command);
|
|
99
|
-
stepSuccess = true;
|
|
100
|
-
currentAttemptSuccess = true;
|
|
101
|
-
stepOutput = `Executed: ${command}`;
|
|
102
|
-
stepError = undefined;
|
|
103
|
-
this.log(` ✅ SUCCESS: ${command}`);
|
|
104
|
-
break; // Step successful, move to next scenario step
|
|
105
295
|
}
|
|
106
296
|
catch (error) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
297
|
+
this.logError(`Orchestrator execution failed for step ${step.stepNumber}: ${error.message}`);
|
|
298
|
+
step.success = false;
|
|
299
|
+
step.error = error.message;
|
|
300
|
+
consecutiveFailures++;
|
|
301
|
+
overallSuccess = false;
|
|
302
|
+
}
|
|
303
|
+
previousSteps.push(step);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
// 3b. LEGACY MODE - Use existing retry loop
|
|
308
|
+
// Execute each step (steps may require multiple commands)
|
|
309
|
+
for (let i = 0; i < steps.length; i++) {
|
|
310
|
+
// Check if we should stop execution due to consecutive failures
|
|
311
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
312
|
+
this.log(`🛑 Stopping execution: ${consecutiveFailures} consecutive failures detected`);
|
|
313
|
+
this.log(` Remaining ${steps.length - i} steps will be skipped to avoid wasting resources`);
|
|
314
|
+
// Emit log events about early termination
|
|
315
|
+
this.emit('log', job.id, `\n🛑 EARLY TERMINATION\n`);
|
|
316
|
+
this.emit('log', job.id, `Reason: ${consecutiveFailures} consecutive step failures\n`);
|
|
317
|
+
this.emit('log', job.id, `Steps attempted: ${i}\n`);
|
|
318
|
+
this.emit('log', job.id, `Steps skipped: ${steps.length - i}\n\n`);
|
|
319
|
+
// Mark remaining steps as skipped
|
|
320
|
+
for (let j = i; j < steps.length; j++) {
|
|
321
|
+
const skippedStep = steps[j];
|
|
322
|
+
skippedStep.stepNumber = j + 1;
|
|
323
|
+
skippedStep.success = false;
|
|
324
|
+
skippedStep.error = `Skipped due to ${consecutiveFailures} consecutive failures in previous steps`;
|
|
325
|
+
skippedStep.playwrightCommands = [];
|
|
326
|
+
previousSteps.push(skippedStep);
|
|
115
327
|
}
|
|
116
|
-
|
|
117
|
-
|
|
328
|
+
overallSuccess = false;
|
|
329
|
+
break; // Exit the loop
|
|
330
|
+
}
|
|
331
|
+
const step = steps[i];
|
|
332
|
+
step.stepNumber = i + 1;
|
|
333
|
+
step.retryCount = 0;
|
|
334
|
+
step.subActions = [];
|
|
335
|
+
// Force new array and clear any previous command data
|
|
336
|
+
step.playwrightCommands = [];
|
|
337
|
+
step.playwrightCommand = undefined;
|
|
338
|
+
let stepSuccess = false;
|
|
339
|
+
let stepError;
|
|
340
|
+
let subActionCount = 0;
|
|
341
|
+
let stepComplete = false;
|
|
342
|
+
let totalFailedAttemptsForStep = 0; // Track FAILED attempts across all sub-actions
|
|
343
|
+
// Detect if this is a complex step that benefits from proactive vision
|
|
344
|
+
const isComplexStep = this.isComplexStep(step.description);
|
|
345
|
+
// A step might need multiple commands (sub-actions) to complete
|
|
346
|
+
while (!stepComplete && subActionCount < MAX_SUBACTIONS_PER_STEP && totalFailedAttemptsForStep < MAX_FAILED_ATTEMPTS_PER_STEP) {
|
|
347
|
+
let subActionSuccess = false;
|
|
348
|
+
let subActionCommand;
|
|
349
|
+
let subActionError;
|
|
350
|
+
let subActionRetries = 0;
|
|
351
|
+
let usedVisionMode = false;
|
|
352
|
+
// Build context about what's been done so far in this step
|
|
353
|
+
const stepContext = step.subActions && step.subActions.length > 0
|
|
354
|
+
? `\nSub-actions completed so far for this step:\n${step.subActions.map((sa, idx) => ` ${idx + 1}. ${sa.command} - ${sa.success ? 'SUCCESS' : 'FAILED'}`).join('\n')}`
|
|
355
|
+
: '';
|
|
356
|
+
for (let attempt = 0; attempt <= MAX_RETRIES_PER_STEP; attempt++) {
|
|
357
|
+
// Check if we've exceeded failed attempts budget BEFORE attempting
|
|
358
|
+
if (totalFailedAttemptsForStep >= MAX_FAILED_ATTEMPTS_PER_STEP) {
|
|
359
|
+
this.log(` ⚠️ Exceeded failed attempts budget (${MAX_FAILED_ATTEMPTS_PER_STEP}) for this step`);
|
|
360
|
+
stepComplete = true;
|
|
361
|
+
stepSuccess = false;
|
|
362
|
+
stepError = `Exceeded maximum failed attempts (${MAX_FAILED_ATTEMPTS_PER_STEP}) for step`;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
let currentAttemptCommand;
|
|
366
|
+
let currentAttemptSuccess = false;
|
|
367
|
+
let currentAttemptError;
|
|
368
|
+
const attemptTimestamp = Date.now();
|
|
369
|
+
try {
|
|
370
|
+
this.log(`Step ${step.stepNumber} - Sub-action ${subActionCount + 1}, Attempt ${attempt + 1}: ${step.description}`);
|
|
371
|
+
// Get current page state - handle navigation in progress
|
|
372
|
+
let domSnapshot;
|
|
373
|
+
let pageInfo;
|
|
374
|
+
try {
|
|
375
|
+
domSnapshot = {
|
|
376
|
+
url: page.url(),
|
|
377
|
+
title: await page.title(),
|
|
378
|
+
accessibilityTree: await page.accessibility.snapshot()
|
|
379
|
+
};
|
|
380
|
+
pageInfo = await (0, page_info_utils_1.getEnhancedPageInfo)(domSnapshot);
|
|
381
|
+
}
|
|
382
|
+
catch (contextError) {
|
|
383
|
+
// If execution context was destroyed (navigation in progress), wait and retry
|
|
384
|
+
if (contextError.message && contextError.message.includes('Execution context was destroyed')) {
|
|
385
|
+
this.log(` ⏳ Navigation in progress, waiting for page to load...`);
|
|
386
|
+
await page.waitForLoadState('domcontentloaded', { timeout: 5000 }).catch(() => { });
|
|
387
|
+
// Retry page state capture
|
|
388
|
+
domSnapshot = {
|
|
389
|
+
url: page.url(),
|
|
390
|
+
title: await page.title(),
|
|
391
|
+
accessibilityTree: await page.accessibility.snapshot()
|
|
392
|
+
};
|
|
393
|
+
pageInfo = await (0, page_info_utils_1.getEnhancedPageInfo)(domSnapshot);
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
throw contextError; // Re-throw if it's not a navigation issue
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Vision trigger: Liberal usage since gpt-5-mini vision is cost-effective
|
|
400
|
+
const modelToUse = job.model || model_constants_1.DEFAULT_MODEL;
|
|
401
|
+
let command;
|
|
402
|
+
// Enhanced logging for vision trigger logic
|
|
403
|
+
this.log(` 🔍 Vision trigger check: subAction=${subActionCount + 1}, attempt=${attempt}, totalFailed=${totalFailedAttemptsForStep}, usedVision=${usedVisionMode}`);
|
|
404
|
+
// Liberal vision strategy (gpt-5-mini is cost-effective):
|
|
405
|
+
// 1. After ANY failure (1+) → use vision
|
|
406
|
+
// 2. Complex steps → use vision from attempt 1
|
|
407
|
+
// 3. No LLM assessment gate → go directly to vision
|
|
408
|
+
const hasFailure = totalFailedAttemptsForStep >= 1 && lastError;
|
|
409
|
+
const shouldUseProactiveVision = isComplexStep && attempt === 0; // First attempt for complex steps
|
|
410
|
+
const shouldUseVision = (hasFailure || shouldUseProactiveVision) && !usedVisionMode;
|
|
411
|
+
if (shouldUseVision) {
|
|
412
|
+
if (shouldUseProactiveVision) {
|
|
413
|
+
this.log(` 🎯 PROACTIVE VISION: Complex step detected, using vision from first attempt`);
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
this.log(` 🎯 VISION TRIGGER: ${totalFailedAttemptsForStep} failure(s) detected, using vision (no LLM gate)`);
|
|
417
|
+
}
|
|
418
|
+
// Two-step supervisor pattern:
|
|
419
|
+
// 1. Supervisor analyzes screenshot and provides instructions
|
|
420
|
+
// 2. Worker generates command based on those instructions
|
|
421
|
+
this.log(` 📸 Taking screenshot for supervisor analysis...`);
|
|
422
|
+
// Capture optimized screenshot using utility method
|
|
423
|
+
const imageDataUrl = await (0, browser_utils_1.captureOptimizedScreenshot)(page, { timeout: 10000 }, // Uses default quality 60
|
|
424
|
+
(msg) => this.log(msg));
|
|
425
|
+
this.log(` 👔 STEP 1: Supervisor analyzing screenshot (${model_constants_1.VISION_MODEL})...`);
|
|
426
|
+
const supervisorDiagnostics = await this.llmFacade.getVisionDiagnostics(step.description + stepContext, pageInfo, previousSteps, lastError, imageDataUrl, model_constants_1.VISION_MODEL);
|
|
427
|
+
// DEBUG: Log vision diagnostics
|
|
428
|
+
this.log(` 📸 Visual insights: ${supervisorDiagnostics.visualAnalysis}`);
|
|
429
|
+
this.log(` 🔍 Root cause: ${supervisorDiagnostics.rootCause}`);
|
|
430
|
+
this.log(` 💡 Recommended approach: ${supervisorDiagnostics.recommendedApproach}`);
|
|
431
|
+
if (supervisorDiagnostics.elementsFound.length > 0) {
|
|
432
|
+
this.log(` ✅ Elements found: ${supervisorDiagnostics.elementsFound.join(', ')}`);
|
|
433
|
+
}
|
|
434
|
+
if (supervisorDiagnostics.elementsNotFound.length > 0) {
|
|
435
|
+
this.log(` ❌ Elements not found: ${supervisorDiagnostics.elementsNotFound.join(', ')}`);
|
|
436
|
+
}
|
|
437
|
+
this.log(` 🔨 STEP 2: Worker generating command from supervisor instructions (${model_constants_1.DEFAULT_MODEL})...`);
|
|
438
|
+
command = await this.llmFacade.generateCommandFromSupervisorInstructions(step.description + stepContext, supervisorDiagnostics, pageInfo, modelToUse // Cheaper model for command generation
|
|
439
|
+
);
|
|
440
|
+
usedVisionMode = true;
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
// Not using vision - use regular DOM-based approach
|
|
444
|
+
if (usedVisionMode) {
|
|
445
|
+
this.log(` 📝 Vision already used - using DOM-based approach`);
|
|
446
|
+
}
|
|
447
|
+
else if (isComplexStep) {
|
|
448
|
+
this.log(` 📝 Complex step, but first attempt - using DOM-based approach (vision on retry)`);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
this.log(` 📝 Using DOM-based approach (${totalFailedAttemptsForStep} failures so far)`);
|
|
452
|
+
}
|
|
453
|
+
const stepDescriptionWithContext = step.description + stepContext;
|
|
454
|
+
command = await this.llmFacade.generatePlaywrightCommand(stepDescriptionWithContext, pageInfo, previousSteps, lastError, step, modelToUse);
|
|
455
|
+
}
|
|
456
|
+
if (!command) {
|
|
457
|
+
throw new Error('LLM failed to generate a Playwright command.');
|
|
458
|
+
}
|
|
459
|
+
currentAttemptCommand = command;
|
|
460
|
+
this.log(` Command: ${command}`);
|
|
461
|
+
// Execute the command
|
|
462
|
+
await this.executePlaywrightCommand(page, browser, context, command);
|
|
463
|
+
// Success
|
|
464
|
+
subActionSuccess = true;
|
|
465
|
+
currentAttemptSuccess = true;
|
|
466
|
+
subActionCommand = command;
|
|
467
|
+
step.playwrightCommands.push(command);
|
|
468
|
+
this.log(` ✅ SUCCESS: ${command}${usedVisionMode ? ' (vision-aided)' : ''}`);
|
|
469
|
+
// Wait a bit for any navigation that might have been triggered
|
|
470
|
+
// This prevents "Execution context destroyed" errors when checking goal completion
|
|
471
|
+
await page.waitForLoadState('domcontentloaded', { timeout: 3000 }).catch(() => {
|
|
472
|
+
// Ignore timeout - page might not be navigating
|
|
473
|
+
});
|
|
474
|
+
break; // Sub-action successful, check if step is complete
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
subActionError = error instanceof Error ? error.message : String(error);
|
|
478
|
+
currentAttemptError = subActionError;
|
|
479
|
+
// Get current URL for context (especially useful for navigation failures)
|
|
480
|
+
let currentUrl = 'unknown';
|
|
481
|
+
try {
|
|
482
|
+
currentUrl = page.url();
|
|
483
|
+
}
|
|
484
|
+
catch (e) {
|
|
485
|
+
// Ignore if we can't get URL
|
|
486
|
+
}
|
|
487
|
+
// Enhanced error message with current URL
|
|
488
|
+
const errorWithContext = `${subActionError} | Current URL: ${currentUrl}`;
|
|
489
|
+
this.logError(` ❌ FAILED (attempt ${attempt + 1}): ${subActionError}`);
|
|
490
|
+
this.logError(` Current URL: ${currentUrl}`);
|
|
491
|
+
this.logError(` Command attempted: ${currentAttemptCommand || 'N/A'}`);
|
|
492
|
+
subActionRetries++;
|
|
493
|
+
totalFailedAttemptsForStep++; // Increment failed attempts counter
|
|
494
|
+
// Only update lastError if this is the final attempt
|
|
495
|
+
if (attempt === MAX_RETRIES_PER_STEP) {
|
|
496
|
+
lastError = errorWithContext; // Include URL in error context for LLM
|
|
497
|
+
}
|
|
498
|
+
// If this is the last attempt, mark sub-action as failed
|
|
499
|
+
if (attempt === MAX_RETRIES_PER_STEP) {
|
|
500
|
+
subActionSuccess = false;
|
|
501
|
+
subActionCommand = currentAttemptCommand;
|
|
502
|
+
this.logError(` 🚫 SUB-ACTION FAILED after ${MAX_RETRIES_PER_STEP + 1} attempts (including vision mode if used)`);
|
|
503
|
+
break; // Exit retry loop
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
finally {
|
|
507
|
+
if (!step.attempts) {
|
|
508
|
+
step.attempts = [];
|
|
509
|
+
}
|
|
510
|
+
step.attempts.push({
|
|
511
|
+
attemptNumber: attempt + 1,
|
|
512
|
+
command: currentAttemptCommand,
|
|
513
|
+
success: currentAttemptSuccess,
|
|
514
|
+
error: currentAttemptError,
|
|
515
|
+
timestamp: attemptTimestamp
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// Record the sub-action
|
|
520
|
+
if (subActionCommand) {
|
|
521
|
+
step.subActions.push({
|
|
522
|
+
command: subActionCommand,
|
|
523
|
+
success: subActionSuccess,
|
|
524
|
+
error: subActionError,
|
|
525
|
+
retryCount: subActionRetries
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
subActionCount++;
|
|
529
|
+
// Determine if step (goal) is complete
|
|
530
|
+
if (subActionSuccess) {
|
|
531
|
+
// After each successful sub-action, ask LLM if goal is complete
|
|
532
|
+
if (subActionCount >= MAX_SUBACTIONS_PER_STEP) {
|
|
533
|
+
// Safety limit - avoid infinite loops
|
|
534
|
+
stepComplete = true;
|
|
535
|
+
stepSuccess = true;
|
|
536
|
+
this.log(` ⚠️ Reached max sub-actions limit (${MAX_SUBACTIONS_PER_STEP}) with ${totalFailedAttemptsForStep} failed attempts, considering step complete`);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
// Ask LLM if goal is complete
|
|
540
|
+
try {
|
|
541
|
+
// Capture page state - if navigation is still happening, retry once
|
|
542
|
+
let domSnapshot;
|
|
543
|
+
let pageInfo;
|
|
544
|
+
try {
|
|
545
|
+
domSnapshot = {
|
|
546
|
+
url: page.url(),
|
|
547
|
+
title: await page.title(),
|
|
548
|
+
accessibilityTree: await page.accessibility.snapshot()
|
|
549
|
+
};
|
|
550
|
+
pageInfo = await (0, page_info_utils_1.getEnhancedPageInfo)(domSnapshot);
|
|
551
|
+
}
|
|
552
|
+
catch (contextError) {
|
|
553
|
+
// If execution context was destroyed (navigation in progress), wait and retry
|
|
554
|
+
if (contextError.message && contextError.message.includes('Execution context was destroyed')) {
|
|
555
|
+
this.log(` ⏳ Navigation detected, waiting for page to load...`);
|
|
556
|
+
await page.waitForLoadState('domcontentloaded', { timeout: 5000 }).catch(() => { });
|
|
557
|
+
// Retry page state capture
|
|
558
|
+
domSnapshot = {
|
|
559
|
+
url: page.url(),
|
|
560
|
+
title: await page.title(),
|
|
561
|
+
accessibilityTree: await page.accessibility.snapshot()
|
|
562
|
+
};
|
|
563
|
+
pageInfo = await (0, page_info_utils_1.getEnhancedPageInfo)(domSnapshot);
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
throw contextError; // Re-throw if it's not a navigation issue
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
// Vision-backed goal completion for complex/verification steps
|
|
570
|
+
const shouldUseVisionForCompletion = isComplexStep && subActionCount >= 1; // At least one action done
|
|
571
|
+
let completionCheck;
|
|
572
|
+
if (shouldUseVisionForCompletion) {
|
|
573
|
+
this.log(` 🎯 Vision-backed goal completion check (complex step)`);
|
|
574
|
+
// Capture screenshot for visual verification
|
|
575
|
+
const imageDataUrl = await (0, browser_utils_1.captureOptimizedScreenshot)(page, { timeout: 10000 }, (msg) => this.log(msg));
|
|
576
|
+
// Use vision model to check goal completion with visual context
|
|
577
|
+
completionCheck = await this.llmFacade.checkGoalCompletionWithVision(step.description, step.playwrightCommands || [], pageInfo, imageDataUrl, model_constants_1.VISION_MODEL);
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
// Regular DOM-based goal completion check
|
|
581
|
+
completionCheck = await this.llmFacade.checkGoalCompletion(step.description, step.playwrightCommands || [], pageInfo, job.model || model_constants_1.DEFAULT_MODEL);
|
|
582
|
+
}
|
|
583
|
+
this.log(` 🎯 Goal completion check: ${completionCheck.isComplete ? 'COMPLETE' : 'INCOMPLETE'} - ${completionCheck.reason}`);
|
|
584
|
+
if (completionCheck.isComplete) {
|
|
585
|
+
stepComplete = true;
|
|
586
|
+
stepSuccess = true;
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
// Continue with next sub-action
|
|
590
|
+
if (completionCheck.nextSubGoal) {
|
|
591
|
+
this.log(` 📍 Next sub-goal: ${completionCheck.nextSubGoal}`);
|
|
592
|
+
}
|
|
593
|
+
// Continue looping to generate next command
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
this.logError(`Error checking goal completion: ${error}`);
|
|
598
|
+
// Fallback: consider complete after 1 successful sub-action if we can't check
|
|
599
|
+
stepComplete = true;
|
|
600
|
+
stepSuccess = true;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
// Sub-action failed
|
|
606
|
+
stepComplete = true; // Move on after failure
|
|
118
607
|
stepSuccess = false;
|
|
119
|
-
|
|
608
|
+
stepError = subActionError;
|
|
120
609
|
overallSuccess = false;
|
|
121
|
-
console.error(` 🚫 STEP FAILED after ${MAX_RETRIES_PER_STEP + 1} attempts`);
|
|
122
|
-
break; // Exit retry loop
|
|
123
610
|
}
|
|
124
611
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
612
|
+
// Set the step's final command (last successful or aggregate)
|
|
613
|
+
if (step.playwrightCommands && step.playwrightCommands.length > 0) {
|
|
614
|
+
step.playwrightCommand = step.playwrightCommands[step.playwrightCommands.length - 1];
|
|
615
|
+
}
|
|
616
|
+
step.success = stepSuccess;
|
|
617
|
+
step.error = stepError;
|
|
618
|
+
previousSteps.push(step);
|
|
619
|
+
// Update consecutive failure counter
|
|
620
|
+
if (stepSuccess) {
|
|
621
|
+
consecutiveFailures = 0; // Reset on success
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
consecutiveFailures++;
|
|
625
|
+
this.log(`⚠️ Consecutive failures: ${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}`);
|
|
626
|
+
}
|
|
627
|
+
// Emit step result log events
|
|
628
|
+
this.emit('log', job.id, `### Step ${step.stepNumber}: ${step.description}\n`);
|
|
629
|
+
this.emit('log', job.id, `Status: ${stepSuccess ? '✅ SUCCESS' : '❌ FAILED'}\n`);
|
|
630
|
+
this.emit('log', job.id, `Sub-actions: ${subActionCount}\n`);
|
|
631
|
+
this.emit('log', job.id, `Failed attempts: ${totalFailedAttemptsForStep}\n`);
|
|
632
|
+
if (step.playwrightCommands && step.playwrightCommands.length > 0) {
|
|
633
|
+
this.emit('log', job.id, `Commands:\n`);
|
|
634
|
+
step.playwrightCommands.forEach((cmd, idx) => {
|
|
635
|
+
this.emit('log', job.id, ` ${idx + 1}. ${cmd}\n`);
|
|
135
636
|
});
|
|
136
637
|
}
|
|
638
|
+
if (stepError) {
|
|
639
|
+
this.emit('log', job.id, `Error: ${stepError}\n`);
|
|
640
|
+
}
|
|
641
|
+
if (step.attempts && step.attempts.length > 0) {
|
|
642
|
+
this.emit('log', job.id, `Total attempts: ${step.attempts.length}\n`);
|
|
643
|
+
}
|
|
644
|
+
this.emit('log', job.id, `\n`);
|
|
137
645
|
}
|
|
138
|
-
|
|
139
|
-
step.error = stepError;
|
|
140
|
-
previousSteps.push(step);
|
|
141
|
-
}
|
|
646
|
+
} // End of else block (legacy mode)
|
|
142
647
|
// Generate test name if not provided
|
|
143
648
|
const testName = job.testName || await this.llmFacade.generateTestName(job.scenario, job.model);
|
|
144
649
|
// Generate hashtags for semantic grouping
|
|
@@ -169,6 +674,28 @@ class ScenarioWorker {
|
|
|
169
674
|
}
|
|
170
675
|
// Generate clean script with TestChimp comment and code
|
|
171
676
|
generatedScript = (0, script_utils_1.generateTestScript)(testName, steps, undefined, hashtags);
|
|
677
|
+
// Perform final cleanup pass to remove redundancies and make minor adjustments
|
|
678
|
+
this.log(`[ScenarioWorker] Performing final script cleanup...`);
|
|
679
|
+
try {
|
|
680
|
+
const cleanupResult = await this.llmFacade.cleanupScript(generatedScript, job.model);
|
|
681
|
+
if (cleanupResult.changes && cleanupResult.changes.length > 0) {
|
|
682
|
+
this.log(`[ScenarioWorker] Cleanup made ${cleanupResult.changes.length} improvement(s):`);
|
|
683
|
+
cleanupResult.changes.forEach((change, i) => {
|
|
684
|
+
this.log(`[ScenarioWorker] ${i + 1}. ${change}`);
|
|
685
|
+
});
|
|
686
|
+
generatedScript = cleanupResult.script;
|
|
687
|
+
}
|
|
688
|
+
else if (cleanupResult.skipped) {
|
|
689
|
+
this.log(`[ScenarioWorker] Cleanup skipped: ${cleanupResult.skipped}`);
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
this.log(`[ScenarioWorker] Cleanup completed - no changes needed`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
this.log(`[ScenarioWorker] Cleanup failed, using original script: ${error.message}`);
|
|
697
|
+
// Continue with original script on error
|
|
698
|
+
}
|
|
172
699
|
// Generate detailed execution log
|
|
173
700
|
const logLines = [];
|
|
174
701
|
logLines.push(`# Scenario Execution Log`);
|
|
@@ -178,6 +705,12 @@ class ScenarioWorker {
|
|
|
178
705
|
logLines.push(`End Time: ${new Date().toISOString()}`);
|
|
179
706
|
logLines.push(`Total Execution Time: ${Date.now() - startTime}ms`);
|
|
180
707
|
logLines.push(`Overall Success: ${overallSuccess ? 'YES' : 'NO'}`);
|
|
708
|
+
// Add early termination info if applicable
|
|
709
|
+
const skippedSteps = steps.filter(s => s.error?.includes('Skipped due to'));
|
|
710
|
+
if (skippedSteps.length > 0) {
|
|
711
|
+
logLines.push(`Early Termination: YES (${consecutiveFailures} consecutive failures)`);
|
|
712
|
+
logLines.push(`Steps Skipped: ${skippedSteps.length}`);
|
|
713
|
+
}
|
|
181
714
|
logLines.push(``);
|
|
182
715
|
for (const step of steps) {
|
|
183
716
|
logLines.push(`## Step ${step.stepNumber}: ${step.description}`);
|
|
@@ -216,7 +749,7 @@ class ScenarioWorker {
|
|
|
216
749
|
}
|
|
217
750
|
catch (error) {
|
|
218
751
|
overallSuccess = false;
|
|
219
|
-
|
|
752
|
+
this.logError(`Overall scenario processing error: ${error}`);
|
|
220
753
|
return {
|
|
221
754
|
success: false,
|
|
222
755
|
steps,
|
|
@@ -235,18 +768,13 @@ class ScenarioWorker {
|
|
|
235
768
|
}
|
|
236
769
|
}
|
|
237
770
|
async executePlaywrightCommand(page, browser, context, command) {
|
|
238
|
-
//
|
|
239
|
-
page.setDefaultTimeout(5000);
|
|
771
|
+
// Keep default timeout for element operations
|
|
772
|
+
page.setDefaultTimeout(5000);
|
|
240
773
|
try {
|
|
241
774
|
// Execute command directly without validation
|
|
242
775
|
const commandFunction = new Function('page', 'browser', 'context', 'expect', `
|
|
243
776
|
return (async () => {
|
|
244
|
-
|
|
245
|
-
${command}
|
|
246
|
-
} catch (error) {
|
|
247
|
-
console.error('Command execution error:', error);
|
|
248
|
-
throw error;
|
|
249
|
-
}
|
|
777
|
+
${command}
|
|
250
778
|
})();
|
|
251
779
|
`);
|
|
252
780
|
// Dynamically import expect
|
|
@@ -254,8 +782,8 @@ class ScenarioWorker {
|
|
|
254
782
|
await commandFunction(page, browser, context, expect);
|
|
255
783
|
}
|
|
256
784
|
finally {
|
|
257
|
-
//
|
|
258
|
-
page.setDefaultTimeout(
|
|
785
|
+
// Ensure timeout is consistent
|
|
786
|
+
page.setDefaultTimeout(5000);
|
|
259
787
|
}
|
|
260
788
|
}
|
|
261
789
|
async cleanup() {
|