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
JavaScript
;
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