refactor(Cypress): add nodemodules

This commit is contained in:
2021-09-02 17:18:41 +02:00
parent 1aa57bbd0a
commit bc6e1bc12e
4238 changed files with 340975 additions and 8 deletions

View File

@@ -0,0 +1,165 @@
const { Parser } = require("gherkin");
const statuses = require("cucumber/lib/status").default;
class CucumberDataCollector {
constructor(uri, spec) {
this.feature = new Parser().parse(spec.toString()).feature;
this.scenarioSteps = {};
this.runScenarios = {};
this.runTests = {};
this.stepResults = {};
this.testError = null;
this.uri = uri;
this.spec = spec;
this.currentScenario = null;
this.currentStep = 0;
this.timer = Date.now();
this.logStep = (step) => {
Cypress.log({
name: "step",
displayName: step.keyword,
message: `**${step.text}**`,
consoleProps: () => ({ feature: this.uri, step }),
});
};
this.onStartTest = () => {};
this.onFinishTest = () => {
if (this.testError) {
this.attachErrorToFailingStep();
}
};
this.onStartScenario = (scenario, stepsToRun) => {
this.currentScenario = scenario;
this.currentStep = 0;
this.stepResults = {};
this.scenarioSteps[scenario.name] = stepsToRun;
this.testError = null;
stepsToRun.forEach((step) => {
this.stepResults[step.index] = { status: statuses.PENDING };
});
this.runScenarios[scenario.name] = scenario;
};
this.onFinishScenario = (scenario) => {
this.markStillPendingStepsAsSkipped(scenario);
this.recordScenarioResult(scenario);
};
this.onStartStep = (step) => {
this.currentStep = step.index;
this.setStepToPending(step);
this.logStep(step);
};
this.onFinishStep = (step, result) => {
this.recordStepResult(step, result);
};
this.onFail = (err) => {
this.testError = err;
if (
err.message &&
err.message.indexOf("Step implementation missing for") > -1
) {
this.stepResults[this.currentStep] = {
status: statuses.UNDEFINED,
duration: this.timeTaken(),
};
} else if (err.constructor.name === "Pending") {
// cypress marks skipped mocha tests as pending
// https://github.com/cypress-io/cypress/issues/3092
// don't record this error and mark the step as skipped
this.stepResults[this.currentStep] = {
status: statuses.SKIPPED,
duration: this.timeTaken(),
};
} else {
this.stepResults[this.currentStep] = {
status: statuses.FAILED,
duration: this.timeTaken(),
exception: this.testError,
};
}
this.onFinishScenario(this.currentScenario);
};
this.timeTaken = () => {
const now = Date.now();
const duration = now - this.timer;
this.timer = now;
return duration;
};
this.formatTestCase = (scenario) => {
const line = scenario.example
? scenario.example.line
: scenario.location.line;
return {
sourceLocation: { uri, line },
};
};
this.attachErrorToFailingStep = () => {
Object.keys(this.runTests).forEach((test) => {
const stepResults = this.runTests[test];
Object.keys(stepResults).forEach((stepIdx) => {
const stepResult = stepResults[stepIdx];
if (stepResult.result === statuses.FAILED) {
stepResult.exception = this.testError;
}
});
});
};
this.markStillPendingStepsAsSkipped = (scenario) => {
this.runTests[scenario.name] = Object.keys(this.stepResults).map(
(key) => {
const result = this.stepResults[key];
return {
...result,
status:
result.status === statuses.PENDING
? statuses.SKIPPED
: result.status,
};
}
);
};
this.recordScenarioResult = (scenario) => {
const allSkipped = this.areAllStepsSkipped(scenario.name);
const anyFailed = this.anyStepsHaveFailed(scenario.name);
if (allSkipped) this.runTests[scenario.name].result = statuses.SKIPPED;
else
this.runTests[scenario.name].result = anyFailed
? statuses.FAILED
: statuses.PASSED;
};
this.setStepToPending = (step) => {
this.stepResults[step.index] = { status: statuses.PENDING };
};
this.recordStepResult = (step, result) => {
this.stepResults[step.index] = {
status: result,
duration: this.timeTaken(),
};
};
this.areAllStepsSkipped = (name) =>
this.runTests[name].every((e) => e.status === statuses.SKIPPED);
this.anyStepsHaveFailed = (name) =>
this.runTests[name].find((e) => e.status === statuses.FAILED) !==
undefined;
}
}
module.exports = { CucumberDataCollector };

View File

@@ -0,0 +1,160 @@
const fs = require("fs");
const statuses = require("cucumber/lib/status").default;
const { CucumberDataCollector } = require("./cucumberDataCollector");
const { generateCucumberJson } = require("./generateCucumberJson");
window.cucumberJson = { generate: true };
const assertCucumberJson = (json, expectedResults) => {
expect(json).to.have.length(1);
expect(json[0].keyword).to.eql("Feature");
expect(json[0].name).to.eql("Being a plugin");
expect(json[0].elements).to.have.length(1);
expect(json[0].elements[0].keyword).to.eql("Scenario");
expect(json[0].elements[0].name).to.eql("Basic example");
expect(json[0].elements[0].steps).to.have.length(3);
expect(json[0].elements[0].steps[0].keyword).to.equal("Given ");
expect(json[0].elements[0].steps[0].name).to.equal(
"a feature and a matching step definition file"
);
// eslint-disable-next-line no-unused-expressions
expect(json[0].elements[0].steps[0].result).to.be.not.null;
expect(json[0].elements[0].steps[0].result.status).to.eql(expectedResults[0]);
expect(json[0].elements[0].steps[1].keyword).to.equal("When ");
expect(json[0].elements[0].steps[1].name).to.equal("I run cypress tests");
// eslint-disable-next-line no-unused-expressions
expect(json[0].elements[0].steps[1].result).to.be.not.null;
expect(json[0].elements[0].steps[1].result.status).to.eql(expectedResults[1]);
expect(json[0].elements[0].steps[2].keyword).to.equal("Then ");
expect(json[0].elements[0].steps[2].name).to.equal("they run properly");
// eslint-disable-next-line no-unused-expressions
expect(json[0].elements[0].steps[2].result).to.be.not.null;
expect(json[0].elements[0].steps[2].result.status).to.eql(expectedResults[2]);
};
describe("Cucumber Data Collector", () => {
const scenario = {
type: "Scenario",
tags: [],
location: { line: 7, column: 3 },
keyword: "Scenario",
name: "Basic example",
steps: [
{
type: "Step",
location: { line: 8, column: 5 },
keyword: "Given ",
text: "a feature and a matching step definition file",
},
{
type: "Step",
location: { line: 9, column: 5 },
keyword: "When ",
text: "I run cypress tests",
},
{
type: "Step",
location: { line: 10, column: 5 },
keyword: "Then ",
text: "they run properly",
},
],
};
const stepsToRun = [
{
type: "Step",
location: { line: 8, column: 5 },
keyword: "Given ",
text: "a feature and a matching step definition file",
index: 0,
},
{
type: "Step",
location: { line: 9, column: 5 },
keyword: "When ",
text: "I run cypress tests",
index: 1,
},
{
type: "Step",
location: { line: 10, column: 5 },
keyword: "Then ",
text: "they run properly",
index: 2,
},
];
beforeEach(() => {
const filePath = "./cypress/integration/Plugin.feature";
const spec = fs.readFileSync(filePath);
this.testState = new CucumberDataCollector(filePath, spec);
this.testState.onStartTest();
});
it("runs", () => {
this.testState.onFinishTest();
const json = generateCucumberJson(this.testState);
expect(json).to.have.length(0);
});
it("records pending scenarios", () => {
this.testState.onStartScenario(scenario, stepsToRun);
this.testState.onFinishScenario(scenario);
this.testState.onFinishTest();
const json = generateCucumberJson(this.testState);
assertCucumberJson(json, [
statuses.SKIPPED,
statuses.SKIPPED,
statuses.SKIPPED,
]);
});
it("records passed scenarios", () => {
this.testState.onStartScenario(scenario, stepsToRun);
this.testState.onStartStep(stepsToRun[0]);
this.testState.onFinishStep(stepsToRun[0], statuses.PASSED);
this.testState.onStartStep(stepsToRun[1]);
this.testState.onFinishStep(stepsToRun[1], statuses.PASSED);
this.testState.onStartStep(stepsToRun[2]);
this.testState.onFinishStep(stepsToRun[2], statuses.PASSED);
this.testState.onFinishScenario(scenario);
this.testState.onFinishTest();
const json = generateCucumberJson(this.testState);
assertCucumberJson(json, [
statuses.PASSED,
statuses.PASSED,
statuses.PASSED,
]);
});
it("records failed scenarios", () => {
this.testState.onStartScenario(scenario, stepsToRun);
this.testState.onStartStep(stepsToRun[0]);
this.testState.onFinishStep(stepsToRun[0], statuses.PASSED);
this.testState.onStartStep(stepsToRun[1]);
this.testState.onFinishStep(stepsToRun[1], statuses.FAILED);
this.testState.onFinishScenario(scenario);
this.testState.onFinishTest();
const json = generateCucumberJson(this.testState);
assertCucumberJson(json, [
statuses.PASSED,
statuses.FAILED,
statuses.SKIPPED,
]);
});
it("handles missing steps", () => {
this.testState.onStartScenario(scenario, stepsToRun);
this.testState.onStartStep(stepsToRun[0]);
this.testState.onFinishStep(stepsToRun[0], statuses.PASSED);
this.testState.onStartStep(stepsToRun[1]);
this.testState.onFinishStep(stepsToRun[1], statuses.UNDEFINED);
this.testState.onFinishScenario(scenario);
this.testState.onFinishTest();
const json = generateCucumberJson(this.testState);
assertCucumberJson(json, [
statuses.PASSED,
statuses.UNDEFINED,
statuses.SKIPPED,
]);
});
});

View File

@@ -0,0 +1,72 @@
const { EventEmitter } = require("events");
const { generateEvents } = require("gherkin");
const JsonFormatter = require("cucumber/lib/formatter/json_formatter").default;
const formatterHelpers = require("cucumber/lib/formatter/helpers");
function generateCucumberJson(state) {
let output = "";
const logFn = (data) => {
output += data;
};
const eventBroadcaster = new EventEmitter();
function storePickle({ pickle, uri }) {
eventBroadcaster.emit("pickle-accepted", { pickle, uri });
}
eventBroadcaster.on("pickle", storePickle);
// eslint-disable-next-line no-new
new JsonFormatter({
eventBroadcaster,
eventDataCollector: new formatterHelpers.EventDataCollector(
eventBroadcaster
),
log: logFn,
});
// Start feeding the recorded test run into the JsonFormatter
// Feed in the static test structure
generateEvents(state.spec.toString(), state.uri).forEach((event) => {
eventBroadcaster.emit(event.type, event);
});
// Feed in the results from the recorded scenarios and steps
Object.keys(state.runTests).forEach((test) => {
const scenario = state.runScenarios[test];
const stepResults = state.runTests[test];
const stepsToRun = state.scenarioSteps[test];
const steps = stepsToRun.map((step) => ({
sourceLocation: { uri: state.uri, line: step.location.line },
}));
eventBroadcaster.emit("test-case-prepared", {
sourceLocation: state.formatTestCase(scenario).sourceLocation,
steps,
});
stepResults.forEach((stepResult, stepIdx) => {
eventBroadcaster.emit("test-step-prepared", {
index: stepIdx,
testCase: state.formatTestCase(scenario),
});
eventBroadcaster.emit("test-step-finished", {
index: stepIdx,
testCase: state.formatTestCase(scenario),
result: stepResult,
});
if (stepResult.attachment) {
eventBroadcaster.emit("test-step-attachment", stepResult.attachment);
}
});
eventBroadcaster.emit("test-case-finished", {
sourceLocation: state.formatTestCase(scenario).sourceLocation,
result: state.runTests[scenario.name].result,
});
});
eventBroadcaster.emit("test-run-finished", {});
return JSON.parse(output);
}
module.exports = { generateCucumberJson };