UNPKG

@badeball/cypress-cucumber-preprocessor

Version:

[![Build status](https://github.com/badeball/cypress-cucumber-preprocessor/actions/workflows/build.yml/badge.svg)](https://github.com/badeball/cypress-cucumber-preprocessor/actions/workflows/build.yml) [![Npm package weekly downloads](https://badgen.net/n

229 lines (228 loc) 12 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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __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.diagnose = exports.position = exports.compareStepDefinition = exports.comparePosition = exports.strictCompare = exports.expressionToString = void 0; const promises_1 = __importDefault(require("fs/promises")); const path_1 = __importDefault(require("path")); const util_1 = __importDefault(require("util")); const cypress_configuration_1 = require("@badeball/cypress-configuration"); const cucumber_expressions_1 = require("@cucumber/cucumber-expressions"); const gherkin_1 = require("@cucumber/gherkin"); const messages_1 = require("@cucumber/messages"); const esbuild = __importStar(require("esbuild")); const source_map_1 = __importDefault(require("source-map")); const assertions_1 = require("../helpers/assertions"); const ast_1 = require("../helpers/ast"); const paths_1 = require("../helpers/paths"); const registry_1 = require("../registry"); const step_definitions_1 = require("../step-definitions"); const type_guards_1 = require("../helpers/type-guards"); function expressionToString(expression) { return expression instanceof cucumber_expressions_1.RegularExpression ? String(expression.regexp) : expression.source; } exports.expressionToString = expressionToString; function strictCompare(a, b) { return a === b; } exports.strictCompare = strictCompare; function comparePosition(a, b) { return a.source === b.source && a.column === b.column && a.line === b.line; } exports.comparePosition = comparePosition; function compareStepDefinition(a, b) { return (expressionToString(a.expression) === expressionToString(b.expression) && comparePosition(position(a), position(b))); } exports.compareStepDefinition = compareStepDefinition; function position(definition) { return (0, assertions_1.assertAndReturn)(definition.position, "Expected to find a position"); } exports.position = position; async function diagnose(configuration) { var _a, _b, _c, _d; const result = { definitionsUsage: [], unmatchedSteps: [], ambiguousSteps: [], }; const testFiles = (0, cypress_configuration_1.getTestFiles)(configuration.cypress); for (const testFile of testFiles) { if (!testFile.endsWith(".feature")) { continue; } const stepDefinitionPatterns = (0, step_definitions_1.getStepDefinitionPatterns)(configuration, testFile); const stepDefinitions = await (0, step_definitions_1.getStepDefinitionPaths)(stepDefinitionPatterns); const randomPart = Math.random().toString(16).slice(2, 8); const inputFileName = path_1.default.join(configuration.cypress.projectRoot, ".input-" + randomPart + ".js"); const outputFileName = path_1.default.join(configuration.cypress.projectRoot, ".output-" + randomPart + ".cjs"); let registry; const newId = messages_1.IdGenerator.uuid(); try { await promises_1.default.writeFile(inputFileName, stepDefinitions .map((stepDefinition) => `require(${JSON.stringify(stepDefinition)});`) .join("\n")); const esbuildResult = await esbuild.build({ entryPoints: [inputFileName], bundle: true, sourcemap: "external", outfile: outputFileName, }); if (esbuildResult.errors.length > 0) { for (const error of esbuildResult.errors) { console.error(JSON.stringify(error)); } throw new Error(`Failed to compile step definitions of ${testFile}, with errors shown above...`); } registry = (0, registry_1.withRegistry)(true, () => { globalThis.Cypress = {}; try { require(outputFileName); } catch (e) { console.log(util_1.default.inspect(e)); throw new Error("Failed to evaluate step definitions, with errors shown above..."); } }); registry.finalize(newId); const consumer = await new source_map_1.default.SourceMapConsumer((await promises_1.default.readFile(outputFileName + ".map")).toString()); for (const stepDefinition of registry.stepDefinitions) { const originalPosition = position(stepDefinition); const newPosition = consumer.originalPositionFor(originalPosition); stepDefinition.position = { line: (0, assertions_1.assertAndReturn)(newPosition.line, "Expected to find a line number"), column: (0, assertions_1.assertAndReturn)(newPosition.column, "Expected to find a column number"), source: (0, assertions_1.assertAndReturn)(newPosition.source, "Expected to find a source"), }; } consumer.destroy(); } finally { /** * Delete without regard for errors. */ await promises_1.default.rm(inputFileName).catch(() => true); await promises_1.default.rm(outputFileName).catch(() => true); await promises_1.default.rm(outputFileName + ".map").catch(() => true); } const options = { includeSource: false, includeGherkinDocument: true, includePickles: true, newId, }; const relativeUri = (0, paths_1.ensureIsRelative)(configuration.cypress.projectRoot, testFile); const envelopes = (0, gherkin_1.generateMessages)((await promises_1.default.readFile(testFile)).toString(), relativeUri, messages_1.SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN, options); const gherkinDocument = (0, assertions_1.assertAndReturn)(envelopes .map((envelope) => envelope.gherkinDocument) .find((document) => document), "Expected to find a gherkin document"); for (const stepDefinition of registry.stepDefinitions) { const usage = result.definitionsUsage.find((usage) => compareStepDefinition(usage.definition, stepDefinition)); if (!usage) { result.definitionsUsage.push({ definition: stepDefinition, steps: [], }); } } const astIdMap = (0, ast_1.createAstIdMap)(gherkinDocument); const pickles = envelopes .map((envelope) => envelope.pickle) .filter(type_guards_1.notNull); for (const pickle of pickles) { if (pickle.steps) { for (const step of pickle.steps) { const text = (0, assertions_1.assertAndReturn)(step.text, "Expected pickle step to have a text"); const matchingStepDefinitions = registry.getMatchingStepDefinitions(text); const astNodeId = (0, assertions_1.assertAndReturn)((_a = step.astNodeIds) === null || _a === void 0 ? void 0 : _a[0], "Expected to find at least one astNodeId"); const astNode = (0, assertions_1.assertAndReturn)(astIdMap.get(astNodeId), `Expected to find scenario step associated with id = ${astNodeId}`); (0, assertions_1.assert)("location" in astNode, "Expected ast node to have a location"); if (matchingStepDefinitions.length === 0) { let argument = null; if ((_b = step.argument) === null || _b === void 0 ? void 0 : _b.dataTable) { argument = "dataTable"; } else if ((_c = step.argument) === null || _c === void 0 ? void 0 : _c.docString) { argument = "docString"; } result.unmatchedSteps.push({ step: { source: testFile, line: astNode.location.line, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion text: step.text, }, type: (0, assertions_1.assertAndReturn)(step.type, "Expected pickleStep to have a type"), argument, parameterTypeRegistry: registry.parameterTypeRegistry, stepDefinitionHints: { stepDefinitions: [ configuration.preprocessor.stepDefinitions, ].flat(), stepDefinitionPatterns, stepDefinitionPaths: stepDefinitions, }, }); } else if (matchingStepDefinitions.length === 1) { const usage = (0, assertions_1.assertAndReturn)(result.definitionsUsage.find((usage) => compareStepDefinition(usage.definition, matchingStepDefinitions[0])), "Expected to find usage"); usage.steps.push({ source: testFile, line: (_d = astNode.location) === null || _d === void 0 ? void 0 : _d.line, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion text: step.text, }); } else { for (const matchingStepDefinition of matchingStepDefinitions) { const usage = (0, assertions_1.assertAndReturn)(result.definitionsUsage.find((usage) => compareStepDefinition(usage.definition, matchingStepDefinition)), "Expected to find usage"); usage.steps.push({ source: testFile, line: astNode.location.line, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion text: step.text, }); } result.ambiguousSteps.push({ step: { source: testFile, line: astNode.location.line, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion text: step.text, }, definitions: matchingStepDefinitions, }); } } } } } return result; } exports.diagnose = diagnose;