UNPKG

k6-cucumber-steps

Version:

Generate k6 test scripts from Cucumber feature files

236 lines 11.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FeatureParser = void 0; //src/generators/feature.parser.ts const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const Gherkin = __importStar(require("@cucumber/gherkin")); const Messages = __importStar(require("@cucumber/messages")); class FeatureParser { async loadAndParseFeatures(featuresPath) { const featureFiles = []; // Handle multiple paths (comma-separated) const paths = featuresPath.split(',').map(p => p.trim()).filter(p => p); for (const singlePath of paths) { const resolvedPath = path_1.default.resolve(process.cwd(), singlePath); if (!fs_1.default.existsSync(resolvedPath)) { console.warn(`⚠️ Warning: The path "${singlePath}" does not exist at ${resolvedPath}`); continue; } if (fs_1.default.statSync(resolvedPath).isDirectory()) { // Recursively find all .feature files in directory const dirFeatureFiles = this.findFeatureFilesRecursive(resolvedPath); for (const file of dirFeatureFiles) { const content = fs_1.default.readFileSync(file, "utf8"); featureFiles.push(this.parseFeature(content, file)); } } else if (singlePath.endsWith(".feature")) { // Single feature file const content = fs_1.default.readFileSync(resolvedPath, "utf8"); featureFiles.push(this.parseFeature(content, resolvedPath)); } else { console.warn(`⚠️ Warning: "${singlePath}" is not a directory or .feature file`); } } if (featureFiles.length === 0) { throw new Error(`No feature files found in: ${featuresPath}`); } return featureFiles; } /** * Recursively find all .feature files in a directory */ findFeatureFilesRecursive(dirPath) { const featureFiles = []; const entries = fs_1.default.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path_1.default.join(dirPath, entry.name); if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') { // Recursively search subdirectories (exclude hidden and node_modules) featureFiles.push(...this.findFeatureFilesRecursive(fullPath)); } else if (entry.isFile() && entry.name.endsWith(".feature")) { featureFiles.push(fullPath); } } return featureFiles; } parseFeature(content, filePath) { try { const uuidFn = Messages.IdGenerator.incrementing(); const builder = new Gherkin.AstBuilder(uuidFn); const matcher = new Gherkin.GherkinClassicTokenMatcher(); const parser = new Gherkin.Parser(builder, matcher); const gherkinDocument = parser.parse(content); if (!gherkinDocument || !gherkinDocument.feature) { throw new Error("No feature found in file."); } // 1. Capture Feature-level tags for inheritance const featureTags = (gherkinDocument.feature.tags || []).map((tag) => tag.name.replace("@", "")); // 2. Extract Background Steps (to be prepended to all scenarios) const backgroundSteps = []; const backgroundChild = gherkinDocument.feature.children.find((child) => child.background); if (backgroundChild && backgroundChild.background) { backgroundChild.background.steps.forEach((step) => { backgroundSteps.push(this.mapGherkinStepToInternalStep(step)); }); } const scenarios = []; // 3. Process Children (Scenarios and Scenario Outlines) if (gherkinDocument.feature.children) { for (const child of gherkinDocument.feature.children) { if (child.scenario) { const scenario = child.scenario; const combinedTags = [ ...featureTags, ...(scenario.tags || []).map((tag) => tag.name.replace("@", "")), ]; // 4. Handle Scenario Outlines (Examples) if (scenario.examples && scenario.examples.length > 0) { for (const exampleGroup of scenario.examples) { const tableHeader = exampleGroup.tableHeader?.cells.map((c) => c.value) || []; const tableBody = exampleGroup.tableBody || []; tableBody.forEach((row, rowIndex) => { const rowData = {}; row.cells.forEach((cell, cellIndex) => { rowData[tableHeader[cellIndex]] = cell.value; }); // Process steps: inject background + replace placeholders + map arguments const scenarioSteps = scenario.steps.map((step) => { let newText = step.text; // Replace <placeholders> Object.keys(rowData).forEach((key) => { newText = newText.replace(new RegExp(`<${key}>`, "g"), rowData[key]); }); return this.mapGherkinStepToInternalStep(step, newText); }); scenarios.push({ name: `${scenario.name} (Row ${rowIndex + 1})`, steps: [...backgroundSteps, ...scenarioSteps], tags: combinedTags, description: scenario.description || "", }); }); } } else { // 5. Handle Standard Scenarios const scenarioSteps = scenario.steps.map((step) => this.mapGherkinStepToInternalStep(step)); scenarios.push({ name: scenario.name, steps: [...backgroundSteps, ...scenarioSteps], tags: combinedTags, description: scenario.description || "", }); } } } } return { path: filePath, content, scenarios }; } catch (error) { throw new Error(`Error parsing feature file ${filePath}: ${error.message}`); } } /** * Helper to map Gherkin AST steps to our internal Scenario Step format, * specifically handling DataTables and DocStrings. */ mapGherkinStepToInternalStep(gherkinStep, overrideText) { let argument = null; // Handle DataTables: Convert to Array of Objects if (gherkinStep.dataTable) { const rows = gherkinStep.dataTable.rows; if (rows.length > 0) { const header = rows[0].cells.map((c) => c.value); argument = rows.slice(1).map((row) => { const obj = {}; row.cells.forEach((cell, i) => { obj[header[i]] = cell.value; }); return obj; }); } } // Handle DocStrings else if (gherkinStep.docString) { argument = gherkinStep.docString.content; } return { keyword: gherkinStep.keyword.trim(), text: overrideText || gherkinStep.text, argument: argument, }; } loadScenarioMetadata(scenarios) { return scenarios.map((scenario) => { const metadata = { scenarioName: scenario.name, tags: scenario.tags, }; for (const tag of scenario.tags) { if (tag.startsWith("vus:")) { metadata.vus = parseInt(tag.split(":")[1]); } else if (tag.startsWith("duration:")) { metadata.duration = tag.split(":")[1]; } else if (tag.startsWith("stages:")) { metadata.stages = tag.split(":")[1]; } else if (tag.startsWith("threshold:")) { const split = tag.split(":")[1].split("="); if (split.length === 2) { metadata.thresholds = metadata.thresholds || {}; metadata.thresholds[split[0]] = split[1]; } } else if (tag.startsWith("group:")) { metadata.group = tag.split(":")[1]; } } return metadata; }); } } exports.FeatureParser = FeatureParser; //# sourceMappingURL=feature.parser.js.map