testchimp-runner-core 0.0.1

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 (74) hide show
  1. package/dist/auth-config.d.ts +33 -0
  2. package/dist/auth-config.d.ts.map +1 -0
  3. package/dist/auth-config.js +69 -0
  4. package/dist/auth-config.js.map +1 -0
  5. package/dist/env-loader.d.ts +20 -0
  6. package/dist/env-loader.d.ts.map +1 -0
  7. package/dist/env-loader.js +83 -0
  8. package/dist/env-loader.js.map +1 -0
  9. package/dist/execution-service.d.ts +61 -0
  10. package/dist/execution-service.d.ts.map +1 -0
  11. package/dist/execution-service.js +822 -0
  12. package/dist/execution-service.js.map +1 -0
  13. package/dist/file-handler.d.ts +59 -0
  14. package/dist/file-handler.d.ts.map +1 -0
  15. package/dist/file-handler.js +75 -0
  16. package/dist/file-handler.js.map +1 -0
  17. package/dist/index.d.ts +46 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +196 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/llm-facade.d.ts +101 -0
  22. package/dist/llm-facade.d.ts.map +1 -0
  23. package/dist/llm-facade.js +289 -0
  24. package/dist/llm-facade.js.map +1 -0
  25. package/dist/playwright-mcp-service.d.ts +42 -0
  26. package/dist/playwright-mcp-service.d.ts.map +1 -0
  27. package/dist/playwright-mcp-service.js +167 -0
  28. package/dist/playwright-mcp-service.js.map +1 -0
  29. package/dist/prompts.d.ts +34 -0
  30. package/dist/prompts.d.ts.map +1 -0
  31. package/dist/prompts.js +237 -0
  32. package/dist/prompts.js.map +1 -0
  33. package/dist/scenario-service.d.ts +25 -0
  34. package/dist/scenario-service.d.ts.map +1 -0
  35. package/dist/scenario-service.js +119 -0
  36. package/dist/scenario-service.js.map +1 -0
  37. package/dist/scenario-worker-class.d.ts +30 -0
  38. package/dist/scenario-worker-class.d.ts.map +1 -0
  39. package/dist/scenario-worker-class.js +263 -0
  40. package/dist/scenario-worker-class.js.map +1 -0
  41. package/dist/script-utils.d.ts +44 -0
  42. package/dist/script-utils.d.ts.map +1 -0
  43. package/dist/script-utils.js +100 -0
  44. package/dist/script-utils.js.map +1 -0
  45. package/dist/types.d.ts +171 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +28 -0
  48. package/dist/types.js.map +1 -0
  49. package/dist/utils/browser-utils.d.ts +13 -0
  50. package/dist/utils/browser-utils.d.ts.map +1 -0
  51. package/dist/utils/browser-utils.js +269 -0
  52. package/dist/utils/browser-utils.js.map +1 -0
  53. package/dist/utils/page-info-utils.d.ts +16 -0
  54. package/dist/utils/page-info-utils.d.ts.map +1 -0
  55. package/dist/utils/page-info-utils.js +77 -0
  56. package/dist/utils/page-info-utils.js.map +1 -0
  57. package/env.prod +1 -0
  58. package/env.staging +1 -0
  59. package/package.json +38 -0
  60. package/src/auth-config.ts +84 -0
  61. package/src/env-loader.ts +91 -0
  62. package/src/execution-service.ts +999 -0
  63. package/src/file-handler.ts +104 -0
  64. package/src/index.ts +205 -0
  65. package/src/llm-facade.ts +413 -0
  66. package/src/playwright-mcp-service.ts +203 -0
  67. package/src/prompts.ts +247 -0
  68. package/src/scenario-service.ts +138 -0
  69. package/src/scenario-worker-class.ts +330 -0
  70. package/src/script-utils.ts +109 -0
  71. package/src/types.ts +202 -0
  72. package/src/utils/browser-utils.ts +272 -0
  73. package/src/utils/page-info-utils.ts +93 -0
  74. package/tsconfig.json +19 -0
package/src/prompts.ts ADDED
@@ -0,0 +1,247 @@
1
+ /**
2
+ * All LLM prompts used throughout the application
3
+ */
4
+
5
+ export const PROMPTS = {
6
+ // Test name generation
7
+ TEST_NAME_GENERATION: {
8
+ SYSTEM: 'You are an AI assistant that generates meaningful test names for user journey tests. Carefully analyze the scenario description to look for any hints, indicators, or explicit mentions of what this test should be called. Pay attention to phrases like "test", "scenario", "check", "verify", "flow", or any descriptive terms that suggest the test purpose. If you find such indicators, use them as the basis for the test name. Otherwise, analyze the overall user journey and business purpose. Generate a concise test name (under 30 characters) in camelCase format. Respond with a JSON object in this format: {"testName": "userJourneyName"}',
9
+
10
+ USER: (scenario: string) => `Analyze this scenario description and generate a meaningful test name:\n\n"${scenario}"\n\nInstructions:\n1. Look for ANY hints or indicators in the text that suggest what this test should be called:\n - Explicit mentions: "Test: ...", "Scenario: ...", "Check: ...", "Verify: ..."\n - Descriptive phrases: "...flow", "...process", "...journey", "...workflow"\n - Action-focused terms: "login", "registration", "purchase", "messaging", "search"\n - Business context: "user onboarding", "checkout process", "team collaboration"\n2. If you find such indicators, use them as the basis for the test name\n3. If not found, analyze the user journey and business purpose\n4. Generate a concise name under 30 characters in camelCase\n\nExamples:\n- "Test: User login and messaging flow" -> "userLoginAndMessagingFlow"\n- "Checkout process with payment" -> "checkoutProcess"\n- "User registration and email verification" -> "userRegistration"\n- "Team messaging and collaboration" -> "teamMessaging"`
11
+ },
12
+
13
+ // Scenario breakdown
14
+ SCENARIO_BREAKDOWN: {
15
+ SYSTEM: `You are an expert test automation engineer that breaks down user scenarios into precise, actionable Playwright steps.
16
+
17
+ RULES:
18
+ - Each step should be a single, specific action
19
+ - Use clear, imperative language (Go to, Click, Type, Verify, etc.)
20
+ - Include specific details (URLs, text content, element descriptions)
21
+ - Order steps logically (navigation first, then interactions, then verifications)
22
+ - Be specific about what to verify/assert
23
+
24
+ COMMON STEP PATTERNS:
25
+ - "Go to [URL]" - for navigation
26
+ - "Click on [element description]" - for clicking
27
+ - "Type '[text]' into [field description]" - for text input
28
+ - "Verify that [condition]" - for assertions
29
+ - "Wait for [element/condition]" - for waiting
30
+
31
+ Respond with JSON: {"steps": ["step1", "step2", "step3"]}`,
32
+
33
+ USER: (scenario: string) => `Break down this scenario into specific, actionable steps for Playwright automation:\n\n"${scenario}"`
34
+ },
35
+
36
+ // Playwright command generation
37
+ PLAYWRIGHT_COMMAND: {
38
+ SYSTEM: 'You are an expert Playwright automation engineer. Generate clean, concise, and reliable commands. Use Playwright\'s built-in auto-waiting instead of explicit timeouts. Keep code readable and maintainable. Learn from previous failures and adapt your approach accordingly.',
39
+
40
+ USER: (stepDescription: string, pageInfo: any, previousCommands: string, attemptHistory: string, errorContext: string) => `You are an expert Playwright automation engineer. Generate a single, precise Playwright command for the given step.
41
+
42
+ CRITICAL RULES:
43
+ - Generate ONLY ONE command per step
44
+ - Use the most reliable selectors (prefer getByRole, getByText, getByLabel)
45
+ - Always wait for elements before interacting (use waitFor, waitForSelector)
46
+ - Use proper error handling and timeouts
47
+ - If previous attempts failed, try a COMPLETELY DIFFERENT approach
48
+ - Learn from failures and adapt your strategy
49
+
50
+ ELEMENT SELECTION PRIORITY:
51
+ 1. getByRole() - Most reliable for interactive elements
52
+ 2. getByText() - For text content
53
+ 3. getByLabel() - For form inputs
54
+ 4. getByPlaceholder() - For input placeholders
55
+ 5. getByTestId() - For test-specific elements
56
+ 6. locator() with CSS selectors - Last resort
57
+
58
+ COMMON PATTERNS:
59
+ - Navigation: await page.goto('url')
60
+ - Click: await page.getByRole('button', { name: 'text' }).click()
61
+ - Type: await page.getByRole('textbox', { name: 'label' }).fill('text')
62
+ - Wait: await page.waitForLoadState('networkidle')
63
+ - Verify: await expect(page).toHaveTitle(/expected/)
64
+
65
+ CODE STYLE GUIDELINES:
66
+ - Keep commands concise and clean
67
+ - Avoid explicit timeouts unless necessary
68
+ - Use Playwright's built-in auto-waiting
69
+ - Only add timeouts for specific slow operations
70
+ - Prefer single-line commands when possible
71
+
72
+ RETRY STRATEGIES:
73
+ - Timeout errors: Add waitFor() or increase timeout
74
+ - Not found errors: Try different selectors or wait for element
75
+ - Not visible errors: Scroll into view or wait for visibility
76
+ - Not enabled errors: Wait for element to be enabled
77
+
78
+ TIMEOUT GUIDELINES:
79
+ - Only add explicit timeouts for slow operations (file uploads, large data loads)
80
+ - Use page.waitForLoadState('networkidle') for page navigation
81
+ - Use element.waitFor() only when waiting for specific conditions
82
+ - Let Playwright's auto-waiting handle most interactions
83
+
84
+ Respond with JSON:
85
+ {
86
+ "command": "await page.goto('https://www.google.com');",
87
+ "reasoning": "Direct navigation to target URL",
88
+ "selectorStrategy": "direct_navigation"
89
+ }
90
+
91
+ Current State:
92
+ - URL: ${pageInfo.url}
93
+ - Title: ${pageInfo.title}
94
+ - Page Structure: ${pageInfo.pageStructure}
95
+ - Interactive Elements: ${pageInfo.interactiveElements}
96
+ - Form Fields: ${pageInfo.formFields}
97
+ - All Elements: ${pageInfo.elements}
98
+
99
+ Previous Commands:
100
+ \`\`\`javascript
101
+ ${previousCommands}
102
+ \`\`\`
103
+
104
+ ${attemptHistory}
105
+
106
+ ${errorContext}
107
+
108
+ Step to execute: "${stepDescription}"`
109
+ },
110
+
111
+ // Script parsing for AI repair
112
+ SCRIPT_PARSING: {
113
+ SYSTEM: 'You are an expert at parsing Playwright test scripts into logical steps. IGNORE doc comments at the top (/** ... */) as they are repair advice, not test steps. ALWAYS prioritize existing step comments over generating new ones. If the script has "// Step N:" comments, use those exactly as they are. Only generate new descriptions if no existing step comments are found. Be conservative and preserve exact code formatting.',
114
+
115
+ USER: (script: string) => `Parse this Playwright test script into logical steps. Be conservative and preserve the exact code.
116
+
117
+ Instructions:
118
+ 1. IGNORE any doc comments at the top of the script (e.g., /** ... */ or /* ... */) - these are repair advice and should not be parsed as steps
119
+ 2. FIRST, look for existing step comments (e.g., "// Step 1:", "// Step 2:", etc.) and use those as step boundaries
120
+ 3. If existing step comments are found, use them exactly as they are - do not modify or regenerate descriptions
121
+ 4. If no existing step comments, then group related commands that work together logically
122
+ 5. Preserve ALL code exactly as written - do not modify, reformat, or change any code
123
+ 6. Each step should contain commands that belong together (e.g., navigation + wait, form filling, verification)
124
+ 7. Keep steps focused and not too granular
125
+
126
+ Script:
127
+ ${script}
128
+
129
+ Return JSON object with steps array:
130
+ {
131
+ "steps": [
132
+ {
133
+ "description": "use existing comment if available, otherwise create meaningful description",
134
+ "code": "exact code from script - preserve all formatting and content"
135
+ }
136
+ ]
137
+ }`
138
+ },
139
+
140
+ // Repair suggestion
141
+ REPAIR_SUGGESTION: {
142
+ SYSTEM: 'You are an expert test automation engineer specializing in fixing failing Playwright tests. Analyze the current DOM state, error message, and step description to suggest the best repair action. Consider the failure history to avoid repeating the same mistakes.',
143
+
144
+ USER: (stepDescription: string, stepCode: string, errorMessage: string, pageInfo: any, failureHistory: string, recentRepairs: string) => `Analyze this failing Playwright test step and suggest a repair action.
145
+
146
+ Current Step:
147
+ Description: ${stepDescription}
148
+ Code: ${stepCode}
149
+ Error: ${errorMessage}
150
+
151
+ Current Page State:
152
+ - URL: ${pageInfo.url}
153
+ - Title: ${pageInfo.title}
154
+ - Interactive Elements: ${pageInfo.interactiveElements}
155
+ - Form Fields: ${pageInfo.formFields}
156
+
157
+ ${failureHistory}
158
+
159
+ ${recentRepairs}
160
+
161
+ Choose the best repair action:
162
+ 1. MODIFY - Fix the current step with better selectors, waits, or logic
163
+ 2. INSERT - Add a new step before the current one (e.g., wait for element, scroll into view)
164
+ 3. REMOVE - Skip this step entirely if it's not essential
165
+
166
+ Respond with JSON:
167
+ {
168
+ "shouldContinue": true/false,
169
+ "reason": "explanation of decision",
170
+ "action": {
171
+ "operation": "MODIFY|INSERT|REMOVE",
172
+ "newStep": {
173
+ "description": "step description",
174
+ "code": "await page.getByRole('button', { name: 'Submit' }).click();"
175
+ }
176
+ }
177
+ }`
178
+ },
179
+
180
+ // Repair confidence assessment
181
+ REPAIR_CONFIDENCE: {
182
+ SYSTEM: 'You are an expert test automation engineer who writes concise repair advice to build a running understanding of this test behavior and repairs done.',
183
+
184
+ USER: (originalScript: string, updatedScript: string) => `You are an expert test automation engineer. Generate a short repair advice that will be used to build a running understanding of this test.
185
+
186
+ Original Script:
187
+ ${originalScript}
188
+
189
+ Repaired Script:
190
+ ${updatedScript}
191
+
192
+ Instructions:
193
+ 1. Compare the original and repaired scripts to identify what was fixed
194
+ 2. Determine confidence level (0-5) where:
195
+ - 0 = Low confidence, repairs may be unreliable
196
+ - 5 = High confidence, repairs are solid and maintainable
197
+ 3. Write SHORT advice (few short sentences max) that:
198
+ - States what specific fix was made
199
+ - Builds on any previous repair advice found in the original script
200
+ - Captures patterns (e.g., "usually fails on selector issues", "often needs deflaking")
201
+ - Will help future repairs understand this test's quirks
202
+
203
+ IMPORTANT:
204
+ - Step comments are EXPECTED and GOOD - do not mention them as issues
205
+ - Be concise and factual
206
+ - Focus on the actual fix made, not general recommendations
207
+ - Build a running understanding of this test's behavior relating to the repairs done
208
+ - If the original script contains previous repair advice, build upon it to create a cumulative understanding
209
+
210
+ Respond with JSON:
211
+ {
212
+ "confidence": 0-5,
213
+ "advice": "short factual statement about the fix and test patterns"
214
+ }`
215
+ },
216
+
217
+ // Final script generation
218
+ FINAL_SCRIPT: {
219
+ SYSTEM: 'You are an expert at creating drop-in replacement scripts. Generate a complete, properly formatted script that preserves the original structure while incorporating repairs and new advice.',
220
+
221
+ USER: (originalScript: string, updatedScript: string, newRepairAdvice: string) => `You are an expert at generating drop-in replacement scripts. Create a final script that can be pasted directly into the original file.
222
+
223
+ Original Script (with existing repair advice):
224
+ ${originalScript}
225
+
226
+ Updated Script (with repairs):
227
+ ${updatedScript}
228
+
229
+ New Repair Advice:
230
+ ${newRepairAdvice}
231
+
232
+ Instructions:
233
+ 1. Create a drop-in replacement that preserves the original test name and structure
234
+ 2. Update the TestChimp comment block at the top to include BOTH existing and new repair advice
235
+ 3. If there was existing repair advice, combine it with the new advice to build a running understanding
236
+ 4. Use the repaired code from the updated script
237
+ 5. Preserve the original test name (don't use 'repairedTest')
238
+ 6. Keep the same import statements and overall structure
239
+ 7. Ensure the script is properly formatted and ready to use
240
+ 8. The repair advice should accumulate knowledge about this test's behavior patterns
241
+
242
+ Return JSON object with the final script:
243
+ {
244
+ "script": "complete final script that can be pasted into the original file"
245
+ }`
246
+ }
247
+ };
@@ -0,0 +1,138 @@
1
+ import { EventEmitter } from 'events';
2
+ import dotenv from 'dotenv';
3
+ import { ScenarioJob, ScenarioRunJob, ScenarioResponse, PlaywrightConfig } from './types';
4
+ import { ScenarioWorker } from './scenario-worker-class';
5
+ import { FileHandler, NoOpFileHandler } from './file-handler';
6
+ import { AuthConfig } from './auth-config';
7
+
8
+ // Load environment variables
9
+ dotenv.config();
10
+
11
+ /**
12
+ * Service for processing scenarios using LLM + Playwright
13
+ */
14
+ export class ScenarioService extends EventEmitter {
15
+ private workers: ScenarioWorker[] = [];
16
+ private jobQueue: ScenarioRunJob[] = [];
17
+ private busyWorkers: Set<ScenarioWorker> = new Set();
18
+ private maxWorkers: number;
19
+ private fileHandler: FileHandler;
20
+ private authConfig: AuthConfig | null;
21
+
22
+ constructor(maxWorkers: number = 2, fileHandler?: FileHandler, authConfig?: AuthConfig) {
23
+ super();
24
+ this.maxWorkers = maxWorkers;
25
+ this.fileHandler = fileHandler || new NoOpFileHandler();
26
+ this.authConfig = authConfig || null;
27
+ }
28
+
29
+ private async initializeWorkers(): Promise<void> {
30
+ for (let i = 0; i < this.maxWorkers; i++) {
31
+ await this.createWorker();
32
+ }
33
+ }
34
+
35
+ private async createWorker(): Promise<void> {
36
+ const worker = new ScenarioWorker(this.fileHandler, this.authConfig || undefined);
37
+ await worker.initialize();
38
+ this.workers.push(worker);
39
+ console.log(`Scenario worker initialized with session: ${worker['sessionId']}`);
40
+ }
41
+
42
+ async initialize(): Promise<void> {
43
+ // Wait for workers to be initialized
44
+ await this.initializeWorkers();
45
+ console.log('Scenario service initialized');
46
+ }
47
+
48
+ processScenario(scenario: string, testName?: string, config?: PlaywrightConfig, model?: string, scenarioFileName?: string): string {
49
+ const jobId = `scenario_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
50
+
51
+ // Add job to queue
52
+ const job: ScenarioRunJob = {
53
+ id: jobId,
54
+ scenario,
55
+ testName,
56
+ playwrightConfig: config,
57
+ model,
58
+ scenarioFileName
59
+ };
60
+
61
+ this.jobQueue.push(job);
62
+ this.processNextJob();
63
+
64
+ return jobId; // Return job ID for tracking
65
+ }
66
+
67
+ private async processNextJob(): Promise<void> {
68
+ if (this.jobQueue.length === 0) {
69
+ return;
70
+ }
71
+
72
+ console.log(`[ScenarioService] Processing next job. Queue length: ${this.jobQueue.length}, Workers: ${this.workers.length}, Busy workers: ${this.busyWorkers.size}`);
73
+
74
+ // Find available worker (proper load balancing)
75
+ const availableWorker = this.workers.find(worker => !this.busyWorkers.has(worker));
76
+ if (!availableWorker) {
77
+ console.log('[ScenarioService] No available workers, waiting...');
78
+ return; // All workers busy, wait for one to become available
79
+ }
80
+
81
+ const job = this.jobQueue.shift();
82
+ if (!job) {
83
+ return;
84
+ }
85
+
86
+ console.log(`[ScenarioService] Processing job ${job.id} with worker`);
87
+
88
+ // Mark worker as busy
89
+ this.busyWorkers.add(availableWorker);
90
+
91
+ try {
92
+ // Process job directly with worker
93
+ const result = await availableWorker.processScenarioJob(job);
94
+ console.log(`[ScenarioService] Job ${job.id} completed with result:`, result);
95
+ this.handleJobResult(job.id, result);
96
+ } catch (error) {
97
+ console.error('Error processing job with worker:', error);
98
+ this.emit('jobError', job.id, error);
99
+ // Put job back in queue if it failed
100
+ this.jobQueue.unshift(job);
101
+ } finally {
102
+ // Mark worker as available again
103
+ this.busyWorkers.delete(availableWorker);
104
+ // Process next job
105
+ this.processNextJob();
106
+ }
107
+ }
108
+
109
+ private handleJobResult(jobId: string, result: ScenarioResponse): void {
110
+ // Emit result event
111
+ this.emit('jobComplete', jobId, result);
112
+
113
+ // Mark worker as available and process next job
114
+ this.busyWorkers.clear(); // Simple approach - clear all busy workers
115
+ this.processNextJob();
116
+ }
117
+
118
+ private handleJobError(jobId: string, error: string): void {
119
+ // Emit error event
120
+ this.emit('jobError', jobId, new Error(error));
121
+
122
+ // Mark worker as available and process next job
123
+ this.busyWorkers.clear(); // Simple approach - clear all busy workers
124
+ this.processNextJob();
125
+ }
126
+
127
+ async shutdown(): Promise<void> {
128
+ console.log('Shutting down scenario service...');
129
+
130
+ // Cleanup all workers
131
+ const cleanupPromises = this.workers.map(worker => worker.cleanup());
132
+ await Promise.all(cleanupPromises);
133
+
134
+ this.workers = [];
135
+ this.busyWorkers.clear();
136
+ console.log('Scenario service shutdown complete');
137
+ }
138
+ }