UNPKG

testgenius-ai

Version:

πŸš€ TestGenius AI - The Ultimate E2E Testing Framework for Everyone | No Coding Required β€’ AI-Powered Automation β€’ Beautiful Reports β€’ Zero Complexity

615 lines (609 loc) β€’ 26.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TestRecorder = void 0; const inquirer_1 = __importDefault(require("inquirer")); const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const chalk_1 = __importDefault(require("chalk")); class TestRecorder { constructor() { this.testSteps = []; this.testInfo = {}; this.bddMode = false; this.bddInputMethod = 'auto-detect'; this.currentScenario = ''; this.testDirectory = ''; } async start() { console.log(chalk_1.default.cyan('\n🎬 Interactive Test Recorder - Enhanced Mode\n')); // Automatically set up directory structure await this.setupDirectoryStructure(); console.log(chalk_1.default.yellow('Commands:')); console.log(' β€’ done/stop - Finish recording and save test'); console.log(' β€’ back - Remove last step'); console.log(' β€’ list - Show all recorded steps'); console.log(' β€’ bdd - Show BDD format preview'); console.log(' β€’ clear - Clear all steps'); console.log(' β€’ help - Show this help\n'); // Get basic test information first await this.getTestInfo(); // Ask for BDD mode preference await this.setupBDDMode(); // Start continuous recording await this.recordSteps(); } async setupDirectoryStructure() { console.log(chalk_1.default.blue('πŸ”§ Setting up directory structure...\n')); // Try to create the standard directory structure const directories = [ 'src/tests', 'tests' ]; let createdDir = ''; for (const dir of directories) { try { const fullPath = path_1.default.join(process.cwd(), dir); await fs_extra_1.default.ensureDir(fullPath); createdDir = dir; console.log(chalk_1.default.green(`βœ… Created ${dir}/ directory`)); break; } catch (error) { console.log(chalk_1.default.yellow(`⚠️ Could not create ${dir}/ directory, trying next...`)); continue; } } if (!createdDir) { console.log(chalk_1.default.red('❌ Could not create any test directory. Please create a tests directory manually.')); throw new Error('No test directory could be created'); } // Store the created directory for later use this.testDirectory = createdDir; console.log(chalk_1.default.green(`πŸ“ Tests will be saved to ${createdDir}/ directory\n`)); } async getTestInfo() { console.log(chalk_1.default.blue('πŸ“‹ Test Information Setup\n')); const answers = await inquirer_1.default.prompt([ { name: 'id', message: 'Test ID:', validate: (v) => !!v ? true : 'Test ID is required', default: `test-${Date.now()}` }, { name: 'name', message: 'Test Name:', validate: (v) => !!v ? true : 'Test name is required', default: 'Recorded Test' }, { name: 'description', message: 'Description:', validate: (v) => !!v ? true : 'Description is required', default: 'Test recorded using interactive recorder' }, { name: 'priority', message: 'Priority (High/Medium/Low):', type: 'list', choices: ['High', 'Medium', 'Low'], default: 'Medium' }, { name: 'tags', message: 'Tags (comma separated):', filter: (v) => v.split(',').map(t => t.trim()).filter(t => t.length > 0), default: 'recorded' }, { name: 'site', message: 'Target site URL:', validate: (v) => !!v ? true : 'Site URL is required', default: 'https://example.com' }, { name: 'format', message: 'Test format:', type: 'list', choices: [ { name: 'πŸš€ Enhanced Async Format (Recommended)', value: 'enhanced' }, { name: 'πŸ“ Legacy Format', value: 'legacy' } ], default: 'enhanced' } ]); this.testInfo = { ...answers, priority: answers.priority || 'Medium', tags: answers.tags || ['recorded'], testData: {}, format: answers.format || 'enhanced', }; console.log(chalk_1.default.green('\nβœ… Test information saved!\n')); } async setupBDDMode() { const { bddMode } = await inquirer_1.default.prompt([ { name: 'bddMode', message: 'Enable BDD mode?', type: 'confirm', default: true } ]); this.bddMode = bddMode; if (bddMode) { console.log(chalk_1.default.green('🎯 BDD mode enabled! You will enter plain steps, then can provide a full BDD scenario at the end.\n')); this.bddInputMethod = 'plain-then-scenario'; } else { console.log(chalk_1.default.blue('πŸ€– Regular mode: AI will auto-assign keywords and structure.\n')); this.bddInputMethod = 'ai-auto'; } } async recordSteps() { while (true) { // Show current step count and BDD preview if enabled this.showCurrentStatus(); console.log(chalk_1.default.cyan(`\nπŸ“ Step ${this.testSteps.length + 1} - What would you like to do?`)); const { action } = await inquirer_1.default.prompt([ { name: 'action', message: 'Action:', type: 'list', choices: [ { name: 'πŸ” Navigate to page', value: 'navigate' }, { name: 'πŸ‘† Click element', value: 'click' }, { name: '✏️ Fill input field', value: 'fill' }, { name: '⌨️ Type text', value: 'type' }, { name: '🧹 Clear field', value: 'clear_field' }, { name: '⏳ Smart Wait (with expected data)', value: 'smart-wait' }, { name: 'βœ… Verify/Assert', value: 'verify' }, { name: '❌ Verify field error', value: 'verify_field_error' }, { name: 'πŸ“Έ Take screenshot', value: 'screenshot' }, { name: '⏸️ Wait (seconds)', value: 'wait-time' }, { name: 'πŸ“‹ Show recorded steps', value: 'list' }, { name: '🎯 Show BDD preview', value: 'bdd' }, { name: '↩️ Remove last step', value: 'back' }, { name: 'πŸ—‘οΈ Clear all steps', value: 'clear' }, { name: '❓ Help', value: 'help' }, { name: 'βœ… Done - Save test', value: 'done' }, { name: 'πŸ›‘ Stop - Cancel', value: 'stop' } ] } ]); switch (action) { case 'done': case 'stop': return await this.finishRecording(action === 'done'); case 'list': this.showSteps(); break; case 'bdd': this.showBDDPreview(); break; case 'back': this.removeLastStep(); break; case 'clear': this.clearSteps(); break; case 'help': this.showHelp(); break; default: await this.recordStep(action); break; } } } showCurrentStatus() { if (this.testSteps.length > 0) { console.log(chalk_1.default.blue(`\nπŸ“Š Current Status: ${this.testSteps.length} steps recorded`)); if (this.bddMode) { console.log(chalk_1.default.green('🎯 BDD Mode: Active')); // Show last 3 steps in BDD format const recentSteps = this.testSteps.slice(-3); if (recentSteps.length > 0) { console.log(chalk_1.default.yellow('\nπŸ“ Recent Steps (BDD Preview):')); recentSteps.forEach((step, index) => { const bddStep = this.convertToBDD(step, index === 0); console.log(chalk_1.default.cyan(` ${bddStep.keyword} ${bddStep.description}`)); }); } } } } convertToBDD(step, isFirst) { let keyword = 'And'; // Check if user has selected a custom BDD keyword if (step.bddKeyword) { keyword = step.bddKeyword; } else if (isFirst) { keyword = 'Given'; } else if (step.action === 'navigate') { keyword = 'Given'; } else if (step.action === 'click' || step.action === 'fill' || step.action === 'type') { keyword = 'When'; } else if (step.action === 'verify' || step.action === 'verify_field_error') { keyword = 'Then'; } return { keyword, description: step.description, action: step.action, target: step.target, value: step.value }; } async recordStep(actionType) { let step; switch (actionType) { case 'navigate': const { url } = await inquirer_1.default.prompt([ { name: 'url', message: 'Navigate to URL:', validate: (v) => !!v ? true : 'URL is required' } ]); step = { action: 'navigate', description: `Navigate to ${url}`, target: url, timestamp: new Date() }; break; case 'click': const { clickTarget } = await inquirer_1.default.prompt([ { name: 'clickTarget', message: 'Click on (element description):', validate: (v) => !!v ? true : 'Target is required' } ]); step = { action: 'click', description: `Click on ${clickTarget}`, target: clickTarget, timestamp: new Date() }; break; case 'fill': const { fillTarget, fillValue } = await inquirer_1.default.prompt([ { name: 'fillTarget', message: 'Fill field (element description):', validate: (v) => !!v ? true : 'Target is required' }, { name: 'fillValue', message: 'Value to fill:', validate: (v) => !!v ? true : 'Value is required' } ]); step = { action: 'fill', description: `Fill ${fillTarget} with ${fillValue}`, target: fillTarget, value: fillValue, timestamp: new Date() }; break; case 'type': const { typeTarget, typeValue } = await inquirer_1.default.prompt([ { name: 'typeTarget', message: 'Type in (element description):', validate: (v) => !!v ? true : 'Target is required' }, { name: 'typeValue', message: 'Text to type:', validate: (v) => !!v ? true : 'Text is required' } ]); step = { action: 'type', description: `Type "${typeValue}" in ${typeTarget}`, target: typeTarget, value: typeValue, timestamp: new Date() }; break; case 'clear_field': const { clearTarget } = await inquirer_1.default.prompt([ { name: 'clearTarget', message: 'Clear field (element description):', validate: (v) => !!v ? true : 'Target is required' } ]); step = { action: 'clear_field', description: `Clear ${clearTarget} field`, target: clearTarget, timestamp: new Date() }; break; case 'smart-wait': const { waitTarget, expectedData, maxTimeout } = await inquirer_1.default.prompt([ { name: 'waitTarget', message: 'Wait for (element description):', validate: (v) => !!v ? true : 'Target is required' }, { name: 'expectedData', message: 'Expected data/text to appear (optional):', default: '' }, { name: 'maxTimeout', message: 'Maximum wait time (seconds):', type: 'number', default: 30, validate: (v) => v > 0 ? true : 'Must be positive number' } ]); const waitDescription = expectedData ? `Wait for ${waitTarget} to appear with "${expectedData}" (max ${maxTimeout}s)` : `Wait for ${waitTarget} to appear (max ${maxTimeout}s)`; step = { action: 'smart-wait', description: waitDescription, target: waitTarget, value: expectedData || undefined, timestamp: new Date() }; break; case 'verify': const { verifyTarget, verifyValue } = await inquirer_1.default.prompt([ { name: 'verifyTarget', message: 'Verify (element description):', validate: (v) => !!v ? true : 'Target is required' }, { name: 'verifyValue', message: 'Expected value (optional):' } ]); step = { action: 'verify', description: `Verify ${verifyTarget}${verifyValue ? ` contains "${verifyValue}"` : ''}`, target: verifyTarget, value: verifyValue, timestamp: new Date() }; break; case 'verify_field_error': const { errorTarget } = await inquirer_1.default.prompt([ { name: 'errorTarget', message: 'Verify field error (element description):', validate: (v) => !!v ? true : 'Target is required' } ]); step = { action: 'verify_field_error', description: `Verify ${errorTarget} has error`, target: errorTarget, timestamp: new Date() }; break; case 'screenshot': const { screenshotName } = await inquirer_1.default.prompt([ { name: 'screenshotName', message: 'Screenshot name (optional):', default: 'screenshot' } ]); step = { action: 'screenshot', description: `Take screenshot: ${screenshotName}`, target: screenshotName, timestamp: new Date() }; break; case 'wait-time': const { waitSeconds } = await inquirer_1.default.prompt([ { name: 'waitSeconds', message: 'Wait for (seconds):', type: 'number', validate: (v) => v > 0 ? true : 'Must be positive number' } ]); step = { action: 'wait-time', description: `Wait for ${waitSeconds} seconds`, target: waitSeconds.toString(), timestamp: new Date() }; break; default: console.log(chalk_1.default.red('❌ Unknown action type')); return; } this.testSteps.push(step); console.log(chalk_1.default.green(`βœ… Step ${this.testSteps.length} recorded: ${step.description}`)); // Show BDD preview if enabled if (this.bddMode) { // No keyword selection, just show as plain const bddStep = this.convertToBDD(step, this.testSteps.length === 1); console.log(chalk_1.default.cyan(`🎯 Step: ${bddStep.description}`)); } } showSteps() { if (this.testSteps.length === 0) { console.log(chalk_1.default.yellow('\nπŸ“‹ No steps recorded yet.\n')); return; } console.log(chalk_1.default.blue('\nπŸ“‹ Recorded Steps:\n')); this.testSteps.forEach((step, index) => { console.log(chalk_1.default.cyan(`${index + 1}. ${step.description}`)); }); console.log(''); } showBDDPreview() { if (this.testSteps.length === 0) { console.log(chalk_1.default.yellow('\nπŸ“‹ No steps recorded yet.\n')); return; } console.log(chalk_1.default.blue('\n🎯 BDD Format Preview:\n')); console.log(chalk_1.default.green('Feature: ' + (this.testInfo.name || 'Recorded Test'))); console.log(chalk_1.default.gray(' ' + (this.testInfo.description || 'Test recorded using interactive recorder'))); console.log(''); console.log(chalk_1.default.yellow(' Scenario: ' + (this.testInfo.name || 'Recorded Scenario'))); this.testSteps.forEach((step, index) => { const bddStep = this.convertToBDD(step, index === 0); console.log(chalk_1.default.cyan(` ${bddStep.keyword} ${bddStep.description}`)); }); console.log(''); } removeLastStep() { if (this.testSteps.length === 0) { console.log(chalk_1.default.yellow('\nπŸ“‹ No steps to remove.\n')); return; } const removedStep = this.testSteps.pop(); console.log(chalk_1.default.yellow(`\n↩️ Removed step: ${removedStep?.description}\n`)); } clearSteps() { this.testSteps = []; console.log(chalk_1.default.yellow('\nπŸ—‘οΈ All steps cleared.\n')); } showHelp() { console.log(chalk_1.default.cyan('\n❓ Help - Available Commands:\n')); console.log(' β€’ done/stop - Finish recording and save test'); console.log(' β€’ back - Remove last step'); console.log(' β€’ list - Show all recorded steps'); console.log(' β€’ bdd - Show BDD format preview'); console.log(' β€’ clear - Clear all steps'); console.log(' β€’ help - Show this help\n'); if (this.bddMode) { console.log(chalk_1.default.green('🎯 BDD Mode Features:\n')); console.log(' β€’ Real-time BDD step preview'); console.log(' β€’ Automatic keyword assignment (Given/When/Then)'); console.log(' β€’ BDD format export capability\n'); } } async finishRecording(save) { if (!save) { console.log(chalk_1.default.yellow('\nπŸ›‘ Recording cancelled.\n')); return; } if (this.testSteps.length === 0) { console.log(chalk_1.default.red('\n❌ No steps recorded. Cannot save empty test.\n')); return; } // If BDD mode, ask if user wants to provide a full BDD scenario if (this.bddMode && this.bddInputMethod === 'plain-then-scenario') { const { provideBDD } = await inquirer_1.default.prompt([ { name: 'provideBDD', message: 'Do you want to provide a full BDD scenario (Given/When/Then format) for these steps?', type: 'confirm', default: false } ]); if (provideBDD) { await this.handleBDDFormatInput(); } } // Show final BDD preview if (this.bddMode) { this.showBDDPreview(); } // Create task description from steps const taskDescription = this.testSteps.map(step => step.description).join(', then '); const test = { ...this.testInfo, task: taskDescription, testData: { steps: this.testSteps } }; const fileName = `${this.testInfo.id?.replace(/[^a-zA-Z0-9_-]/g, '_')}.ts`; // Use the directory that was created during setup if (!this.testDirectory) { console.log(chalk_1.default.red('❌ No test directory available. Please restart the recorder.')); return; } const filePath = path_1.default.join(process.cwd(), this.testDirectory, fileName); try { // Save the test file const fileContent = this.generateFileContent(test); await fs_extra_1.default.writeFile(filePath, fileContent, 'utf8'); console.log(chalk_1.default.green('\nβœ… Test saved successfully!')); console.log(chalk_1.default.blue(`πŸ“ File: ${this.testDirectory}/${fileName}`)); console.log(chalk_1.default.blue(`πŸ“ Steps recorded: ${this.testSteps.length}`)); console.log(chalk_1.default.blue(`🎯 Task: ${taskDescription}`)); if (this.bddMode) { console.log(chalk_1.default.green(`🎯 BDD Mode: Enabled - Test ready for BDD execution`)); } console.log(''); } catch (error) { console.log(chalk_1.default.red(`❌ Could not save test file: ${error.message}`)); console.log(chalk_1.default.yellow('Please check file permissions and try again.')); } } generateFileContent(test) { const testId = this.testInfo.id?.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase(); const format = this.testInfo.format || 'enhanced'; if (format === 'legacy') { // Generate legacy format return `// Auto-generated by TestRecorder (Legacy Format) // Generated on: ${new Date().toISOString()} module.exports = { id: '${test.id}', name: '${test.name}', description: '${test.description}', priority: '${test.priority}', tags: ${JSON.stringify(test.tags)}, site: '${test.site}', task: '${this.testSteps.map(step => step.description).join(', then ')}', testData: { steps: ${JSON.stringify(this.testSteps, null, 4)} } }; `; } else { // Generate enhanced test case format with async functions return `// Auto-generated by TestRecorder (Enhanced Async Format) // Generated on: ${new Date().toISOString()} exports.${testId} = { id: '${test.id}', name: '${test.name}', description: '${test.description}', priority: '${test.priority}', tags: ${JSON.stringify(test.tags)}, site: '${test.site}', // Setup test environment setup: async () => { return { baseUrl: '${test.site}', startTime: new Date().toISOString(), environment: process.env.NODE_ENV || 'development', recordedSteps: ${JSON.stringify(this.testSteps.length)} }; }, // Generate test data dynamically data: async () => { return { // Recorded steps for AI execution steps: ${JSON.stringify(this.testSteps, null, 6)}, // Additional test data can be added here testData: ${JSON.stringify(test.testData || {}, null, 6)} }; }, // Main test task using generated data task: async (data, setupData) => { return \` ${this.testSteps.map((step, index) => `STEP ${index + 1}: ${step.description}`).join('\n ')} // AI will execute the above steps automatically // Using the recorded step data: \${data.steps.length} steps \`; } }; `; } } async handleBDDFormatInput() { console.log(chalk_1.default.blue('\nπŸ“‹ BDD Format Input\n')); console.log(chalk_1.default.yellow('Please provide your BDD scenario. You can use:')); console.log(chalk_1.default.cyan(' β€’ Given - for preconditions')); console.log(chalk_1.default.cyan(' β€’ When - for actions')); console.log(chalk_1.default.cyan(' β€’ Then - for verifications')); console.log(chalk_1.default.cyan(' β€’ And/But - for additional steps\n')); const { bddScenario } = await inquirer_1.default.prompt([ { name: 'bddScenario', message: 'Enter your BDD scenario (one step per line, starting with Given/When/Then):', type: 'editor', default: this.generateDefaultBDDScenario() } ]); // Parse BDD scenario and update steps this.parseBDDScenario(bddScenario); console.log(chalk_1.default.green('βœ… BDD scenario applied to recorded steps!\n')); } generateDefaultBDDScenario() { let scenario = 'Feature: ' + (this.testInfo.name || 'Recorded Test') + '\n'; scenario += ' ' + (this.testInfo.description || 'Test recorded using interactive recorder') + '\n\n'; scenario += ' Scenario: ' + (this.testInfo.name || 'Recorded Scenario') + '\n'; this.testSteps.forEach((step, index) => { const bddStep = this.convertToBDD(step, index === 0); scenario += ` ${bddStep.keyword} ${step.description}\n`; }); return scenario; } parseBDDScenario(bddScenario) { const lines = bddScenario.split('\n'); const bddSteps = []; for (const line of lines) { const trimmedLine = line.trim(); if (trimmedLine.startsWith('Given ') || trimmedLine.startsWith('When ') || trimmedLine.startsWith('Then ') || trimmedLine.startsWith('And ') || trimmedLine.startsWith('But ')) { bddSteps.push(trimmedLine); } } // Apply BDD keywords to recorded steps for (let i = 0; i < Math.min(bddSteps.length, this.testSteps.length); i++) { const bddStep = bddSteps[i]; const keyword = bddStep.split(' ')[0]; this.testSteps[i].bddKeyword = keyword; } } } exports.TestRecorder = TestRecorder; //# sourceMappingURL=TestRecorder.js.map