generator.ts•6.06 kB
import * as path from 'path';
import { CodegenAction, CodegenOptions, CodegenResult, CodegenSession, PlaywrightTestCase } from './types.js';
export class PlaywrightGenerator {
private static readonly DEFAULT_OPTIONS: Required<CodegenOptions> = {
outputPath: 'tests',
testNamePrefix: 'MCP',
includeComments: true,
};
private options: Required<CodegenOptions>;
constructor(options: CodegenOptions = {}) {
this.validateOptions(options);
this.options = { ...PlaywrightGenerator.DEFAULT_OPTIONS, ...options };
}
private validateOptions(options: CodegenOptions): void {
if (options.outputPath && typeof options.outputPath !== 'string') {
throw new Error('outputPath must be a string');
}
if (options.testNamePrefix && typeof options.testNamePrefix !== 'string') {
throw new Error('testNamePrefix must be a string');
}
if (options.includeComments !== undefined && typeof options.includeComments !== 'boolean') {
throw new Error('includeComments must be a boolean');
}
}
async generateTest(session: CodegenSession): Promise<CodegenResult> {
if (!session || !Array.isArray(session.actions)) {
throw new Error('Invalid session data');
}
const testCase = this.createTestCase(session);
const testCode = this.generateTestCode(testCase);
const filePath = this.getOutputFilePath(session);
return {
testCode,
filePath,
sessionId: session.id,
};
}
private createTestCase(session: CodegenSession): PlaywrightTestCase {
const testCase: PlaywrightTestCase = {
name: `${this.options.testNamePrefix}_${new Date(session.startTime).toISOString().split('T')[0]}`,
steps: [],
imports: new Set(['test', 'expect']),
};
for (const action of session.actions) {
const step = this.convertActionToStep(action);
if (step) {
testCase.steps.push(step);
}
}
return testCase;
}
private convertActionToStep(action: CodegenAction): string | null {
const { toolName, parameters } = action;
switch (toolName) {
case 'playwright_navigate':
return this.generateNavigateStep(parameters);
case 'playwright_fill':
return this.generateFillStep(parameters);
case 'playwright_click':
return this.generateClickStep(parameters);
case 'playwright_screenshot':
return this.generateScreenshotStep(parameters);
case 'playwright_expect_response':
return this.generateExpectResponseStep(parameters);
case 'playwright_assert_response':
return this.generateAssertResponseStep(parameters);
case 'playwright_hover':
return this.generateHoverStep(parameters);
case 'playwright_select':
return this.generateSelectStep(parameters);
case 'playwright_custom_user_agent':
return this.generateCustomUserAgentStep(parameters);
default:
console.warn(`Unsupported tool: ${toolName}`);
return null;
}
}
private generateNavigateStep(parameters: Record<string, unknown>): string {
const { url, waitUntil } = parameters;
const options = waitUntil ? `, { waitUntil: '${waitUntil}' }` : '';
return `
// Navigate to URL
await page.goto('${url}'${options});`;
}
private generateFillStep(parameters: Record<string, unknown>): string {
const { selector, value } = parameters;
return `
// Fill input field
await page.fill('${selector}', '${value}');`;
}
private generateClickStep(parameters: Record<string, unknown>): string {
const { selector } = parameters;
return `
// Click element
await page.click('${selector}');`;
}
private generateScreenshotStep(parameters: Record<string, unknown>): string {
const { name, fullPage = false, path } = parameters;
const options = [];
if (fullPage) options.push('fullPage: true');
if (path) options.push(`path: '${path}'`);
const optionsStr = options.length > 0 ? `, { ${options.join(', ')} }` : '';
return `
// Take screenshot
await page.screenshot({ path: '${name}.png'${optionsStr} });`;
}
private generateExpectResponseStep(parameters: Record<string, unknown>): string {
const { url, id } = parameters;
return `
// Wait for response
const ${id}Response = page.waitForResponse('${url}');`;
}
private generateAssertResponseStep(parameters: Record<string, unknown>): string {
const { id, value } = parameters;
const assertion = value
? `\n const responseText = await ${id}Response.text();\n expect(responseText).toContain('${value}');`
: `\n expect(${id}Response.ok()).toBeTruthy();`;
return `
// Assert response${assertion}`;
}
private generateHoverStep(parameters: Record<string, unknown>): string {
const { selector } = parameters;
return `
// Hover over element
await page.hover('${selector}');`;
}
private generateSelectStep(parameters: Record<string, unknown>): string {
const { selector, value } = parameters;
return `
// Select option
await page.selectOption('${selector}', '${value}');`;
}
private generateCustomUserAgentStep(parameters: Record<string, unknown>): string {
const { userAgent } = parameters;
return `
// Set custom user agent
await context.setUserAgent('${userAgent}');`;
}
private generateTestCode(testCase: PlaywrightTestCase): string {
const imports = Array.from(testCase.imports)
.map(imp => `import { ${imp} } from '@playwright/test';`)
.join('\n');
return `
${imports}
test('${testCase.name}', async ({ page, context }) => {
${testCase.steps.join('\n')}
});`;
}
private getOutputFilePath(session: CodegenSession): string {
if (!session.id) {
throw new Error('Session ID is required');
}
const sanitizedPrefix = this.options.testNamePrefix.toLowerCase().replace(/[^a-z0-9_]/g, '_');
const fileName = `${sanitizedPrefix}_${session.id}.spec.ts`;
return path.resolve(this.options.outputPath, fileName);
}
}