UNPKG

@labnex/cli

Version:

CLI for Labnex, an AI-Powered Testing Automation Platform

182 lines 8.64 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.lintCommand = void 0; const commander_1 = require("commander"); const chalk_1 = __importDefault(require("chalk")); const ora_1 = __importDefault(require("ora")); const cli_table3_1 = __importDefault(require("cli-table3")); const inquirer_1 = __importDefault(require("inquirer")); const client_1 = require("../api/client"); const testStepParser_1 = require("../testStepParser"); // Simple heuristic validation rules with fix suggestions function lintTestCase(testCase) { const problems = []; if (!Array.isArray(testCase.steps) || testCase.steps.length === 0) { problems.push({ error: 'No steps defined', suggestion: 'Add at least one actionable step like "Navigate to https://example.com"' }); return problems; } testCase.steps.forEach((step, idx) => { let parsed; try { parsed = testStepParser_1.TestStepParser.parseStep(step); } catch (err) { problems.push({ error: `[Step ${idx + 1}] Parse error: ${err.message || err}`, suggestion: 'Rewrite the step in the standard imperative form, e.g. "Click (css: #login-button)"' }); return; } if (!parsed || !parsed.action) { problems.push({ error: `[Step ${idx + 1}] Unrecognised action`, suggestion: 'Supported actions: navigate, click, type, wait, assert, select, hover, scroll, upload, dragAndDrop' }); return; } const needsTarget = [ 'click', 'type', 'select', 'hover', 'scroll', 'upload', 'dragAndDrop', ]; if (needsTarget.includes(parsed.action) && !parsed.target) { problems.push({ error: `[Step ${idx + 1}] Action "${parsed.action}" missing target selector`, suggestion: 'Add a selector hint, e.g. "Click (css: #submit)" or "Click (text: \"Submit\")"' }); } if (parsed.action === 'type' && !parsed.value) { problems.push({ error: `[Step ${idx + 1}] Type action missing value`, suggestion: 'Specify the value to type, e.g. "Type \"john@example.com\" into (css: #email)"' }); } if (parsed.action === 'assert') { if (!parsed.assertion && !parsed.expectedText) { problems.push({ error: `[Step ${idx + 1}] Assert step lacks expected text/assertion details`, suggestion: 'Provide an assertion object, e.g. "assert (type=url, expected=\"/dashboard\", condition=contains)"' }); } } }); if (!testCase.expectedResult || !testCase.expectedResult.trim()) { problems.push({ error: 'expectedResult field is empty', suggestion: 'Fill in a human-readable expected result for the whole case' }); } return problems; } exports.lintCommand = new commander_1.Command('lint-tests') .description('Static analysis of test cases for a project.') .argument('<projectCode>', 'Project code (e.g., DEMO)') .option('--json', 'Output JSON instead of table') .option('--fix', 'Interactively fix issues') .action(async (projectCode, options) => { const spinner = (0, ora_1.default)(`Linting tests for project ${chalk_1.default.cyan(projectCode.toUpperCase())}...`).start(); try { // Resolve project ID const projectsRes = await client_1.apiClient.getProjects(); if (!projectsRes.success) { spinner.fail(chalk_1.default.red(`Failed to fetch projects: ${projectsRes.error}`)); process.exit(1); } const project = projectsRes.data.find((p) => p.projectCode === projectCode.toUpperCase()); if (!project) { spinner.fail(chalk_1.default.red(`Project ${projectCode} not found`)); process.exit(1); } const tcRes = await client_1.apiClient.getTestCases(project._id); if (!tcRes.success) { spinner.fail(chalk_1.default.red(`Failed to fetch test cases: ${tcRes.error}`)); process.exit(1); } const results = []; let totalErrors = 0; for (const tc of tcRes.data) { let modified = false; const probs = lintTestCase(tc); totalErrors += probs.length; // attempt fixes if --fix if (options.fix && probs.length > 0) { for (const p of probs) { if (/missing target selector/.test(p.error)) { spinner.stop(); const ans = await inquirer_1.default.prompt([{ type: 'input', name: 'sel', message: `Provide selector for step issue: ${p.error}` }]); spinner.start(); const stepIdx = parseInt(p.error.match(/Step (\d+)/)?.[1] || '0', 10) - 1; const stepStr = tc.steps[stepIdx]; const newStep = stepStr.replace(/\)$/, `) || (css: ${ans.sel})`); tc.steps[stepIdx] = newStep; modified = true; } else if (/Type action missing value/.test(p.error)) { spinner.stop(); const ans = await inquirer_1.default.prompt([{ type: 'input', name: 'val', message: `Provide value for step ${p.error}` }]); spinner.start(); const stepIdx = parseInt(p.error.match(/Step (\d+)/)?.[1] || '0', 10) - 1; const stepStr = tc.steps[stepIdx]; const newStep = stepStr.replace(/Type\s+"?"?/, `Type "${ans.val}" `); tc.steps[stepIdx] = newStep; modified = true; } else if (/expectedResult field is empty/.test(p.error)) { spinner.stop(); const ans = await inquirer_1.default.prompt([{ type: 'input', name: 'exp', message: 'Enter expectedResult for test case' }]); spinner.start(); tc.expectedResult = ans.exp; modified = true; } } } if (modified && options.fix) { const updateRes = await client_1.apiClient.updateTestCase(project._id, tc._id, { steps: tc.steps, expectedResult: tc.expectedResult }); if (updateRes.success) { console.log(chalk_1.default.green(`✓ Updated test case ${tc.title}`)); } else { console.log(chalk_1.default.red(`Failed to update ${tc.title}: ${updateRes.error}`)); } } results.push({ id: tc._id, title: tc.title, errCount: probs.length, probs }); } spinner.stop(); if (options.json) { console.log(JSON.stringify(results, null, 2)); } else { const tbl = new cli_table3_1.default({ head: ['ID', 'Title', 'Errors'] }); results.forEach((r) => { tbl.push([r.id, r.title.substring(0, 40), r.errCount === 0 ? chalk_1.default.green('0') : chalk_1.default.red(String(r.errCount))]); }); console.log(tbl.toString()); if (totalErrors > 0) { console.log(chalk_1.default.red(`❌ ${totalErrors} issues found.`)); // Detailed list results.filter(r => r.errCount > 0).forEach(r => { console.log(`\n${chalk_1.default.bold(r.title)} (${r.id})`); r.probs.forEach((p) => { console.log(` • ${chalk_1.default.red(p.error)}`); if (p.suggestion) { console.log(` ${chalk_1.default.gray('Suggestion:')} ${p.suggestion}`); } }); }); } else { console.log(chalk_1.default.green('✅ All test cases passed lint checks.')); } } process.exit(totalErrors > 0 ? 1 : 0); } catch (err) { spinner.fail(chalk_1.default.red(err.message || 'Unknown error')); process.exit(1); } }); //# sourceMappingURL=lint.js.map