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

503 lines (502 loc) 19.6 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 __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;