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.
- package/dist/auth-config.d.ts +33 -0
- package/dist/auth-config.d.ts.map +1 -0
- package/dist/auth-config.js +69 -0
- package/dist/auth-config.js.map +1 -0
- package/dist/env-loader.d.ts +20 -0
- package/dist/env-loader.d.ts.map +1 -0
- package/dist/env-loader.js +83 -0
- package/dist/env-loader.js.map +1 -0
- package/dist/execution-service.d.ts +61 -0
- package/dist/execution-service.d.ts.map +1 -0
- package/dist/execution-service.js +822 -0
- package/dist/execution-service.js.map +1 -0
- package/dist/file-handler.d.ts +59 -0
- package/dist/file-handler.d.ts.map +1 -0
- package/dist/file-handler.js +75 -0
- package/dist/file-handler.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +196 -0
- package/dist/index.js.map +1 -0
- package/dist/llm-facade.d.ts +101 -0
- package/dist/llm-facade.d.ts.map +1 -0
- package/dist/llm-facade.js +289 -0
- package/dist/llm-facade.js.map +1 -0
- package/dist/playwright-mcp-service.d.ts +42 -0
- package/dist/playwright-mcp-service.d.ts.map +1 -0
- package/dist/playwright-mcp-service.js +167 -0
- package/dist/playwright-mcp-service.js.map +1 -0
- package/dist/prompts.d.ts +34 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +237 -0
- package/dist/prompts.js.map +1 -0
- package/dist/scenario-service.d.ts +25 -0
- package/dist/scenario-service.d.ts.map +1 -0
- package/dist/scenario-service.js +119 -0
- package/dist/scenario-service.js.map +1 -0
- package/dist/scenario-worker-class.d.ts +30 -0
- package/dist/scenario-worker-class.d.ts.map +1 -0
- package/dist/scenario-worker-class.js +263 -0
- package/dist/scenario-worker-class.js.map +1 -0
- package/dist/script-utils.d.ts +44 -0
- package/dist/script-utils.d.ts.map +1 -0
- package/dist/script-utils.js +100 -0
- package/dist/script-utils.js.map +1 -0
- package/dist/types.d.ts +171 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +28 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/browser-utils.d.ts +13 -0
- package/dist/utils/browser-utils.d.ts.map +1 -0
- package/dist/utils/browser-utils.js +269 -0
- package/dist/utils/browser-utils.js.map +1 -0
- package/dist/utils/page-info-utils.d.ts +16 -0
- package/dist/utils/page-info-utils.d.ts.map +1 -0
- package/dist/utils/page-info-utils.js +77 -0
- package/dist/utils/page-info-utils.js.map +1 -0
- package/env.prod +1 -0
- package/env.staging +1 -0
- package/package.json +38 -0
- package/src/auth-config.ts +84 -0
- package/src/env-loader.ts +91 -0
- package/src/execution-service.ts +999 -0
- package/src/file-handler.ts +104 -0
- package/src/index.ts +205 -0
- package/src/llm-facade.ts +413 -0
- package/src/playwright-mcp-service.ts +203 -0
- package/src/prompts.ts +247 -0
- package/src/scenario-service.ts +138 -0
- package/src/scenario-worker-class.ts +330 -0
- package/src/script-utils.ts +109 -0
- package/src/types.ts +202 -0
- package/src/utils/browser-utils.ts +272 -0
- package/src/utils/page-info-utils.ts +93 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { Browser, Page, BrowserContext } from 'playwright';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { getEnhancedPageInfo } from './utils/page-info-utils';
|
|
5
|
+
import { initializeBrowser } from './utils/browser-utils';
|
|
6
|
+
import { LLMFacade } from './llm-facade';
|
|
7
|
+
import { ScenarioRunJob, ScenarioResponse, ScenarioStep } from './types';
|
|
8
|
+
import { FileHandler } from './file-handler';
|
|
9
|
+
import { AuthConfig } from './auth-config';
|
|
10
|
+
import { generateTestScript } from './script-utils';
|
|
11
|
+
|
|
12
|
+
// Define a simple logging interface for compatibility
|
|
13
|
+
interface OutputChannel {
|
|
14
|
+
appendLine: (text: string) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Legacy interface for backward compatibility
|
|
18
|
+
interface ScenarioJob {
|
|
19
|
+
id: string;
|
|
20
|
+
scenario: string;
|
|
21
|
+
testName?: string;
|
|
22
|
+
playwrightConfig?: string;
|
|
23
|
+
model?: string;
|
|
24
|
+
scenarioFileName?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
const MAX_RETRIES_PER_STEP = 2;
|
|
29
|
+
|
|
30
|
+
export class ScenarioWorker {
|
|
31
|
+
private initialized = false;
|
|
32
|
+
private sessionId: string | null = null;
|
|
33
|
+
private llmFacade: LLMFacade;
|
|
34
|
+
private fileHandler?: FileHandler;
|
|
35
|
+
private outputChannel?: OutputChannel;
|
|
36
|
+
|
|
37
|
+
constructor(fileHandler?: FileHandler, authConfig?: AuthConfig, outputChannel?: OutputChannel) {
|
|
38
|
+
this.llmFacade = new LLMFacade(authConfig);
|
|
39
|
+
this.fileHandler = fileHandler;
|
|
40
|
+
this.outputChannel = outputChannel;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private log(message: string): void {
|
|
44
|
+
console.log(message);
|
|
45
|
+
if (this.outputChannel) {
|
|
46
|
+
this.outputChannel.appendLine(`[ScenarioWorker] ${message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private logError(message: string): void {
|
|
51
|
+
console.error(message);
|
|
52
|
+
if (this.outputChannel) {
|
|
53
|
+
this.outputChannel.appendLine(`[ScenarioWorker] ERROR: ${message}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async initialize(): Promise<void> {
|
|
58
|
+
try {
|
|
59
|
+
this.log('Initializing Scenario worker...');
|
|
60
|
+
this.sessionId = `scenario_worker_${Date.now()}`;
|
|
61
|
+
this.initialized = true;
|
|
62
|
+
this.log(`Scenario worker initialized with session: ${this.sessionId}`);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
this.logError(`Scenario worker initialization error: ${error}`);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async processScenarioJob(job: ScenarioJob): Promise<ScenarioResponse> {
|
|
70
|
+
if (!this.initialized) {
|
|
71
|
+
throw new Error('Scenario worker not initialized');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const startTime = Date.now();
|
|
75
|
+
const steps: ScenarioStep[] = [];
|
|
76
|
+
let generatedScript = '';
|
|
77
|
+
let scriptPath: string | undefined;
|
|
78
|
+
let browser: Browser | undefined;
|
|
79
|
+
let context: BrowserContext | undefined;
|
|
80
|
+
let page: Page | undefined;
|
|
81
|
+
let overallSuccess = true;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// 1. Break down scenario into steps using LLM
|
|
85
|
+
const scenarioSteps = await this.llmFacade.breakdownScenario(job.scenario, job.model);
|
|
86
|
+
steps.push(...scenarioSteps);
|
|
87
|
+
|
|
88
|
+
// 2. Start a new browser session using centralized utility
|
|
89
|
+
// Default to headed mode (headless: false) for better debugging
|
|
90
|
+
const browserInstance = await initializeBrowser(job.playwrightConfig, false);
|
|
91
|
+
browser = browserInstance.browser;
|
|
92
|
+
context = browserInstance.context;
|
|
93
|
+
page = browserInstance.page;
|
|
94
|
+
|
|
95
|
+
// Set reasonable timeout for all operations
|
|
96
|
+
page.setDefaultTimeout(5000); // 5 seconds
|
|
97
|
+
|
|
98
|
+
let previousSteps: ScenarioStep[] = [];
|
|
99
|
+
let lastError: string | undefined;
|
|
100
|
+
|
|
101
|
+
// 3. Execute each step
|
|
102
|
+
for (let i = 0; i < steps.length; i++) {
|
|
103
|
+
const step = steps[i];
|
|
104
|
+
step.stepNumber = i + 1;
|
|
105
|
+
step.retryCount = 0;
|
|
106
|
+
|
|
107
|
+
let stepSuccess = false;
|
|
108
|
+
let stepOutput = '';
|
|
109
|
+
let stepError: string | undefined;
|
|
110
|
+
|
|
111
|
+
for (let attempt = 0; attempt <= MAX_RETRIES_PER_STEP; attempt++) {
|
|
112
|
+
let currentAttemptCommand: string | undefined;
|
|
113
|
+
let currentAttemptSuccess = false;
|
|
114
|
+
let currentAttemptError: string | undefined;
|
|
115
|
+
const attemptTimestamp = Date.now();
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
this.log(`Attempt ${attempt + 1} for step: ${step.description}`);
|
|
119
|
+
|
|
120
|
+
// Get current page state using Playwright's accessibility tree
|
|
121
|
+
const domSnapshot = {
|
|
122
|
+
url: page.url(),
|
|
123
|
+
title: await page.title(),
|
|
124
|
+
accessibilityTree: await page.accessibility.snapshot()
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Generate Playwright command using LLM
|
|
128
|
+
const pageInfo = await getEnhancedPageInfo(domSnapshot);
|
|
129
|
+
const command = await this.llmFacade.generatePlaywrightCommand(step.description, pageInfo, previousSteps, lastError, step, job.model);
|
|
130
|
+
|
|
131
|
+
if (!command) {
|
|
132
|
+
throw new Error('LLM failed to generate a Playwright command.');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
step.playwrightCommand = command;
|
|
136
|
+
currentAttemptCommand = command;
|
|
137
|
+
this.log(` Command: ${command}`);
|
|
138
|
+
|
|
139
|
+
// Execute the command
|
|
140
|
+
await this.executePlaywrightCommand(page, browser, context, command);
|
|
141
|
+
|
|
142
|
+
stepSuccess = true;
|
|
143
|
+
currentAttemptSuccess = true;
|
|
144
|
+
stepOutput = `Executed: ${command}`;
|
|
145
|
+
stepError = undefined;
|
|
146
|
+
this.log(` ✅ SUCCESS: ${command}`);
|
|
147
|
+
break; // Step successful, move to next scenario step
|
|
148
|
+
} catch (error: any) {
|
|
149
|
+
stepError = error instanceof Error ? error.message : String(error);
|
|
150
|
+
currentAttemptError = stepError;
|
|
151
|
+
console.error(` ❌ FAILED (attempt ${attempt + 1}): ${stepError}`);
|
|
152
|
+
console.error(` Command attempted: ${currentAttemptCommand || 'N/A'}`);
|
|
153
|
+
step.retryCount++;
|
|
154
|
+
|
|
155
|
+
// Only update lastError if this is the final attempt
|
|
156
|
+
if (attempt === MAX_RETRIES_PER_STEP) {
|
|
157
|
+
lastError = stepError;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// If this is the last attempt, mark as failed and move on
|
|
161
|
+
if (attempt === MAX_RETRIES_PER_STEP) {
|
|
162
|
+
stepSuccess = false;
|
|
163
|
+
stepOutput = `Failed after ${MAX_RETRIES_PER_STEP + 1} attempts.`;
|
|
164
|
+
overallSuccess = false;
|
|
165
|
+
console.error(` 🚫 STEP FAILED after ${MAX_RETRIES_PER_STEP + 1} attempts`);
|
|
166
|
+
break; // Exit retry loop
|
|
167
|
+
}
|
|
168
|
+
} finally {
|
|
169
|
+
if (!step.attempts) {
|
|
170
|
+
step.attempts = [];
|
|
171
|
+
}
|
|
172
|
+
step.attempts.push({
|
|
173
|
+
attemptNumber: attempt + 1,
|
|
174
|
+
command: currentAttemptCommand,
|
|
175
|
+
success: currentAttemptSuccess,
|
|
176
|
+
error: currentAttemptError,
|
|
177
|
+
timestamp: attemptTimestamp
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
step.success = stepSuccess;
|
|
183
|
+
step.error = stepError;
|
|
184
|
+
previousSteps.push(step);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Generate test name if not provided
|
|
188
|
+
const testName = job.testName || await this.llmFacade.generateTestName(job.scenario, job.model);
|
|
189
|
+
|
|
190
|
+
// Generate preferred filename
|
|
191
|
+
let preferredFileName: string;
|
|
192
|
+
if (testName && testName !== 'test') {
|
|
193
|
+
// Use the generated test name
|
|
194
|
+
const sanitizedName = testName
|
|
195
|
+
.replace(/[^a-zA-Z0-9\s-_]/g, '') // Remove special characters except spaces, hyphens, underscores
|
|
196
|
+
.replace(/\s+/g, '_') // Replace spaces with underscores
|
|
197
|
+
.replace(/_+/g, '_') // Replace multiple underscores with single underscore
|
|
198
|
+
.replace(/^_|_$/g, '') // Remove leading/trailing underscores
|
|
199
|
+
.toLowerCase();
|
|
200
|
+
preferredFileName = `${sanitizedName}.spec.js`;
|
|
201
|
+
} else {
|
|
202
|
+
// Use scenario file name if no meaningful test name was generated
|
|
203
|
+
const scenarioFileName = job.scenarioFileName || 'scenario';
|
|
204
|
+
const baseName = scenarioFileName.replace(/\.[^/.]+$/, ''); // Remove extension
|
|
205
|
+
const sanitizedName = baseName
|
|
206
|
+
.replace(/[^a-zA-Z0-9\s-_]/g, '') // Remove special characters except spaces, hyphens, underscores
|
|
207
|
+
.replace(/\s+/g, '_') // Replace spaces with underscores
|
|
208
|
+
.replace(/_+/g, '_') // Replace multiple underscores with single underscore
|
|
209
|
+
.replace(/^_|_$/g, '') // Remove leading/trailing underscores
|
|
210
|
+
.toLowerCase();
|
|
211
|
+
preferredFileName = `${sanitizedName}.spec.js`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Generate clean script with TestChimp comment and code
|
|
215
|
+
generatedScript = generateTestScript(testName, steps);
|
|
216
|
+
|
|
217
|
+
// Generate detailed execution log
|
|
218
|
+
const logLines: string[] = [];
|
|
219
|
+
logLines.push(`# Scenario Execution Log`);
|
|
220
|
+
logLines.push(`Job ID: ${job.id}`);
|
|
221
|
+
logLines.push(`Scenario: ${job.scenario}`);
|
|
222
|
+
logLines.push(`Start Time: ${new Date(startTime).toISOString()}`);
|
|
223
|
+
logLines.push(`End Time: ${new Date().toISOString()}`);
|
|
224
|
+
logLines.push(`Total Execution Time: ${Date.now() - startTime}ms`);
|
|
225
|
+
logLines.push(`Overall Success: ${overallSuccess ? 'YES' : 'NO'}`);
|
|
226
|
+
logLines.push(``);
|
|
227
|
+
|
|
228
|
+
for (const step of steps) {
|
|
229
|
+
logLines.push(`## Step ${step.stepNumber}: ${step.description}`);
|
|
230
|
+
logLines.push(`Status: ${step.success ? 'SUCCESS' : 'FAILED'}`);
|
|
231
|
+
logLines.push(`Retry Count: ${step.retryCount || 0}`);
|
|
232
|
+
|
|
233
|
+
if (step.playwrightCommand) {
|
|
234
|
+
logLines.push(`Final Command: ${step.playwrightCommand}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (step.error) {
|
|
238
|
+
logLines.push(`Final Error: ${step.error}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (step.attempts && step.attempts.length > 0) {
|
|
242
|
+
logLines.push(`### Attempts:`);
|
|
243
|
+
for (const attempt of step.attempts) {
|
|
244
|
+
logLines.push(`- Attempt ${attempt.attemptNumber}:`);
|
|
245
|
+
logLines.push(` Command: ${attempt.command || 'N/A'}`);
|
|
246
|
+
logLines.push(` Success: ${attempt.success ? 'YES' : 'NO'}`);
|
|
247
|
+
if (attempt.error) {
|
|
248
|
+
logLines.push(` Error: ${attempt.error}`);
|
|
249
|
+
}
|
|
250
|
+
logLines.push(` Timestamp: ${new Date(attempt.timestamp).toISOString()}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
logLines.push(``);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const executionLog = logLines.join('\n');
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
success: overallSuccess,
|
|
261
|
+
steps,
|
|
262
|
+
generatedScript,
|
|
263
|
+
executionLog,
|
|
264
|
+
executionTime: Date.now() - startTime,
|
|
265
|
+
testName,
|
|
266
|
+
preferredFileName
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
} catch (error: any) {
|
|
270
|
+
overallSuccess = false;
|
|
271
|
+
console.error('Overall scenario processing error:', error);
|
|
272
|
+
return {
|
|
273
|
+
success: false,
|
|
274
|
+
steps,
|
|
275
|
+
generatedScript,
|
|
276
|
+
executionLog: `# Scenario Execution Log\nJob ID: ${job.id}\nScenario: ${job.scenario}\nError: ${error instanceof Error ? error.message : 'Unknown error during scenario processing'}`,
|
|
277
|
+
executionTime: Date.now() - startTime,
|
|
278
|
+
testName: job.testName || 'test',
|
|
279
|
+
preferredFileName: 'test.spec.js',
|
|
280
|
+
error: error instanceof Error ? error.message : 'Unknown error during scenario processing'
|
|
281
|
+
};
|
|
282
|
+
} finally {
|
|
283
|
+
if (browser) {
|
|
284
|
+
await browser.close();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
private async executePlaywrightCommand(
|
|
295
|
+
page: Page,
|
|
296
|
+
browser: Browser,
|
|
297
|
+
context: BrowserContext,
|
|
298
|
+
command: string
|
|
299
|
+
): Promise<void> {
|
|
300
|
+
// Set reasonable timeouts
|
|
301
|
+
page.setDefaultTimeout(5000); // 5 seconds
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
// Execute command directly without validation
|
|
305
|
+
const commandFunction = new Function('page', 'browser', 'context', 'expect', `
|
|
306
|
+
return (async () => {
|
|
307
|
+
try {
|
|
308
|
+
${command}
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.error('Command execution error:', error);
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
})();
|
|
314
|
+
`);
|
|
315
|
+
|
|
316
|
+
await commandFunction(page, browser, context, require('@playwright/test').expect);
|
|
317
|
+
|
|
318
|
+
} finally {
|
|
319
|
+
// Reset to default timeout
|
|
320
|
+
page.setDefaultTimeout(10000); // Reset to default 10 seconds
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
async cleanup(): Promise<void> {
|
|
327
|
+
this.initialized = false;
|
|
328
|
+
this.sessionId = null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script Generation Utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for generating and formatting test scripts
|
|
5
|
+
* with TestChimp-specific markers and comments.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* TestChimp managed test comment that should be added to all generated scripts
|
|
10
|
+
*/
|
|
11
|
+
export const TESTCHIMP_MANAGED_COMMENT = `/*
|
|
12
|
+
|
|
13
|
+
This is a TestChimp Managed Test.
|
|
14
|
+
|
|
15
|
+
*/`;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generates TestChimp managed test comment with optional repair advice
|
|
19
|
+
* @param repairAdvice Optional repair advice to include in the comment
|
|
20
|
+
* @returns The complete comment block
|
|
21
|
+
*/
|
|
22
|
+
export function generateTestChimpComment(repairAdvice?: string): string {
|
|
23
|
+
if (repairAdvice) {
|
|
24
|
+
return `/*
|
|
25
|
+
|
|
26
|
+
This is a TestChimp Managed Test.
|
|
27
|
+
|
|
28
|
+
Repair Advice:
|
|
29
|
+
${repairAdvice}
|
|
30
|
+
|
|
31
|
+
*/`;
|
|
32
|
+
}
|
|
33
|
+
return TESTCHIMP_MANAGED_COMMENT;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Adds the TestChimp managed test comment to the beginning of a script
|
|
38
|
+
* @param script The original script content
|
|
39
|
+
* @param repairAdvice Optional repair advice to include in the comment
|
|
40
|
+
* @returns The script with TestChimp comment prepended
|
|
41
|
+
*/
|
|
42
|
+
export function addTestChimpComment(script: string, repairAdvice?: string): string {
|
|
43
|
+
// If the script already has the TestChimp comment, update it with repair advice if provided
|
|
44
|
+
if (script.includes('This is a TestChimp Managed Test')) {
|
|
45
|
+
if (repairAdvice) {
|
|
46
|
+
// Replace existing comment with new one that includes repair advice
|
|
47
|
+
const commentRegex = /\/\*[\s\S]*?This is a TestChimp Managed Test\.[\s\S]*?\*\//;
|
|
48
|
+
const newComment = generateTestChimpComment(repairAdvice);
|
|
49
|
+
return script.replace(commentRegex, newComment);
|
|
50
|
+
}
|
|
51
|
+
return script;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Add the comment at the beginning of the script
|
|
55
|
+
const comment = generateTestChimpComment(repairAdvice);
|
|
56
|
+
return `${comment}\n\n${script}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generates a complete test script with TestChimp comment, imports, and test structure
|
|
61
|
+
* @param testName The name of the test
|
|
62
|
+
* @param steps Array of test steps with descriptions and commands
|
|
63
|
+
* @param includeTestChimpComment Whether to include the TestChimp managed test comment
|
|
64
|
+
* @param repairAdvice Optional repair advice to include in the comment
|
|
65
|
+
* @returns The complete test script
|
|
66
|
+
*/
|
|
67
|
+
export function generateTestScript(
|
|
68
|
+
testName: string,
|
|
69
|
+
steps: Array<{ stepNumber: number; description: string; playwrightCommand?: string; success?: boolean }>,
|
|
70
|
+
includeTestChimpComment: boolean = true,
|
|
71
|
+
repairAdvice?: string
|
|
72
|
+
): string {
|
|
73
|
+
const scriptLines: string[] = [];
|
|
74
|
+
|
|
75
|
+
// Add TestChimp comment if requested
|
|
76
|
+
if (includeTestChimpComment) {
|
|
77
|
+
const comment = generateTestChimpComment(repairAdvice);
|
|
78
|
+
scriptLines.push(comment);
|
|
79
|
+
scriptLines.push('');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Add imports
|
|
83
|
+
scriptLines.push(`import { test, expect } from '@playwright/test';`);
|
|
84
|
+
|
|
85
|
+
// Add test structure
|
|
86
|
+
scriptLines.push(`test('${testName.replace(/'/g, "\\'")}', async ({ page, browser, context }) => {`);
|
|
87
|
+
|
|
88
|
+
// Add steps
|
|
89
|
+
for (const step of steps) {
|
|
90
|
+
const status = step.success === false ? ' [FAILED]' : '';
|
|
91
|
+
scriptLines.push(` // Step ${step.stepNumber}: ${step.description}${status}`);
|
|
92
|
+
if (step.playwrightCommand && step.success !== false) {
|
|
93
|
+
scriptLines.push(` ${step.playwrightCommand}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
scriptLines.push(`});`);
|
|
98
|
+
|
|
99
|
+
return scriptLines.join('\n');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Checks if a script is a TestChimp managed test
|
|
104
|
+
* @param script The script content to check
|
|
105
|
+
* @returns True if the script contains the TestChimp managed test comment
|
|
106
|
+
*/
|
|
107
|
+
export function isTestChimpManagedTest(script: string): boolean {
|
|
108
|
+
return script.includes('This is a TestChimp Managed Test');
|
|
109
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// CORE TYPES
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Playwright MCP configuration - JavaScript config file content (playwright.config.js)
|
|
7
|
+
*/
|
|
8
|
+
export type PlaywrightConfig = string;
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// SCRIPT EXECUTION TYPES
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Request structure for the Playwright script executor
|
|
16
|
+
*/
|
|
17
|
+
export interface PlaywrightExecutionRequest {
|
|
18
|
+
/** Main Playwright script content */
|
|
19
|
+
script: string;
|
|
20
|
+
/** Optional pre-script to run before the main script */
|
|
21
|
+
prescript?: string;
|
|
22
|
+
/** Optional post-script to run after the main script */
|
|
23
|
+
postscript?: string;
|
|
24
|
+
/** Playwright configuration file content */
|
|
25
|
+
playwrightConfig: string;
|
|
26
|
+
/** Optional GPT model to use for AI operations */
|
|
27
|
+
model?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Response structure for the Playwright script executor
|
|
32
|
+
*/
|
|
33
|
+
export interface PlaywrightExecutionResponse {
|
|
34
|
+
/** Whether the execution was successful */
|
|
35
|
+
success: boolean;
|
|
36
|
+
/** Execution results from each script phase */
|
|
37
|
+
results: {
|
|
38
|
+
prescript?: ScriptResult;
|
|
39
|
+
script: ScriptResult;
|
|
40
|
+
postscript?: ScriptResult;
|
|
41
|
+
};
|
|
42
|
+
/** Overall execution time in milliseconds */
|
|
43
|
+
executionTime: number;
|
|
44
|
+
/** Any errors that occurred during execution */
|
|
45
|
+
error?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Individual script execution result
|
|
50
|
+
*/
|
|
51
|
+
export interface ScriptResult {
|
|
52
|
+
/** Whether this specific script executed successfully */
|
|
53
|
+
success: boolean;
|
|
54
|
+
/** Output from the script execution */
|
|
55
|
+
output: string;
|
|
56
|
+
/** Any errors from this script */
|
|
57
|
+
error?: string;
|
|
58
|
+
/** Execution time for this script in milliseconds */
|
|
59
|
+
executionTime: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// SCENARIO EXECUTION TYPES
|
|
64
|
+
// ============================================================================
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Scenario execution request
|
|
68
|
+
*/
|
|
69
|
+
export interface ScenarioRequest {
|
|
70
|
+
scenario: string;
|
|
71
|
+
testName?: string;
|
|
72
|
+
playwrightConfig?: PlaywrightConfig;
|
|
73
|
+
model?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Scenario execution job for worker queue
|
|
78
|
+
*/
|
|
79
|
+
export interface ScenarioRunJob {
|
|
80
|
+
id: string;
|
|
81
|
+
scenario: string;
|
|
82
|
+
testName?: string;
|
|
83
|
+
playwrightConfig?: PlaywrightConfig;
|
|
84
|
+
model?: string;
|
|
85
|
+
scenarioFileName?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Scenario execution response
|
|
90
|
+
*/
|
|
91
|
+
export interface ScenarioResponse {
|
|
92
|
+
success: boolean;
|
|
93
|
+
steps: ScenarioStep[];
|
|
94
|
+
generatedScript: string;
|
|
95
|
+
executionLog: string;
|
|
96
|
+
executionTime: number;
|
|
97
|
+
testName?: string;
|
|
98
|
+
preferredFileName?: string;
|
|
99
|
+
error?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Individual scenario step
|
|
104
|
+
*/
|
|
105
|
+
export interface ScenarioStep {
|
|
106
|
+
stepNumber: number;
|
|
107
|
+
description: string;
|
|
108
|
+
playwrightCommand?: string;
|
|
109
|
+
success?: boolean;
|
|
110
|
+
error?: string;
|
|
111
|
+
retryCount?: number;
|
|
112
|
+
attempts?: Array<{
|
|
113
|
+
attemptNumber: number;
|
|
114
|
+
command?: string;
|
|
115
|
+
success: boolean;
|
|
116
|
+
error?: string;
|
|
117
|
+
timestamp: number;
|
|
118
|
+
}>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Legacy scenario job interface (for backward compatibility)
|
|
123
|
+
*/
|
|
124
|
+
export interface ScenarioJob {
|
|
125
|
+
id: string;
|
|
126
|
+
scenario: string;
|
|
127
|
+
config?: PlaywrightConfig;
|
|
128
|
+
resolve: (result: ScenarioResponse) => void;
|
|
129
|
+
reject: (error: Error) => void;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ============================================================================
|
|
133
|
+
// AI REPAIR TYPES
|
|
134
|
+
// ============================================================================
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Execution mode for script execution
|
|
138
|
+
*/
|
|
139
|
+
export enum ExecutionMode {
|
|
140
|
+
RUN_EXACTLY = 'RUN_EXACTLY',
|
|
141
|
+
RUN_WITH_AI_REPAIR = 'RUN_WITH_AI_REPAIR'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Script execution request with AI repair capabilities
|
|
146
|
+
*/
|
|
147
|
+
export interface ScriptExecutionRequest {
|
|
148
|
+
script?: string; // Optional if scriptFilePath is provided
|
|
149
|
+
scriptFilePath?: string; // Path to script file (alternative to script content)
|
|
150
|
+
mode: ExecutionMode;
|
|
151
|
+
repair_flexibility?: number; // 0-5, defaults to 3
|
|
152
|
+
playwrightConfig?: PlaywrightConfig;
|
|
153
|
+
playwrightConfigFilePath?: string; // Path to playwright config file (alternative to playwrightConfig content)
|
|
154
|
+
model?: string;
|
|
155
|
+
headless?: boolean; // defaults to false (headed)
|
|
156
|
+
deflake_run_count?: number; // defaults to 1
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Script execution response with repair information
|
|
161
|
+
*/
|
|
162
|
+
export interface ScriptExecutionResponse {
|
|
163
|
+
run_status: 'success' | 'failed';
|
|
164
|
+
repair_status?: 'success' | 'failed' | 'partial';
|
|
165
|
+
repair_confidence?: number; // 0-5
|
|
166
|
+
repair_advice?: string;
|
|
167
|
+
updated_script?: string;
|
|
168
|
+
executionTime: number;
|
|
169
|
+
num_deflake_runs?: number; // Number of deflaking runs made (excluding original run)
|
|
170
|
+
error?: string;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Individual script step for AI repair
|
|
175
|
+
*/
|
|
176
|
+
export interface ScriptStep {
|
|
177
|
+
description: string;
|
|
178
|
+
code: string;
|
|
179
|
+
success?: boolean;
|
|
180
|
+
error?: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Step operation types for AI repair
|
|
185
|
+
*/
|
|
186
|
+
export enum StepOperation {
|
|
187
|
+
MODIFY = 'MODIFY',
|
|
188
|
+
INSERT = 'INSERT',
|
|
189
|
+
REMOVE = 'REMOVE'
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Step repair action
|
|
194
|
+
*/
|
|
195
|
+
export interface StepRepairAction {
|
|
196
|
+
operation: StepOperation;
|
|
197
|
+
stepIndex?: number; // For MODIFY and REMOVE operations
|
|
198
|
+
newStep?: ScriptStep; // For MODIFY and INSERT operations
|
|
199
|
+
insertAfterIndex?: number; // For INSERT operation
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Repair suggestion and confidence interfaces are now in llm-facade.ts
|