@badeball/cypress-cucumber-preprocessor
Version:
[](https://github.com/badeball/cypress-cucumber-preprocessor/actions/workflows/build.yml) [ • 19.6 kB
JavaScript
;
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 __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createStringAttachmentHandler = exports.testCaseFinishedHandler = exports.testStepFinishedHandler = exports.testStepStartedHandler = exports.testCaseStartedHandler = exports.specEnvelopesHandler = exports.afterScreenshotHandler = exports.afterSpecHandler = exports.beforeSpecHandler = exports.afterRunHandler = exports.beforeRunHandler = void 0;
const fs_1 = __importStar(require("fs"));
const path_1 = __importDefault(require("path"));
const promises_1 = require("stream/promises");
const stream_1 = __importDefault(require("stream"));
const chalk_1 = __importDefault(require("chalk"));
const message_streams_1 = require("@cucumber/message-streams");
const messages = __importStar(require("@cucumber/messages"));
const split_1 = __importDefault(require("split"));
const constants_1 = require("./constants");
const preprocessor_configuration_1 = require("./preprocessor-configuration");
const paths_1 = require("./helpers/paths");
const messages_1 = require("./helpers/messages");
const memoize_1 = require("./helpers/memoize");
const debug_1 = __importDefault(require("./helpers/debug"));
const error_1 = require("./helpers/error");
const assertions_1 = require("./helpers/assertions");
const formatters_1 = require("./helpers/formatters");
const colors_1 = require("./helpers/colors");
const resolve = (0, memoize_1.memoize)(preprocessor_configuration_1.resolve);
let state = {
state: "initial",
};
const isFeature = (spec) => spec.name.endsWith(".feature");
const end = (stream) => new Promise((resolve) => stream.end(resolve));
const createPrettyStream = () => {
const line = (0, split_1.default)();
const indent = new stream_1.default.Transform({
objectMode: true,
transform(chunk, _, callback) {
callback(null, chunk.length === 0 ? "" : " " + chunk);
},
});
const log = new stream_1.default.Writable({
write(chunk, _, callback) {
console.log(chunk.toString("utf8"));
callback();
},
});
return stream_1.default.compose(line, indent, log);
};
async function beforeRunHandler(config) {
(0, debug_1.default)("beforeRunHandler()");
if (!config.isTextTerminal) {
return;
}
const preprocessor = await resolve(config, config.env, "/");
if (!preprocessor.messages.enabled) {
return;
}
const messagesPath = (0, paths_1.ensureIsAbsolute)(config.projectRoot, preprocessor.messages.output);
await fs_1.promises.rm(messagesPath, { force: true });
const testRunStarted = {
testRunStarted: {
timestamp: (0, messages_1.createTimestamp)(),
},
};
await fs_1.promises.mkdir(path_1.default.dirname(messagesPath), { recursive: true });
await fs_1.promises.writeFile(messagesPath, JSON.stringify(testRunStarted) + "\n");
}
exports.beforeRunHandler = beforeRunHandler;
async function afterRunHandler(config) {
(0, debug_1.default)("afterRunHandler()");
if (!config.isTextTerminal) {
return;
}
const preprocessor = await resolve(config, config.env, "/");
if (!preprocessor.messages.enabled &&
!preprocessor.json.enabled &&
!preprocessor.html.enabled) {
return;
}
const messagesPath = (0, paths_1.ensureIsAbsolute)(config.projectRoot, preprocessor.messages.output);
try {
await fs_1.promises.access(messagesPath, fs_1.constants.F_OK);
}
catch (_a) {
return;
}
if (preprocessor.messages.enabled) {
const testRunFinished = {
testRunFinished: {
/**
* We're missing a "success" attribute here, but cucumber-js doesn't output it, so I won't.
* Mostly because I don't want to look into the semantics of it right now.
*/
timestamp: (0, messages_1.createTimestamp)(),
},
};
await fs_1.promises.writeFile(messagesPath, JSON.stringify(testRunFinished) + "\n", {
flag: "a",
});
}
if (preprocessor.json.enabled) {
const jsonPath = (0, paths_1.ensureIsAbsolute)(config.projectRoot, preprocessor.json.output);
await fs_1.promises.mkdir(path_1.default.dirname(jsonPath), { recursive: true });
const messages = (await fs_1.promises.readFile(messagesPath))
.toString()
.trim()
.split("\n")
.map((line) => JSON.parse(line));
let jsonOutput;
const eventBroadcaster = (0, formatters_1.createJsonFormatter)(messages, (chunk) => {
jsonOutput = chunk;
});
for (const message of messages) {
eventBroadcaster.emit("envelope", message);
}
(0, assertions_1.assertIsString)(jsonOutput, "Expected JSON formatter to have finished, but it never returned");
await fs_1.promises.writeFile(jsonPath, jsonOutput);
}
if (preprocessor.html.enabled) {
const htmlPath = (0, paths_1.ensureIsAbsolute)(config.projectRoot, preprocessor.html.output);
await fs_1.promises.mkdir(path_1.default.dirname(htmlPath), { recursive: true });
const input = fs_1.default.createReadStream(messagesPath);
const output = fs_1.default.createWriteStream(htmlPath);
await (0, promises_1.pipeline)(input, new message_streams_1.NdjsonToMessageStream(), (0, formatters_1.createHtmlStream)(), output);
}
}
exports.afterRunHandler = afterRunHandler;
async function beforeSpecHandler(config, spec) {
(0, debug_1.default)("beforeSpecHandler()");
if (!config.isTextTerminal || !isFeature(spec)) {
return;
}
const preprocessor = await resolve(config, config.env, "/");
if (!preprocessor.messages.enabled && !preprocessor.pretty.enabled) {
return;
}
switch (state.state) {
case "initial":
case "after-spec":
{
if (preprocessor.pretty.enabled) {
const writable = createPrettyStream();
const eventBroadcaster = (0, formatters_1.createPrettyFormatter)((0, colors_1.useColors)(), (chunk) => writable.write(chunk));
state = {
state: "before-spec",
pretty: {
enabled: true,
broadcaster: eventBroadcaster,
writable,
},
};
}
else {
state = {
state: "before-spec",
pretty: {
enabled: false,
},
};
}
}
break;
// This happens in case of visting a new domain, ref. https://github.com/cypress-io/cypress/issues/26300.
// In this case, we want to disgard messages obtained in the current test and allow execution to continue
// as if nothing happened.
case "before-spec":
case "step-started":
break;
default:
throw (0, error_1.createError)("Unexpected state in beforeSpecHandler: " + state.state);
}
}
exports.beforeSpecHandler = beforeSpecHandler;
async function afterSpecHandler(config, spec, results) {
(0, debug_1.default)("afterSpecHandler()");
if (!config.isTextTerminal || !isFeature(spec)) {
return;
}
const preprocessor = await resolve(config, config.env, "/");
const messagesPath = (0, paths_1.ensureIsAbsolute)(config.projectRoot, preprocessor.messages.output);
// `results` is undefined when running via `cypress open`.
if (preprocessor.messages.enabled && results) {
const wasRemainingSkipped = results.tests.some((test) => { var _a; return (_a = test.displayError) === null || _a === void 0 ? void 0 : _a.match(constants_1.HOOK_FAILURE_EXPR); });
if (wasRemainingSkipped) {
console.log(chalk_1.default.yellow(` Hook failures can't be represented in any reports (messages / json / html), thus none is created for ${spec.relative}.`));
}
else if ("messages" in state) {
await fs_1.promises.writeFile(messagesPath, state.messages.map((message) => JSON.stringify(message)).join("\n") +
"\n", {
flag: "a",
});
}
}
if ("pretty" in state && state.pretty.enabled) {
await end(state.pretty.writable);
}
state = {
state: "after-spec",
};
}
exports.afterSpecHandler = afterSpecHandler;
async function afterScreenshotHandler(config, details) {
(0, debug_1.default)("afterScreenshotHandler()");
if (!config.isTextTerminal) {
return details;
}
const preprocessor = await resolve(config, config.env, "/");
if (!preprocessor.messages.enabled) {
return details;
}
switch (state.state) {
case "step-started":
break;
default:
return details;
}
let buffer;
try {
buffer = await fs_1.promises.readFile(details.path);
}
catch (_a) {
return details;
}
const message = {
attachment: {
testCaseStartedId: state.testCaseStartedId,
testStepId: state.testStepStartedId,
body: buffer.toString("base64"),
mediaType: "image/png",
contentEncoding: "BASE64",
},
};
state.messages.push(message);
return details;
}
exports.afterScreenshotHandler = afterScreenshotHandler;
async function specEnvelopesHandler(config, data) {
(0, debug_1.default)("specEnvelopesHandler()");
if (!config.isTextTerminal) {
return true;
}
switch (state.state) {
case "before-spec":
break;
// This happens in case of visting a new domain, ref. https://github.com/cypress-io/cypress/issues/26300.
// In this case, we want to disgard messages obtained in the current test and allow execution to continue
// as if nothing happened.
case "step-started":
{
const iTestCaseStarted = state.messages.findLastIndex((message) => !!message.testCaseStarted);
if (iTestCaseStarted === -1) {
throw (0, error_1.createError)("Expected to find a testCaseStarted envelope");
}
let pretty;
if (state.pretty.enabled) {
await end(state.pretty.writable);
console.log(" Reloading..");
console.log();
const writable = createPrettyStream();
const eventBroadcaster = (0, formatters_1.createPrettyFormatter)((0, colors_1.useColors)(), (chunk) => writable.write(chunk));
for (const message of data.messages) {
eventBroadcaster.emit("envelope", message);
}
pretty = {
enabled: true,
writable,
broadcaster: eventBroadcaster,
};
}
else {
pretty = state.pretty;
}
state = {
state: "received-envelopes",
pretty,
messages: state.messages.slice(0, iTestCaseStarted),
};
}
return true;
default:
throw (0, error_1.createError)("Unexpected state in specEnvelopesHandler: " + state.state);
}
if (state.pretty.enabled) {
for (const message of data.messages) {
state.pretty.broadcaster.emit("envelope", message);
}
}
state = {
state: "received-envelopes",
pretty: state.pretty,
messages: data.messages,
};
return true;
}
exports.specEnvelopesHandler = specEnvelopesHandler;
function testCaseStartedHandler(config, data) {
(0, debug_1.default)("testCaseStartedHandler()");
if (!config.isTextTerminal) {
return true;
}
switch (state.state) {
case "received-envelopes":
case "test-finished":
break;
default:
throw (0, error_1.createError)("Unexpected state in testCaseStartedHandler: " + state.state);
}
if (state.pretty.enabled) {
state.pretty.broadcaster.emit("envelope", {
testCaseStarted: data,
});
}
state = {
state: "test-started",
pretty: state.pretty,
messages: state.messages.concat({ testCaseStarted: data }),
testCaseStartedId: data.id,
};
return true;
}
exports.testCaseStartedHandler = testCaseStartedHandler;
function testStepStartedHandler(config, data) {
(0, debug_1.default)("testStepStartedHandler()");
if (!config.isTextTerminal) {
return true;
}
switch (state.state) {
case "test-started":
case "step-finished":
break;
// This state can happen in cases where an error is "rescued".
case "step-started":
break;
default:
throw (0, error_1.createError)("Unexpected state in testStepStartedHandler: " + state.state);
}
if (state.pretty.enabled) {
state.pretty.broadcaster.emit("envelope", {
testStepStarted: data,
});
}
state = {
state: "step-started",
pretty: state.pretty,
messages: state.messages.concat({ testStepStarted: data }),
testCaseStartedId: state.testCaseStartedId,
testStepStartedId: data.testStepId,
};
return true;
}
exports.testStepStartedHandler = testStepStartedHandler;
async function testStepFinishedHandler(config, options, _a) {
var _b;
var { wasLastStep } = _a, testStepFinished = __rest(_a, ["wasLastStep"]);
(0, debug_1.default)("testStepFinishedHandler()");
if (!config.isTextTerminal) {
return true;
}
switch (state.state) {
case "step-started":
break;
default:
throw (0, error_1.createError)("Unexpected state in testStepFinishedHandler: " + state.state);
}
if (state.pretty.enabled) {
state.pretty.broadcaster.emit("envelope", {
testStepFinished,
});
}
const attachments = [];
await ((_b = options.onAfterStep) === null || _b === void 0 ? void 0 : _b.call(options, {
wasLastStep,
attach(data, mediaType) {
if (typeof data === "string") {
mediaType = mediaType !== null && mediaType !== void 0 ? mediaType : "text/plain";
if (mediaType.startsWith("base64:")) {
attachments.push({
data,
mediaType: mediaType.replace("base64:", ""),
encoding: messages.AttachmentContentEncoding.BASE64,
});
}
else {
attachments.push({
data,
mediaType: mediaType !== null && mediaType !== void 0 ? mediaType : "text/plain",
encoding: messages.AttachmentContentEncoding.IDENTITY,
});
}
}
else if (data instanceof Buffer) {
if (typeof mediaType !== "string") {
throw Error("Buffer attachments must specify a media type");
}
attachments.push({
data: data.toString("base64"),
mediaType,
encoding: messages.AttachmentContentEncoding.BASE64,
});
}
else {
throw Error("Invalid attachment data: must be a Buffer or string");
}
},
}));
for (const attachment of attachments) {
await createStringAttachmentHandler(config, attachment);
}
state = {
state: "step-finished",
pretty: state.pretty,
messages: state.messages.concat({ testStepFinished }),
testCaseStartedId: state.testCaseStartedId,
};
return true;
}
exports.testStepFinishedHandler = testStepFinishedHandler;
function testCaseFinishedHandler(config, data) {
(0, debug_1.default)("testCaseFinishedHandler()");
if (!config.isTextTerminal) {
return true;
}
switch (state.state) {
case "test-started":
case "step-finished":
break;
default:
throw (0, error_1.createError)("Unexpected state in testCaseFinishedHandler: " + state.state);
}
if (state.pretty.enabled) {
state.pretty.broadcaster.emit("envelope", {
testCaseFinished: data,
});
}
state = {
state: "test-finished",
pretty: state.pretty,
messages: state.messages.concat({ testCaseFinished: data }),
};
return true;
}
exports.testCaseFinishedHandler = testCaseFinishedHandler;
async function createStringAttachmentHandler(config, { data, mediaType, encoding }) {
(0, debug_1.default)("createStringAttachmentHandler()");
if (!config.isTextTerminal) {
return true;
}
const preprocessor = await resolve(config, config.env, "/");
if (!preprocessor.messages.enabled) {
return true;
}
switch (state.state) {
case "step-started":
break;
default:
throw (0, error_1.createError)("Unexpected state in createStringAttachmentHandler: " + state.state);
}
const message = {
attachment: {
testCaseStartedId: state.testCaseStartedId,
testStepId: state.testStepStartedId,
body: data,
mediaType: mediaType,
contentEncoding: encoding,
},
};
state.messages.push(message);
return true;
}
exports.createStringAttachmentHandler = createStringAttachmentHandler;