@cucumber/cucumber
Version:
The official JavaScript implementation of Cucumber.
327 lines • 13.9 kB
JavaScript
"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 });
const messages = __importStar(require("@cucumber/messages"));
const messages_1 = require("@cucumber/messages");
const value_checker_1 = require("../value_checker");
const stopwatch_1 = require("./stopwatch");
const step_runner_1 = __importDefault(require("./step_runner"));
const attachment_manager_1 = __importDefault(require("./attachment_manager"));
const helpers_1 = require("./helpers");
const make_suggestion_1 = require("./make_suggestion");
class TestCaseRunner {
workerId;
attachmentManager;
currentTestCaseStartedId;
currentTestStepId;
eventBroadcaster;
gherkinDocument;
newId;
pickle;
testCase;
maxAttempts;
skip;
filterStackTraces;
supportCodeLibrary;
snippetBuilder;
testStepResults;
world;
worldParameters;
constructor({ workerId, eventBroadcaster, gherkinDocument, newId, pickle, testCase, retries = 0, skip, filterStackTraces, supportCodeLibrary, worldParameters, snippetBuilder, }) {
this.workerId = workerId;
this.attachmentManager = new attachment_manager_1.default(({ data, media, fileName }) => {
if ((0, value_checker_1.doesNotHaveValue)(this.currentTestStepId)) {
throw new Error('Cannot attach when a step/hook is not running. Ensure your step/hook waits for the attach to finish.');
}
const attachment = {
attachment: {
body: data,
contentEncoding: media.encoding,
mediaType: media.contentType,
fileName,
testCaseStartedId: this.currentTestCaseStartedId,
testStepId: this.currentTestStepId,
timestamp: (0, stopwatch_1.timestamp)(),
},
};
this.eventBroadcaster.emit('envelope', attachment);
});
this.eventBroadcaster = eventBroadcaster;
this.gherkinDocument = gherkinDocument;
this.maxAttempts = 1 + (skip ? 0 : retries);
this.newId = newId;
this.pickle = pickle;
this.testCase = testCase;
this.skip = skip;
this.filterStackTraces = filterStackTraces;
this.supportCodeLibrary = supportCodeLibrary;
this.worldParameters = worldParameters;
this.snippetBuilder = snippetBuilder;
this.resetTestProgressData();
}
resetTestProgressData() {
this.world = new this.supportCodeLibrary.World({
attach: this.attachmentManager.create.bind(this.attachmentManager),
log: this.attachmentManager.log.bind(this.attachmentManager),
link: this.attachmentManager.link.bind(this.attachmentManager),
parameters: structuredClone(this.worldParameters),
});
this.testStepResults = [];
}
getBeforeStepHookDefinitions() {
return this.supportCodeLibrary.beforeTestStepHookDefinitions.filter((hookDefinition) => hookDefinition.appliesToTestCase(this.pickle));
}
getAfterStepHookDefinitions() {
return this.supportCodeLibrary.afterTestStepHookDefinitions
.slice(0)
.reverse()
.filter((hookDefinition) => hookDefinition.appliesToTestCase(this.pickle));
}
getWorstStepResult() {
if (this.testStepResults.length === 0) {
return {
status: this.skip
? messages.TestStepResultStatus.SKIPPED
: messages.TestStepResultStatus.PASSED,
duration: messages.TimeConversion.millisecondsToDuration(0),
};
}
return (0, messages_1.getWorstTestStepResult)(this.testStepResults);
}
async invokeStep(step, stepDefinition, hookParameter) {
return await step_runner_1.default.run({
defaultTimeout: this.supportCodeLibrary.defaultTimeout,
filterStackTraces: this.filterStackTraces,
hookParameter,
step,
stepDefinition,
world: this.world,
});
}
isSkippingSteps() {
return (this.getWorstStepResult().status !== messages.TestStepResultStatus.PASSED);
}
shouldSkipHook(isBeforeHook) {
return this.skip || (this.isSkippingSteps() && isBeforeHook);
}
async aroundTestStep(testStepId, runStepFn) {
const testStepStarted = {
testStepStarted: {
testCaseStartedId: this.currentTestCaseStartedId,
testStepId,
timestamp: (0, stopwatch_1.timestamp)(),
},
};
this.eventBroadcaster.emit('envelope', testStepStarted);
this.currentTestStepId = testStepId;
const testStepResult = await runStepFn();
this.currentTestStepId = null;
this.testStepResults.push(testStepResult);
const testStepFinished = {
testStepFinished: {
testCaseStartedId: this.currentTestCaseStartedId,
testStepId,
testStepResult,
timestamp: (0, stopwatch_1.timestamp)(),
},
};
this.eventBroadcaster.emit('envelope', testStepFinished);
}
async run() {
for (let attempt = 0; attempt < this.maxAttempts; attempt++) {
const moreAttemptsRemaining = attempt + 1 < this.maxAttempts;
const willBeRetried = await this.runAttempt(attempt, moreAttemptsRemaining);
if (!willBeRetried) {
break;
}
this.resetTestProgressData();
}
return this.getWorstStepResult().status;
}
async runAttempt(attempt, moreAttemptsRemaining) {
this.currentTestCaseStartedId = this.newId();
const testCaseStarted = {
testCaseStarted: {
attempt,
testCaseId: this.testCase.id,
id: this.currentTestCaseStartedId,
timestamp: (0, stopwatch_1.timestamp)(),
},
};
if (this.workerId) {
testCaseStarted.testCaseStarted.workerId = this.workerId;
}
this.eventBroadcaster.emit('envelope', testCaseStarted);
// used to determine whether a hook is a Before or After
let didWeRunStepsYet = false;
let error = false;
for (const testStep of this.testCase.testSteps) {
await this.aroundTestStep(testStep.id, async () => {
if ((0, value_checker_1.doesHaveValue)(testStep.hookId)) {
const hookParameter = {
gherkinDocument: this.gherkinDocument,
pickle: this.pickle,
testCaseStartedId: this.currentTestCaseStartedId,
};
if (didWeRunStepsYet) {
hookParameter.result = this.getWorstStepResult();
hookParameter.error = error;
hookParameter.willBeRetried =
this.getWorstStepResult().status ===
messages.TestStepResultStatus.FAILED && moreAttemptsRemaining;
}
return await this.runHook(findHookDefinition(testStep.hookId, this.supportCodeLibrary), hookParameter, !didWeRunStepsYet);
}
else {
const pickleStep = this.pickle.steps.find((pickleStep) => pickleStep.id === testStep.pickleStepId);
const testStepResult = await this.runStep(pickleStep, testStep);
didWeRunStepsYet = true;
error = testStepResult.error;
return testStepResult.result;
}
});
}
const willBeRetried = this.getWorstStepResult().status ===
messages.TestStepResultStatus.FAILED && moreAttemptsRemaining;
const testCaseFinished = {
testCaseFinished: {
testCaseStartedId: this.currentTestCaseStartedId,
timestamp: (0, stopwatch_1.timestamp)(),
willBeRetried,
},
};
this.eventBroadcaster.emit('envelope', testCaseFinished);
return willBeRetried;
}
async runHook(hookDefinition, hookParameter, isBeforeHook) {
if (this.shouldSkipHook(isBeforeHook)) {
return {
status: messages.TestStepResultStatus.SKIPPED,
duration: messages.TimeConversion.millisecondsToDuration(0),
};
}
const { result } = await this.invokeStep(null, hookDefinition, hookParameter);
return result;
}
async runStepHooks(stepHooks, pickleStep, stepResult) {
const stepHooksResult = [];
const hookParameter = {
gherkinDocument: this.gherkinDocument,
pickle: this.pickle,
pickleStep,
testCaseStartedId: this.currentTestCaseStartedId,
testStepId: this.currentTestStepId,
result: stepResult?.result,
error: stepResult?.error,
};
for (const stepHookDefinition of stepHooks) {
const { result } = await this.invokeStep(null, stepHookDefinition, hookParameter);
stepHooksResult.push(result);
}
return stepHooksResult;
}
async runStep(pickleStep, testStep) {
const stepDefinitions = testStep.stepDefinitionIds.map((stepDefinitionId) => {
return findStepDefinition(stepDefinitionId, this.supportCodeLibrary);
});
if (stepDefinitions.length === 0) {
this.eventBroadcaster.emit('envelope', {
suggestion: (0, make_suggestion_1.makeSuggestion)({
newId: this.newId,
snippetBuilder: this.snippetBuilder,
pickleStep,
}),
});
return {
result: {
status: messages.TestStepResultStatus.UNDEFINED,
duration: messages.TimeConversion.millisecondsToDuration(0),
},
};
}
else if (stepDefinitions.length > 1) {
return {
result: {
message: (0, helpers_1.getAmbiguousStepException)(stepDefinitions),
status: messages.TestStepResultStatus.AMBIGUOUS,
duration: messages.TimeConversion.millisecondsToDuration(0),
},
};
}
else if (this.isSkippingSteps()) {
return {
result: {
status: messages.TestStepResultStatus.SKIPPED,
duration: messages.TimeConversion.millisecondsToDuration(0),
},
};
}
let stepResult;
let error;
let stepResults = await this.runStepHooks(this.getBeforeStepHookDefinitions(), pickleStep);
if ((0, messages_1.getWorstTestStepResult)(stepResults).status !==
messages.TestStepResultStatus.FAILED) {
stepResult = await this.invokeStep(pickleStep, stepDefinitions[0]);
stepResults.push(stepResult.result);
error = stepResult.error;
}
const afterStepHookResults = await this.runStepHooks(this.getAfterStepHookDefinitions(), pickleStep, stepResult);
stepResults = stepResults.concat(afterStepHookResults);
const finalStepResult = (0, messages_1.getWorstTestStepResult)(stepResults);
let finalDuration = messages.TimeConversion.millisecondsToDuration(0);
for (const result of stepResults) {
finalDuration = messages.TimeConversion.addDurations(finalDuration, result.duration);
}
finalStepResult.duration = finalDuration;
return {
result: finalStepResult,
error,
};
}
}
exports.default = TestCaseRunner;
function findHookDefinition(id, supportCodeLibrary) {
return [
...supportCodeLibrary.beforeTestCaseHookDefinitions,
...supportCodeLibrary.afterTestCaseHookDefinitions,
].find((definition) => definition.id === id);
}
function findStepDefinition(id, supportCodeLibrary) {
return supportCodeLibrary.stepDefinitions.find((definition) => definition.id === id);
}
//# sourceMappingURL=test_case_runner.js.map