UNPKG

@serenity-js/cucumber

Version:

Serenity/JS test runner adapter for seamless integration with any version of Cucumber.js, facilitating BDD-style test automation and leveraging Serenity/JS reporting capabilities

215 lines 12.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CucumberMessagesParser = void 0; const messages_1 = require("@cucumber/messages"); const core_1 = require("@serenity-js/core"); const events_1 = require("@serenity-js/core/lib/events"); const io_1 = require("@serenity-js/core/lib/io"); const io_2 = require("@serenity-js/core/lib/io"); const model_1 = require("@serenity-js/core/lib/model"); const TestStepFormatter_1 = require("./TestStepFormatter"); /** * @package */ class CucumberMessagesParser { serenity; formatterHelpers; shouldReportStep; testStepFormatter = new TestStepFormatter_1.TestStepFormatter(); currentScenario; currentStepActivityId; cwd; eventDataCollector; snippetBuilder; supportCodeLibrary; requirementsHierarchy; constructor(serenity, formatterHelpers, // eslint-disable-line @typescript-eslint/explicit-module-boundary-types formatterOptionsAndDependencies, shouldReportStep) { this.serenity = serenity; this.formatterHelpers = formatterHelpers; this.shouldReportStep = shouldReportStep; this.cwd = formatterOptionsAndDependencies.cwd; this.eventDataCollector = formatterOptionsAndDependencies.eventDataCollector; this.snippetBuilder = formatterOptionsAndDependencies.snippetBuilder; this.supportCodeLibrary = formatterOptionsAndDependencies.supportCodeLibrary; this.requirementsHierarchy = new io_2.RequirementsHierarchy(new io_1.FileSystem(io_1.Path.from(formatterOptionsAndDependencies.cwd)), formatterOptionsAndDependencies.parsedArgvOptions?.specDirectory && io_1.Path.from(formatterOptionsAndDependencies.parsedArgvOptions?.specDirectory)); } parseTestCaseStarted(message) { const testCaseAttempt = this.eventDataCollector.getTestCaseAttempt(message.id), currentSceneId = this.serenity.assignNewSceneId(); this.currentScenario = this.scenarioDetailsFor(testCaseAttempt.gherkinDocument, testCaseAttempt.pickle, this.formatterHelpers.PickleParser.getPickleLocation(testCaseAttempt)); return [ ...this.extract(this.outlineFrom(testCaseAttempt), (outline) => [ new events_1.SceneSequenceDetected(currentSceneId, outline.details, this.serenity.currentTime()), new events_1.SceneTemplateDetected(currentSceneId, outline.template, this.serenity.currentTime()), new events_1.SceneParametersDetected(currentSceneId, this.currentScenario, outline.parameters, this.serenity.currentTime()), ]), ...this.extract(this.scenarioFrom(testCaseAttempt), ({ featureDescription, rule, scenarioDescription, tags, testRunnerName }) => [ new events_1.SceneStarts(currentSceneId, this.currentScenario, this.serenity.currentTime()), featureDescription && new events_1.FeatureNarrativeDetected(currentSceneId, featureDescription, this.serenity.currentTime()), new events_1.TestRunnerDetected(currentSceneId, testRunnerName, this.serenity.currentTime()), !!scenarioDescription && new events_1.SceneDescriptionDetected(currentSceneId, scenarioDescription, this.serenity.currentTime()), !!rule && new events_1.BusinessRuleDetected(currentSceneId, this.currentScenario, rule, this.serenity.currentTime()), ...tags.map(tag => new events_1.SceneTagged(currentSceneId, tag, this.serenity.currentTime())), ]), ]; } parseTestStepStarted(message) { return this.extract(this.stepFrom(message), (step) => { if (this.shouldReportStep(step)) { const activityDetails = this.activityDetailsFor(step); this.currentStepActivityId = this.serenity.assignNewActivityId(activityDetails); return new events_1.TaskStarts(this.serenity.currentSceneId(), this.currentStepActivityId, this.activityDetailsFor(step), this.serenity.currentTime()); } }); } parseTestStepFinished(message) { return this.extract(this.stepFrom(message), (step) => { if (this.shouldReportStep(step)) { return new events_1.TaskFinished(this.serenity.currentSceneId(), this.currentStepActivityId, this.activityDetailsFor(step), this.outcomeFrom(step.result, step), this.serenity.currentTime()); } }); } parseTestCaseFinished(message) { const testCaseAttempt = this.eventDataCollector.getTestCaseAttempt(message.testCaseStartedId), currentSceneId = this.serenity.currentSceneId(); return this.extract(this.scenarioOutcomeFrom(testCaseAttempt), ({ outcome, willBeRetried, tags }) => [ willBeRetried ? new events_1.RetryableSceneDetected(currentSceneId, this.serenity.currentTime()) : undefined, ...tags.map(tag => new events_1.SceneTagged(currentSceneId, tag, this.serenity.currentTime())), new events_1.SceneFinished(currentSceneId, this.currentScenario, outcome, this.serenity.currentTime()), ]); } // --- extract(maybeValue, fn) { return (maybeValue === undefined) ? [] : [].concat(fn(maybeValue)).filter(item => !!item); } scenarioDetailsFor(gherkinDocument, pickle, location) { return new model_1.ScenarioDetails(new model_1.Name(pickle.name), new model_1.Category(gherkinDocument.feature.name), new io_1.FileSystemLocation(this.absolutePathFrom(gherkinDocument.uri), location.line, location.column)); } outlineFrom(testCaseAttempt) { const { gherkinDocument, pickle } = testCaseAttempt, gherkinScenarioMap = this.formatterHelpers.GherkinDocumentParser.getGherkinScenarioMap(gherkinDocument); if (gherkinScenarioMap[pickle.astNodeIds[0]].examples.length === 0) { return; // this is not an outline, skip it } const outline = gherkinScenarioMap[pickle.astNodeIds[0]]; const details = this.scenarioDetailsFor(gherkinDocument, outline, outline.location); const template = new model_1.Description(outline.steps.map(step => this.testStepFormatter.format(step.keyword, step.text, step)).join('\n')); const examples = flatten(outline.examples.map(exampleSet => exampleSet.tableBody.map(row => ({ header: exampleSet.tableHeader, row, name: exampleSet.name, description: exampleSet.description, })))).map((example) => ({ rowId: example.row.id, name: example.name.trim(), description: example.description.trim(), values: example.header.cells .map(cell => cell.value) .reduce((values, header, i) => { values[header] = example.row.cells[i].value; return values; }, {}), })); const parameters = examples.find(example => example.rowId === pickle.astNodeIds.at(-1)); return { details, template, parameters: new model_1.ScenarioParameters(new model_1.Name(parameters.name), new model_1.Description(parameters.description), parameters.values), }; } scenarioFrom({ gherkinDocument, pickle }) { const gherkinScenarioMap = this.formatterHelpers.GherkinDocumentParser.getGherkinScenarioMap(gherkinDocument), gherkinExampleRuleMap = this.formatterHelpers.GherkinDocumentParser.getGherkinExampleRuleMap(gherkinDocument), scenarioDescription = this.formatterHelpers.PickleParser.getScenarioDescription({ gherkinScenarioMap, pickle }), scenarioTags = flatten(pickle.tags.map(tag => model_1.Tags.from(tag.name))), rule = gherkinExampleRuleMap[pickle.astNodeIds[0]]; return { featureDescription: gherkinDocument.feature.description && new model_1.Description(gherkinDocument.feature.description), scenarioDescription: scenarioDescription && new model_1.Description(scenarioDescription), rule: rule && new model_1.BusinessRule(new model_1.Name(rule.name), new model_1.Description(rule.description.trim())), testRunnerName: new model_1.Name('JS'), tags: this.requirementsHierarchy.requirementTagsFor(io_1.Path.from(this.cwd).resolve(io_1.Path.from(gherkinDocument.uri)), gherkinDocument.feature.name).concat(scenarioTags), }; } stepFrom(message) { const { testCaseStartedId, testStepId } = message; const testCaseAttempt = this.eventDataCollector.getTestCaseAttempt(testCaseStartedId); const index = testCaseAttempt.testCase.testSteps.findIndex(step => step.id === testStepId); return this.parseTestCaseAttempt(testCaseAttempt).testSteps[index]; } parseTestCaseAttempt(testCaseAttempt) { // workaround for a bug in Cucumber 7, that's fixed in Cucumber 8 by https://github.com/cucumber/cucumber-js/pull/1531 testCaseAttempt.testCase.testSteps.forEach(step => { if (!testCaseAttempt.stepResults[step.id]) { testCaseAttempt.stepResults[step.id] = { duration: { seconds: 0, nanos: 0 }, status: messages_1.TestStepResultStatus.UNKNOWN, willBeRetried: false }; } }); // --- return this.formatterHelpers.parseTestCaseAttempt({ cwd: this.cwd, testCaseAttempt, snippetBuilder: this.snippetBuilder, supportCodeLibrary: this.supportCodeLibrary, }); } activityDetailsFor(parsedTestStep) { const location = parsedTestStep.sourceLocation || parsedTestStep.actionLocation; return new model_1.ActivityDetails(new model_1.Name(this.testStepFormatter.format(parsedTestStep.keyword, parsedTestStep.text || parsedTestStep.name, parsedTestStep.argument)), new io_1.FileSystemLocation(this.absolutePathFrom(location.uri), location.line)); } outcomeFrom(worstResult, ...steps) { const Status = messages_1.TestStepResultStatus; // todo: how does it treat failed but retryable scenarios? switch (worstResult.status) { case Status.SKIPPED: return new model_1.ExecutionSkipped(); case Status.UNDEFINED: { const snippets = steps .filter(step => step.result.status === Status.UNDEFINED) .map(step => step.snippet); const message = snippets.length > 0 ? ['Step implementation missing:', ...snippets].join('\n\n') : 'Step implementation missing'; return new model_1.ImplementationPending(new core_1.ImplementationPendingError(message)); } case Status.PENDING: return new model_1.ImplementationPending(new core_1.ImplementationPendingError('Step implementation pending')); case Status.AMBIGUOUS: case Status.FAILED: { const error = core_1.ErrorSerialiser.deserialiseFromStackTrace(worstResult.message); if (error instanceof core_1.AssertionError) { return new model_1.ExecutionFailedWithAssertionError(error); } if (error instanceof core_1.TestCompromisedError) { return new model_1.ExecutionCompromised(error); } return new model_1.ExecutionFailedWithError(error); } case Status.UNKNOWN: // ignore case Status.PASSED: // eslint-disable-line no-fallthrough return new model_1.ExecutionSuccessful(); } } scenarioOutcomeFrom(testCaseAttempt) { const parsed = this.formatterHelpers.parseTestCaseAttempt({ cwd: this.cwd, snippetBuilder: this.snippetBuilder, supportCodeLibrary: this.supportCodeLibrary, testCaseAttempt }); const worstStepResult = parsed.testCase.worstTestStepResult; const willBeRetried = worstStepResult.willBeRetried || // Cucumber 7 testCaseAttempt.willBeRetried; // Cucumber 8 const outcome = this.outcomeFrom(worstStepResult, ...parsed.testSteps); const tags = []; if (testCaseAttempt.attempt > 0 || willBeRetried) { tags.push(new model_1.ArbitraryTag('retried')); } if (testCaseAttempt.attempt > 0) { tags.push(new model_1.ExecutionRetriedTag(testCaseAttempt.attempt)); } return { outcome, willBeRetried, tags }; } absolutePathFrom(relativePath) { return io_1.Path.from(this.cwd).resolve(io_1.Path.from(relativePath)); } } exports.CucumberMessagesParser = CucumberMessagesParser; function flatten(listOfLists) { return listOfLists.reduce((acc, current) => acc.concat(current), []); } //# sourceMappingURL=CucumberMessagesParser.js.map