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.
Files changed (146) hide show
  1. package/VISION_DIAGNOSTICS_IMPROVEMENTS.md +336 -0
  2. package/dist/credit-usage-service.d.ts +9 -0
  3. package/dist/credit-usage-service.d.ts.map +1 -1
  4. package/dist/credit-usage-service.js +20 -5
  5. package/dist/credit-usage-service.js.map +1 -1
  6. package/dist/execution-service.d.ts +7 -2
  7. package/dist/execution-service.d.ts.map +1 -1
  8. package/dist/execution-service.js +91 -36
  9. package/dist/execution-service.js.map +1 -1
  10. package/dist/index.d.ts +30 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +91 -26
  13. package/dist/index.js.map +1 -1
  14. package/dist/llm-facade.d.ts +64 -8
  15. package/dist/llm-facade.d.ts.map +1 -1
  16. package/dist/llm-facade.js +361 -109
  17. package/dist/llm-facade.js.map +1 -1
  18. package/dist/llm-provider.d.ts +39 -0
  19. package/dist/llm-provider.d.ts.map +1 -0
  20. package/dist/llm-provider.js +7 -0
  21. package/dist/llm-provider.js.map +1 -0
  22. package/dist/model-constants.d.ts +21 -0
  23. package/dist/model-constants.d.ts.map +1 -0
  24. package/dist/model-constants.js +24 -0
  25. package/dist/model-constants.js.map +1 -0
  26. package/dist/orchestrator/index.d.ts +8 -0
  27. package/dist/orchestrator/index.d.ts.map +1 -0
  28. package/dist/orchestrator/index.js +23 -0
  29. package/dist/orchestrator/index.js.map +1 -0
  30. package/dist/orchestrator/orchestrator-agent.d.ts +66 -0
  31. package/dist/orchestrator/orchestrator-agent.d.ts.map +1 -0
  32. package/dist/orchestrator/orchestrator-agent.js +855 -0
  33. package/dist/orchestrator/orchestrator-agent.js.map +1 -0
  34. package/dist/orchestrator/tool-registry.d.ts +74 -0
  35. package/dist/orchestrator/tool-registry.d.ts.map +1 -0
  36. package/dist/orchestrator/tool-registry.js +131 -0
  37. package/dist/orchestrator/tool-registry.js.map +1 -0
  38. package/dist/orchestrator/tools/check-page-ready.d.ts +13 -0
  39. package/dist/orchestrator/tools/check-page-ready.d.ts.map +1 -0
  40. package/dist/orchestrator/tools/check-page-ready.js +72 -0
  41. package/dist/orchestrator/tools/check-page-ready.js.map +1 -0
  42. package/dist/orchestrator/tools/extract-data.d.ts +13 -0
  43. package/dist/orchestrator/tools/extract-data.d.ts.map +1 -0
  44. package/dist/orchestrator/tools/extract-data.js +84 -0
  45. package/dist/orchestrator/tools/extract-data.js.map +1 -0
  46. package/dist/orchestrator/tools/index.d.ts +10 -0
  47. package/dist/orchestrator/tools/index.d.ts.map +1 -0
  48. package/dist/orchestrator/tools/index.js +18 -0
  49. package/dist/orchestrator/tools/index.js.map +1 -0
  50. package/dist/orchestrator/tools/inspect-page.d.ts +13 -0
  51. package/dist/orchestrator/tools/inspect-page.d.ts.map +1 -0
  52. package/dist/orchestrator/tools/inspect-page.js +39 -0
  53. package/dist/orchestrator/tools/inspect-page.js.map +1 -0
  54. package/dist/orchestrator/tools/recall-history.d.ts +13 -0
  55. package/dist/orchestrator/tools/recall-history.d.ts.map +1 -0
  56. package/dist/orchestrator/tools/recall-history.js +64 -0
  57. package/dist/orchestrator/tools/recall-history.js.map +1 -0
  58. package/dist/orchestrator/tools/take-screenshot.d.ts +15 -0
  59. package/dist/orchestrator/tools/take-screenshot.d.ts.map +1 -0
  60. package/dist/orchestrator/tools/take-screenshot.js +112 -0
  61. package/dist/orchestrator/tools/take-screenshot.js.map +1 -0
  62. package/dist/orchestrator/types.d.ts +133 -0
  63. package/dist/orchestrator/types.d.ts.map +1 -0
  64. package/dist/orchestrator/types.js +28 -0
  65. package/dist/orchestrator/types.js.map +1 -0
  66. package/dist/playwright-mcp-service.d.ts +9 -0
  67. package/dist/playwright-mcp-service.d.ts.map +1 -1
  68. package/dist/playwright-mcp-service.js +20 -5
  69. package/dist/playwright-mcp-service.js.map +1 -1
  70. package/dist/progress-reporter.d.ts +97 -0
  71. package/dist/progress-reporter.d.ts.map +1 -0
  72. package/dist/progress-reporter.js +18 -0
  73. package/dist/progress-reporter.js.map +1 -0
  74. package/dist/prompts.d.ts +24 -0
  75. package/dist/prompts.d.ts.map +1 -1
  76. package/dist/prompts.js +593 -68
  77. package/dist/prompts.js.map +1 -1
  78. package/dist/providers/backend-proxy-llm-provider.d.ts +25 -0
  79. package/dist/providers/backend-proxy-llm-provider.d.ts.map +1 -0
  80. package/dist/providers/backend-proxy-llm-provider.js +76 -0
  81. package/dist/providers/backend-proxy-llm-provider.js.map +1 -0
  82. package/dist/providers/local-llm-provider.d.ts +21 -0
  83. package/dist/providers/local-llm-provider.d.ts.map +1 -0
  84. package/dist/providers/local-llm-provider.js +35 -0
  85. package/dist/providers/local-llm-provider.js.map +1 -0
  86. package/dist/scenario-service.d.ts +27 -1
  87. package/dist/scenario-service.d.ts.map +1 -1
  88. package/dist/scenario-service.js +48 -12
  89. package/dist/scenario-service.js.map +1 -1
  90. package/dist/scenario-worker-class.d.ts +39 -2
  91. package/dist/scenario-worker-class.d.ts.map +1 -1
  92. package/dist/scenario-worker-class.js +614 -86
  93. package/dist/scenario-worker-class.js.map +1 -1
  94. package/dist/script-utils.d.ts +2 -0
  95. package/dist/script-utils.d.ts.map +1 -1
  96. package/dist/script-utils.js +44 -4
  97. package/dist/script-utils.js.map +1 -1
  98. package/dist/types.d.ts +11 -0
  99. package/dist/types.d.ts.map +1 -1
  100. package/dist/types.js.map +1 -1
  101. package/dist/utils/browser-utils.d.ts +20 -1
  102. package/dist/utils/browser-utils.d.ts.map +1 -1
  103. package/dist/utils/browser-utils.js +102 -51
  104. package/dist/utils/browser-utils.js.map +1 -1
  105. package/dist/utils/page-info-utils.d.ts +23 -4
  106. package/dist/utils/page-info-utils.d.ts.map +1 -1
  107. package/dist/utils/page-info-utils.js +174 -43
  108. package/dist/utils/page-info-utils.js.map +1 -1
  109. package/package.json +1 -2
  110. package/plandocs/HUMAN_LIKE_IMPROVEMENTS.md +642 -0
  111. package/plandocs/MULTI_AGENT_ARCHITECTURE_REVIEW.md +844 -0
  112. package/plandocs/ORCHESTRATOR_MVP_SUMMARY.md +539 -0
  113. package/plandocs/PHASE1_ABSTRACTION_COMPLETE.md +241 -0
  114. package/plandocs/PHASE1_FINAL_STATUS.md +210 -0
  115. package/plandocs/PLANNING_SESSION_SUMMARY.md +372 -0
  116. package/plandocs/SCRIPT_CLEANUP_FEATURE.md +201 -0
  117. package/plandocs/SCRIPT_GENERATION_ARCHITECTURE.md +364 -0
  118. package/plandocs/SELECTOR_IMPROVEMENTS.md +139 -0
  119. package/src/credit-usage-service.ts +23 -5
  120. package/src/execution-service.ts +152 -42
  121. package/src/index.ts +169 -26
  122. package/src/llm-facade.ts +500 -126
  123. package/src/llm-provider.ts +43 -0
  124. package/src/model-constants.ts +23 -0
  125. package/src/orchestrator/index.ts +33 -0
  126. package/src/orchestrator/orchestrator-agent.ts +1037 -0
  127. package/src/orchestrator/tool-registry.ts +182 -0
  128. package/src/orchestrator/tools/check-page-ready.ts +75 -0
  129. package/src/orchestrator/tools/extract-data.ts +92 -0
  130. package/src/orchestrator/tools/index.ts +11 -0
  131. package/src/orchestrator/tools/inspect-page.ts +42 -0
  132. package/src/orchestrator/tools/recall-history.ts +72 -0
  133. package/src/orchestrator/tools/take-screenshot.ts +128 -0
  134. package/src/orchestrator/types.ts +200 -0
  135. package/src/playwright-mcp-service.ts +23 -5
  136. package/src/progress-reporter.ts +109 -0
  137. package/src/prompts.ts +606 -69
  138. package/src/providers/backend-proxy-llm-provider.ts +91 -0
  139. package/src/providers/local-llm-provider.ts +38 -0
  140. package/src/scenario-service.ts +83 -13
  141. package/src/scenario-worker-class.ts +740 -72
  142. package/src/script-utils.ts +50 -5
  143. package/src/types.ts +13 -1
  144. package/src/utils/browser-utils.ts +123 -51
  145. package/src/utils/page-info-utils.ts +210 -53
  146. 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 MAX_RETRIES_PER_STEP = 2;
9
- class ScenarioWorker {
10
- constructor(fileHandler, authConfig, outputChannel, backendUrl) {
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
- this.llmFacade = new llm_facade_1.LLMFacade(authConfig, backendUrl);
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.outputChannel = outputChannel;
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
- console.log(message);
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(`[ScenarioWorker] ${message}`);
81
+ this.outputChannel.appendLine(formattedMessage);
21
82
  }
22
83
  }
23
84
  logError(message) {
24
- console.error(message);
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(`[ScenarioWorker] ERROR: ${message}`);
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
- const browserInstance = await (0, browser_utils_1.initializeBrowser)(job.playwrightConfig, false);
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 all operations
64
- page.setDefaultTimeout(5000); // 5 seconds
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
- // 3. Execute each step
68
- for (let i = 0; i < steps.length; i++) {
69
- const step = steps[i];
70
- step.stepNumber = i + 1;
71
- step.retryCount = 0;
72
- let stepSuccess = false;
73
- let stepOutput = '';
74
- let stepError;
75
- for (let attempt = 0; attempt <= MAX_RETRIES_PER_STEP; attempt++) {
76
- let currentAttemptCommand;
77
- let currentAttemptSuccess = false;
78
- let currentAttemptError;
79
- const attemptTimestamp = Date.now();
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
- this.log(`Attempt ${attempt + 1} for step: ${step.description}`);
82
- // Get current page state using Playwright's accessibility tree
83
- const domSnapshot = {
84
- url: page.url(),
85
- title: await page.title(),
86
- accessibilityTree: await page.accessibility.snapshot()
87
- };
88
- // Generate Playwright command using LLM
89
- const pageInfo = await (0, page_info_utils_1.getEnhancedPageInfo)(domSnapshot);
90
- const command = await this.llmFacade.generatePlaywrightCommand(step.description, pageInfo, previousSteps, lastError, step, job.model);
91
- if (!command) {
92
- throw new Error('LLM failed to generate a Playwright command.');
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
- stepError = error instanceof Error ? error.message : String(error);
108
- currentAttemptError = stepError;
109
- console.error(` ❌ FAILED (attempt ${attempt + 1}): ${stepError}`);
110
- console.error(` Command attempted: ${currentAttemptCommand || 'N/A'}`);
111
- step.retryCount++;
112
- // Only update lastError if this is the final attempt
113
- if (attempt === MAX_RETRIES_PER_STEP) {
114
- lastError = stepError;
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
- // If this is the last attempt, mark as failed and move on
117
- if (attempt === MAX_RETRIES_PER_STEP) {
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
- stepOutput = `Failed after ${MAX_RETRIES_PER_STEP + 1} attempts.`;
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
- finally {
126
- if (!step.attempts) {
127
- step.attempts = [];
128
- }
129
- step.attempts.push({
130
- attemptNumber: attempt + 1,
131
- command: currentAttemptCommand,
132
- success: currentAttemptSuccess,
133
- error: currentAttemptError,
134
- timestamp: attemptTimestamp
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
- step.success = stepSuccess;
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
- console.error('Overall scenario processing error:', error);
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
- // Set reasonable timeouts
239
- page.setDefaultTimeout(5000); // 5 seconds
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
- try {
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
- // Reset to default timeout
258
- page.setDefaultTimeout(10000); // Reset to default 10 seconds
785
+ // Ensure timeout is consistent
786
+ page.setDefaultTimeout(5000);
259
787
  }
260
788
  }
261
789
  async cleanup() {