From 66e33b7b00da1e0b1d80aa247f08bcd613d459d5 Mon Sep 17 00:00:00 2001 From: Krivega Dmitriy Date: Sat, 21 Oct 2023 11:25:37 +0300 Subject: [PATCH] init --- .gitignore | 46 + README.md | 0 bin/cli.d.ts | 1 + bin/cli.js | 3640 ++++++++++++++++++++++++++++++++++++++++++++ bin/cli.mjs | 3600 +++++++++++++++++++++++++++++++++++++++++++ index.d.ts | 346 +++++ index.js | 3267 +++++++++++++++++++++++++++++++++++++++ index.mjs | 3233 +++++++++++++++++++++++++++++++++++++++ package.json | 137 ++ plugin/index.d.ts | 3 + plugin/index.js | 144 ++ plugin/index.mjs | 113 ++ support/index.d.ts | 2 + support/index.js | 103 ++ support/index.mjs | 79 + 15 files changed, 14714 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 bin/cli.d.ts create mode 100755 bin/cli.js create mode 100755 bin/cli.mjs create mode 100644 index.d.ts create mode 100644 index.js create mode 100644 index.mjs create mode 100644 package.json create mode 100644 plugin/index.d.ts create mode 100644 plugin/index.js create mode 100644 plugin/index.mjs create mode 100644 support/index.d.ts create mode 100644 support/index.js create mode 100644 support/index.mjs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3893435 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js +.yarn + +# testing +examples/coverage/coverage/* +.nyc_output + +# next.js +.next/ +out/ +build +dist +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# turbo +.turbo + +# Cypress test outputs +**/cypress/videos/ +**/cypress/screenshots/ + +# ENV file +.env + +# parcel +.parcel-cache +dist \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/bin/cli.d.ts b/bin/cli.d.ts new file mode 100755 index 0000000..908ba84 --- /dev/null +++ b/bin/cli.d.ts @@ -0,0 +1 @@ +#!/usr/bin/env node diff --git a/bin/cli.js b/bin/cli.js new file mode 100755 index 0000000..f5b53f8 --- /dev/null +++ b/bin/cli.js @@ -0,0 +1,3640 @@ +#!/usr/bin/env node +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +var getImportMetaUrl, importMetaUrl; +var init_cjs_shims = __esm({ + "../../node_modules/tsup/assets/cjs_shims.js"() { + "use strict"; + getImportMetaUrl = () => typeof document === "undefined" ? new URL("file:" + __filename).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href; + importMetaUrl = getImportMetaUrl(); + } +}); + +var require_extra_typings = __commonJS({ + "bin/lib/@commander-js/extra-typings/index.js"(exports, module2) { + "use strict"; + init_cjs_shims(); + var commander = require("commander"); + exports = module2.exports = {}; + exports.program = new commander.Command(); + exports.Argument = commander.Argument; + exports.Command = commander.Command; + exports.CommanderError = commander.CommanderError; + exports.Help = commander.Help; + exports.InvalidArgumentError = commander.InvalidArgumentError; + exports.InvalidOptionArgumentError = commander.InvalidArgumentError; + exports.Option = commander.Option; + exports.createCommand = (name) => new commander.Command(name); + exports.createOption = (flags, description) => new commander.Option(flags, description); + exports.createArgument = (name, description) => new commander.Argument(name, description); + } +}); + +init_cjs_shims(); +var import_register = require("source-map-support/register"); + +init_cjs_shims(); +var ValidationError = class extends Error { + constructor(message) { + super(message); + this.name = ""; + } +}; + +init_cjs_shims(); +var import_chalk = __toESM(require("chalk")); +var import_util = __toESM(require("util")); +var log = (...args) => console.log(import_util.default.format(...args)); +var info = log; +var format = import_util.default.format; +var withError = (msg) => import_chalk.default.bgRed.white(" ERROR ") + " " + msg; +var withWarning = (msg) => import_chalk.default.bgYellow.black(" WARNING ") + " " + msg; +var warn = (...args) => log(withWarning(import_util.default.format(...args))); +var error = (...args) => log(withError(import_util.default.format(...args)) + "\n"); +var title = (color, ...args) => info("\n " + import_chalk.default[color].bold(import_util.default.format(...args)) + " \n"); +var divider = () => console.log("\n" + import_chalk.default.gray(Array(100).fill("=").join("")) + "\n"); +var spacer = (n = 0) => console.log(Array(n).fill("").join("\n")); +var cyan = import_chalk.default.cyan; +var blue = import_chalk.default.blueBright; +var red = import_chalk.default.red; +var green = import_chalk.default.greenBright; +var gray = import_chalk.default.gray; +var white = import_chalk.default.white; +var magenta = import_chalk.default.magenta; +var bold = import_chalk.default.bold; +var dim = import_chalk.default.dim; + +init_cjs_shims(); + +init_cjs_shims(); + +init_cjs_shims(); +var import_module = require("module"); +var require2 = (0, import_module.createRequire)(importMetaUrl); + +init_cjs_shims(); +var import_child_process = __toESM(require("child_process")); +var orginal = import_child_process.default.spawn; +import_child_process.default.spawn = function(command, args, options) { + if (command.match(/Cypress/)) { + const process2 = orginal(command, args, { + ...options, + stdio: ["pipe", "pipe", "pipe"] + }); + return process2; + } + return orginal(command, args, options); +}; + +init_cjs_shims(); + +init_cjs_shims(); +var import_debug = __toESM(require("debug")); +var import_http = __toESM(require("http")); +var import_lil_http_terminator = __toESM(require("lil-http-terminator")); +var import_ts_pattern = require("ts-pattern"); +var WebSocket = __toESM(require("ws")); + +init_cjs_shims(); + +init_cjs_shims(); +var Event = ((Event2) => { + Event2["RUN_CANCELLED"] = "run:cancelled"; + Event2["RUN_RESULT"] = "run:result"; + Event2["TEST_AFTER_RUN"] = "test:after:run"; + Event2["TEST_BEFORE_RUN"] = "test:before:run"; + Event2["AFTER_SCREENSHOT"] = "after:screenshot"; + Event2["AFTER_SPEC"] = "after:spec"; + return Event2; +})(Event || {}); +var allEvents = Object.values(Event); + +init_cjs_shims(); +var import_events = __toESM(require("events")); +var _pubsub = null; +var getPubSub = () => { + if (!_pubsub) { + _pubsub = new import_events.default(); + } + return _pubsub; +}; + +var debug = (0, import_debug.default)("cc:ws"); +var server = null; +var wss = null; +var httpTerminator = null; +var getWSSPort = () => (0, import_ts_pattern.match)(server?.address()).with({ port: import_ts_pattern.P.number }, (address) => address.port).otherwise(() => 0); +var stopWSS = async () => { + debug("terminating wss server: %d", getWSSPort()); + if (!httpTerminator) { + debug("no wss server"); + return; + } + const { success, code, message, error: error2 } = await httpTerminator.terminate(); + if (!success) { + if (code === "TIMED_OUT") + error2(message); + if (code === "SERVER_ERROR") + error2(message, error2); + if (code === "INTERNAL_ERROR") + error2(message, error2); + } + debug("terminated wss server: %d", getWSSPort()); +}; +var startWSS = () => { + if (wss) { + return; + } + server = import_http.default.createServer().on("listening", () => { + if (!server) { + throw new Error("Server not initialized"); + } + wss = new WebSocket.WebSocketServer({ + server + }); + debug("starting wss on port %d", getWSSPort()); + wss.on("connection", function connection(ws) { + ws.on("message", function incoming(event) { + const message = JSON.parse(event.toString()); + getPubSub().emit(message.type, message.payload); + }); + }); + }).listen(); + httpTerminator = (0, import_lil_http_terminator.default)({ + server + }); +}; + +init_cjs_shims(); +var import_debug2 = __toESM(require("debug")); +var debug2 = (0, import_debug2.default)("cc:capture"); +var _write = process.stdout.write; +var _log = process.log; +var restore = function() { + process.stdout.write = _write; + process.log = _log; +}; +var stdout = function() { + debug2("capturing stdout"); + let logs = []; + const { write } = process.stdout; + const { log: log2 } = process; + if (log2) { + process.log = function(str) { + logs.push(str); + return log2.apply(this, arguments); + }; + } + process.stdout.write = function(str) { + logs.push(str); + return write.apply(this, arguments); + }; + return { + toString() { + return logs.join(""); + }, + data: logs, + restore, + reset: () => { + debug2("resetting captured stdout"); + logs = []; + } + }; +}; +var initialOutput = ""; +var capturedOutput = null; +var initCapture = () => capturedOutput = stdout(); +var cutInitialOutput = () => { + if (!capturedOutput) + throw new Error("capturedOutput is null"); + initialOutput = capturedOutput.toString(); + capturedOutput.reset(); +}; +var resetCapture = () => { + if (!capturedOutput) + throw new Error("capturedOutput is null"); + capturedOutput.reset(); +}; +var getCapturedOutput = () => { + if (!capturedOutput) + throw new Error("capturedOutput is null"); + return capturedOutput.toString(); +}; +var getInitialOutput = () => initialOutput; + +init_cjs_shims(); +var _runId = void 0; +var setRunId = (runId) => { + _runId = runId; +}; +var _cypressVersion = void 0; +var setCypressVersion = (cypressVersion) => { + _cypressVersion = cypressVersion; +}; +var _ccVersion = void 0; +var setCcVersion = (v) => { + _ccVersion = v; +}; + +var cypressPkg = require2("cypress/package.json"); +var pkg = require2("@krivega/cc/package.json"); +initCapture(); +setCypressVersion(cypressPkg.version); +setCcVersion(pkg.version); + +var import_debug25 = __toESM(require("debug")); + +init_cjs_shims(); +function getLegalNotice() { + return ` +Copyright (C) ${(/* @__PURE__ */ new Date()).getFullYear()} cc +`; +} + +init_cjs_shims(); + +init_cjs_shims(); + +init_cjs_shims(); + +init_cjs_shims(); +var import_axios = require("axios"); +var isRetriableError = (err) => { + if (err.code === "ECONNABORTED") { + return true; + } + if (err.code === "ECONNREFUSED") { + return true; + } + if (err.code === "ETIMEDOUT") { + return true; + } + if (!(0, import_axios.isAxiosError)(err)) { + return false; + } + return !!(err?.response?.status && 500 <= err.response.status && err.response.status < 600); +}; +var getDelay = (i) => [5 * 1e3, 10 * 1e3, 30 * 1e3][i - 1]; +var baseURL = "set baseURL"; +var getAPIBaseUrl = () => baseURL ?? "set baseURL"; +var setAPIBaseUrl = (url) => baseURL = url ?? "set baseURL"; + +init_cjs_shims(); +var import_axios2 = __toESM(require("axios")); +var import_axios_retry = __toESM(require("axios-retry")); +var import_debug10 = __toESM(require("debug")); +var import_lodash5 = __toESM(require("lodash")); +var import_pretty_ms = __toESM(require("pretty-ms")); + +init_cjs_shims(); + +init_cjs_shims(); +var import_debug7 = __toESM(require("debug")); +var import_ts_pattern3 = require("ts-pattern"); + +init_cjs_shims(); + +init_cjs_shims(); +var import_cy2 = require("cy2"); +var import_debug6 = __toESM(require("debug")); +var import_execa = __toESM(require("execa")); +var import_fs = __toESM(require("fs")); + +init_cjs_shims(); +var import_tmp_promise = require("tmp-promise"); +var createTempFile = async () => { + const { path: path5 } = await (0, import_tmp_promise.file)(); + return path5; +}; + +init_cjs_shims(); +var import_debug4 = __toESM(require("debug")); +var import_lodash = __toESM(require("lodash")); + +init_cjs_shims(); +var import_debug3 = __toESM(require("debug")); +var import_ts_pattern2 = require("ts-pattern"); + +init_cjs_shims(); +var DebugMode = ((DebugMode2) => { + DebugMode2["None"] = "none"; + DebugMode2["All"] = "all"; + DebugMode2["Cc"] = "cc"; + DebugMode2["Cypress"] = "cypress"; + DebugMode2["CommitInfo"] = "commit-info"; + return DebugMode2; +})(DebugMode || {}); + +function shouldEnablePluginDebug(param) { + return (0, import_ts_pattern2.match)(param).with(import_ts_pattern2.P.nullish, () => false).with("none", () => false).with(true, () => true).with("all", () => true).with("cc", () => true).with( + import_ts_pattern2.P.array(import_ts_pattern2.P.string), + (v) => v.includes("all") || v.includes("cc") + ).otherwise(() => false); +} +function activateDebug(mode) { + (0, import_ts_pattern2.match)(mode).with(import_ts_pattern2.P.instanceOf(Array), (i) => i.forEach(setDebugMode)).with(true, () => setDebugMode("all")).with( + import_ts_pattern2.P.union( + "all", + "cc", + "cypress", + "commit-info" + ), + (i) => setDebugMode(i) + ).otherwise(() => setDebugMode("none")); +} +function setDebugMode(mode) { + if (mode === "none") { + return; + } + const tokens = new Set(process.env.DEBUG ? process.env.DEBUG.split(",") : []); + (0, import_ts_pattern2.match)(mode).with("all", () => { + tokens.add("commit-info"); + tokens.add("cc:*"); + tokens.add("cypress:*"); + }).with("cc", () => tokens.add("cc:*")).with("cypress", () => tokens.add("cypress:*")).with("commit-info", () => tokens.add("commit-info")).otherwise(() => { + }); + import_debug3.default.enable(Array.from(tokens).join(",")); +} + +init_cjs_shims(); +var import_bluebird = __toESM(require("bluebird")); +import_bluebird.default.Promise.config({ + cancellation: true +}); +var BPromise = import_bluebird.default.Promise; +var safe = (fn, ifFaled, ifSucceed) => async (...args) => { + try { + const r = await fn(...args); + ifSucceed(); + return r; + } catch (e) { + return ifFaled(e); + } +}; +var sortObjectKeys = (obj) => { + return Object.keys(obj).sort().reduce((acc, key) => { + acc[key] = obj[key]; + return acc; + }, {}); +}; + +init_cjs_shims(); +var import_nanoid = require("nanoid"); +var getRandomString = (0, import_nanoid.customAlphabet)("abcdefghijklmnopqrstuvwxyz", 10); + +var debug4 = (0, import_debug4.default)("cc:boot"); +function getBootstrapArgs({ + params, + tempFilePath +}) { + return import_lodash.default.chain(getCypressCLIParams(params)).thru((opts) => ({ + ...opts, + env: { + ...opts.env ?? {}, + cc_marker: true, + cc_temp_file: tempFilePath, + cc_debug_enabled: shouldEnablePluginDebug(params.cloudDebug) + } + })).tap((opts) => { + debug4("cypress bootstrap params: %o", opts); + }).thru((opts) => ({ + ...opts, + env: sortObjectKeys(opts.env ?? {}) + })).thru(serializeOptions).tap((opts) => { + debug4("cypress bootstrap serialized params: %o", opts); + }).thru((args) => { + return [ + ...args, + "--spec", + getRandomString(), + params.testingType === "component" ? "--component" : "--e2e" + ]; + }).value(); +} +function getCypressCLIParams(params) { + const result = getCypressRunAPIParams(params); + const testingType = result.testingType === "component" ? { + component: true + } : {}; + return { + ...import_lodash.default.omit(result, "testingType"), + ...testingType + }; +} +function serializeOptions(options) { + return Object.entries(options).flatMap(([key, value]) => { + const _key = dashed(key); + if (typeof value === "boolean") { + return value === true ? [`--${_key}`] : [`--${_key}`, false]; + } + if (import_lodash.default.isObject(value)) { + return [`--${_key}`, serializeComplexParam(value)]; + } + return [`--${_key}`, value.toString()]; + }); +} +function serializeComplexParam(param) { + return JSON.stringify(param); +} +var dashed = (v) => v.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()); + +var debug5 = (0, import_debug6.default)("cc:boot"); +var bootCypress = async (params) => { + debug5("booting cypress..."); + const tempFilePath = await createTempFile(); + const cypressBin = await (0, import_cy2.getBinPath)(require2.resolve("cypress")); + debug5("cypress executable location: %s", cypressBin); + const args = getBootstrapArgs({ tempFilePath, params }); + debug5("booting cypress with args: %o", args); + const { stdout: stdout2, stderr } = await execCypress(cypressBin, args); + if (!import_fs.default.existsSync(tempFilePath)) { + throw new Error( + `Cannot resolve cypress configuration from ${tempFilePath}. Please report the issue.` + ); + } + try { + const f = import_fs.default.readFileSync(tempFilePath, "utf-8"); + if (!f) { + throw new Error("Is @krivega/cc/plugin installed?"); + } + debug5("cypress config '%s': '%s'", tempFilePath, f); + return JSON.parse(f); + } catch (err) { + debug5("read config temp file failed: %o", err); + info(bold("Cypress stdout:\n"), stdout2); + info(bold("Cypress stderr:\n"), stderr); + throw new ValidationError(`Unable to resolve cypress configuration +- make sure that '@krivega/cc/plugin' is installed +- report the issue together with cypress stdout and stderr +`); + } +}; +async function execCypress(cypressBin, args) { + let stdout2 = ""; + let stderr = ""; + try { + await (0, import_execa.default)(cypressBin, ["run", ...args], { + stdio: "pipe", + env: { + ...process.env, + CYPRESS_RECORD_KEY: void 0, + CYPRESS_PROJECT_ID: void 0 + } + }); + } catch (err) { + debug5("exec cypress failed (certain failures are expected): %o", err); + stdout2 = err.stdout; + stderr = err.stderr; + } + return { stdout: stdout2, stderr }; +} + +init_cjs_shims(); +var import_is_absolute = __toESM(require("is-absolute")); +var import_lodash2 = __toESM(require("lodash")); +var import_path = __toESM(require("path")); +var defaultFilenames = [ + "cc.config.js", + "cc.config.cjs", + "cc.config.mjs" +]; +function getConfigFilePath(projectRoot = null, explicitConfigFilePath) { + const prefix = projectRoot ?? process.cwd(); + if (import_lodash2.default.isString(explicitConfigFilePath) && (0, import_is_absolute.default)(explicitConfigFilePath)) { + return [explicitConfigFilePath]; + } + if (import_lodash2.default.isString(explicitConfigFilePath)) { + return [normalizePath(prefix, explicitConfigFilePath)]; + } + return defaultFilenames.map((p) => normalizePath(prefix, p)); +} +function normalizePath(prefix, filename) { + return `file://${import_path.default.resolve(prefix, filename)}`; +} + +var debug6 = (0, import_debug7.default)("cc:config"); +var _config = null; +var defaultConfig = { + e2e: { + batchSize: 3 + }, + component: { + batchSize: 5 + }, + cloudServiceUrl: "set baseURL", + networkHeaders: void 0 +}; +async function getCcConfig(projectRoot, explicitConfigFilePath) { + if (_config) { + return _config; + } + const configFilePath = getConfigFilePath(projectRoot, explicitConfigFilePath); + for (const filepath of configFilePath) { + const config = (0, import_ts_pattern3.match)(await loadConfigFile(filepath)).with({ default: import_ts_pattern3.P.not(import_ts_pattern3.P.nullish) }, (c) => c.default).with(import_ts_pattern3.P.not(import_ts_pattern3.P.nullish), (c) => c).otherwise(() => null); + if (config) { + debug6("loaded cc config from '%s'\n%O", filepath, config); + info(`Using config file: ${dim(filepath)}`); + _config = { + ...defaultConfig, + ...config + }; + return _config; + } + } + warn( + "Failed to load config file, falling back to the default config. Attempted locations: %s", + configFilePath + ); + _config = defaultConfig; + return _config; +} +async function loadConfigFile(filepath) { + try { + debug6("loading cc config file from '%s'", filepath); + return await import(filepath); + } catch (e) { + debug6("failed loading config file from: %s", e); + return null; + } +} +async function getMergedConfig(params) { + debug6("resolving cypress config"); + const cypressResolvedConfig = await bootCypress(params); + debug6("cypress resolvedConfig: %O", cypressResolvedConfig); + const rawE2EPattern = cypressResolvedConfig.rawJson?.e2e?.specPattern; + let additionalIgnorePattern = []; + if (params.testingType === "component" && rawE2EPattern) { + additionalIgnorePattern = rawE2EPattern; + } + const result = { + projectRoot: cypressResolvedConfig?.projectRoot || process.cwd(), + projectId: params.projectId, + specPattern: cypressResolvedConfig?.specPattern || "**/*.*", + excludeSpecPattern: ( + cypressResolvedConfig?.resolved.excludeSpecPattern.value ?? [] + ), + additionalIgnorePattern, + resolved: cypressResolvedConfig, + experimentalCoverageRecording: params.experimentalCoverageRecording + }; + debug6("merged config: %O", result); + return result; +} + +init_cjs_shims(); +var import_debug8 = __toESM(require("debug")); +var import_lodash3 = __toESM(require("lodash")); +var debug7 = (0, import_debug8.default)("cc:validateParams"); +async function resolveCcParams(params) { + const configFromFile = await getCcConfig( + params.project, + params.cloudConfigFile + ); + debug7("resolving cc params: %o", params); + debug7("resolving cc config file: %o", configFromFile); + const cloudServiceUrl = params.cloudServiceUrl ?? process.env.CC_API_URL ?? configFromFile.cloudServiceUrl; + const recordKey = params.recordKey ?? process.env.CC_RECORD_KEY ?? configFromFile.recordKey; + const projectId = params.projectId ?? process.env.CC_PROJECT_ID ?? configFromFile.projectId; + const testingType = params.testingType ?? "e2e"; + let batchSize = params.batchSize; + if (!batchSize) { + batchSize = testingType === "e2e" ? configFromFile.e2e.batchSize : configFromFile.component.batchSize; + } + return { + ...params, + cloudServiceUrl, + recordKey, + projectId, + batchSize, + testingType + }; +} +var projectIdError = `Cannot resolve projectId. Please use one of the following: +- provide it as a "projectId" property for "run" API method +- set CC_PROJECT_ID environment variable +- set "projectId" in "cc.config.{c}js" file`; +var cloudServiceUrlError = `Cannot resolve cloud service URL. Please use one of the following: +- provide it as a "cloudServiceUrl" property for "run" API method +- set CC_API_URL environment variable +- set "cloudServiceUrl" in "cc.config.{c}js" file`; +var cloudServiceInvalidUrlError = `Invalid cloud service URL provided`; +var recordKeyError = `Cannot resolve record key. Please use one of the following: + +- pass it as a CLI flag '-k, --key ' +- provide it as a "recordKey" property for "run" API method +- set CC_RECORD_KEY environment variable +- set "recordKey" in "cc.config.{c}js" file +`; +async function validateParams(_params) { + const params = await resolveCcParams(_params); + debug7("validating cc params: %o", params); + if (!params.cloudServiceUrl) { + throw new ValidationError(cloudServiceUrlError); + } + if (!params.projectId) { + throw new ValidationError(projectIdError); + } + if (!params.recordKey) { + throw new ValidationError(recordKeyError); + } + validateURL(params.cloudServiceUrl); + const requiredParameters = [ + "testingType", + "batchSize", + "projectId" + ]; + requiredParameters.forEach((key) => { + if (typeof params[key] === "undefined") { + error('Missing required parameter "%s"', key); + throw new Error("Missing required parameter"); + } + }); + params.tag = parseTags(params.tag); + params.autoCancelAfterFailures = getAutoCancelValue( + params.autoCancelAfterFailures + ); + debug7("validated cc params: %o", params); + return params; +} +function getAutoCancelValue(value) { + if (typeof value === "undefined") { + return void 0; + } + if (typeof value === "boolean") { + return value ? 1 : false; + } + if (typeof value === "number" && value > 0) { + return value; + } + throw new ValidationError( + `autoCancelAfterFailures: should be a positive integer or "false". Got: "${value}"` + ); +} +function isOffline(params) { + return params.record === false; +} +function parseTags(tagString) { + if (!tagString) { + return []; + } + if (Array.isArray(tagString)) { + return tagString.filter(Boolean); + } + return tagString.split(",").map((tag) => tag.trim()).filter(Boolean); +} +function validateURL(url) { + try { + new URL(url); + } catch (err) { + throw new ValidationError(`${cloudServiceInvalidUrlError}: "${url}"`); + } +} +function getCypressRunAPIParams(params) { + return { + ...import_lodash3.default.pickBy( + import_lodash3.default.omit(params, [ + "cloudDebug", + "cloudConfigFile", + "autoCancelAfterFailures", + "cloudServiceUrl", + "batchSize", + "projectId", + "key", + "recordKey", + "record", + "group", + "parallel", + "tag", + "ciBuildId", + "spec", + "exit", + "headless", + "experimentalCoverageRecording" + ]), + Boolean + ), + record: false, + env: { + ...params.env, + cc_debug_enabled: shouldEnablePluginDebug(params.cloudDebug) + } + }; +} +function preprocessParams(params) { + return { + ...params, + spec: processSpecParam(params.spec) + }; +} +function processSpecParam(spec) { + if (!spec) { + return void 0; + } + if (Array.isArray(spec)) { + return import_lodash3.default.flatten(spec.map((i) => i.split(","))); + } + return spec.split(","); +} + +init_cjs_shims(); +var import_lodash4 = __toESM(require("lodash")); +function maybePrintErrors(err) { + if (!err.response?.data || !err.response?.status) { + return; + } + const { message, errors } = err.response.data; + switch (err.response.status) { + case 401: + warn("Received 401 Unauthorized"); + break; + case 422: + spacer(1); + warn(...formatGenericError(message, errors)); + spacer(1); + break; + default: + break; + } +} +function formatGenericError(message, errors) { + if (!import_lodash4.default.isString(message)) { + return ["Unexpected error from the cloud service"]; + } + if (errors?.length === 0) { + return [message]; + } + return [ + message, + ` +${(errors ?? []).map((e) => ` - ${e}`).join("\n")} +` + ]; +} + +var debug8 = (0, import_debug10.default)("cc:api"); +var MAX_RETRIES = 3; +var TIMEOUT_MS = 30 * 1e3; +var _client = null; +async function getClient() { + if (_client) { + return _client; + } + const ccConfig = await getCcConfig(); + _client = import_axios2.default.create({ + baseURL: getAPIBaseUrl(), + timeout: TIMEOUT_MS + }); + _client.interceptors.request.use((config) => { + const ccyVerson = _ccVersion ?? "0.0.0"; + const headers = { + ...config.headers, + "x-cypress-request-attempt": config["axios-retry"]?.retryCount ?? 0, + "x-cypress-version": _cypressVersion ?? "0.0.0", + "x-ccy-version": ccyVerson, + "User-Agent": `@krivega/cc/${ccyVerson}` + }; + if (_runId) { + headers["x-cypress-run-id"] = _runId; + } + if (!headers["Content-Type"]) { + headers["Content-Type"] = "application/json"; + } + if (ccConfig.networkHeaders) { + const filteredHeaders = import_lodash5.default.omit(ccConfig.networkHeaders, [ + "x-cypress-request-attempt", + "x-cypress-version", + "x-ccy-version", + "x-cypress-run-id", + "Content-Type" + ]); + debug8("using custom network headers: %o", filteredHeaders); + Object.assign(headers, filteredHeaders); + } + const req = { + ...config, + headers + }; + debug8("network request: %o", { + ...import_lodash5.default.pick(req, "method", "url", "headers"), + data: Buffer.isBuffer(req.data) ? "buffer" : req.data + }); + return req; + }); + (0, import_axios_retry.default)(_client, { + retries: MAX_RETRIES, + retryCondition: isRetriableError, + retryDelay: getDelay, + onRetry, + shouldResetTimeout: true + }); + return _client; +} +function onRetry(retryCount, err, config) { + warn( + "Network request '%s' failed: '%s'. Next attempt is in %s (%d/%d).", + `${config.method} ${config.url}`, + err.message, + (0, import_pretty_ms.default)(getDelay(retryCount)), + retryCount, + MAX_RETRIES + ); +} +var makeRequest = async (config) => { + return (await getClient())(config).then((res) => { + debug8("network response: %o", import_lodash5.default.omit(res, "request", "config")); + return res; + }).catch((error2) => { + maybePrintErrors(error2); + throw new ValidationError(error2.message); + }); +}; + +init_cjs_shims(); +var import_lodash6 = __toESM(require("lodash")); +function printWarnings(warnings) { + warn("Notice from cloud service:"); + warnings.map((w) => { + spacer(1); + info(magenta.bold(w.message)); + Object.entries(import_lodash6.default.omit(w, "message")).map(([key, value]) => { + info("- %s: %s", key, value); + }); + spacer(1); + }); +} + +var createRun = async (payload) => { + const response = await makeRequest({ + method: "POST", + url: "/runs", + data: payload + }); + if ((response.data.warnings?.length ?? 0) > 0) { + printWarnings(response.data.warnings); + } + return response.data; +}; +var createInstance = async ({ + runId, + groupId, + machineId, + platform: platform2 +}) => { + const response = await makeRequest({ + method: "POST", + url: `runs/${runId}/instances`, + data: { + runId, + groupId, + machineId, + platform: platform2 + } + }); + return response.data; +}; +var createBatchedInstances = async (data) => { + const respone = await makeRequest({ + method: "POST", + url: `runs/${data.runId}/cy/instances`, + data + }); + return respone.data; +}; +var setInstanceTests = (instanceId, payload) => makeRequest({ + method: "POST", + url: `instances/${instanceId}/tests`, + data: payload +}).then((result) => result.data); +var updateInstanceResults = (instanceId, payload) => makeRequest({ + method: "POST", + url: `instances/${instanceId}/results`, + data: payload +}).then((result) => result.data); +var reportInstanceResultsMerged = (instanceId, payload) => makeRequest({ + method: "POST", + url: `instances/${instanceId}/cy/results`, + data: payload +}).then((result) => result.data); +var updateInstanceStdout = (instanceId, stdout2) => makeRequest({ + method: "PUT", + url: `instances/${instanceId}/stdout`, + data: { + stdout: stdout2 + } +}); + +init_cjs_shims(); + +init_cjs_shims(); + +init_cjs_shims(); + +init_cjs_shims(); +var import_debug11 = __toESM(require("debug")); +var import_lodash7 = __toESM(require("lodash")); +var debug9 = (0, import_debug11.default)("cc:ci"); +var join = (char, ...pieces) => { + return import_lodash7.default.chain(pieces).compact().join(char).value(); +}; +var toCamelObject = (obj, key) => { + return import_lodash7.default.set(obj, import_lodash7.default.camelCase(key), process.env[key]); +}; +var extract = (envKeys) => { + return import_lodash7.default.transform(envKeys, toCamelObject, {}); +}; +var isTeamFoundation = () => { + return process.env.TF_BUILD && process.env.TF_BUILD_BUILDNUMBER; +}; +var isAzureCi = () => { + return process.env.TF_BUILD && process.env.AZURE_HTTP_USER_AGENT; +}; +var isAWSCodeBuild = () => { + return import_lodash7.default.some(process.env, (val, key) => { + return /^CODEBUILD_/.test(key); + }); +}; +var isBamboo = () => { + return process.env.bamboo_buildNumber; +}; +var isCodeshipBasic = () => { + return process.env.CI_NAME && process.env.CI_NAME === "codeship" && process.env.CODESHIP; +}; +var isCodeshipPro = () => { + return process.env.CI_NAME && process.env.CI_NAME === "codeship" && !process.env.CODESHIP; +}; +var isConcourse = () => { + return import_lodash7.default.some(process.env, (val, key) => { + return /^CONCOURSE_/.test(key); + }); +}; +var isGitlab = () => { + return process.env.GITLAB_CI || process.env.CI_SERVER_NAME && /^GitLab/.test(process.env.CI_SERVER_NAME); +}; +var isGoogleCloud = () => { + return process.env.GCP_PROJECT || process.env.GCLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT; +}; +var isJenkins = () => { + return process.env.JENKINS_URL || process.env.JENKINS_HOME || process.env.JENKINS_VERSION || process.env.HUDSON_URL || process.env.HUDSON_HOME; +}; +var isWercker = () => { + return process.env.WERCKER || process.env.WERCKER_MAIN_PIPELINE_STARTED; +}; +var CI_PROVIDERS = { + appveyor: "APPVEYOR", + azure: isAzureCi, + awsCodeBuild: isAWSCodeBuild, + bamboo: isBamboo, + bitbucket: "BITBUCKET_BUILD_NUMBER", + buildkite: "BUILDKITE", + circle: "CIRCLECI", + codeshipBasic: isCodeshipBasic, + codeshipPro: isCodeshipPro, + concourse: isConcourse, + codeFresh: "CF_BUILD_ID", + drone: "DRONE", + githubActions: "GITHUB_ACTIONS", + gitlab: isGitlab, + goCD: "GO_JOB_NAME", + googleCloud: isGoogleCloud, + jenkins: isJenkins, + semaphore: "SEMAPHORE", + shippable: "SHIPPABLE", + teamcity: "TEAMCITY_VERSION", + teamfoundation: isTeamFoundation, + travis: "TRAVIS", + wercker: isWercker, + netlify: "NETLIFY", + layerci: "LAYERCI" +}; +function _detectProviderName() { + const { env } = process; + return import_lodash7.default.findKey(CI_PROVIDERS, (value) => { + if (import_lodash7.default.isString(value)) { + return env[value]; + } + if (import_lodash7.default.isFunction(value)) { + return value(); + } + }); +} +var _providerCiParams = () => { + return { + appveyor: extract([ + "APPVEYOR_JOB_ID", + "APPVEYOR_ACCOUNT_NAME", + "APPVEYOR_PROJECT_SLUG", + "APPVEYOR_BUILD_NUMBER", + "APPVEYOR_BUILD_VERSION", + "APPVEYOR_PULL_REQUEST_NUMBER", + "APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH" + ]), + azure: extract([ + "BUILD_BUILDID", + "BUILD_BUILDNUMBER", + "BUILD_CONTAINERID", + "BUILD_REPOSITORY_URI" + ]), + awsCodeBuild: extract([ + "CODEBUILD_BUILD_ID", + "CODEBUILD_BUILD_NUMBER", + "CODEBUILD_RESOLVED_SOURCE_VERSION", + "CODEBUILD_SOURCE_REPO_URL", + "CODEBUILD_SOURCE_VERSION" + ]), + bamboo: extract([ + "bamboo_buildNumber", + "bamboo_buildResultsUrl", + "bamboo_planRepository_repositoryUrl", + "bamboo_buildKey" + ]), + bitbucket: extract([ + "BITBUCKET_REPO_SLUG", + "BITBUCKET_REPO_OWNER", + "BITBUCKET_BUILD_NUMBER", + "BITBUCKET_PARALLEL_STEP", + "BITBUCKET_STEP_RUN_NUMBER", + "BITBUCKET_PR_ID", + "BITBUCKET_PR_DESTINATION_BRANCH", + "BITBUCKET_PR_DESTINATION_COMMIT" + ]), + buildkite: extract([ + "BUILDKITE_REPO", + "BUILDKITE_SOURCE", + "BUILDKITE_JOB_ID", + "BUILDKITE_BUILD_ID", + "BUILDKITE_BUILD_URL", + "BUILDKITE_BUILD_NUMBER", + "BUILDKITE_PULL_REQUEST", + "BUILDKITE_PULL_REQUEST_REPO", + "BUILDKITE_PULL_REQUEST_BASE_BRANCH" + ]), + circle: extract([ + "CIRCLE_JOB", + "CIRCLE_BUILD_NUM", + "CIRCLE_BUILD_URL", + "CIRCLE_PR_NUMBER", + "CIRCLE_PR_REPONAME", + "CIRCLE_PR_USERNAME", + "CIRCLE_COMPARE_URL", + "CIRCLE_WORKFLOW_ID", + "CIRCLE_PULL_REQUEST", + "CIRCLE_REPOSITORY_URL", + "CI_PULL_REQUEST" + ]), + codeshipBasic: extract([ + "CI_BUILD_ID", + "CI_REPO_NAME", + "CI_BUILD_URL", + "CI_PROJECT_ID", + "CI_BUILD_NUMBER", + "CI_PULL_REQUEST" + ]), + codeshipPro: extract(["CI_BUILD_ID", "CI_REPO_NAME", "CI_PROJECT_ID"]), + concourse: extract([ + "BUILD_ID", + "BUILD_NAME", + "BUILD_JOB_NAME", + "BUILD_PIPELINE_NAME", + "BUILD_TEAM_NAME", + "ATC_EXTERNAL_URL" + ]), + codeFresh: extract([ + "CF_BUILD_ID", + "CF_BUILD_URL", + "CF_CURRENT_ATTEMPT", + "CF_STEP_NAME", + "CF_PIPELINE_NAME", + "CF_PIPELINE_TRIGGER_ID", + "CF_PULL_REQUEST_ID", + "CF_PULL_REQUEST_IS_FORK", + "CF_PULL_REQUEST_NUMBER", + "CF_PULL_REQUEST_TARGET" + ]), + drone: extract([ + "DRONE_JOB_NUMBER", + "DRONE_BUILD_LINK", + "DRONE_BUILD_NUMBER", + "DRONE_PULL_REQUEST" + ]), + githubActions: extract([ + "GITHUB_WORKFLOW", + "GITHUB_ACTION", + "GITHUB_EVENT_NAME", + "GITHUB_RUN_ID", + "GITHUB_RUN_ATTEMPT", + "GITHUB_REPOSITORY" + ]), + gitlab: extract([ + "CI_PIPELINE_ID", + "CI_PIPELINE_URL", + "CI_BUILD_ID", + "CI_JOB_ID", + "CI_JOB_URL", + "CI_JOB_NAME", + "GITLAB_HOST", + "CI_PROJECT_ID", + "CI_PROJECT_URL", + "CI_REPOSITORY_URL", + "CI_ENVIRONMENT_URL", + "CI_DEFAULT_BRANCH" + ]), + goCD: extract([ + "GO_SERVER_URL", + "GO_ENVIRONMENT_NAME", + "GO_PIPELINE_NAME", + "GO_PIPELINE_COUNTER", + "GO_PIPELINE_LABEL", + "GO_STAGE_NAME", + "GO_STAGE_COUNTER", + "GO_JOB_NAME", + "GO_TRIGGER_USER", + "GO_REVISION", + "GO_TO_REVISION", + "GO_FROM_REVISION", + "GO_MATERIAL_HAS_CHANGED" + ]), + googleCloud: extract([ + "BUILD_ID", + "PROJECT_ID", + "REPO_NAME", + "BRANCH_NAME", + "TAG_NAME", + "COMMIT_SHA", + "SHORT_SHA" + ]), + jenkins: extract(["BUILD_ID", "BUILD_URL", "BUILD_NUMBER", "ghprbPullId"]), + semaphore: extract([ + "SEMAPHORE_BRANCH_ID", + "SEMAPHORE_BUILD_NUMBER", + "SEMAPHORE_CURRENT_JOB", + "SEMAPHORE_CURRENT_THREAD", + "SEMAPHORE_EXECUTABLE_UUID", + "SEMAPHORE_GIT_BRANCH", + "SEMAPHORE_GIT_DIR", + "SEMAPHORE_GIT_REF", + "SEMAPHORE_GIT_REF_TYPE", + "SEMAPHORE_GIT_REPO_SLUG", + "SEMAPHORE_GIT_SHA", + "SEMAPHORE_GIT_URL", + "SEMAPHORE_JOB_COUNT", + "SEMAPHORE_JOB_ID", + "SEMAPHORE_JOB_NAME", + "SEMAPHORE_JOB_UUID", + "SEMAPHORE_PIPELINE_ID", + "SEMAPHORE_PLATFORM", + "SEMAPHORE_PROJECT_DIR", + "SEMAPHORE_PROJECT_HASH_ID", + "SEMAPHORE_PROJECT_ID", + "SEMAPHORE_PROJECT_NAME", + "SEMAPHORE_PROJECT_UUID", + "SEMAPHORE_REPO_SLUG", + "SEMAPHORE_TRIGGER_SOURCE", + "SEMAPHORE_WORKFLOW_ID", + "PULL_REQUEST_NUMBER" + ]), + shippable: extract([ + "SHIPPABLE_BUILD_ID", + "SHIPPABLE_BUILD_NUMBER", + "SHIPPABLE_COMMIT_RANGE", + "SHIPPABLE_CONTAINER_NAME", + "SHIPPABLE_JOB_ID", + "SHIPPABLE_JOB_NUMBER", + "SHIPPABLE_REPO_SLUG", + "IS_FORK", + "IS_GIT_TAG", + "IS_PRERELEASE", + "IS_RELEASE", + "REPOSITORY_URL", + "REPO_FULL_NAME", + "REPO_NAME", + "BUILD_URL", + "BASE_BRANCH", + "HEAD_BRANCH", + "IS_PULL_REQUEST", + "PULL_REQUEST", + "PULL_REQUEST_BASE_BRANCH", + "PULL_REQUEST_REPO_FULL_NAME" + ]), + teamcity: null, + teamfoundation: extract([ + "BUILD_BUILDID", + "BUILD_BUILDNUMBER", + "BUILD_CONTAINERID" + ]), + travis: extract([ + "TRAVIS_JOB_ID", + "TRAVIS_BUILD_ID", + "TRAVIS_BUILD_WEB_URL", + "TRAVIS_REPO_SLUG", + "TRAVIS_JOB_NUMBER", + "TRAVIS_EVENT_TYPE", + "TRAVIS_COMMIT_RANGE", + "TRAVIS_BUILD_NUMBER", + "TRAVIS_PULL_REQUEST", + "TRAVIS_PULL_REQUEST_BRANCH", + "TRAVIS_PULL_REQUEST_SHA" + ]), + wercker: null, + netlify: extract([ + "BUILD_ID", + "CONTEXT", + "URL", + "DEPLOY_URL", + "DEPLOY_PRIME_URL", + "DEPLOY_ID" + ]), + layerci: extract([ + "LAYERCI_JOB_ID", + "LAYERCI_RUNNER_ID", + "RETRY_INDEX", + "LAYERCI_PULL_REQUEST", + "LAYERCI_REPO_NAME", + "LAYERCI_REPO_OWNER", + "LAYERCI_BRANCH", + "GIT_TAG" + ]) + }; +}; +var _providerCommitParams = () => { + const { env } = process; + return { + appveyor: { + sha: env.APPVEYOR_REPO_COMMIT, + branch: env.APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH || env.APPVEYOR_REPO_BRANCH, + message: join( + "\n", + env.APPVEYOR_REPO_COMMIT_MESSAGE, + env.APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED + ), + authorName: env.APPVEYOR_REPO_COMMIT_AUTHOR, + authorEmail: env.APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL + }, + awsCodeBuild: { + sha: env.CODEBUILD_RESOLVED_SOURCE_VERSION, + remoteOrigin: env.CODEBUILD_SOURCE_REPO_URL + }, + azure: { + sha: env.BUILD_SOURCEVERSION, + branch: env.BUILD_SOURCEBRANCHNAME, + message: env.BUILD_SOURCEVERSIONMESSAGE, + authorName: env.BUILD_SOURCEVERSIONAUTHOR, + authorEmail: env.BUILD_REQUESTEDFOREMAIL + }, + bamboo: { + sha: env.bamboo_planRepository_revision, + branch: env.bamboo_planRepository_branch, + authorName: env.bamboo_planRepository_username, + remoteOrigin: env.bamboo_planRepository_repositoryURL + }, + bitbucket: { + sha: env.BITBUCKET_COMMIT, + branch: env.BITBUCKET_BRANCH + }, + buildkite: { + sha: env.BUILDKITE_COMMIT, + branch: env.BUILDKITE_BRANCH, + message: env.BUILDKITE_MESSAGE, + authorName: env.BUILDKITE_BUILD_CREATOR, + authorEmail: env.BUILDKITE_BUILD_CREATOR_EMAIL, + remoteOrigin: env.BUILDKITE_REPO, + defaultBranch: env.BUILDKITE_PIPELINE_DEFAULT_BRANCH + }, + circle: { + sha: env.CIRCLE_SHA1, + branch: env.CIRCLE_BRANCH, + authorName: env.CIRCLE_USERNAME, + remoteOrigin: env.CIRCLE_REPOSITORY_URL + }, + codeshipBasic: { + sha: env.CI_COMMIT_ID, + branch: env.CI_BRANCH, + message: env.CI_COMMIT_MESSAGE, + authorName: env.CI_COMMITTER_NAME, + authorEmail: env.CI_COMMITTER_EMAIL + }, + codeshipPro: { + sha: env.CI_COMMIT_ID, + branch: env.CI_BRANCH, + message: env.CI_COMMIT_MESSAGE, + authorName: env.CI_COMMITTER_NAME, + authorEmail: env.CI_COMMITTER_EMAIL + }, + codeFresh: { + sha: env.CF_REVISION, + branch: env.CF_BRANCH, + message: env.CF_COMMIT_MESSAGE, + authorName: env.CF_COMMIT_AUTHOR + }, + drone: { + sha: env.DRONE_COMMIT_SHA, + branch: env.DRONE_SOURCE_BRANCH, + message: env.DRONE_COMMIT_MESSAGE, + authorName: env.DRONE_COMMIT_AUTHOR, + authorEmail: env.DRONE_COMMIT_AUTHOR_EMAIL, + remoteOrigin: env.DRONE_GIT_HTTP_URL, + defaultBranch: env.DRONE_REPO_BRANCH + }, + githubActions: { + sha: env.GITHUB_SHA, + branch: env.GH_BRANCH || env.GITHUB_REF, + defaultBranch: env.GITHUB_BASE_REF, + remoteBranch: env.GITHUB_HEAD_REF, + runAttempt: env.GITHUB_RUN_ATTEMPT + }, + gitlab: { + sha: env.CI_COMMIT_SHA, + branch: env.CI_COMMIT_REF_NAME, + message: env.CI_COMMIT_MESSAGE, + authorName: env.GITLAB_USER_NAME, + authorEmail: env.GITLAB_USER_EMAIL, + remoteOrigin: env.CI_REPOSITORY_URL, + defaultBranch: env.CI_DEFAULT_BRANCH + }, + googleCloud: { + sha: env.COMMIT_SHA, + branch: env.BRANCH_NAME + }, + jenkins: { + sha: env.GIT_COMMIT, + branch: env.GIT_BRANCH + }, + semaphore: { + sha: env.SEMAPHORE_GIT_SHA, + branch: env.SEMAPHORE_GIT_BRANCH, + remoteOrigin: env.SEMAPHORE_GIT_REPO_SLUG + }, + shippable: { + sha: env.COMMIT, + branch: env.BRANCH, + message: env.COMMIT_MESSAGE, + authorName: env.COMMITTER + }, + snap: null, + teamcity: null, + teamfoundation: { + sha: env.BUILD_SOURCEVERSION, + branch: env.BUILD_SOURCEBRANCHNAME, + message: env.BUILD_SOURCEVERSIONMESSAGE, + authorName: env.BUILD_SOURCEVERSIONAUTHOR + }, + travis: { + sha: env.TRAVIS_PULL_REQUEST_SHA || env.TRAVIS_COMMIT, + branch: env.TRAVIS_PULL_REQUEST_BRANCH || env.TRAVIS_BRANCH, + message: env.TRAVIS_COMMIT_MESSAGE + }, + wercker: null, + netlify: { + sha: env.COMMIT_REF, + branch: env.BRANCH, + remoteOrigin: env.REPOSITORY_URL + }, + layerci: { + sha: env.GIT_COMMIT, + branch: env.LAYERCI_BRANCH, + message: env.GIT_COMMIT_TITLE + } + }; +}; +var _get = (fn) => { + const providerName = getCiProvider(); + if (!providerName) + return {}; + return import_lodash7.default.chain(fn()).get(providerName).value(); +}; +function checkForCiBuildFromCi(ciProvider) { + if (ciProvider && detectableCiBuildIdProviders().includes(ciProvider)) + return true; + throw new ValidationError( + `Could not determine CI build ID from the environment. Please provide a unique CI build ID using the --ci-build-id CLI flag or 'ciBuildId' parameter for 'run' method.` + ); +} +function detectableCiBuildIdProviders() { + return import_lodash7.default.chain(_providerCiParams()).omitBy(import_lodash7.default.isNull).keys().value(); +} +function getCiProvider() { + return _detectProviderName() || null; +} +function getCiParams() { + return _get(_providerCiParams); +} +function getCommitParams() { + return _get(_providerCommitParams); +} +function getCI(ciBuildId) { + const params = getCiParams(); + const provider = getCiProvider(); + if (!ciBuildId) + checkForCiBuildFromCi(provider); + debug9("detected CI provider: %s", provider); + debug9("detected CI params: %O", params); + return { + params, + provider + }; +} +function getCommitDefaults(existingInfo) { + debug9("git commit existing info"); + debug9(existingInfo); + const commitParamsObj = getCommitParams(); + debug9("commit info from provider environment variables: %O", commitParamsObj); + const combined = import_lodash7.default.transform( + existingInfo, + (memo, value, key) => { + return memo[key] = import_lodash7.default.defaultTo(value || commitParamsObj[key], null); + } + ); + debug9("combined git and environment variables from provider"); + debug9(combined); + return combined; +} + +init_cjs_shims(); + +init_cjs_shims(); +var import_cypress = __toESM(require("cypress")); +var import_debug12 = __toESM(require("debug")); +var import_lodash8 = __toESM(require("lodash")); + +init_cjs_shims(); +var import_date_fns2 = require("date-fns"); + +init_cjs_shims(); +var import_date_fns = require("date-fns"); +var import_ts_pattern4 = require("ts-pattern"); +var SpecAfterResult = class _SpecAfterResult { + /** + * Combine standalone attempts and screenshots into standard result + * @param specResult - spec:after results + * @param executionState - ccy execution state + * @returns unified results, including attempts and screenshot details + */ + static getSpecAfterStandard(specAfterResults, executionState) { + return { + error: specAfterResults.error, + hooks: null, + reporter: specAfterResults.reporter, + reporterStats: specAfterResults.reporterStats, + spec: _SpecAfterResult.getSpecStandard(specAfterResults.spec), + tests: _SpecAfterResult.getTestStandard( + specAfterResults, + executionState.getAttemptsData() + ), + video: specAfterResults.video, + stats: _SpecAfterResult.getStatsStandard(specAfterResults.stats), + screenshots: _SpecAfterResult.getScreenshotsStandard( + specAfterResults.screenshots, + executionState.getScreenshotsData() + ) + }; + } + static getAttemptError(err) { + if (!err) { + return null; + } + return { + name: err.name, + message: err.message, + stack: err.stack, + codeFrame: err.codeFrame + }; + } + static getAttemptVideoTimestamp(attemptStartedAtMs, specStartedAtMs) { + return Math.max(attemptStartedAtMs - specStartedAtMs, 0); + } + static getSpecStartedAt(stats) { + if ("startedAt" in stats) { + return (0, import_date_fns.parseISO)(stats.startedAt); + } + if ("wallClockStartedAt" in stats) { + return (0, import_date_fns.parseISO)(stats.wallClockStartedAt); + } + warn("Cannot determine spec start date from stats: %o", stats); + return new Date(); + } + static getDummyTestAttemptError(attemptState) { + return (0, import_ts_pattern4.match)(attemptState).with("failed", () => ({ + name: "Error", + message: "[@krivega/cc] Could not get cypress attempt error details", + stack: "", + codeFrame: null + })).with("skipped", () => ({ + name: "Error", + message: "The test was skipped because of a hook failure", + stack: "", + codeFrame: null + })).otherwise(() => null); + } + static getTestAttemptStandard(mochaAttempt, cypressAttempt, specStartedAt) { + if (!mochaAttempt) { + const error2 = "error" in cypressAttempt ? cypressAttempt.error : null; + const duration = "wallClockDuration" in cypressAttempt ? cypressAttempt.wallClockDuration : null; + return { + state: cypressAttempt.state, + error: error2 ? error2 : _SpecAfterResult.getDummyTestAttemptError(cypressAttempt.state), + timings: "timings" in cypressAttempt ? cypressAttempt.timings : null, + wallClockStartedAt: "wallClockStartedAt" in cypressAttempt ? cypressAttempt.wallClockStartedAt : ( new Date()).toISOString(), + wallClockDuration: duration ? duration : 0, + failedFromHookId: "failedFromHookId" in cypressAttempt ? cypressAttempt.failedFromHookId : null, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : 0 + }; + } + return { + state: cypressAttempt.state, + error: "error" in cypressAttempt ? cypressAttempt.error : _SpecAfterResult.getAttemptError(mochaAttempt.err), + timings: "timings" in cypressAttempt ? cypressAttempt.timings : mochaAttempt.timings, + wallClockStartedAt: mochaAttempt.wallClockStartedAt ?? ( new Date()).toISOString(), + wallClockDuration: mochaAttempt.duration ?? -1, + failedFromHookId: "failedFromHookId" in cypressAttempt ? cypressAttempt.failedFromHookId : null, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : _SpecAfterResult.getAttemptVideoTimestamp( + (0, import_date_fns.parseISO)(mochaAttempt.wallClockStartedAt).getTime(), + specStartedAt.getTime() + ) + }; + } + static getTestStandard(specAfterResults, attempts) { + const standardTestList = (specAfterResults.tests ?? []).map((test, i) => { + const mochaAttempts = attempts.filter( + (attempt) => attempt.fullTitle === test.title.join(" ") + ); + const standardAttempts = (test.attempts ?? []).map( + (cypressAttempt, j) => { + const mochaAttempt = mochaAttempts.find( + (ma) => ma.currentRetry === j + ); + return _SpecAfterResult.getTestAttemptStandard( + mochaAttempt ?? null, + cypressAttempt, + _SpecAfterResult.getSpecStartedAt(specAfterResults.stats) + ); + } + ); + return { + body: "body" in test ? test.body : mochaAttempts[0]?.body ?? "", + testId: "testId" in test ? test.testId : mochaAttempts[0]?.id ?? `r${i}`, + title: test.title, + displayError: test.displayError, + state: test.state, + attempts: standardAttempts + }; + }); + return standardTestList; + } + static getSpecStandard(spec) { + return { + name: spec.name, + relative: spec.relative, + absolute: spec.absolute, + fileExtension: spec.fileExtension, + baseName: "baseName" in spec ? spec.baseName : "", + fileName: "fileName" in spec ? spec.fileName : "", + relativeToCommonRoot: "relativeToCommonRoot" in spec ? spec.relativeToCommonRoot : "", + specFileExtension: "specFileExtension" in spec ? spec.specFileExtension : "", + specType: "specType" in spec ? spec.specType : "" + }; + } + static getStatsStandard(stats) { + const result = { + skipped: stats.skipped, + suites: stats.suites, + tests: stats.tests, + passes: stats.passes, + pending: stats.pending, + failures: stats.failures, + wallClockStartedAt: "wallClockStartedAt" in stats ? stats.wallClockStartedAt : stats.startedAt, + wallClockEndedAt: "wallClockEndedAt" in stats ? stats.wallClockEndedAt : stats.endedAt, + wallClockDuration: "wallClockDuration" in stats ? stats.wallClockDuration : stats.duration ?? 0 + }; + result.tests = result.passes + result.failures + result.pending + result.skipped; + return result; + } + static getScreenshotsStandard(specAfterScreenshots, screenshotEvents) { + if (!specAfterScreenshots.length) { + return []; + } + return specAfterScreenshots.map((specScreenshot) => { + const es = screenshotEvents.find( + (screenshot) => screenshot.path === specScreenshot.path + ); + if (!es) { + warn( + 'Could not find details for screenshot at path "%s", skipping...', + specScreenshot.path + ); + } + return { + height: specScreenshot.height, + width: specScreenshot.width, + name: specScreenshot.name ?? es?.name ?? null, + path: specScreenshot.path, + takenAt: specScreenshot.takenAt, + testAttemptIndex: "testAttemptIndex" in specScreenshot ? specScreenshot.testAttemptIndex : es?.testAttemptIndex ?? -1, + testId: "testId" in specScreenshot ? specScreenshot.testId : es?.testId ?? "unknown", + screenshotId: "screenshotId" in specScreenshot ? specScreenshot.screenshotId : getRandomString() + }; + }); + } +}; + +var ModuleAPIResults = class _ModuleAPIResults { + static getRunScreenshots(run2) { + if ("screenshots" in run2) { + return run2.screenshots; + } + return (run2.tests ?? []).flatMap( + (t) => t.attempts.flatMap((a) => a.screenshots) + ); + } + static getTests(run2, executionState) { + const tests = run2.tests ?? []; + return tests.map((test, i) => { + const mochaAttempts = executionState.getAttemptsData().filter((attempt) => attempt.fullTitle === test.title.join(" ")); + const testId = "testId" in test ? test.testId : mochaAttempts[0]?.id ?? `r${i}`; + const runScreenshotPaths = _ModuleAPIResults.getRunScreenshots(run2).map( + (i2) => i2.path + ); + const testScreenshots = executionState.getScreenshotsData().filter((s) => runScreenshotPaths.includes(s.path)).filter((s) => s.testId === testId); + const standardAttempts = (test.attempts ?? []).map( + (cypressAttempt, j) => { + const mochaAttempt = mochaAttempts.find( + (ma) => ma.currentRetry === j + ); + const attemptScreenshots = testScreenshots.filter( + (t) => t.testAttemptIndex === j + ); + return _ModuleAPIResults.getTestAttempt( + mochaAttempt ?? null, + cypressAttempt, + attemptScreenshots, + SpecAfterResult.getSpecStartedAt(run2.stats) + ); + } + ); + return { + body: "body" in test ? test.body : mochaAttempts[0]?.body ?? "", + testId, + title: test.title, + displayError: test.displayError, + state: test.state, + attempts: standardAttempts + }; + }); + } + /** + * Convert version-specific attempt to a standard test attempt + */ + static getTestAttempt(mochaAttempt, cypressAttempt, screenshots, specStartedAt) { + if (!mochaAttempt) { + return { + state: cypressAttempt.state, + error: "error" in cypressAttempt ? cypressAttempt.error : SpecAfterResult.getDummyTestAttemptError(cypressAttempt.state), + startedAt: "startedAt" in cypressAttempt ? cypressAttempt.startedAt : ( new Date()).toISOString(), + duration: "duration" in cypressAttempt ? cypressAttempt.duration : 0, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : 0, + screenshots: "screenshots" in cypressAttempt ? cypressAttempt.screenshots : screenshots + }; + } + return { + state: cypressAttempt.state, + error: "error" in cypressAttempt ? cypressAttempt.error : SpecAfterResult.getAttemptError(mochaAttempt.err), + startedAt: "startedAt" in cypressAttempt ? cypressAttempt.startedAt : mochaAttempt.wallClockStartedAt ?? ( new Date()).toISOString(), + duration: "duration" in cypressAttempt ? cypressAttempt.duration : mochaAttempt.duration ?? -1, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : SpecAfterResult.getAttemptVideoTimestamp( + (0, import_date_fns2.parseISO)(mochaAttempt.wallClockStartedAt).getTime(), + specStartedAt.getTime() + ), + screenshots: "screenshots" in cypressAttempt ? cypressAttempt.screenshots : screenshots + }; + } + static getRun(run2, executionState) { + return { + ...run2, + tests: _ModuleAPIResults.getTests(run2, executionState), + spec: SpecAfterResult.getSpecStandard(run2.spec), + hooks: null, + shouldUploadVideo: "shouldUploadVideo" in run2 ? run2.shouldUploadVideo : true + }; + } + /** + * Converts different Cypress versions to standard form + */ + static getStandardResult(result, executionState) { + if (result.runs.length !== 1) { + throw new Error("Expected single run"); + } + const run2 = result.runs[0]; + const stats = SpecAfterResult.getStatsStandard(run2.stats); + return { + ...result, + runs: [_ModuleAPIResults.getRun(run2, executionState)], + totalSuites: 1, + totalDuration: stats.wallClockDuration, + totalTests: stats.tests, + totalFailed: stats.failures, + totalPassed: stats.passes, + totalPending: stats.pending, + totalSkipped: stats.skipped, + startedTestsAt: stats.wallClockStartedAt, + endedTestsAt: stats.wallClockEndedAt, + status: "finished" + }; + } + static isFailureResult(result) { + return "status" in result && result.status === "failed"; + } + static { + this.isSuccessResult = (result) => { + if ("status" in result) { + return result.status === "finished"; + } + return true; + }; + } + static getEmptyResult(config) { + return { + status: "finished", + totalDuration: 0, + totalSuites: 0, + totalPending: 0, + totalFailed: 0, + totalSkipped: 0, + totalPassed: 0, + totalTests: 0, + startedTestsAt: ( new Date()).toISOString(), + endedTestsAt: ( new Date()).toISOString(), + runs: [], + config + }; + } +}; + +var debug10 = (0, import_debug12.default)("cc:cypress"); +function runBareCypress(params = {}) { + const p = { + ...params, + ciBuildId: void 0, + tag: void 0, + parallel: void 0, + record: false, + group: void 0, + spec: import_lodash8.default.flatten(params.spec).join(",") + }; + debug10("Running bare Cypress with params %o", p); + return import_cypress.default.run(p); +} +async function runSpecFile({ spec }, cypressRunOptions) { + const runAPIOptions = getCypressRunAPIParams(cypressRunOptions); + const options = { + ...runAPIOptions, + config: { + ...runAPIOptions.config, + trashAssetsBeforeRuns: false + }, + env: { + ...runAPIOptions.env, + cc_ws: getWSSPort(), + cc_marker: true + }, + spec + }; + debug10("running cypress with options %o", options); + const result = await import_cypress.default.run(options); + if (ModuleAPIResults.isFailureResult(result)) { + warn('Cypress runner failed with message: "%s"', result.message); + warn( + "The following spec files will be marked as failed: %s", + spec.split(",").map((i) => ` + - ${i}`).join("") + ); + } + debug10("cypress run result %o", result); + return result; +} +var runSpecFileSafe = (spec, cypressRunOptions) => safe( + runSpecFile, + (error2) => { + const message = `Cypress runnner crashed with an error: +${error2.message} +${error2.stack}}`; + debug10("cypress run exception %o", error2); + warn('Cypress runner crashed: "%s"', message); + warn( + "The following spec files will be marked as failed: %s", + spec.spec.split(",").map((i) => ` + - ${i}`).join("") + ); + return { + status: "failed", + failures: 1, + message + }; + }, + () => { + } +)(spec, cypressRunOptions); + +init_cjs_shims(); +var isCc = () => !!process.env.CC_ENFORCE_IS_CC || getAPIBaseUrl() === "set baseURL"; + +init_cjs_shims(); +var import_commit_info = __toESM(require("@cypress/commit-info")); +var getGitInfo = async (projectRoot) => { + const commitInfo = await import_commit_info.default.commitInfo(projectRoot); + return getCommitDefaults({ + branch: commitInfo.branch, + remoteOrigin: commitInfo.remote, + authorEmail: commitInfo.email, + authorName: commitInfo.author, + message: commitInfo.message, + sha: commitInfo.sha + }); +}; + +init_cjs_shims(); +var import_debug20 = __toESM(require("debug")); + +init_cjs_shims(); +var import_debug19 = __toESM(require("debug")); + +init_cjs_shims(); +var import_promises = __toESM(require("fs/promises")); +var import_path3 = require("path"); +var getCoverageFilePath = async (coverageFile = "./.nyc_output/out.json") => { + const path5 = (0, import_path3.join)(process.cwd(), coverageFile); + try { + await import_promises.default.access(path5); + return { + path: path5, + error: false + }; + } catch (error2) { + return { + path: path5, + error: error2 + }; + } +}; + +init_cjs_shims(); + +init_cjs_shims(); + +init_cjs_shims(); +var import_debug18 = __toESM(require("debug")); + +init_cjs_shims(); +var import_debug17 = __toESM(require("debug")); + +init_cjs_shims(); + +init_cjs_shims(); +var import_lodash9 = __toESM(require("lodash")); + +init_cjs_shims(); +var emptyStats = { + totalDuration: 0, + totalSuites: 0, + totalPending: 0, + totalFailed: 0, + totalSkipped: 0, + totalPassed: 0, + totalTests: 0 +}; +var getDummyFailedTest = (start, error2) => ({ + title: ["Unknown"], + state: "failed", + body: "// This test is automatically generated due to execution failure", + displayError: error2, + attempts: [ + { + state: "failed", + startedAt: start, + duration: 0, + videoTimestamp: 0, + screenshots: [], + error: { + name: "CypressExecutionError", + message: error2, + stack: "", + codeFrame: null + } + } + ] +}); +function getFailedFakeInstanceResult(configState, { + specs, + error: error2 +}) { + const start = ( new Date()).toISOString(); + const end = ( new Date()).toISOString(); + return { + config: configState.getConfig() ?? {}, + status: "finished", + startedTestsAt: ( new Date()).toISOString(), + endedTestsAt: ( new Date()).toISOString(), + totalDuration: 0, + totalSuites: 1, + totalFailed: 1, + totalPassed: 0, + totalPending: 0, + totalSkipped: 0, + totalTests: 1, + browserName: "unknown", + browserVersion: "unknown", + browserPath: "unknown", + osName: "unknown", + osVersion: "unknown", + cypressVersion: "unknown", + runs: specs.map((s) => ({ + stats: { + suites: 1, + tests: 1, + passes: 0, + pending: 0, + skipped: 0, + failures: 1, + startedAt: start, + endedAt: end, + duration: 0 + }, + reporter: "spec", + reporterStats: { + suites: 1, + tests: 1, + passes: 0, + pending: 0, + failures: 1, + start, + end, + duration: 0 + }, + hooks: [], + error: error2, + video: null, + spec: { + name: s, + relative: s, + absolute: s, + relativeToCommonRoot: s, + baseName: s, + specType: "integration", + fileExtension: "js", + fileName: s, + specFileExtension: "js" + }, + tests: [getDummyFailedTest(start, error2)], + shouldUploadVideo: false, + skippedSpec: false + })) + }; +} + +var summarizeExecution = (input, config) => { + if (!input.length) { + return ModuleAPIResults.getEmptyResult(config); + } + const overall = input.reduce( + (acc, { + totalDuration, + totalFailed, + totalPassed, + totalPending, + totalSkipped, + totalTests, + totalSuites + }) => ({ + totalDuration: acc.totalDuration + totalDuration, + totalSuites: acc.totalSuites + totalSuites, + totalPending: acc.totalPending + totalPending, + totalFailed: acc.totalFailed + totalFailed, + totalSkipped: acc.totalSkipped + totalSkipped, + totalPassed: acc.totalPassed + totalPassed, + totalTests: acc.totalTests + totalTests + }), + emptyStats + ); + const firstResult = input[0]; + const startItems = input.map((i) => i.startedTestsAt).sort(); + const endItems = input.map((i) => i.endedTestsAt).sort(); + const runs = input.map((i) => i.runs).flat(); + return { + ...overall, + runs, + startedTestsAt: import_lodash9.default.first(startItems), + endedTestsAt: import_lodash9.default.last(endItems), + ...import_lodash9.default.pick( + firstResult, + "browserName", + "browserVersion", + "browserPath", + "osName", + "osVersion", + "cypressVersion", + "config" + ), + status: "finished" + }; +}; + +init_cjs_shims(); +var import_common_path_prefix = __toESM(require("common-path-prefix")); +var import_lodash10 = __toESM(require("lodash")); +var import_path4 = __toESM(require("path")); +var import_pretty_ms2 = __toESM(require("pretty-ms")); +var import_table = require("table"); +var failureIcon = red("\u2716"); +var successIcon = green("\u2714"); +var summaryTable = (r) => { + const overallSpecCount = r.runs.length; + const failedSpecsCount = import_lodash10.default.sum( + r.runs.filter((v) => v.stats.failures + v.stats.skipped > 0).map(() => 1) + ); + const hasFailed = failedSpecsCount > 0; + const verdict = hasFailed ? red(`${failedSpecsCount} of ${overallSpecCount} failed`) : overallSpecCount > 0 ? "All specs passed!" : "No specs executed"; + const specs = r.runs.map((r2) => r2.spec.relative); + const commonPath = getCommonPath(specs); + const data = r.runs.map((r2) => [ + r2.stats.failures + r2.stats.skipped > 0 ? failureIcon : successIcon, + stripCommonPath(r2.spec.relative, commonPath), + gray((0, import_pretty_ms2.default)(r2.stats.duration ?? 0)), + white(r2.stats.tests ?? 0), + r2.stats.passes ? green(r2.stats.passes) : gray("-"), + r2.stats.failures ? red(r2.stats.failures) : gray("-"), + r2.stats.pending ? cyan(r2.stats.pending) : gray("-"), + r2.stats.skipped ? red(r2.stats.skipped) : gray("-") + ]); + return (0, import_table.table)( + [ + [ + "", + gray("Spec"), + "", + gray("Tests"), + gray("Passing"), + gray("Failing"), + gray("Pending"), + gray("Skipped") + ], + ...data, + [ + hasFailed ? failureIcon : successIcon, + verdict, + gray((0, import_pretty_ms2.default)(r.totalDuration ?? 0)), + overallSpecCount > 0 ? white(r.totalTests ?? 0) : gray("-"), + r.totalPassed ? green(r.totalPassed) : gray("-"), + r.totalFailed ? red(r.totalFailed) : gray("-"), + r.totalPending ? cyan(r.totalPending) : gray("-"), + r.totalSkipped ? red(r.totalSkipped) : gray("-") + ] + ], + { + border, + columnDefault: { + width: 8 + }, + columns: [ + { alignment: "left", width: 2 }, + { alignment: "left", width: 30 }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" } + ], + drawHorizontalLine: (lineIndex, rowCount) => { + return lineIndex === 1 || lineIndex === 0 || lineIndex === rowCount - 1 || lineIndex === rowCount; + }, + drawVerticalLine: (lineIndex, rowCount) => { + return lineIndex === 0 || rowCount === lineIndex; + } + } + ); +}; +var border = import_lodash10.default.mapValues( + { + topBody: `\u2500`, + topJoin: `\u252C`, + topLeft: ` \u250C`, + topRight: `\u2510`, + bottomBody: `\u2500`, + bottomJoin: `\u2534`, + bottomLeft: ` \u2514`, + bottomRight: `\u2518`, + bodyLeft: ` \u2502`, + bodyRight: `\u2502`, + bodyJoin: `\u2502`, + joinBody: `\u2500`, + joinLeft: ` \u251C`, + joinRight: `\u2524`, + joinJoin: `\u253C` + }, + (v) => gray(v) +); +function getCommonPath(specs) { + if (specs.length === 0) { + return ""; + } + if (specs.length === 1) { + return import_path4.default.dirname(specs[0]) + import_path4.default.sep; + } + return (0, import_common_path_prefix.default)(specs); +} +function stripCommonPath(spec, commonPath) { + return spec.replace(commonPath, ""); +} + +init_cjs_shims(); +var import_debug16 = __toESM(require("debug")); + +init_cjs_shims(); +var import_debug14 = __toESM(require("debug")); + +init_cjs_shims(); +var import_debug13 = __toESM(require("debug")); +var import_fs3 = __toESM(require("fs")); +var readFile = import_fs3.default.promises.readFile; +var debug11 = (0, import_debug13.default)("cc:upload"); +function uploadVideo(file2, url) { + return uploadFile(file2, url, "video/mp4"); +} +function uploadImage(file2, url) { + return uploadFile(file2, url, "image/png"); +} +function uploadJson(file2, url) { + return uploadFile(file2, url, "application/json"); +} +async function uploadFile(file2, url, type) { + debug11('uploading file "%s" to "%s"', file2, url); + const f = await readFile(file2); + await makeRequest({ + url, + method: "PUT", + data: f, + headers: { + "Content-Type": type, + "Content-Disposition": `inline` + } + }); +} + +var debug12 = (0, import_debug14.default)("cc:artifacts"); +async function uploadArtifacts({ + executionState, + videoPath, + videoUploadUrl, + screenshots, + screenshotUploadUrls, + coverageFilePath, + coverageUploadUrl +}) { + debug12("uploading artifacts: %o", { + videoPath, + videoUploadUrl, + screenshots, + screenshotUploadUrls, + coverageFilePath, + coverageUploadUrl + }); + const totalUploads = (videoPath ? 1 : 0) + screenshots.length + (coverageUploadUrl ? 1 : 0); + if (totalUploads === 0) { + return; + } + if (videoUploadUrl && videoPath) { + await safe( + uploadVideo, + (e) => { + debug12("failed uploading video %s. Error: %o", videoPath, e); + executionState.addWarning( + `Failed uploading video ${videoPath}. +${dim(e)}` + ); + }, + () => debug12("success uploading", videoPath) + )(videoPath, videoUploadUrl); + } + if (screenshotUploadUrls && screenshotUploadUrls.length) { + await Promise.all( + screenshots.map((screenshot) => { + const url = screenshotUploadUrls.find( + (urls) => urls.screenshotId === screenshot.screenshotId + )?.uploadUrl; + if (!url) { + debug12( + "No upload url for screenshot %o, screenshotUploadUrls: %o", + screenshot, + screenshotUploadUrls + ); + executionState.addWarning( + `No upload URL for screenshot ${screenshot.path}` + ); + return Promise.resolve(); + } + return safe( + uploadImage, + (e) => { + debug12( + "failed uploading screenshot %s. Error: %o", + screenshot.path, + e + ); + executionState.addWarning( + `Failed uploading screenshot ${screenshot.path}. +${dim(e)}` + ); + }, + () => debug12("success uploading", screenshot.path) + )(screenshot.path, url); + }) + ); + } + if (coverageUploadUrl && coverageFilePath) { + await safe( + uploadJson, + (e) => { + debug12( + "failed uploading coverage file %s. Error: %o", + coverageFilePath, + e + ); + executionState.addWarning( + `Failed uploading coverage file ${coverageFilePath}. +${dim(e)}` + ); + }, + () => debug12("success uploading", coverageFilePath) + )(coverageFilePath, coverageUploadUrl); + } +} +var uploadStdoutSafe = safe( + updateInstanceStdout, + () => { + }, + () => { + } +); + +init_cjs_shims(); + +init_cjs_shims(); +var state = { + cancellationReason: null +}; +var setCancellationReason = (reason) => { + if (state.cancellationReason) { + return; + } + state.cancellationReason = reason; + getPubSub().emit("run:cancelled", reason); +}; + +init_cjs_shims(); +var import_debug15 = __toESM(require("debug")); +var debug13 = (0, import_debug15.default)("cc:results"); +var getInstanceResultPayload = (runResult, coverageFilePath) => { + debug13("generating instance result payload from %o", runResult); + return { + stats: StandardResultsToAPIResults.getStats(runResult.stats), + reporterStats: runResult.reporterStats, + exception: runResult.error ?? null, + video: !!runResult.video, + screenshots: StandardResultsToAPIResults.getAllScreenshots(runResult), + hasCoverage: !!coverageFilePath, + tests: (runResult.tests ?? []).map( + StandardResultsToAPIResults.getTestForResults + ) + }; +}; +var getInstanceTestsPayload = (runResult, config) => { + return { + config: { + ...config.getConfig(), + videoUploadOnPasses: config.getConfig()?.videoUploadOnPasses ?? true + }, + tests: (runResult.tests ?? []).map( + StandardResultsToAPIResults.getTestForSetTests + ), + hooks: runResult.hooks + }; +}; +var StandardResultsToAPIResults = class _StandardResultsToAPIResults { + static getTestAttempt(attempt) { + return { + state: attempt.state, + error: attempt.error, + wallClockStartedAt: attempt.startedAt, + wallClockDuration: attempt.duration, + videoTimestamp: attempt.videoTimestamp + }; + } + static getTestForResults(test, index) { + return { + displayError: test.displayError, + state: test.state, + attempts: (test.attempts ?? []).map( + _StandardResultsToAPIResults.getTestAttempt + ), + clientId: `r${index}` + }; + } + static getTestForSetTests(test, index) { + return { + body: "redacted", + title: test.title, + clientId: `r${index}` + }; + } + static getAllScreenshots(run2) { + return (run2.tests ?? []).flatMap( + (t, i) => t.attempts.flatMap( + (a, j) => a.screenshots.map((s) => ({ + ...s, + testId: `r${i}`, + testAttemptIndex: j, + screenshotId: getRandomString() + })) + ) + ); + } + static getStats(stats) { + return { + ...stats, + wallClockDuration: stats.duration, + wallClockStartedAt: stats.startedAt, + wallClockEndedAt: stats.endedAt + }; + } +}; + +var debug14 = (0, import_debug16.default)("cc:results"); +async function getReportResultsTask(instanceId, executionState, configState, stdout2, coverageFilePath) { + const results = executionState.getInstanceResults(configState, instanceId); + const run2 = results.runs[0]; + if (!run2) { + throw new Error("No run found in Cypress results"); + } + const instanceResults = getInstanceResultPayload(run2, coverageFilePath); + const instanceTests = getInstanceTestsPayload(run2, configState); + const { videoUploadUrl, screenshotUploadUrls, coverageUploadUrl, cloud } = await reportResults(instanceId, instanceTests, instanceResults); + if (cloud?.shouldCancel) { + debug14("instance %s should cancel", instanceId); + setCancellationReason(cloud.shouldCancel); + } + debug14("instance %s artifact upload instructions %o", instanceId, { + videoUploadUrl, + screenshotUploadUrls, + coverageUploadUrl + }); + return Promise.all([ + uploadArtifacts({ + executionState, + videoUploadUrl, + videoPath: run2.video, + screenshotUploadUrls, + screenshots: instanceResults.screenshots, + coverageUploadUrl, + coverageFilePath + }), + uploadStdoutSafe(instanceId, getInitialOutput() + stdout2) + ]); +} +async function reportResults(instanceId, instanceTests, instanceResults) { + debug14("reporting instance %s results...", instanceId); + if (isCc()) { + return reportInstanceResultsMerged(instanceId, { + tests: instanceTests, + results: instanceResults + }); + } + await setInstanceTests(instanceId, instanceTests); + return updateInstanceResults(instanceId, instanceResults); +} + +var debug15 = (0, import_debug17.default)("cc:reportTask"); +var reportTasks = []; +var createReportTask = (configState, executionState, instanceId) => { + const instance = executionState.getInstance(instanceId); + if (!instance) { + error("Cannot find execution state for instance %s", instanceId); + return; + } + if (instance.reportStartedAt) { + debug15("Report task already created for instance %s", instanceId); + return; + } + instance.reportStartedAt = new Date(); + debug15("Creating report task for instanceId %s", instanceId); + reportTasks.push( + getReportResultsTask( + instanceId, + executionState, + configState, + instance.output ?? "no output captured", + instance.coverageFilePath + ).catch(error) + ); +}; +var createReportTaskSpec = (configState, executionState, spec) => { + const i = executionState.getSpec(spec); + if (!i) { + error("Cannot find execution state for spec %s", spec); + return; + } + debug15("Creating report task for spec %s", spec); + return createReportTask(configState, executionState, i.instanceId); +}; + +var debug16 = (0, import_debug18.default)("cc:runner"); +async function runTillDone(executionState, configState, { + runId, + groupId, + machineId, + platform: platform2, + specs: allSpecs +}, params) { + let hasMore = true; + while (hasMore) { + const newTasks = await runBatch(executionState, configState, { + runMeta: { + runId, + groupId, + machineId, + platform: platform2 + }, + allSpecs, + params + }); + if (!newTasks.length) { + debug16("No more tasks to run. Uploads queue: %d", reportTasks.length); + hasMore = false; + break; + } + newTasks.forEach( + (t) => createReportTask(configState, executionState, t.instanceId) + ); + } +} +async function runBatch(executionState, configState, { + runMeta, + params, + allSpecs +}) { + let batch = { + specs: [], + claimedInstances: 0, + totalInstances: 0 + }; + if (isCc()) { + debug16("Getting batched tasks: %d", params.batchSize); + batch = await createBatchedInstances({ + ...runMeta, + batchSize: params.batchSize + }); + debug16("Got batched tasks: %o", batch); + } else { + const response = await createInstance(runMeta); + if (response.spec !== null && response.instanceId !== null) { + batch.specs.push({ + spec: response.spec, + instanceId: response.instanceId + }); + } + batch.claimedInstances = response.claimedInstances; + batch.totalInstances = response.totalInstances; + } + if (batch.specs.length === 0) { + return []; + } + batch.specs.forEach((i) => executionState.initInstance(i)); + divider(); + info( + "Running: %s (%d/%d)", + batch.specs.map((s) => s.spec).join(", "), + batch.claimedInstances, + batch.totalInstances + ); + const batchedResult = await runSpecFileSafe( + { + spec: batch.specs.map((bs) => getSpecAbsolutePath(allSpecs, bs.spec)).join(",") + }, + params + ); + title("blue", "Reporting results and artifacts in background..."); + const output = getCapturedOutput(); + batch.specs.forEach((spec) => { + executionState.setInstanceOutput(spec.instanceId, output); + const singleSpecResult = getSingleSpecRunResult(spec.spec, batchedResult); + if (!singleSpecResult) { + return; + } + getPubSub().emit("run:result", { + specRelative: spec.spec, + instanceId: spec.instanceId, + runResult: singleSpecResult + }); + }); + resetCapture(); + return batch.specs; +} +function getSingleSpecRunResult(specRelative, batchedResult) { + if (!ModuleAPIResults.isSuccessResult(batchedResult)) { + return; + } + const run2 = batchedResult.runs.find((r) => r.spec.relative === specRelative); + if (!run2) { + return; + } + return { + ...batchedResult, + runs: [run2] + }; +} +function getSpecAbsolutePath(allSpecs, relative) { + const absolutePath = allSpecs.find((i) => i.relative === relative)?.absolute; + if (!absolutePath) { + warn( + 'Cannot find absolute path for spec. Spec: "%s", candidates: %o', + relative, + allSpecs + ); + throw new Error(`Cannot find absolute path for spec`); + } + return absolutePath; +} + +var cancellable = null; +function onRunCancelled(reason) { + warn( + `Run cancelled: %s. Waiting for uploads to complete and stopping execution...`, + reason + ); + cancellable?.cancel(); +} +async function runTillDoneOrCancelled(...args) { + return new Promise((_resolve, _reject) => { + cancellable = new BPromise((resolve, reject, onCancel) => { + if (!onCancel) { + _reject(new Error("BlueBird is misconfigured: onCancel is undefined")); + return; + } + onCancel(() => _resolve(void 0)); + runTillDone(...args).then( + () => { + resolve(); + _resolve(void 0); + }, + (error2) => { + reject(); + _reject(error2); + } + ); + }); + getPubSub().addListener("run:cancelled", onRunCancelled); + }).finally(() => { + getPubSub().removeListener("run:cancelled", onRunCancelled); + }); +} + +var debug17 = (0, import_debug19.default)("cc:events"); +function handleScreenshotEvent(screenshot, executionState) { + const data = { + ...screenshot, + testId: executionState.getCurrentTestID(), + height: screenshot.dimensions.height, + width: screenshot.dimensions.width + }; + executionState.addScreenshotsData(data); +} +function handleTestBefore(testAttempt, executionState) { + const parsed = JSON.parse(testAttempt); + executionState.setCurrentTestID(parsed.id); +} +function handleTestAfter(testAttempt, executionState) { + const test = JSON.parse(testAttempt); + executionState.addAttemptsData(test); +} +async function handleSpecAfter({ + executionState, + configState, + spec, + results, + experimentalCoverageRecording = false +}) { + debug17("after:spec %s %o", spec.relative, results); + executionState.setSpecAfter( + spec.relative, + SpecAfterResult.getSpecAfterStandard(results, executionState) + ); + executionState.setSpecOutput(spec.relative, getCapturedOutput()); + const config = configState.getConfig(); + if (experimentalCoverageRecording) { + const config2 = configState.getConfig(); + const { path: path5, error: error2 } = await getCoverageFilePath( + config2?.env?.coverageFile + ); + if (!error2) { + executionState.setSpecCoverage(spec.relative, path5); + } else { + executionState.addWarning( + `Error reading coverage file "${path5}". Coverage recording will be skipped. +${dim( + error2 + )}` + ); + } + } + createReportTaskSpec(configState, executionState, spec.relative); +} + +var debug18 = (0, import_debug20.default)("cc:events"); +function listenToEvents(configState, executionState, experimentalCoverageRecording = false) { + getPubSub().on( + "run:result", + ({ + instanceId, + runResult, + specRelative + }) => { + debug18("%s %s: %o", "run:result", instanceId, runResult); + executionState.setInstanceResult( + instanceId, + ModuleAPIResults.getStandardResult(runResult, executionState) + ); + } + ); + getPubSub().on("test:after:run", (payload) => { + debug18("%s %o", "test:after:run", payload); + handleTestAfter(payload, executionState); + }); + getPubSub().on("test:before:run", (payload) => { + debug18("%s %o", "test:before:run", payload); + handleTestBefore(payload, executionState); + }); + getPubSub().on( + "after:screenshot", + (screenshot) => { + debug18("%s %o", "after:screenshot", screenshot); + handleScreenshotEvent(screenshot, executionState); + } + ); + getPubSub().on( + "after:spec", + async ({ + spec, + results + }) => { + await handleSpecAfter({ + spec, + results, + executionState, + configState, + experimentalCoverageRecording + }); + } + ); +} + +init_cjs_shims(); + +init_cjs_shims(); +var import_debug21 = __toESM(require("debug")); +var debug19 = (0, import_debug21.default)("cc:browser"); +function guessBrowser(browser, availableBrowsers = []) { + debug19( + "guessing browser from '%s', available browsers: %o", + browser, + availableBrowsers + ); + let result = availableBrowsers.find((b) => b.name === browser); + if (result) { + debug19("identified browser by name: %o", result); + return { + browserName: result.displayName, + browserVersion: result.version + }; + } + result = availableBrowsers.find((b) => b.path === browser); + if (result) { + debug19("identified browser by path: %o", result); + return { + browserName: result.displayName ?? result.name, + browserVersion: result.version + }; + } + warn("Unable to identify browser name and version"); + return { + browserName: "unknown", + browserVersion: "unknown" + }; +} + +init_cjs_shims(); +var import_debug22 = __toESM(require("debug")); +var import_getos = __toESM(require("getos")); +var import_os = require("os"); +var import_util2 = require("util"); +var debug20 = (0, import_debug22.default)("cc:platform"); +var getOsVersion = async () => { + if ((0, import_os.platform)() === "linux") { + try { + const linuxOs = await (0, import_util2.promisify)(import_getos.default)(); + if ("dist" in linuxOs && "release" in linuxOs) { + return [linuxOs.dist, linuxOs.release].join(" - "); + } else { + return (0, import_os.release)(); + } + } catch { + return (0, import_os.release)(); + } + } + return (0, import_os.release)(); +}; +var getPlatformInfo = async () => { + const osVersion = await getOsVersion(); + const result = { + osName: (0, import_os.platform)(), + osVersion, + osCpus: (0, import_os.cpus)(), + osMemory: { + free: (0, import_os.freemem)(), + total: (0, import_os.totalmem)() + } + }; + debug20("platform info: %o", result); + return result; +}; + +async function getPlatform({ + browser, + config +}) { + return { + ...await getPlatformInfo(), + ...guessBrowser(browser ?? "electron", config.resolved?.browsers) + }; +} + +init_cjs_shims(); +async function shutdown() { + await stopWSS(); +} + +init_cjs_shims(); + +init_cjs_shims(); + +init_cjs_shims(); +var import_debug23 = __toESM(require("debug")); +var import_path6 = __toESM(require("path")); +var import_common_path_prefix2 = __toESM(require("common-path-prefix")); +var import_globby = __toESM(require("globby")); +var import_lodash11 = __toESM(require("lodash")); +var import_os2 = __toESM(require("os")); + +init_cjs_shims(); +var import_path5 = __toESM(require("path")); +function toArray(val) { + return val ? typeof val === "string" ? [val] : val : []; +} +function toPosix(file2, sep = import_path5.default.sep) { + return file2.split(sep).join(import_path5.default.posix.sep); +} + +var debug21 = (0, import_debug23.default)("cc:specs"); +async function findSpecs({ + projectRoot, + testingType, + specPattern, + configSpecPattern, + excludeSpecPattern, + additionalIgnorePattern +}) { + configSpecPattern = toArray(configSpecPattern); + specPattern = toArray(specPattern); + excludeSpecPattern = toArray(excludeSpecPattern) || []; + additionalIgnorePattern = toArray(additionalIgnorePattern) || []; + debug21("exploring spec files for execution %O", { + testingType, + projectRoot, + specPattern, + configSpecPattern, + excludeSpecPattern, + additionalIgnorePattern + }); + if (!specPattern || !configSpecPattern) { + throw Error("Could not find glob patterns for exploring specs"); + } + let specAbsolutePaths = await getFilesByGlob(projectRoot, specPattern, { + absolute: true, + ignore: [...excludeSpecPattern, ...additionalIgnorePattern] + }); + if (!import_lodash11.default.isEqual(specPattern, configSpecPattern)) { + const defaultSpecAbsolutePaths = await getFilesByGlob( + projectRoot, + configSpecPattern, + { + absolute: true, + ignore: [...excludeSpecPattern, ...additionalIgnorePattern] + } + ); + specAbsolutePaths = import_lodash11.default.intersection( + specAbsolutePaths, + defaultSpecAbsolutePaths + ); + } + return matchedSpecs({ + projectRoot, + testingType, + specAbsolutePaths, + specPattern + }); +} +async function getFilesByGlob(projectRoot, glob, globOptions) { + const workingDirectoryPrefix = import_path6.default.join(projectRoot, import_path6.default.sep); + const globs = [].concat(glob).map( + (globPattern) => globPattern.startsWith("./") ? globPattern.replace("./", "") : globPattern + ).map((globPattern) => { + if (globPattern.startsWith(workingDirectoryPrefix)) { + return globPattern.replace(workingDirectoryPrefix, ""); + } + return globPattern; + }); + if (import_os2.default.platform() === "win32") { + debug21("updating glob patterns to POSIX"); + for (const i in globs) { + const cur = globs[i]; + if (!cur) + throw new Error("undefined glob received"); + globs[i] = toPosix(cur); + } + } + try { + debug21("globbing pattern(s): %o", globs); + debug21("within directory: %s", projectRoot); + return matchGlobs(globs, { + onlyFiles: true, + absolute: true, + cwd: projectRoot, + ...globOptions, + ignore: (globOptions?.ignore ?? []).concat("**/node_modules/**") + }); + } catch (e) { + debug21("error in getFilesByGlob %o", e); + return []; + } +} +var matchGlobs = async (globs, globbyOptions) => { + return await (0, import_globby.default)(globs, globbyOptions); +}; +function matchedSpecs({ + projectRoot, + testingType, + specAbsolutePaths +}) { + debug21("found specs %o", specAbsolutePaths); + let commonRoot = ""; + if (specAbsolutePaths.length === 1) { + commonRoot = import_path6.default.dirname(specAbsolutePaths[0]); + } else { + commonRoot = (0, import_common_path_prefix2.default)(specAbsolutePaths); + } + return specAbsolutePaths.map( + (absolute) => transformSpec({ + projectRoot, + absolute, + testingType, + commonRoot, + platform: import_os2.default.platform(), + sep: import_path6.default.sep + }) + ); +} +function transformSpec({ + projectRoot, + absolute, + testingType, + commonRoot, + platform: platform2, + sep +}) { + if (platform2 === "win32") { + absolute = toPosix(absolute, sep); + projectRoot = toPosix(projectRoot, sep); + } + const relative = import_path6.default.relative(projectRoot, absolute); + const parsedFile = import_path6.default.parse(absolute); + const fileExtension = import_path6.default.extname(absolute); + const specFileExtension = [".spec", ".test", "-spec", "-test", ".cy"].map((ext) => ext + fileExtension).find((ext) => absolute.endsWith(ext)) || fileExtension; + const parts = absolute.split(projectRoot); + let name = parts[parts.length - 1] || ""; + if (name.startsWith("/")) { + name = name.slice(1); + } + const LEADING_SLASH = /^\/|/g; + const relativeToCommonRoot = absolute.replace(commonRoot, "").replace(LEADING_SLASH, ""); + return { + fileExtension, + baseName: parsedFile.base, + fileName: parsedFile.base.replace(specFileExtension, ""), + specFileExtension, + relativeToCommonRoot, + specType: testingType === "component" ? "component" : "integration", + name, + relative, + absolute + }; +} + +var getSpecFiles = async ({ + config, + params +}) => { + const specPattern = getSpecPattern(config.specPattern, params.spec); + const specs = await findSpecs({ + projectRoot: params.project ?? config.projectRoot, + testingType: params.testingType, + specPattern, + configSpecPattern: config.specPattern, + excludeSpecPattern: config.excludeSpecPattern, + additionalIgnorePattern: config.additionalIgnorePattern + }); + if (specs.length === 0) { + warn( + "Found no spec files. Was looking for spec files that match both configSpecPattern and specPattern relative to projectRoot. Configuration: %O", + { + projectRoot: config.projectRoot, + specPattern, + configSpecPattern: config.specPattern, + excludeSpecPattern: [ + config.excludeSpecPattern, + config.additionalIgnorePattern + ].flat(2), + testingType: params.testingType + } + ); + } + return { specs, specPattern }; +}; +function getSpecPattern(configPattern, explicit) { + return explicit || configPattern; +} + +init_cjs_shims(); + +init_cjs_shims(); +var ConfigState = class { + constructor() { + this._config = void 0; + } + setConfig(c) { + this._config = c; + } + getConfig() { + return this._config; + } +}; + +init_cjs_shims(); + +init_cjs_shims(); +var SpecAfterToModuleAPIMapper = class _SpecAfterToModuleAPIMapper { + static getTestAttempt(attempt, screenshots) { + return { + ...attempt, + duration: attempt.wallClockDuration, + startedAt: attempt.wallClockStartedAt, + screenshots + }; + } + static getTest(t, screenshots) { + return { + ...t, + attempts: t.attempts.map( + (a, i) => _SpecAfterToModuleAPIMapper.getTestAttempt( + a, + screenshots.filter( + (s) => s.testId === t.testId && s.testAttemptIndex === i + ) + ) + ) + }; + } + static convert(specAfterResult, configState) { + const stats = { + duration: specAfterResult.stats.wallClockDuration, + endedAt: specAfterResult.stats.wallClockEndedAt, + startedAt: specAfterResult.stats.wallClockStartedAt, + failures: specAfterResult.stats.failures ?? 0, + passes: specAfterResult.stats.passes ?? 0, + pending: specAfterResult.stats.pending ?? 0, + skipped: specAfterResult.stats.skipped ?? 0, + suites: specAfterResult.stats.suites ?? 0, + tests: specAfterResult.stats.tests ?? 0 + }; + return { + status: "finished", + config: configState.getConfig(), + totalDuration: stats.duration, + totalSuites: stats.suites, + totalTests: stats.tests, + totalFailed: stats.failures, + totalPassed: stats.passes, + totalPending: stats.pending, + totalSkipped: stats.skipped ?? 0, + startedTestsAt: stats.startedAt, + endedTestsAt: stats.endedAt, + runs: [ + { + stats, + reporter: specAfterResult.reporter, + reporterStats: specAfterResult.reporterStats ?? null, + spec: specAfterResult.spec, + error: specAfterResult.error, + video: specAfterResult.video, + shouldUploadVideo: true, + hooks: specAfterResult.hooks, + tests: (specAfterResult.tests ?? []).map( + (t) => _SpecAfterToModuleAPIMapper.getTest(t, specAfterResult.screenshots) + ) + } + ] + }; + } + static backfillException(result) { + return { + ...result, + runs: result.runs.map(_SpecAfterToModuleAPIMapper.backfillExceptionRun) + }; + } + static backfillExceptionRun(run2) { + if (!run2.error) { + return run2; + } + return { + ...run2, + tests: [getFakeTestFromException(run2.error, run2.stats)] + }; + } +}; +function getFakeTestFromException(error2, stats) { + return { + title: ["Unknown"], + body: "", + displayError: error2.split("\n")[0], + state: "failed", + attempts: [ + { + state: "failed", + duration: 0, + error: { + name: "Error", + message: error2.split("\n")[0], + stack: error2, + codeFrame: null + }, + screenshots: [], + startedAt: stats.startedAt, + videoTimestamp: 0 + } + ] + }; +} + +var import_debug24 = __toESM(require("debug")); +var debug22 = (0, import_debug24.default)("cc:state"); +var ExecutionState = class { + constructor() { + this.warnings = new Set(); + this.attemptsData = []; + this.screenshotsData = []; + this.state = {}; + } + getWarnings() { + return this.warnings; + } + addWarning(warning) { + this.warnings.add(warning); + } + getResults(configState) { + return Object.values(this.state).map( + (i) => this.getInstanceResults(configState, i.instanceId) + ); + } + getInstance(instanceId) { + return this.state[instanceId]; + } + getSpec(spec) { + return Object.values(this.state).find((i) => i.spec === spec); + } + initInstance({ + instanceId, + spec + }) { + debug22('Init execution state for "%s"', spec); + this.state[instanceId] = { + instanceId, + spec, + createdAt: new Date() + }; + } + setSpecBefore(spec) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + i.specBefore = new Date(); + } + setSpecCoverage(spec, coverageFilePath) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + debug22("Experimental: coverageFilePath was set"); + i.coverageFilePath = coverageFilePath; + } + setSpecAfter(spec, results) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + i.specAfter = new Date(); + i.specAfterResults = results; + } + setSpecOutput(spec, output) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + this.setInstanceOutput(i.instanceId, output); + } + setInstanceOutput(instanceId, output) { + const i = this.state[instanceId]; + if (!i) { + warn('Cannot find execution state for instance "%s"', instanceId); + return; + } + if (i.output) { + debug22('Instance "%s" already has output', instanceId); + return; + } + i.output = output; + } + setInstanceResult(instanceId, runResults) { + const i = this.state[instanceId]; + if (!i) { + warn('Cannot find execution state for instance "%s"', instanceId); + return; + } + i.runResults = { + ...runResults, + status: "finished" + }; + i.runResultsReportedAt = new Date(); + } + getInstanceResults(configState, instanceId) { + const i = this.getInstance(instanceId); + if (!i) { + error('Cannot find execution state for instance "%s"', instanceId); + return getFailedFakeInstanceResult(configState, { + specs: ["unknown"], + error: `[cc] Error while processing cypress results for instance ${instanceId}. See the console output for details.` + }); + } + if (i.specAfterResults) { + debug22('Using spec:after results for %s "%s"', instanceId, i.spec); + return SpecAfterToModuleAPIMapper.backfillException( + SpecAfterToModuleAPIMapper.convert(i.specAfterResults, configState) + ); + } + if (i.runResults) { + debug22('Using runResults for %s "%s"', instanceId, i.spec); + return SpecAfterToModuleAPIMapper.backfillException(i.runResults); + } + debug22('No results detected for "%s"', i.spec); + return getFailedFakeInstanceResult(configState, { + specs: [i.spec], + error: `No results detected for the spec file. That usually happens because of cypress crash. See the console output for details.` + }); + } + addAttemptsData(attemptDetails) { + this.attemptsData.push(attemptDetails); + } + getAttemptsData() { + return this.attemptsData; + } + addScreenshotsData(screenshotsData) { + this.screenshotsData.push(screenshotsData); + } + getScreenshotsData() { + return this.screenshotsData; + } + setCurrentTestID(testID) { + this.currentTestID = testID; + } + getCurrentTestID() { + return this.currentTestID; + } +}; + +init_cjs_shims(); +var import_chalk2 = __toESM(require("chalk")); +var import_plur = __toESM(require("plur")); +function printWarnings2(executionState) { + const warnings = Array.from(executionState.getWarnings()); + if (warnings.length > 0) { + warn( + `${warnings.length} ${(0, import_plur.default)( + "Warning", + warnings.length + )} encountered during the execution: +${warnings.map( + (w, i) => ` +${import_chalk2.default.yellow(`[${i + 1}/${warnings.length}]`)} ${w}` + ).join("\n")}` + ); + } +} + +var debug23 = (0, import_debug25.default)("cc:run"); +async function run(params = {}) { + const executionState = new ExecutionState(); + const configState = new ConfigState(); + activateDebug(params.cloudDebug); + debug23("run params %o", params); + params = preprocessParams(params); + debug23("params after preprocess %o", params); + if (isOffline(params)) { + info(`Skipping cloud orchestration because --record is set to false`); + return runBareCypress(params); + } + const validatedParams = await validateParams(params); + setAPIBaseUrl(validatedParams.cloudServiceUrl); + if (!isCc()) { + console.log(getLegalNotice()); + } + const { + recordKey, + projectId, + group, + parallel, + ciBuildId, + tag, + testingType, + batchSize, + autoCancelAfterFailures, + experimentalCoverageRecording + } = validatedParams; + const config = await getMergedConfig(validatedParams); + configState.setConfig(config?.resolved); + const { specs, specPattern } = await getSpecFiles({ + config, + params: validatedParams + }); + if (specs.length === 0) { + return; + } + const platform2 = await getPlatform({ + config, + browser: validatedParams.browser + }); + info(`@krivega/cc version: ${dim(_ccVersion)}`); + info(`Cypress version: ${dim(_cypressVersion)}`); + info("Discovered %d spec files", specs.length); + info( + `Tags: ${tag.length > 0 ? tag.join(",") : false}; Group: ${group ?? false}; Parallel: ${parallel ?? false}; Batch Size: ${batchSize}` + ); + info("Connecting to cloud orchestration service..."); + const run2 = await createRun({ + ci: getCI(ciBuildId), + specs: specs.map((spec) => spec.relative), + commit: await getGitInfo(config.projectRoot), + group, + platform: platform2, + parallel: parallel ?? false, + ciBuildId, + projectId, + recordKey, + specPattern: [specPattern].flat(2), + tags: tag, + testingType, + batchSize, + autoCancelAfterFailures, + coverageEnabled: experimentalCoverageRecording + }); + setRunId(run2.runId); + info("\u{1F3A5} Run URL:", bold(run2.runUrl)); + cutInitialOutput(); + await startWSS(); + listenToEvents( + configState, + executionState, + config.experimentalCoverageRecording + ); + await runTillDoneOrCancelled( + executionState, + configState, + { + runId: run2.runId, + groupId: run2.groupId, + machineId: run2.machineId, + platform: platform2, + specs + }, + validatedParams + ); + divider(); + await Promise.allSettled(reportTasks); + const _summary = summarizeExecution( + executionState.getResults(configState), + config + ); + title("white", "Cloud Run Finished"); + console.log(summaryTable(_summary)); + printWarnings2(executionState); + info("\n\u{1F3C1} Recorded Run:", bold(run2.runUrl)); + await shutdown(); + spacer(); + return { + ..._summary, + runUrl: run2.runUrl + }; +} + +init_cjs_shims(); + +init_cjs_shims(); +var import_debug27 = __toESM(require("debug")); + +init_cjs_shims(); +var import_lodash12 = __toESM(require("lodash")); +var import_node_assert = __toESM(require("assert")); +var nestedObjectsInCurlyBracesRe = /\{(.+?)\}/g; +var nestedArraysInSquareBracketsRe = /\[(.+?)\]/g; +var everythingAfterFirstEqualRe = /=(.*)/; +var sanitizeAndConvertNestedArgs = (str, argName) => { + if (!str) { + return; + } + (0, import_node_assert.default)(import_lodash12.default.isString(argName) && argName.trim() !== ""); + try { + if (typeof str === "object") { + return str; + } + const parsed = tryJSONParse(str); + if (parsed) { + return parsed; + } + return import_lodash12.default.chain(str).replace(nestedObjectsInCurlyBracesRe, commasToPipes).replace(nestedArraysInSquareBracketsRe, commasToPipes).split(",").map((pair) => { + return pair.split(everythingAfterFirstEqualRe); + }).fromPairs().mapValues(JSONOrCoerce).value(); + } catch (err) { + error("could not parse CLI option '%s' value: %s", argName, str); + error("error %o", err); + return void 0; + } +}; +var tryJSONParse = (str) => { + try { + return JSON.parse(str) === Infinity ? null : JSON.parse(str); + } catch (err) { + return null; + } +}; +var commasToPipes = (match5) => { + return match5.split(",").join("|"); +}; +var pipesToCommas = (str) => { + return str.split("|").join(","); +}; +var JSONOrCoerce = (str) => { + const parsed = tryJSONParse(str); + if (parsed) { + return parsed; + } + str = pipesToCommas(str); + const parsed2 = tryJSONParse(str); + if (parsed2) { + return parsed2; + } + return coerce(str); +}; +var coerce = (value) => { + const num = import_lodash12.default.toNumber(value); + if (import_lodash12.default.invoke(num, "toString") === value) { + return num; + } + const bool = toBoolean(value); + if (import_lodash12.default.invoke(bool, "toString") === value) { + return bool; + } + const obj = tryJSONParse(value); + if (obj && typeof obj === "object") { + return obj; + } + const arr = import_lodash12.default.toArray(value); + if (import_lodash12.default.invoke(arr, "toString") === value) { + return arr; + } + return value; +}; +var toBoolean = (value) => { + switch (value) { + case "true": + return true; + case "false": + return false; + default: + return value; + } +}; + +init_cjs_shims(); +var import_extra_typings = __toESM(require_extra_typings()); +var createProgram = (command = new import_extra_typings.Command()) => command.name("@krivega/cc").description( + ` +Run Cypress tests on CI using https://cc.dev or https://sorry-cypress.dev as an orchestration and reporting service + +${getLegalNotice()} + ` +).option( + "-b, --browser ", + "runs Cypress in the browser with the given name; if a filesystem path is supplied, Cypress will attempt to use the browser at that path" +).option( + "--ci-build-id ", + "the unique identifier for a run, this value is automatically detected for most CI providers" +).addOption( + new import_extra_typings.Option("--component", "runs Cypress component test").default(false).implies({ + e2e: false + }) +).option( + "-c, --config ", + "sets Cypress configuration values. separate multiple values with a comma. overrides any value in cypress.config.{js,ts,mjs,cjs}" +).option( + "-e, --env ", + "sets environment variables. separate multiple values with a comma. overrides any value in cypress.config.{js,ts,mjs,cjs} or cypress.env.json" +).option( + "-C, --config-file ", + 'specify Cypress config file, path to script file where Cypress configuration values are set. defaults to "cypress.config.{js,ts,mjs,cjs}"' +).addOption(new import_extra_typings.Option("--e2e", "runs end to end tests").default(true)).option("--group ", "a named group for recorded runs in Cc").addOption( + new import_extra_typings.Option( + "-k, --key ", + "your secret Record Key obtained from Cc. you can omit this if you set a CC_RECORD_KEY environment variable" + ).env("CC_RECORD_KEY") +).option( + "--parallel", + "enables concurrent runs and automatic load balancing of specs across multiple machines or processes", + false +).addOption( + new import_extra_typings.Option( + "-p, --port ", + "runs Cypress on a specific port. overrides any value in cypress.config.{js,ts,mjs,cjs}" + ).argParser((i) => parseInt(i, 10)) +).option( + "-P, --project ", + "path to your Cypress project root location - defaults to the current working directory" +).option("-q, --quiet", "suppress verbose output from Cypress").addOption( + new import_extra_typings.Option( + "--record [bool]", + "records the run and sends test results, screenshots and videos to Cc" + ).default(true).argParser((i) => i === "false" ? false : true) +).option( + "-r, --reporter ", + 'use a specific mocha reporter for Cypress, pass a path to use a custom reporter, defaults to "spec"' +).option( + "-o, --reporter-options ", + 'options for the mocha reporter. defaults to "null"' +).addOption( + new import_extra_typings.Option( + "-s, --spec ", + 'define specific glob pattern for running the spec file(s), Defaults to the "specMatch" entry from the "cypress.config.{js,ts,mjs,cjs}" file' + ).argParser(parseCommaSeparatedList) +).option( + "-t, --tag ", + "comma-separated tag(s) for recorded runs in Cc", + parseCommaSeparatedList +).addOption( + new import_extra_typings.Option( + "--auto-cancel-after-failures ", + "Automatically abort the run after the specified number of failed tests. Overrides the default project settings. If set, must be a positive integer or 'false' to disable (Cc-only)" + ).argParser(parseAutoCancelFailures) +).addOption( + new import_extra_typings.Option("--headed [bool]", "Run cypress in headed mode").default(false).argParser((i) => i === "false" ? false : true) +).addOption( + new import_extra_typings.Option( + "--cloud-config-file ", + "Specify the config file for @krivega/cc, defaults to 'cc.config.js' and will be searched in the project root, unless an aboslue path is provided" + ).default(void 0) +).addOption( + new import_extra_typings.Option( + `--cloud-debug [true | string]`, + `Enable debug mode for @krivega/cc, this will print out logs for troubleshooting. Values: [true | ${Object.values( + DebugMode + ).join( + " | " + )}]. Use comma to separate multiple values, e.g. --cloud-debug commit-info,cc` + ).argParser(parseCommaSeparatedList).default(void 0) +).addOption( + new import_extra_typings.Option( + `--experimental-coverage-recording [bool]`, + `Enable recording coverage results, specify the "coverageFile" Cypress environment variable for a custom coverage file, default is "./.nyc_output/out.json"` + ).default(void 0).argParser((i) => i === "false" ? false : true) +); +var program = createProgram(); +function parseCommaSeparatedList(value, previous = []) { + if (value) { + return previous.concat(value.split(",").map((t) => t.trim())); + } + return previous; +} +function parseAutoCancelFailures(value) { + if (value === "false") { + return false; + } + const parsedValue = parseInt(value, 10); + if (isNaN(parsedValue) || parsedValue < 1) { + throw new Error( + "Invalid argument provided. Must be a positive integer or 'false'." + ); + } + return parsedValue; +} + +var debug24 = (0, import_debug27.default)("cc:cli"); +function parseCLIOptions(_program = program, ...args) { + const opts = _program.parse(...args).opts(); + activateDebug(opts.cloudDebug); + debug24("parsed CLI flags %o", opts); + const { e2e, component } = opts; + if (e2e && component) { + _program.error("Cannot use both e2e and component options"); + } + return getRunParametersFromCLI(opts); +} +function getRunParametersFromCLI(cliOptions) { + const { component, e2e, ...restOptions } = cliOptions; + const testingType = component ? "component" : "e2e"; + const result = { + ...restOptions, + config: sanitizeAndConvertNestedArgs(cliOptions.config, "config"), + env: sanitizeAndConvertNestedArgs(cliOptions.env, "env"), + reporterOptions: sanitizeAndConvertNestedArgs( + cliOptions.reporterOptions, + "reporterOptions" + ), + testingType, + recordKey: cliOptions.key + }; + debug24("parsed run params: %o", result); + return result; +} + +async function main() { + return run(parseCLIOptions()); +} +main().then((result) => { + if (!result) { + process.exit(1); + } + if (result.status === "failed") { + process.exit(1); + } + const overallFailed = result.totalFailed + result.totalSkipped; + if (overallFailed > 0) { + process.exit(overallFailed); + } + process.exit(0); +}).catch((err) => { + if (err instanceof ValidationError) { + program.error(withError(err.toString())); + } else { + console.error(err); + } + process.exit(1); +}); diff --git a/bin/cli.mjs b/bin/cli.mjs new file mode 100755 index 0000000..994ea06 --- /dev/null +++ b/bin/cli.mjs @@ -0,0 +1,3600 @@ +#!/usr/bin/env node +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __require = ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { + get: (a, b) => (typeof require !== "undefined" ? require : a)[b] +}) : x)(function(x) { + if (typeof require !== "undefined") + return require.apply(this, arguments); + throw new Error('Dynamic require of "' + x + '" is not supported'); +}); +var __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __commonJS = (cb, mod) => function __require2() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +import chalk from "chalk"; +import cp from "child_process"; +import { default as Debug, default as Debug2 } from "debug"; +import EventEmitter from "events"; +import http from "http"; +import HttpTerminator from "lil-http-terminator"; +import { createRequire } from "module"; +import "source-map-support/register"; +import { P, match } from "ts-pattern"; +import util from "util"; +import * as WebSocket from "ws"; +var init_esm_shims = __esm({ + "../../node_modules/tsup/assets/esm_shims.js"() { + "use strict"; + } +}); + +var require_extra_typings = __commonJS({ + "bin/lib/@commander-js/extra-typings/index.js"(exports, module) { + "use strict"; + init_esm_shims(); + var commander = __require("commander"); + exports = module.exports = {}; + exports.program = new commander.Command(); + exports.Argument = commander.Argument; + exports.Command = commander.Command; + exports.CommanderError = commander.CommanderError; + exports.Help = commander.Help; + exports.InvalidArgumentError = commander.InvalidArgumentError; + exports.InvalidOptionArgumentError = commander.InvalidArgumentError; + exports.Option = commander.Option; + exports.createCommand = (name) => new commander.Command(name); + exports.createOption = (flags, description) => new commander.Option(flags, description); + exports.createArgument = (name, description) => new commander.Argument(name, description); + } +}); + +init_esm_shims(); + +init_esm_shims(); +var ValidationError = class extends Error { + constructor(message) { + super(message); + this.name = ""; + } +}; + +init_esm_shims(); +var log = (...args) => console.log(util.format(...args)); +var info = log; +var format = util.format; +var withError = (msg) => chalk.bgRed.white(" ERROR ") + " " + msg; +var withWarning = (msg) => chalk.bgYellow.black(" WARNING ") + " " + msg; +var warn = (...args) => log(withWarning(util.format(...args))); +var error = (...args) => log(withError(util.format(...args)) + "\n"); +var title = (color, ...args) => info("\n " + chalk[color].bold(util.format(...args)) + " \n"); +var divider = () => console.log("\n" + chalk.gray(Array(100).fill("=").join("")) + "\n"); +var spacer = (n = 0) => console.log(Array(n).fill("").join("\n")); +var cyan = chalk.cyan; +var blue = chalk.blueBright; +var red = chalk.red; +var green = chalk.greenBright; +var gray = chalk.gray; +var white = chalk.white; +var magenta = chalk.magenta; +var bold = chalk.bold; +var dim = chalk.dim; + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); +var require2 = createRequire(import.meta.url); + +init_esm_shims(); +var orginal = cp.spawn; +cp.spawn = function(command, args, options) { + if (command.match(/Cypress/)) { + const process2 = orginal(command, args, { + ...options, + stdio: ["pipe", "pipe", "pipe"] + }); + return process2; + } + return orginal(command, args, options); +}; + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); +var Event = ((Event2) => { + Event2["RUN_CANCELLED"] = "run:cancelled"; + Event2["RUN_RESULT"] = "run:result"; + Event2["TEST_AFTER_RUN"] = "test:after:run"; + Event2["TEST_BEFORE_RUN"] = "test:before:run"; + Event2["AFTER_SCREENSHOT"] = "after:screenshot"; + Event2["AFTER_SPEC"] = "after:spec"; + return Event2; +})(Event || {}); +var allEvents = Object.values(Event); + +init_esm_shims(); +var _pubsub = null; +var getPubSub = () => { + if (!_pubsub) { + _pubsub = new EventEmitter(); + } + return _pubsub; +}; + +var debug = Debug("cc:ws"); +var server = null; +var wss = null; +var httpTerminator = null; +var getWSSPort = () => match(server?.address()).with({ port: P.number }, (address) => address.port).otherwise(() => 0); +var stopWSS = async () => { + debug("terminating wss server: %d", getWSSPort()); + if (!httpTerminator) { + debug("no wss server"); + return; + } + const { success, code, message, error: error2 } = await httpTerminator.terminate(); + if (!success) { + if (code === "TIMED_OUT") + error2(message); + if (code === "SERVER_ERROR") + error2(message, error2); + if (code === "INTERNAL_ERROR") + error2(message, error2); + } + debug("terminated wss server: %d", getWSSPort()); +}; +var startWSS = () => { + if (wss) { + return; + } + server = http.createServer().on("listening", () => { + if (!server) { + throw new Error("Server not initialized"); + } + wss = new WebSocket.WebSocketServer({ + server + }); + debug("starting wss on port %d", getWSSPort()); + wss.on("connection", function connection(ws) { + ws.on("message", function incoming(event) { + const message = JSON.parse(event.toString()); + getPubSub().emit(message.type, message.payload); + }); + }); + }).listen(); + httpTerminator = HttpTerminator({ + server + }); +}; + +init_esm_shims(); +var debug2 = Debug2("cc:capture"); +var _write = process.stdout.write; +var _log = process.log; +var restore = function() { + process.stdout.write = _write; + process.log = _log; +}; +var stdout = function() { + debug2("capturing stdout"); + let logs = []; + const { write } = process.stdout; + const { log: log2 } = process; + if (log2) { + process.log = function(str) { + logs.push(str); + return log2.apply(this, arguments); + }; + } + process.stdout.write = function(str) { + logs.push(str); + return write.apply(this, arguments); + }; + return { + toString() { + return logs.join(""); + }, + data: logs, + restore, + reset: () => { + debug2("resetting captured stdout"); + logs = []; + } + }; +}; +var initialOutput = ""; +var capturedOutput = null; +var initCapture = () => capturedOutput = stdout(); +var cutInitialOutput = () => { + if (!capturedOutput) + throw new Error("capturedOutput is null"); + initialOutput = capturedOutput.toString(); + capturedOutput.reset(); +}; +var resetCapture = () => { + if (!capturedOutput) + throw new Error("capturedOutput is null"); + capturedOutput.reset(); +}; +var getCapturedOutput = () => { + if (!capturedOutput) + throw new Error("capturedOutput is null"); + return capturedOutput.toString(); +}; +var getInitialOutput = () => initialOutput; + +init_esm_shims(); +var _runId = void 0; +var setRunId = (runId) => { + _runId = runId; +}; +var _cypressVersion = void 0; +var setCypressVersion = (cypressVersion) => { + _cypressVersion = cypressVersion; +}; +var _ccVersion = void 0; +var setCcVersion = (v) => { + _ccVersion = v; +}; + +var cypressPkg = require2("cypress/package.json"); +var pkg = require2("@krivega/cc/package.json"); +initCapture(); +setCypressVersion(cypressPkg.version); +setCcVersion(pkg.version); + +import git from "@cypress/commit-info"; +import axios, { isAxiosError } from "axios"; +import axiosRetry from "axios-retry"; +import bluebird from "bluebird"; +import { default as commonPathPrefix, default as getCommonPathPrefix } from "common-path-prefix"; +import { getBinPath } from "cy2"; +import cypress from "cypress"; +import { parseISO, parseISO as parseISO2 } from "date-fns"; +import { default as Debug10, default as Debug11, default as Debug12, default as Debug13, default as Debug14, default as Debug15, default as Debug16, default as Debug17, default as Debug18, default as Debug19, default as Debug21, default as Debug3, default as Debug4, default as Debug5, default as Debug6, default as Debug7, default as Debug8, default as Debug9, default as debug3, default as debugFn } from "debug"; +import execa from "execa"; +import { default as fs, default as fs3 } from "fs"; +import fs2 from "fs/promises"; +import getos from "getos"; +import globby from "globby"; +import isAbsolute from "is-absolute"; +import { default as _, default as _10, default as _11, default as _2, default as _3, default as _4, default as _5, default as _6, default as _7, default as _8, default as _9 } from "lodash"; +import { customAlphabet } from "nanoid"; +import os, { cpus, freemem, platform, release, totalmem } from "os"; +import { join as join2, default as path2, default as path3, default as path4, default as path5 } from "path"; +import { default as prettyMS, default as prettyMilliseconds } from "pretty-ms"; +import { table } from "table"; +import { file } from "tmp-promise"; +import { P as P2, P as P3, match as match2, match as match3, match as match4 } from "ts-pattern"; +import { promisify } from "util"; + +init_esm_shims(); +function getLegalNotice() { + return ` +Copyright (C) ${(/* @__PURE__ */ new Date()).getFullYear()} cc +`; +} + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); +var isRetriableError = (err) => { + if (err.code === "ECONNABORTED") { + return true; + } + if (err.code === "ECONNREFUSED") { + return true; + } + if (err.code === "ETIMEDOUT") { + return true; + } + if (!isAxiosError(err)) { + return false; + } + return !!(err?.response?.status && 500 <= err.response.status && err.response.status < 600); +}; +var getDelay = (i) => [5 * 1e3, 10 * 1e3, 30 * 1e3][i - 1]; +var baseURL = "set baseURL"; +var getAPIBaseUrl = () => baseURL ?? "set baseURL"; +var setAPIBaseUrl = (url) => baseURL = url ?? "set baseURL"; + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); +var createTempFile = async () => { + const { path: path6 } = await file(); + return path6; +}; + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); +var DebugMode = ((DebugMode2) => { + DebugMode2["None"] = "none"; + DebugMode2["All"] = "all"; + DebugMode2["Cc"] = "cc"; + DebugMode2["Cypress"] = "cypress"; + DebugMode2["CommitInfo"] = "commit-info"; + return DebugMode2; +})(DebugMode || {}); + +function shouldEnablePluginDebug(param) { + return match2(param).with(P2.nullish, () => false).with("none", () => false).with(true, () => true).with("all", () => true).with("cc", () => true).with( + P2.array(P2.string), + (v) => v.includes("all") || v.includes("cc") + ).otherwise(() => false); +} +function activateDebug(mode) { + match2(mode).with(P2.instanceOf(Array), (i) => i.forEach(setDebugMode)).with(true, () => setDebugMode("all")).with( + P2.union( + "all", + "cc", + "cypress", + "commit-info" + ), + (i) => setDebugMode(i) + ).otherwise(() => setDebugMode("none")); +} +function setDebugMode(mode) { + if (mode === "none") { + return; + } + const tokens = new Set(process.env.DEBUG ? process.env.DEBUG.split(",") : []); + match2(mode).with("all", () => { + tokens.add("commit-info"); + tokens.add("cc:*"); + tokens.add("cypress:*"); + }).with("cc", () => tokens.add("cc:*")).with("cypress", () => tokens.add("cypress:*")).with("commit-info", () => tokens.add("commit-info")).otherwise(() => { + }); + debug3.enable(Array.from(tokens).join(",")); +} + +init_esm_shims(); +bluebird.Promise.config({ + cancellation: true +}); +var BPromise = bluebird.Promise; +var safe = (fn, ifFaled, ifSucceed) => async (...args) => { + try { + const r = await fn(...args); + ifSucceed(); + return r; + } catch (e) { + return ifFaled(e); + } +}; +var sortObjectKeys = (obj) => { + return Object.keys(obj).sort().reduce((acc, key) => { + acc[key] = obj[key]; + return acc; + }, {}); +}; + +init_esm_shims(); +var getRandomString = customAlphabet("abcdefghijklmnopqrstuvwxyz", 10); + +var debug4 = Debug3("cc:boot"); +function getBootstrapArgs({ + params, + tempFilePath +}) { + return _.chain(getCypressCLIParams(params)).thru((opts) => ({ + ...opts, + env: { + ...opts.env ?? {}, + cc_marker: true, + cc_temp_file: tempFilePath, + cc_debug_enabled: shouldEnablePluginDebug(params.cloudDebug) + } + })).tap((opts) => { + debug4("cypress bootstrap params: %o", opts); + }).thru((opts) => ({ + ...opts, + env: sortObjectKeys(opts.env ?? {}) + })).thru(serializeOptions).tap((opts) => { + debug4("cypress bootstrap serialized params: %o", opts); + }).thru((args) => { + return [ + ...args, + "--spec", + getRandomString(), + params.testingType === "component" ? "--component" : "--e2e" + ]; + }).value(); +} +function getCypressCLIParams(params) { + const result = getCypressRunAPIParams(params); + const testingType = result.testingType === "component" ? { + component: true + } : {}; + return { + ..._.omit(result, "testingType"), + ...testingType + }; +} +function serializeOptions(options) { + return Object.entries(options).flatMap(([key, value]) => { + const _key = dashed(key); + if (typeof value === "boolean") { + return value === true ? [`--${_key}`] : [`--${_key}`, false]; + } + if (_.isObject(value)) { + return [`--${_key}`, serializeComplexParam(value)]; + } + return [`--${_key}`, value.toString()]; + }); +} +function serializeComplexParam(param) { + return JSON.stringify(param); +} +var dashed = (v) => v.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()); + +var debug5 = Debug4("cc:boot"); +var bootCypress = async (params) => { + debug5("booting cypress..."); + const tempFilePath = await createTempFile(); + const cypressBin = await getBinPath(require2.resolve("cypress")); + debug5("cypress executable location: %s", cypressBin); + const args = getBootstrapArgs({ tempFilePath, params }); + debug5("booting cypress with args: %o", args); + const { stdout: stdout2, stderr } = await execCypress(cypressBin, args); + if (!fs.existsSync(tempFilePath)) { + throw new Error( + `Cannot resolve cypress configuration from ${tempFilePath}. Please report the issue.` + ); + } + try { + const f = fs.readFileSync(tempFilePath, "utf-8"); + if (!f) { + throw new Error("Is @krivega/cc/plugin installed?"); + } + debug5("cypress config '%s': '%s'", tempFilePath, f); + return JSON.parse(f); + } catch (err) { + debug5("read config temp file failed: %o", err); + info(bold("Cypress stdout:\n"), stdout2); + info(bold("Cypress stderr:\n"), stderr); + throw new ValidationError(`Unable to resolve cypress configuration +- make sure that '@krivega/cc/plugin' is installed +- report the issue together with cypress stdout and stderr +`); + } +}; +async function execCypress(cypressBin, args) { + let stdout2 = ""; + let stderr = ""; + try { + await execa(cypressBin, ["run", ...args], { + stdio: "pipe", + env: { + ...process.env, + CYPRESS_RECORD_KEY: void 0, + CYPRESS_PROJECT_ID: void 0 + } + }); + } catch (err) { + debug5("exec cypress failed (certain failures are expected): %o", err); + stdout2 = err.stdout; + stderr = err.stderr; + } + return { stdout: stdout2, stderr }; +} + +init_esm_shims(); +var defaultFilenames = [ + "cc.config.js", + "cc.config.cjs", + "cc.config.mjs" +]; +function getConfigFilePath(projectRoot = null, explicitConfigFilePath) { + const prefix = projectRoot ?? process.cwd(); + if (_2.isString(explicitConfigFilePath) && isAbsolute(explicitConfigFilePath)) { + return [explicitConfigFilePath]; + } + if (_2.isString(explicitConfigFilePath)) { + return [normalizePath(prefix, explicitConfigFilePath)]; + } + return defaultFilenames.map((p) => normalizePath(prefix, p)); +} +function normalizePath(prefix, filename) { + return `file://${path2.resolve(prefix, filename)}`; +} + +var debug6 = Debug5("cc:config"); +var _config = null; +var defaultConfig = { + e2e: { + batchSize: 3 + }, + component: { + batchSize: 5 + }, + cloudServiceUrl: "set baseURL", + networkHeaders: void 0 +}; +async function getCcConfig(projectRoot, explicitConfigFilePath) { + if (_config) { + return _config; + } + const configFilePath = getConfigFilePath(projectRoot, explicitConfigFilePath); + for (const filepath of configFilePath) { + const config = match3(await loadConfigFile(filepath)).with({ default: P3.not(P3.nullish) }, (c) => c.default).with(P3.not(P3.nullish), (c) => c).otherwise(() => null); + if (config) { + debug6("loaded cc config from '%s'\n%O", filepath, config); + info(`Using config file: ${dim(filepath)}`); + _config = { + ...defaultConfig, + ...config + }; + return _config; + } + } + warn( + "Failed to load config file, falling back to the default config. Attempted locations: %s", + configFilePath + ); + _config = defaultConfig; + return _config; +} +async function loadConfigFile(filepath) { + try { + debug6("loading cc config file from '%s'", filepath); + return await import(filepath); + } catch (e) { + debug6("failed loading config file from: %s", e); + return null; + } +} +async function getMergedConfig(params) { + debug6("resolving cypress config"); + const cypressResolvedConfig = await bootCypress(params); + debug6("cypress resolvedConfig: %O", cypressResolvedConfig); + const rawE2EPattern = cypressResolvedConfig.rawJson?.e2e?.specPattern; + let additionalIgnorePattern = []; + if (params.testingType === "component" && rawE2EPattern) { + additionalIgnorePattern = rawE2EPattern; + } + const result = { + projectRoot: cypressResolvedConfig?.projectRoot || process.cwd(), + projectId: params.projectId, + specPattern: cypressResolvedConfig?.specPattern || "**/*.*", + excludeSpecPattern: ( + cypressResolvedConfig?.resolved.excludeSpecPattern.value ?? [] + ), + additionalIgnorePattern, + resolved: cypressResolvedConfig, + experimentalCoverageRecording: params.experimentalCoverageRecording + }; + debug6("merged config: %O", result); + return result; +} + +init_esm_shims(); +var debug7 = Debug6("cc:validateParams"); +async function resolveCcParams(params) { + const configFromFile = await getCcConfig( + params.project, + params.cloudConfigFile + ); + debug7("resolving cc params: %o", params); + debug7("resolving cc config file: %o", configFromFile); + const cloudServiceUrl = params.cloudServiceUrl ?? process.env.CC_API_URL ?? configFromFile.cloudServiceUrl; + const recordKey = params.recordKey ?? process.env.CC_RECORD_KEY ?? configFromFile.recordKey; + const projectId = params.projectId ?? process.env.CC_PROJECT_ID ?? configFromFile.projectId; + const testingType = params.testingType ?? "e2e"; + let batchSize = params.batchSize; + if (!batchSize) { + batchSize = testingType === "e2e" ? configFromFile.e2e.batchSize : configFromFile.component.batchSize; + } + return { + ...params, + cloudServiceUrl, + recordKey, + projectId, + batchSize, + testingType + }; +} +var projectIdError = `Cannot resolve projectId. Please use one of the following: +- provide it as a "projectId" property for "run" API method +- set CC_PROJECT_ID environment variable +- set "projectId" in "cc.config.{c}js" file`; +var cloudServiceUrlError = `Cannot resolve cloud service URL. Please use one of the following: +- provide it as a "cloudServiceUrl" property for "run" API method +- set CC_API_URL environment variable +- set "cloudServiceUrl" in "cc.config.{c}js" file`; +var cloudServiceInvalidUrlError = `Invalid cloud service URL provided`; +var recordKeyError = `Cannot resolve record key. Please use one of the following: + +- pass it as a CLI flag '-k, --key ' +- provide it as a "recordKey" property for "run" API method +- set CC_RECORD_KEY environment variable +- set "recordKey" in "cc.config.{c}js" file +`; +async function validateParams(_params) { + const params = await resolveCcParams(_params); + debug7("validating cc params: %o", params); + if (!params.cloudServiceUrl) { + throw new ValidationError(cloudServiceUrlError); + } + if (!params.projectId) { + throw new ValidationError(projectIdError); + } + if (!params.recordKey) { + throw new ValidationError(recordKeyError); + } + validateURL(params.cloudServiceUrl); + const requiredParameters = [ + "testingType", + "batchSize", + "projectId" + ]; + requiredParameters.forEach((key) => { + if (typeof params[key] === "undefined") { + error('Missing required parameter "%s"', key); + throw new Error("Missing required parameter"); + } + }); + params.tag = parseTags(params.tag); + params.autoCancelAfterFailures = getAutoCancelValue( + params.autoCancelAfterFailures + ); + debug7("validated cc params: %o", params); + return params; +} +function getAutoCancelValue(value) { + if (typeof value === "undefined") { + return void 0; + } + if (typeof value === "boolean") { + return value ? 1 : false; + } + if (typeof value === "number" && value > 0) { + return value; + } + throw new ValidationError( + `autoCancelAfterFailures: should be a positive integer or "false". Got: "${value}"` + ); +} +function isOffline(params) { + return params.record === false; +} +function parseTags(tagString) { + if (!tagString) { + return []; + } + if (Array.isArray(tagString)) { + return tagString.filter(Boolean); + } + return tagString.split(",").map((tag) => tag.trim()).filter(Boolean); +} +function validateURL(url) { + try { + new URL(url); + } catch (err) { + throw new ValidationError(`${cloudServiceInvalidUrlError}: "${url}"`); + } +} +function getCypressRunAPIParams(params) { + return { + ..._3.pickBy( + _3.omit(params, [ + "cloudDebug", + "cloudConfigFile", + "autoCancelAfterFailures", + "cloudServiceUrl", + "batchSize", + "projectId", + "key", + "recordKey", + "record", + "group", + "parallel", + "tag", + "ciBuildId", + "spec", + "exit", + "headless", + "experimentalCoverageRecording" + ]), + Boolean + ), + record: false, + env: { + ...params.env, + cc_debug_enabled: shouldEnablePluginDebug(params.cloudDebug) + } + }; +} +function preprocessParams(params) { + return { + ...params, + spec: processSpecParam(params.spec) + }; +} +function processSpecParam(spec) { + if (!spec) { + return void 0; + } + if (Array.isArray(spec)) { + return _3.flatten(spec.map((i) => i.split(","))); + } + return spec.split(","); +} + +init_esm_shims(); +function maybePrintErrors(err) { + if (!err.response?.data || !err.response?.status) { + return; + } + const { message, errors } = err.response.data; + switch (err.response.status) { + case 401: + warn("Received 401 Unauthorized"); + break; + case 422: + spacer(1); + warn(...formatGenericError(message, errors)); + spacer(1); + break; + default: + break; + } +} +function formatGenericError(message, errors) { + if (!_4.isString(message)) { + return ["Unexpected error from the cloud service"]; + } + if (errors?.length === 0) { + return [message]; + } + return [ + message, + ` +${(errors ?? []).map((e) => ` - ${e}`).join("\n")} +` + ]; +} + +var debug8 = Debug7("cc:api"); +var MAX_RETRIES = 3; +var TIMEOUT_MS = 30 * 1e3; +var _client = null; +async function getClient() { + if (_client) { + return _client; + } + const ccConfig = await getCcConfig(); + _client = axios.create({ + baseURL: getAPIBaseUrl(), + timeout: TIMEOUT_MS + }); + _client.interceptors.request.use((config) => { + const ccyVerson = _ccVersion ?? "0.0.0"; + const headers = { + ...config.headers, + "x-cypress-request-attempt": config["axios-retry"]?.retryCount ?? 0, + "x-cypress-version": _cypressVersion ?? "0.0.0", + "x-ccy-version": ccyVerson, + "User-Agent": `@krivega/cc/${ccyVerson}` + }; + if (_runId) { + headers["x-cypress-run-id"] = _runId; + } + if (!headers["Content-Type"]) { + headers["Content-Type"] = "application/json"; + } + if (ccConfig.networkHeaders) { + const filteredHeaders = _5.omit(ccConfig.networkHeaders, [ + "x-cypress-request-attempt", + "x-cypress-version", + "x-ccy-version", + "x-cypress-run-id", + "Content-Type" + ]); + debug8("using custom network headers: %o", filteredHeaders); + Object.assign(headers, filteredHeaders); + } + const req = { + ...config, + headers + }; + debug8("network request: %o", { + ..._5.pick(req, "method", "url", "headers"), + data: Buffer.isBuffer(req.data) ? "buffer" : req.data + }); + return req; + }); + axiosRetry(_client, { + retries: MAX_RETRIES, + retryCondition: isRetriableError, + retryDelay: getDelay, + onRetry, + shouldResetTimeout: true + }); + return _client; +} +function onRetry(retryCount, err, config) { + warn( + "Network request '%s' failed: '%s'. Next attempt is in %s (%d/%d).", + `${config.method} ${config.url}`, + err.message, + prettyMilliseconds(getDelay(retryCount)), + retryCount, + MAX_RETRIES + ); +} +var makeRequest = async (config) => { + return (await getClient())(config).then((res) => { + debug8("network response: %o", _5.omit(res, "request", "config")); + return res; + }).catch((error2) => { + maybePrintErrors(error2); + throw new ValidationError(error2.message); + }); +}; + +init_esm_shims(); +function printWarnings(warnings) { + warn("Notice from cloud service:"); + warnings.map((w) => { + spacer(1); + info(magenta.bold(w.message)); + Object.entries(_6.omit(w, "message")).map(([key, value]) => { + info("- %s: %s", key, value); + }); + spacer(1); + }); +} + +var createRun = async (payload) => { + const response = await makeRequest({ + method: "POST", + url: "/runs", + data: payload + }); + if ((response.data.warnings?.length ?? 0) > 0) { + printWarnings(response.data.warnings); + } + return response.data; +}; +var createInstance = async ({ + runId, + groupId, + machineId, + platform: platform2 +}) => { + const response = await makeRequest({ + method: "POST", + url: `runs/${runId}/instances`, + data: { + runId, + groupId, + machineId, + platform: platform2 + } + }); + return response.data; +}; +var createBatchedInstances = async (data) => { + const respone = await makeRequest({ + method: "POST", + url: `runs/${data.runId}/cy/instances`, + data + }); + return respone.data; +}; +var setInstanceTests = (instanceId, payload) => makeRequest({ + method: "POST", + url: `instances/${instanceId}/tests`, + data: payload +}).then((result) => result.data); +var updateInstanceResults = (instanceId, payload) => makeRequest({ + method: "POST", + url: `instances/${instanceId}/results`, + data: payload +}).then((result) => result.data); +var reportInstanceResultsMerged = (instanceId, payload) => makeRequest({ + method: "POST", + url: `instances/${instanceId}/cy/results`, + data: payload +}).then((result) => result.data); +var updateInstanceStdout = (instanceId, stdout2) => makeRequest({ + method: "PUT", + url: `instances/${instanceId}/stdout`, + data: { + stdout: stdout2 + } +}); + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); +var debug9 = debugFn("cc:ci"); +var join = (char, ...pieces) => { + return _7.chain(pieces).compact().join(char).value(); +}; +var toCamelObject = (obj, key) => { + return _7.set(obj, _7.camelCase(key), process.env[key]); +}; +var extract = (envKeys) => { + return _7.transform(envKeys, toCamelObject, {}); +}; +var isTeamFoundation = () => { + return process.env.TF_BUILD && process.env.TF_BUILD_BUILDNUMBER; +}; +var isAzureCi = () => { + return process.env.TF_BUILD && process.env.AZURE_HTTP_USER_AGENT; +}; +var isAWSCodeBuild = () => { + return _7.some(process.env, (val, key) => { + return /^CODEBUILD_/.test(key); + }); +}; +var isBamboo = () => { + return process.env.bamboo_buildNumber; +}; +var isCodeshipBasic = () => { + return process.env.CI_NAME && process.env.CI_NAME === "codeship" && process.env.CODESHIP; +}; +var isCodeshipPro = () => { + return process.env.CI_NAME && process.env.CI_NAME === "codeship" && !process.env.CODESHIP; +}; +var isConcourse = () => { + return _7.some(process.env, (val, key) => { + return /^CONCOURSE_/.test(key); + }); +}; +var isGitlab = () => { + return process.env.GITLAB_CI || process.env.CI_SERVER_NAME && /^GitLab/.test(process.env.CI_SERVER_NAME); +}; +var isGoogleCloud = () => { + return process.env.GCP_PROJECT || process.env.GCLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT; +}; +var isJenkins = () => { + return process.env.JENKINS_URL || process.env.JENKINS_HOME || process.env.JENKINS_VERSION || process.env.HUDSON_URL || process.env.HUDSON_HOME; +}; +var isWercker = () => { + return process.env.WERCKER || process.env.WERCKER_MAIN_PIPELINE_STARTED; +}; +var CI_PROVIDERS = { + appveyor: "APPVEYOR", + azure: isAzureCi, + awsCodeBuild: isAWSCodeBuild, + bamboo: isBamboo, + bitbucket: "BITBUCKET_BUILD_NUMBER", + buildkite: "BUILDKITE", + circle: "CIRCLECI", + codeshipBasic: isCodeshipBasic, + codeshipPro: isCodeshipPro, + concourse: isConcourse, + codeFresh: "CF_BUILD_ID", + drone: "DRONE", + githubActions: "GITHUB_ACTIONS", + gitlab: isGitlab, + goCD: "GO_JOB_NAME", + googleCloud: isGoogleCloud, + jenkins: isJenkins, + semaphore: "SEMAPHORE", + shippable: "SHIPPABLE", + teamcity: "TEAMCITY_VERSION", + teamfoundation: isTeamFoundation, + travis: "TRAVIS", + wercker: isWercker, + netlify: "NETLIFY", + layerci: "LAYERCI" +}; +function _detectProviderName() { + const { env } = process; + return _7.findKey(CI_PROVIDERS, (value) => { + if (_7.isString(value)) { + return env[value]; + } + if (_7.isFunction(value)) { + return value(); + } + }); +} +var _providerCiParams = () => { + return { + appveyor: extract([ + "APPVEYOR_JOB_ID", + "APPVEYOR_ACCOUNT_NAME", + "APPVEYOR_PROJECT_SLUG", + "APPVEYOR_BUILD_NUMBER", + "APPVEYOR_BUILD_VERSION", + "APPVEYOR_PULL_REQUEST_NUMBER", + "APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH" + ]), + azure: extract([ + "BUILD_BUILDID", + "BUILD_BUILDNUMBER", + "BUILD_CONTAINERID", + "BUILD_REPOSITORY_URI" + ]), + awsCodeBuild: extract([ + "CODEBUILD_BUILD_ID", + "CODEBUILD_BUILD_NUMBER", + "CODEBUILD_RESOLVED_SOURCE_VERSION", + "CODEBUILD_SOURCE_REPO_URL", + "CODEBUILD_SOURCE_VERSION" + ]), + bamboo: extract([ + "bamboo_buildNumber", + "bamboo_buildResultsUrl", + "bamboo_planRepository_repositoryUrl", + "bamboo_buildKey" + ]), + bitbucket: extract([ + "BITBUCKET_REPO_SLUG", + "BITBUCKET_REPO_OWNER", + "BITBUCKET_BUILD_NUMBER", + "BITBUCKET_PARALLEL_STEP", + "BITBUCKET_STEP_RUN_NUMBER", + "BITBUCKET_PR_ID", + "BITBUCKET_PR_DESTINATION_BRANCH", + "BITBUCKET_PR_DESTINATION_COMMIT" + ]), + buildkite: extract([ + "BUILDKITE_REPO", + "BUILDKITE_SOURCE", + "BUILDKITE_JOB_ID", + "BUILDKITE_BUILD_ID", + "BUILDKITE_BUILD_URL", + "BUILDKITE_BUILD_NUMBER", + "BUILDKITE_PULL_REQUEST", + "BUILDKITE_PULL_REQUEST_REPO", + "BUILDKITE_PULL_REQUEST_BASE_BRANCH" + ]), + circle: extract([ + "CIRCLE_JOB", + "CIRCLE_BUILD_NUM", + "CIRCLE_BUILD_URL", + "CIRCLE_PR_NUMBER", + "CIRCLE_PR_REPONAME", + "CIRCLE_PR_USERNAME", + "CIRCLE_COMPARE_URL", + "CIRCLE_WORKFLOW_ID", + "CIRCLE_PULL_REQUEST", + "CIRCLE_REPOSITORY_URL", + "CI_PULL_REQUEST" + ]), + codeshipBasic: extract([ + "CI_BUILD_ID", + "CI_REPO_NAME", + "CI_BUILD_URL", + "CI_PROJECT_ID", + "CI_BUILD_NUMBER", + "CI_PULL_REQUEST" + ]), + codeshipPro: extract(["CI_BUILD_ID", "CI_REPO_NAME", "CI_PROJECT_ID"]), + concourse: extract([ + "BUILD_ID", + "BUILD_NAME", + "BUILD_JOB_NAME", + "BUILD_PIPELINE_NAME", + "BUILD_TEAM_NAME", + "ATC_EXTERNAL_URL" + ]), + codeFresh: extract([ + "CF_BUILD_ID", + "CF_BUILD_URL", + "CF_CURRENT_ATTEMPT", + "CF_STEP_NAME", + "CF_PIPELINE_NAME", + "CF_PIPELINE_TRIGGER_ID", + "CF_PULL_REQUEST_ID", + "CF_PULL_REQUEST_IS_FORK", + "CF_PULL_REQUEST_NUMBER", + "CF_PULL_REQUEST_TARGET" + ]), + drone: extract([ + "DRONE_JOB_NUMBER", + "DRONE_BUILD_LINK", + "DRONE_BUILD_NUMBER", + "DRONE_PULL_REQUEST" + ]), + githubActions: extract([ + "GITHUB_WORKFLOW", + "GITHUB_ACTION", + "GITHUB_EVENT_NAME", + "GITHUB_RUN_ID", + "GITHUB_RUN_ATTEMPT", + "GITHUB_REPOSITORY" + ]), + gitlab: extract([ + "CI_PIPELINE_ID", + "CI_PIPELINE_URL", + "CI_BUILD_ID", + "CI_JOB_ID", + "CI_JOB_URL", + "CI_JOB_NAME", + "GITLAB_HOST", + "CI_PROJECT_ID", + "CI_PROJECT_URL", + "CI_REPOSITORY_URL", + "CI_ENVIRONMENT_URL", + "CI_DEFAULT_BRANCH" + ]), + goCD: extract([ + "GO_SERVER_URL", + "GO_ENVIRONMENT_NAME", + "GO_PIPELINE_NAME", + "GO_PIPELINE_COUNTER", + "GO_PIPELINE_LABEL", + "GO_STAGE_NAME", + "GO_STAGE_COUNTER", + "GO_JOB_NAME", + "GO_TRIGGER_USER", + "GO_REVISION", + "GO_TO_REVISION", + "GO_FROM_REVISION", + "GO_MATERIAL_HAS_CHANGED" + ]), + googleCloud: extract([ + "BUILD_ID", + "PROJECT_ID", + "REPO_NAME", + "BRANCH_NAME", + "TAG_NAME", + "COMMIT_SHA", + "SHORT_SHA" + ]), + jenkins: extract(["BUILD_ID", "BUILD_URL", "BUILD_NUMBER", "ghprbPullId"]), + semaphore: extract([ + "SEMAPHORE_BRANCH_ID", + "SEMAPHORE_BUILD_NUMBER", + "SEMAPHORE_CURRENT_JOB", + "SEMAPHORE_CURRENT_THREAD", + "SEMAPHORE_EXECUTABLE_UUID", + "SEMAPHORE_GIT_BRANCH", + "SEMAPHORE_GIT_DIR", + "SEMAPHORE_GIT_REF", + "SEMAPHORE_GIT_REF_TYPE", + "SEMAPHORE_GIT_REPO_SLUG", + "SEMAPHORE_GIT_SHA", + "SEMAPHORE_GIT_URL", + "SEMAPHORE_JOB_COUNT", + "SEMAPHORE_JOB_ID", + "SEMAPHORE_JOB_NAME", + "SEMAPHORE_JOB_UUID", + "SEMAPHORE_PIPELINE_ID", + "SEMAPHORE_PLATFORM", + "SEMAPHORE_PROJECT_DIR", + "SEMAPHORE_PROJECT_HASH_ID", + "SEMAPHORE_PROJECT_ID", + "SEMAPHORE_PROJECT_NAME", + "SEMAPHORE_PROJECT_UUID", + "SEMAPHORE_REPO_SLUG", + "SEMAPHORE_TRIGGER_SOURCE", + "SEMAPHORE_WORKFLOW_ID", + "PULL_REQUEST_NUMBER" + ]), + shippable: extract([ + "SHIPPABLE_BUILD_ID", + "SHIPPABLE_BUILD_NUMBER", + "SHIPPABLE_COMMIT_RANGE", + "SHIPPABLE_CONTAINER_NAME", + "SHIPPABLE_JOB_ID", + "SHIPPABLE_JOB_NUMBER", + "SHIPPABLE_REPO_SLUG", + "IS_FORK", + "IS_GIT_TAG", + "IS_PRERELEASE", + "IS_RELEASE", + "REPOSITORY_URL", + "REPO_FULL_NAME", + "REPO_NAME", + "BUILD_URL", + "BASE_BRANCH", + "HEAD_BRANCH", + "IS_PULL_REQUEST", + "PULL_REQUEST", + "PULL_REQUEST_BASE_BRANCH", + "PULL_REQUEST_REPO_FULL_NAME" + ]), + teamcity: null, + teamfoundation: extract([ + "BUILD_BUILDID", + "BUILD_BUILDNUMBER", + "BUILD_CONTAINERID" + ]), + travis: extract([ + "TRAVIS_JOB_ID", + "TRAVIS_BUILD_ID", + "TRAVIS_BUILD_WEB_URL", + "TRAVIS_REPO_SLUG", + "TRAVIS_JOB_NUMBER", + "TRAVIS_EVENT_TYPE", + "TRAVIS_COMMIT_RANGE", + "TRAVIS_BUILD_NUMBER", + "TRAVIS_PULL_REQUEST", + "TRAVIS_PULL_REQUEST_BRANCH", + "TRAVIS_PULL_REQUEST_SHA" + ]), + wercker: null, + netlify: extract([ + "BUILD_ID", + "CONTEXT", + "URL", + "DEPLOY_URL", + "DEPLOY_PRIME_URL", + "DEPLOY_ID" + ]), + layerci: extract([ + "LAYERCI_JOB_ID", + "LAYERCI_RUNNER_ID", + "RETRY_INDEX", + "LAYERCI_PULL_REQUEST", + "LAYERCI_REPO_NAME", + "LAYERCI_REPO_OWNER", + "LAYERCI_BRANCH", + "GIT_TAG" + ]) + }; +}; +var _providerCommitParams = () => { + const { env } = process; + return { + appveyor: { + sha: env.APPVEYOR_REPO_COMMIT, + branch: env.APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH || env.APPVEYOR_REPO_BRANCH, + message: join( + "\n", + env.APPVEYOR_REPO_COMMIT_MESSAGE, + env.APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED + ), + authorName: env.APPVEYOR_REPO_COMMIT_AUTHOR, + authorEmail: env.APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL + }, + awsCodeBuild: { + sha: env.CODEBUILD_RESOLVED_SOURCE_VERSION, + remoteOrigin: env.CODEBUILD_SOURCE_REPO_URL + }, + azure: { + sha: env.BUILD_SOURCEVERSION, + branch: env.BUILD_SOURCEBRANCHNAME, + message: env.BUILD_SOURCEVERSIONMESSAGE, + authorName: env.BUILD_SOURCEVERSIONAUTHOR, + authorEmail: env.BUILD_REQUESTEDFOREMAIL + }, + bamboo: { + sha: env.bamboo_planRepository_revision, + branch: env.bamboo_planRepository_branch, + authorName: env.bamboo_planRepository_username, + remoteOrigin: env.bamboo_planRepository_repositoryURL + }, + bitbucket: { + sha: env.BITBUCKET_COMMIT, + branch: env.BITBUCKET_BRANCH + }, + buildkite: { + sha: env.BUILDKITE_COMMIT, + branch: env.BUILDKITE_BRANCH, + message: env.BUILDKITE_MESSAGE, + authorName: env.BUILDKITE_BUILD_CREATOR, + authorEmail: env.BUILDKITE_BUILD_CREATOR_EMAIL, + remoteOrigin: env.BUILDKITE_REPO, + defaultBranch: env.BUILDKITE_PIPELINE_DEFAULT_BRANCH + }, + circle: { + sha: env.CIRCLE_SHA1, + branch: env.CIRCLE_BRANCH, + authorName: env.CIRCLE_USERNAME, + remoteOrigin: env.CIRCLE_REPOSITORY_URL + }, + codeshipBasic: { + sha: env.CI_COMMIT_ID, + branch: env.CI_BRANCH, + message: env.CI_COMMIT_MESSAGE, + authorName: env.CI_COMMITTER_NAME, + authorEmail: env.CI_COMMITTER_EMAIL + }, + codeshipPro: { + sha: env.CI_COMMIT_ID, + branch: env.CI_BRANCH, + message: env.CI_COMMIT_MESSAGE, + authorName: env.CI_COMMITTER_NAME, + authorEmail: env.CI_COMMITTER_EMAIL + }, + codeFresh: { + sha: env.CF_REVISION, + branch: env.CF_BRANCH, + message: env.CF_COMMIT_MESSAGE, + authorName: env.CF_COMMIT_AUTHOR + }, + drone: { + sha: env.DRONE_COMMIT_SHA, + branch: env.DRONE_SOURCE_BRANCH, + message: env.DRONE_COMMIT_MESSAGE, + authorName: env.DRONE_COMMIT_AUTHOR, + authorEmail: env.DRONE_COMMIT_AUTHOR_EMAIL, + remoteOrigin: env.DRONE_GIT_HTTP_URL, + defaultBranch: env.DRONE_REPO_BRANCH + }, + githubActions: { + sha: env.GITHUB_SHA, + branch: env.GH_BRANCH || env.GITHUB_REF, + defaultBranch: env.GITHUB_BASE_REF, + remoteBranch: env.GITHUB_HEAD_REF, + runAttempt: env.GITHUB_RUN_ATTEMPT + }, + gitlab: { + sha: env.CI_COMMIT_SHA, + branch: env.CI_COMMIT_REF_NAME, + message: env.CI_COMMIT_MESSAGE, + authorName: env.GITLAB_USER_NAME, + authorEmail: env.GITLAB_USER_EMAIL, + remoteOrigin: env.CI_REPOSITORY_URL, + defaultBranch: env.CI_DEFAULT_BRANCH + }, + googleCloud: { + sha: env.COMMIT_SHA, + branch: env.BRANCH_NAME + }, + jenkins: { + sha: env.GIT_COMMIT, + branch: env.GIT_BRANCH + }, + semaphore: { + sha: env.SEMAPHORE_GIT_SHA, + branch: env.SEMAPHORE_GIT_BRANCH, + remoteOrigin: env.SEMAPHORE_GIT_REPO_SLUG + }, + shippable: { + sha: env.COMMIT, + branch: env.BRANCH, + message: env.COMMIT_MESSAGE, + authorName: env.COMMITTER + }, + snap: null, + teamcity: null, + teamfoundation: { + sha: env.BUILD_SOURCEVERSION, + branch: env.BUILD_SOURCEBRANCHNAME, + message: env.BUILD_SOURCEVERSIONMESSAGE, + authorName: env.BUILD_SOURCEVERSIONAUTHOR + }, + travis: { + sha: env.TRAVIS_PULL_REQUEST_SHA || env.TRAVIS_COMMIT, + branch: env.TRAVIS_PULL_REQUEST_BRANCH || env.TRAVIS_BRANCH, + message: env.TRAVIS_COMMIT_MESSAGE + }, + wercker: null, + netlify: { + sha: env.COMMIT_REF, + branch: env.BRANCH, + remoteOrigin: env.REPOSITORY_URL + }, + layerci: { + sha: env.GIT_COMMIT, + branch: env.LAYERCI_BRANCH, + message: env.GIT_COMMIT_TITLE + } + }; +}; +var _get = (fn) => { + const providerName = getCiProvider(); + if (!providerName) + return {}; + return _7.chain(fn()).get(providerName).value(); +}; +function checkForCiBuildFromCi(ciProvider) { + if (ciProvider && detectableCiBuildIdProviders().includes(ciProvider)) + return true; + throw new ValidationError( + `Could not determine CI build ID from the environment. Please provide a unique CI build ID using the --ci-build-id CLI flag or 'ciBuildId' parameter for 'run' method.` + ); +} +function detectableCiBuildIdProviders() { + return _7.chain(_providerCiParams()).omitBy(_7.isNull).keys().value(); +} +function getCiProvider() { + return _detectProviderName() || null; +} +function getCiParams() { + return _get(_providerCiParams); +} +function getCommitParams() { + return _get(_providerCommitParams); +} +function getCI(ciBuildId) { + const params = getCiParams(); + const provider = getCiProvider(); + if (!ciBuildId) + checkForCiBuildFromCi(provider); + debug9("detected CI provider: %s", provider); + debug9("detected CI params: %O", params); + return { + params, + provider + }; +} +function getCommitDefaults(existingInfo) { + debug9("git commit existing info"); + debug9(existingInfo); + const commitParamsObj = getCommitParams(); + debug9("commit info from provider environment variables: %O", commitParamsObj); + const combined = _7.transform( + existingInfo, + (memo, value, key) => { + return memo[key] = _7.defaultTo(value || commitParamsObj[key], null); + } + ); + debug9("combined git and environment variables from provider"); + debug9(combined); + return combined; +} + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); +var SpecAfterResult = class _SpecAfterResult { + /** + * Combine standalone attempts and screenshots into standard result + * @param specResult - spec:after results + * @param executionState - ccy execution state + * @returns unified results, including attempts and screenshot details + */ + static getSpecAfterStandard(specAfterResults, executionState) { + return { + error: specAfterResults.error, + hooks: null, + reporter: specAfterResults.reporter, + reporterStats: specAfterResults.reporterStats, + spec: _SpecAfterResult.getSpecStandard(specAfterResults.spec), + tests: _SpecAfterResult.getTestStandard( + specAfterResults, + executionState.getAttemptsData() + ), + video: specAfterResults.video, + stats: _SpecAfterResult.getStatsStandard(specAfterResults.stats), + screenshots: _SpecAfterResult.getScreenshotsStandard( + specAfterResults.screenshots, + executionState.getScreenshotsData() + ) + }; + } + static getAttemptError(err) { + if (!err) { + return null; + } + return { + name: err.name, + message: err.message, + stack: err.stack, + codeFrame: err.codeFrame + }; + } + static getAttemptVideoTimestamp(attemptStartedAtMs, specStartedAtMs) { + return Math.max(attemptStartedAtMs - specStartedAtMs, 0); + } + static getSpecStartedAt(stats) { + if ("startedAt" in stats) { + return parseISO(stats.startedAt); + } + if ("wallClockStartedAt" in stats) { + return parseISO(stats.wallClockStartedAt); + } + warn("Cannot determine spec start date from stats: %o", stats); + return new Date(); + } + static getDummyTestAttemptError(attemptState) { + return match4(attemptState).with("failed", () => ({ + name: "Error", + message: "[@krivega/cc] Could not get cypress attempt error details", + stack: "", + codeFrame: null + })).with("skipped", () => ({ + name: "Error", + message: "The test was skipped because of a hook failure", + stack: "", + codeFrame: null + })).otherwise(() => null); + } + static getTestAttemptStandard(mochaAttempt, cypressAttempt, specStartedAt) { + if (!mochaAttempt) { + const error2 = "error" in cypressAttempt ? cypressAttempt.error : null; + const duration = "wallClockDuration" in cypressAttempt ? cypressAttempt.wallClockDuration : null; + return { + state: cypressAttempt.state, + error: error2 ? error2 : _SpecAfterResult.getDummyTestAttemptError(cypressAttempt.state), + timings: "timings" in cypressAttempt ? cypressAttempt.timings : null, + wallClockStartedAt: "wallClockStartedAt" in cypressAttempt ? cypressAttempt.wallClockStartedAt : ( new Date()).toISOString(), + wallClockDuration: duration ? duration : 0, + failedFromHookId: "failedFromHookId" in cypressAttempt ? cypressAttempt.failedFromHookId : null, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : 0 + }; + } + return { + state: cypressAttempt.state, + error: "error" in cypressAttempt ? cypressAttempt.error : _SpecAfterResult.getAttemptError(mochaAttempt.err), + timings: "timings" in cypressAttempt ? cypressAttempt.timings : mochaAttempt.timings, + wallClockStartedAt: mochaAttempt.wallClockStartedAt ?? ( new Date()).toISOString(), + wallClockDuration: mochaAttempt.duration ?? -1, + failedFromHookId: "failedFromHookId" in cypressAttempt ? cypressAttempt.failedFromHookId : null, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : _SpecAfterResult.getAttemptVideoTimestamp( + parseISO(mochaAttempt.wallClockStartedAt).getTime(), + specStartedAt.getTime() + ) + }; + } + static getTestStandard(specAfterResults, attempts) { + const standardTestList = (specAfterResults.tests ?? []).map((test, i) => { + const mochaAttempts = attempts.filter( + (attempt) => attempt.fullTitle === test.title.join(" ") + ); + const standardAttempts = (test.attempts ?? []).map( + (cypressAttempt, j) => { + const mochaAttempt = mochaAttempts.find( + (ma) => ma.currentRetry === j + ); + return _SpecAfterResult.getTestAttemptStandard( + mochaAttempt ?? null, + cypressAttempt, + _SpecAfterResult.getSpecStartedAt(specAfterResults.stats) + ); + } + ); + return { + body: "body" in test ? test.body : mochaAttempts[0]?.body ?? "", + testId: "testId" in test ? test.testId : mochaAttempts[0]?.id ?? `r${i}`, + title: test.title, + displayError: test.displayError, + state: test.state, + attempts: standardAttempts + }; + }); + return standardTestList; + } + static getSpecStandard(spec) { + return { + name: spec.name, + relative: spec.relative, + absolute: spec.absolute, + fileExtension: spec.fileExtension, + baseName: "baseName" in spec ? spec.baseName : "", + fileName: "fileName" in spec ? spec.fileName : "", + relativeToCommonRoot: "relativeToCommonRoot" in spec ? spec.relativeToCommonRoot : "", + specFileExtension: "specFileExtension" in spec ? spec.specFileExtension : "", + specType: "specType" in spec ? spec.specType : "" + }; + } + static getStatsStandard(stats) { + const result = { + skipped: stats.skipped, + suites: stats.suites, + tests: stats.tests, + passes: stats.passes, + pending: stats.pending, + failures: stats.failures, + wallClockStartedAt: "wallClockStartedAt" in stats ? stats.wallClockStartedAt : stats.startedAt, + wallClockEndedAt: "wallClockEndedAt" in stats ? stats.wallClockEndedAt : stats.endedAt, + wallClockDuration: "wallClockDuration" in stats ? stats.wallClockDuration : stats.duration ?? 0 + }; + result.tests = result.passes + result.failures + result.pending + result.skipped; + return result; + } + static getScreenshotsStandard(specAfterScreenshots, screenshotEvents) { + if (!specAfterScreenshots.length) { + return []; + } + return specAfterScreenshots.map((specScreenshot) => { + const es = screenshotEvents.find( + (screenshot) => screenshot.path === specScreenshot.path + ); + if (!es) { + warn( + 'Could not find details for screenshot at path "%s", skipping...', + specScreenshot.path + ); + } + return { + height: specScreenshot.height, + width: specScreenshot.width, + name: specScreenshot.name ?? es?.name ?? null, + path: specScreenshot.path, + takenAt: specScreenshot.takenAt, + testAttemptIndex: "testAttemptIndex" in specScreenshot ? specScreenshot.testAttemptIndex : es?.testAttemptIndex ?? -1, + testId: "testId" in specScreenshot ? specScreenshot.testId : es?.testId ?? "unknown", + screenshotId: "screenshotId" in specScreenshot ? specScreenshot.screenshotId : getRandomString() + }; + }); + } +}; + +var ModuleAPIResults = class _ModuleAPIResults { + static getRunScreenshots(run2) { + if ("screenshots" in run2) { + return run2.screenshots; + } + return (run2.tests ?? []).flatMap( + (t) => t.attempts.flatMap((a) => a.screenshots) + ); + } + static getTests(run2, executionState) { + const tests = run2.tests ?? []; + return tests.map((test, i) => { + const mochaAttempts = executionState.getAttemptsData().filter((attempt) => attempt.fullTitle === test.title.join(" ")); + const testId = "testId" in test ? test.testId : mochaAttempts[0]?.id ?? `r${i}`; + const runScreenshotPaths = _ModuleAPIResults.getRunScreenshots(run2).map( + (i2) => i2.path + ); + const testScreenshots = executionState.getScreenshotsData().filter((s) => runScreenshotPaths.includes(s.path)).filter((s) => s.testId === testId); + const standardAttempts = (test.attempts ?? []).map( + (cypressAttempt, j) => { + const mochaAttempt = mochaAttempts.find( + (ma) => ma.currentRetry === j + ); + const attemptScreenshots = testScreenshots.filter( + (t) => t.testAttemptIndex === j + ); + return _ModuleAPIResults.getTestAttempt( + mochaAttempt ?? null, + cypressAttempt, + attemptScreenshots, + SpecAfterResult.getSpecStartedAt(run2.stats) + ); + } + ); + return { + body: "body" in test ? test.body : mochaAttempts[0]?.body ?? "", + testId, + title: test.title, + displayError: test.displayError, + state: test.state, + attempts: standardAttempts + }; + }); + } + /** + * Convert version-specific attempt to a standard test attempt + */ + static getTestAttempt(mochaAttempt, cypressAttempt, screenshots, specStartedAt) { + if (!mochaAttempt) { + return { + state: cypressAttempt.state, + error: "error" in cypressAttempt ? cypressAttempt.error : SpecAfterResult.getDummyTestAttemptError(cypressAttempt.state), + startedAt: "startedAt" in cypressAttempt ? cypressAttempt.startedAt : ( new Date()).toISOString(), + duration: "duration" in cypressAttempt ? cypressAttempt.duration : 0, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : 0, + screenshots: "screenshots" in cypressAttempt ? cypressAttempt.screenshots : screenshots + }; + } + return { + state: cypressAttempt.state, + error: "error" in cypressAttempt ? cypressAttempt.error : SpecAfterResult.getAttemptError(mochaAttempt.err), + startedAt: "startedAt" in cypressAttempt ? cypressAttempt.startedAt : mochaAttempt.wallClockStartedAt ?? ( new Date()).toISOString(), + duration: "duration" in cypressAttempt ? cypressAttempt.duration : mochaAttempt.duration ?? -1, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : SpecAfterResult.getAttemptVideoTimestamp( + parseISO2(mochaAttempt.wallClockStartedAt).getTime(), + specStartedAt.getTime() + ), + screenshots: "screenshots" in cypressAttempt ? cypressAttempt.screenshots : screenshots + }; + } + static getRun(run2, executionState) { + return { + ...run2, + tests: _ModuleAPIResults.getTests(run2, executionState), + spec: SpecAfterResult.getSpecStandard(run2.spec), + hooks: null, + shouldUploadVideo: "shouldUploadVideo" in run2 ? run2.shouldUploadVideo : true + }; + } + /** + * Converts different Cypress versions to standard form + */ + static getStandardResult(result, executionState) { + if (result.runs.length !== 1) { + throw new Error("Expected single run"); + } + const run2 = result.runs[0]; + const stats = SpecAfterResult.getStatsStandard(run2.stats); + return { + ...result, + runs: [_ModuleAPIResults.getRun(run2, executionState)], + totalSuites: 1, + totalDuration: stats.wallClockDuration, + totalTests: stats.tests, + totalFailed: stats.failures, + totalPassed: stats.passes, + totalPending: stats.pending, + totalSkipped: stats.skipped, + startedTestsAt: stats.wallClockStartedAt, + endedTestsAt: stats.wallClockEndedAt, + status: "finished" + }; + } + static isFailureResult(result) { + return "status" in result && result.status === "failed"; + } + static { + this.isSuccessResult = (result) => { + if ("status" in result) { + return result.status === "finished"; + } + return true; + }; + } + static getEmptyResult(config) { + return { + status: "finished", + totalDuration: 0, + totalSuites: 0, + totalPending: 0, + totalFailed: 0, + totalSkipped: 0, + totalPassed: 0, + totalTests: 0, + startedTestsAt: ( new Date()).toISOString(), + endedTestsAt: ( new Date()).toISOString(), + runs: [], + config + }; + } +}; + +var debug10 = Debug8("cc:cypress"); +function runBareCypress(params = {}) { + const p = { + ...params, + ciBuildId: void 0, + tag: void 0, + parallel: void 0, + record: false, + group: void 0, + spec: _8.flatten(params.spec).join(",") + }; + debug10("Running bare Cypress with params %o", p); + return cypress.run(p); +} +async function runSpecFile({ spec }, cypressRunOptions) { + const runAPIOptions = getCypressRunAPIParams(cypressRunOptions); + const options = { + ...runAPIOptions, + config: { + ...runAPIOptions.config, + trashAssetsBeforeRuns: false + }, + env: { + ...runAPIOptions.env, + cc_ws: getWSSPort(), + cc_marker: true + }, + spec + }; + debug10("running cypress with options %o", options); + const result = await cypress.run(options); + if (ModuleAPIResults.isFailureResult(result)) { + warn('Cypress runner failed with message: "%s"', result.message); + warn( + "The following spec files will be marked as failed: %s", + spec.split(",").map((i) => ` + - ${i}`).join("") + ); + } + debug10("cypress run result %o", result); + return result; +} +var runSpecFileSafe = (spec, cypressRunOptions) => safe( + runSpecFile, + (error2) => { + const message = `Cypress runnner crashed with an error: +${error2.message} +${error2.stack}}`; + debug10("cypress run exception %o", error2); + warn('Cypress runner crashed: "%s"', message); + warn( + "The following spec files will be marked as failed: %s", + spec.spec.split(",").map((i) => ` + - ${i}`).join("") + ); + return { + status: "failed", + failures: 1, + message + }; + }, + () => { + } +)(spec, cypressRunOptions); + +init_esm_shims(); +var isCc = () => !!process.env.CC_ENFORCE_IS_CC || getAPIBaseUrl() === "set baseURL"; + +init_esm_shims(); +var getGitInfo = async (projectRoot) => { + const commitInfo = await git.commitInfo(projectRoot); + return getCommitDefaults({ + branch: commitInfo.branch, + remoteOrigin: commitInfo.remote, + authorEmail: commitInfo.email, + authorName: commitInfo.author, + message: commitInfo.message, + sha: commitInfo.sha + }); +}; + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); +var getCoverageFilePath = async (coverageFile = "./.nyc_output/out.json") => { + const path6 = join2(process.cwd(), coverageFile); + try { + await fs2.access(path6); + return { + path: path6, + error: false + }; + } catch (error2) { + return { + path: path6, + error: error2 + }; + } +}; + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); +var emptyStats = { + totalDuration: 0, + totalSuites: 0, + totalPending: 0, + totalFailed: 0, + totalSkipped: 0, + totalPassed: 0, + totalTests: 0 +}; +var getDummyFailedTest = (start, error2) => ({ + title: ["Unknown"], + state: "failed", + body: "// This test is automatically generated due to execution failure", + displayError: error2, + attempts: [ + { + state: "failed", + startedAt: start, + duration: 0, + videoTimestamp: 0, + screenshots: [], + error: { + name: "CypressExecutionError", + message: error2, + stack: "", + codeFrame: null + } + } + ] +}); +function getFailedFakeInstanceResult(configState, { + specs, + error: error2 +}) { + const start = ( new Date()).toISOString(); + const end = ( new Date()).toISOString(); + return { + config: configState.getConfig() ?? {}, + status: "finished", + startedTestsAt: ( new Date()).toISOString(), + endedTestsAt: ( new Date()).toISOString(), + totalDuration: 0, + totalSuites: 1, + totalFailed: 1, + totalPassed: 0, + totalPending: 0, + totalSkipped: 0, + totalTests: 1, + browserName: "unknown", + browserVersion: "unknown", + browserPath: "unknown", + osName: "unknown", + osVersion: "unknown", + cypressVersion: "unknown", + runs: specs.map((s) => ({ + stats: { + suites: 1, + tests: 1, + passes: 0, + pending: 0, + skipped: 0, + failures: 1, + startedAt: start, + endedAt: end, + duration: 0 + }, + reporter: "spec", + reporterStats: { + suites: 1, + tests: 1, + passes: 0, + pending: 0, + failures: 1, + start, + end, + duration: 0 + }, + hooks: [], + error: error2, + video: null, + spec: { + name: s, + relative: s, + absolute: s, + relativeToCommonRoot: s, + baseName: s, + specType: "integration", + fileExtension: "js", + fileName: s, + specFileExtension: "js" + }, + tests: [getDummyFailedTest(start, error2)], + shouldUploadVideo: false, + skippedSpec: false + })) + }; +} + +var summarizeExecution = (input, config) => { + if (!input.length) { + return ModuleAPIResults.getEmptyResult(config); + } + const overall = input.reduce( + (acc, { + totalDuration, + totalFailed, + totalPassed, + totalPending, + totalSkipped, + totalTests, + totalSuites + }) => ({ + totalDuration: acc.totalDuration + totalDuration, + totalSuites: acc.totalSuites + totalSuites, + totalPending: acc.totalPending + totalPending, + totalFailed: acc.totalFailed + totalFailed, + totalSkipped: acc.totalSkipped + totalSkipped, + totalPassed: acc.totalPassed + totalPassed, + totalTests: acc.totalTests + totalTests + }), + emptyStats + ); + const firstResult = input[0]; + const startItems = input.map((i) => i.startedTestsAt).sort(); + const endItems = input.map((i) => i.endedTestsAt).sort(); + const runs = input.map((i) => i.runs).flat(); + return { + ...overall, + runs, + startedTestsAt: _9.first(startItems), + endedTestsAt: _9.last(endItems), + ..._9.pick( + firstResult, + "browserName", + "browserVersion", + "browserPath", + "osName", + "osVersion", + "cypressVersion", + "config" + ), + status: "finished" + }; +}; + +init_esm_shims(); +var failureIcon = red("\u2716"); +var successIcon = green("\u2714"); +var summaryTable = (r) => { + const overallSpecCount = r.runs.length; + const failedSpecsCount = _10.sum( + r.runs.filter((v) => v.stats.failures + v.stats.skipped > 0).map(() => 1) + ); + const hasFailed = failedSpecsCount > 0; + const verdict = hasFailed ? red(`${failedSpecsCount} of ${overallSpecCount} failed`) : overallSpecCount > 0 ? "All specs passed!" : "No specs executed"; + const specs = r.runs.map((r2) => r2.spec.relative); + const commonPath = getCommonPath(specs); + const data = r.runs.map((r2) => [ + r2.stats.failures + r2.stats.skipped > 0 ? failureIcon : successIcon, + stripCommonPath(r2.spec.relative, commonPath), + gray(prettyMS(r2.stats.duration ?? 0)), + white(r2.stats.tests ?? 0), + r2.stats.passes ? green(r2.stats.passes) : gray("-"), + r2.stats.failures ? red(r2.stats.failures) : gray("-"), + r2.stats.pending ? cyan(r2.stats.pending) : gray("-"), + r2.stats.skipped ? red(r2.stats.skipped) : gray("-") + ]); + return table( + [ + [ + "", + gray("Spec"), + "", + gray("Tests"), + gray("Passing"), + gray("Failing"), + gray("Pending"), + gray("Skipped") + ], + ...data, + [ + hasFailed ? failureIcon : successIcon, + verdict, + gray(prettyMS(r.totalDuration ?? 0)), + overallSpecCount > 0 ? white(r.totalTests ?? 0) : gray("-"), + r.totalPassed ? green(r.totalPassed) : gray("-"), + r.totalFailed ? red(r.totalFailed) : gray("-"), + r.totalPending ? cyan(r.totalPending) : gray("-"), + r.totalSkipped ? red(r.totalSkipped) : gray("-") + ] + ], + { + border, + columnDefault: { + width: 8 + }, + columns: [ + { alignment: "left", width: 2 }, + { alignment: "left", width: 30 }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" } + ], + drawHorizontalLine: (lineIndex, rowCount) => { + return lineIndex === 1 || lineIndex === 0 || lineIndex === rowCount - 1 || lineIndex === rowCount; + }, + drawVerticalLine: (lineIndex, rowCount) => { + return lineIndex === 0 || rowCount === lineIndex; + } + } + ); +}; +var border = _10.mapValues( + { + topBody: `\u2500`, + topJoin: `\u252C`, + topLeft: ` \u250C`, + topRight: `\u2510`, + bottomBody: `\u2500`, + bottomJoin: `\u2534`, + bottomLeft: ` \u2514`, + bottomRight: `\u2518`, + bodyLeft: ` \u2502`, + bodyRight: `\u2502`, + bodyJoin: `\u2502`, + joinBody: `\u2500`, + joinLeft: ` \u251C`, + joinRight: `\u2524`, + joinJoin: `\u253C` + }, + (v) => gray(v) +); +function getCommonPath(specs) { + if (specs.length === 0) { + return ""; + } + if (specs.length === 1) { + return path3.dirname(specs[0]) + path3.sep; + } + return getCommonPathPrefix(specs); +} +function stripCommonPath(spec, commonPath) { + return spec.replace(commonPath, ""); +} + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); +var readFile = fs3.promises.readFile; +var debug11 = Debug9("cc:upload"); +function uploadVideo(file2, url) { + return uploadFile(file2, url, "video/mp4"); +} +function uploadImage(file2, url) { + return uploadFile(file2, url, "image/png"); +} +function uploadJson(file2, url) { + return uploadFile(file2, url, "application/json"); +} +async function uploadFile(file2, url, type) { + debug11('uploading file "%s" to "%s"', file2, url); + const f = await readFile(file2); + await makeRequest({ + url, + method: "PUT", + data: f, + headers: { + "Content-Type": type, + "Content-Disposition": `inline` + } + }); +} + +var debug12 = Debug10("cc:artifacts"); +async function uploadArtifacts({ + executionState, + videoPath, + videoUploadUrl, + screenshots, + screenshotUploadUrls, + coverageFilePath, + coverageUploadUrl +}) { + debug12("uploading artifacts: %o", { + videoPath, + videoUploadUrl, + screenshots, + screenshotUploadUrls, + coverageFilePath, + coverageUploadUrl + }); + const totalUploads = (videoPath ? 1 : 0) + screenshots.length + (coverageUploadUrl ? 1 : 0); + if (totalUploads === 0) { + return; + } + if (videoUploadUrl && videoPath) { + await safe( + uploadVideo, + (e) => { + debug12("failed uploading video %s. Error: %o", videoPath, e); + executionState.addWarning( + `Failed uploading video ${videoPath}. +${dim(e)}` + ); + }, + () => debug12("success uploading", videoPath) + )(videoPath, videoUploadUrl); + } + if (screenshotUploadUrls && screenshotUploadUrls.length) { + await Promise.all( + screenshots.map((screenshot) => { + const url = screenshotUploadUrls.find( + (urls) => urls.screenshotId === screenshot.screenshotId + )?.uploadUrl; + if (!url) { + debug12( + "No upload url for screenshot %o, screenshotUploadUrls: %o", + screenshot, + screenshotUploadUrls + ); + executionState.addWarning( + `No upload URL for screenshot ${screenshot.path}` + ); + return Promise.resolve(); + } + return safe( + uploadImage, + (e) => { + debug12( + "failed uploading screenshot %s. Error: %o", + screenshot.path, + e + ); + executionState.addWarning( + `Failed uploading screenshot ${screenshot.path}. +${dim(e)}` + ); + }, + () => debug12("success uploading", screenshot.path) + )(screenshot.path, url); + }) + ); + } + if (coverageUploadUrl && coverageFilePath) { + await safe( + uploadJson, + (e) => { + debug12( + "failed uploading coverage file %s. Error: %o", + coverageFilePath, + e + ); + executionState.addWarning( + `Failed uploading coverage file ${coverageFilePath}. +${dim(e)}` + ); + }, + () => debug12("success uploading", coverageFilePath) + )(coverageFilePath, coverageUploadUrl); + } +} +var uploadStdoutSafe = safe( + updateInstanceStdout, + () => { + }, + () => { + } +); + +init_esm_shims(); + +init_esm_shims(); +var state = { + cancellationReason: null +}; +var setCancellationReason = (reason) => { + if (state.cancellationReason) { + return; + } + state.cancellationReason = reason; + getPubSub().emit("run:cancelled", reason); +}; + +init_esm_shims(); +var debug13 = Debug11("cc:results"); +var getInstanceResultPayload = (runResult, coverageFilePath) => { + debug13("generating instance result payload from %o", runResult); + return { + stats: StandardResultsToAPIResults.getStats(runResult.stats), + reporterStats: runResult.reporterStats, + exception: runResult.error ?? null, + video: !!runResult.video, + screenshots: StandardResultsToAPIResults.getAllScreenshots(runResult), + hasCoverage: !!coverageFilePath, + tests: (runResult.tests ?? []).map( + StandardResultsToAPIResults.getTestForResults + ) + }; +}; +var getInstanceTestsPayload = (runResult, config) => { + return { + config: { + ...config.getConfig(), + videoUploadOnPasses: config.getConfig()?.videoUploadOnPasses ?? true + }, + tests: (runResult.tests ?? []).map( + StandardResultsToAPIResults.getTestForSetTests + ), + hooks: runResult.hooks + }; +}; +var StandardResultsToAPIResults = class _StandardResultsToAPIResults { + static getTestAttempt(attempt) { + return { + state: attempt.state, + error: attempt.error, + wallClockStartedAt: attempt.startedAt, + wallClockDuration: attempt.duration, + videoTimestamp: attempt.videoTimestamp + }; + } + static getTestForResults(test, index) { + return { + displayError: test.displayError, + state: test.state, + attempts: (test.attempts ?? []).map( + _StandardResultsToAPIResults.getTestAttempt + ), + clientId: `r${index}` + }; + } + static getTestForSetTests(test, index) { + return { + body: "redacted", + title: test.title, + clientId: `r${index}` + }; + } + static getAllScreenshots(run2) { + return (run2.tests ?? []).flatMap( + (t, i) => t.attempts.flatMap( + (a, j) => a.screenshots.map((s) => ({ + ...s, + testId: `r${i}`, + testAttemptIndex: j, + screenshotId: getRandomString() + })) + ) + ); + } + static getStats(stats) { + return { + ...stats, + wallClockDuration: stats.duration, + wallClockStartedAt: stats.startedAt, + wallClockEndedAt: stats.endedAt + }; + } +}; + +var debug14 = Debug12("cc:results"); +async function getReportResultsTask(instanceId, executionState, configState, stdout2, coverageFilePath) { + const results = executionState.getInstanceResults(configState, instanceId); + const run2 = results.runs[0]; + if (!run2) { + throw new Error("No run found in Cypress results"); + } + const instanceResults = getInstanceResultPayload(run2, coverageFilePath); + const instanceTests = getInstanceTestsPayload(run2, configState); + const { videoUploadUrl, screenshotUploadUrls, coverageUploadUrl, cloud } = await reportResults(instanceId, instanceTests, instanceResults); + if (cloud?.shouldCancel) { + debug14("instance %s should cancel", instanceId); + setCancellationReason(cloud.shouldCancel); + } + debug14("instance %s artifact upload instructions %o", instanceId, { + videoUploadUrl, + screenshotUploadUrls, + coverageUploadUrl + }); + return Promise.all([ + uploadArtifacts({ + executionState, + videoUploadUrl, + videoPath: run2.video, + screenshotUploadUrls, + screenshots: instanceResults.screenshots, + coverageUploadUrl, + coverageFilePath + }), + uploadStdoutSafe(instanceId, getInitialOutput() + stdout2) + ]); +} +async function reportResults(instanceId, instanceTests, instanceResults) { + debug14("reporting instance %s results...", instanceId); + if (isCc()) { + return reportInstanceResultsMerged(instanceId, { + tests: instanceTests, + results: instanceResults + }); + } + await setInstanceTests(instanceId, instanceTests); + return updateInstanceResults(instanceId, instanceResults); +} + +var debug15 = Debug13("cc:reportTask"); +var reportTasks = []; +var createReportTask = (configState, executionState, instanceId) => { + const instance = executionState.getInstance(instanceId); + if (!instance) { + error("Cannot find execution state for instance %s", instanceId); + return; + } + if (instance.reportStartedAt) { + debug15("Report task already created for instance %s", instanceId); + return; + } + instance.reportStartedAt = new Date(); + debug15("Creating report task for instanceId %s", instanceId); + reportTasks.push( + getReportResultsTask( + instanceId, + executionState, + configState, + instance.output ?? "no output captured", + instance.coverageFilePath + ).catch(error) + ); +}; +var createReportTaskSpec = (configState, executionState, spec) => { + const i = executionState.getSpec(spec); + if (!i) { + error("Cannot find execution state for spec %s", spec); + return; + } + debug15("Creating report task for spec %s", spec); + return createReportTask(configState, executionState, i.instanceId); +}; + +var debug16 = Debug14("cc:runner"); +async function runTillDone(executionState, configState, { + runId, + groupId, + machineId, + platform: platform2, + specs: allSpecs +}, params) { + let hasMore = true; + while (hasMore) { + const newTasks = await runBatch(executionState, configState, { + runMeta: { + runId, + groupId, + machineId, + platform: platform2 + }, + allSpecs, + params + }); + if (!newTasks.length) { + debug16("No more tasks to run. Uploads queue: %d", reportTasks.length); + hasMore = false; + break; + } + newTasks.forEach( + (t) => createReportTask(configState, executionState, t.instanceId) + ); + } +} +async function runBatch(executionState, configState, { + runMeta, + params, + allSpecs +}) { + let batch = { + specs: [], + claimedInstances: 0, + totalInstances: 0 + }; + if (isCc()) { + debug16("Getting batched tasks: %d", params.batchSize); + batch = await createBatchedInstances({ + ...runMeta, + batchSize: params.batchSize + }); + debug16("Got batched tasks: %o", batch); + } else { + const response = await createInstance(runMeta); + if (response.spec !== null && response.instanceId !== null) { + batch.specs.push({ + spec: response.spec, + instanceId: response.instanceId + }); + } + batch.claimedInstances = response.claimedInstances; + batch.totalInstances = response.totalInstances; + } + if (batch.specs.length === 0) { + return []; + } + batch.specs.forEach((i) => executionState.initInstance(i)); + divider(); + info( + "Running: %s (%d/%d)", + batch.specs.map((s) => s.spec).join(", "), + batch.claimedInstances, + batch.totalInstances + ); + const batchedResult = await runSpecFileSafe( + { + spec: batch.specs.map((bs) => getSpecAbsolutePath(allSpecs, bs.spec)).join(",") + }, + params + ); + title("blue", "Reporting results and artifacts in background..."); + const output = getCapturedOutput(); + batch.specs.forEach((spec) => { + executionState.setInstanceOutput(spec.instanceId, output); + const singleSpecResult = getSingleSpecRunResult(spec.spec, batchedResult); + if (!singleSpecResult) { + return; + } + getPubSub().emit("run:result", { + specRelative: spec.spec, + instanceId: spec.instanceId, + runResult: singleSpecResult + }); + }); + resetCapture(); + return batch.specs; +} +function getSingleSpecRunResult(specRelative, batchedResult) { + if (!ModuleAPIResults.isSuccessResult(batchedResult)) { + return; + } + const run2 = batchedResult.runs.find((r) => r.spec.relative === specRelative); + if (!run2) { + return; + } + return { + ...batchedResult, + runs: [run2] + }; +} +function getSpecAbsolutePath(allSpecs, relative) { + const absolutePath = allSpecs.find((i) => i.relative === relative)?.absolute; + if (!absolutePath) { + warn( + 'Cannot find absolute path for spec. Spec: "%s", candidates: %o', + relative, + allSpecs + ); + throw new Error(`Cannot find absolute path for spec`); + } + return absolutePath; +} + +var cancellable = null; +function onRunCancelled(reason) { + warn( + `Run cancelled: %s. Waiting for uploads to complete and stopping execution...`, + reason + ); + cancellable?.cancel(); +} +async function runTillDoneOrCancelled(...args) { + return new Promise((_resolve, _reject) => { + cancellable = new BPromise((resolve, reject, onCancel) => { + if (!onCancel) { + _reject(new Error("BlueBird is misconfigured: onCancel is undefined")); + return; + } + onCancel(() => _resolve(void 0)); + runTillDone(...args).then( + () => { + resolve(); + _resolve(void 0); + }, + (error2) => { + reject(); + _reject(error2); + } + ); + }); + getPubSub().addListener("run:cancelled", onRunCancelled); + }).finally(() => { + getPubSub().removeListener("run:cancelled", onRunCancelled); + }); +} + +var debug17 = Debug15("cc:events"); +function handleScreenshotEvent(screenshot, executionState) { + const data = { + ...screenshot, + testId: executionState.getCurrentTestID(), + height: screenshot.dimensions.height, + width: screenshot.dimensions.width + }; + executionState.addScreenshotsData(data); +} +function handleTestBefore(testAttempt, executionState) { + const parsed = JSON.parse(testAttempt); + executionState.setCurrentTestID(parsed.id); +} +function handleTestAfter(testAttempt, executionState) { + const test = JSON.parse(testAttempt); + executionState.addAttemptsData(test); +} +async function handleSpecAfter({ + executionState, + configState, + spec, + results, + experimentalCoverageRecording = false +}) { + debug17("after:spec %s %o", spec.relative, results); + executionState.setSpecAfter( + spec.relative, + SpecAfterResult.getSpecAfterStandard(results, executionState) + ); + executionState.setSpecOutput(spec.relative, getCapturedOutput()); + const config = configState.getConfig(); + if (experimentalCoverageRecording) { + const config2 = configState.getConfig(); + const { path: path6, error: error2 } = await getCoverageFilePath( + config2?.env?.coverageFile + ); + if (!error2) { + executionState.setSpecCoverage(spec.relative, path6); + } else { + executionState.addWarning( + `Error reading coverage file "${path6}". Coverage recording will be skipped. +${dim( + error2 + )}` + ); + } + } + createReportTaskSpec(configState, executionState, spec.relative); +} + +var debug18 = Debug16("cc:events"); +function listenToEvents(configState, executionState, experimentalCoverageRecording = false) { + getPubSub().on( + "run:result", + ({ + instanceId, + runResult, + specRelative + }) => { + debug18("%s %s: %o", "run:result", instanceId, runResult); + executionState.setInstanceResult( + instanceId, + ModuleAPIResults.getStandardResult(runResult, executionState) + ); + } + ); + getPubSub().on("test:after:run", (payload) => { + debug18("%s %o", "test:after:run", payload); + handleTestAfter(payload, executionState); + }); + getPubSub().on("test:before:run", (payload) => { + debug18("%s %o", "test:before:run", payload); + handleTestBefore(payload, executionState); + }); + getPubSub().on( + "after:screenshot", + (screenshot) => { + debug18("%s %o", "after:screenshot", screenshot); + handleScreenshotEvent(screenshot, executionState); + } + ); + getPubSub().on( + "after:spec", + async ({ + spec, + results + }) => { + await handleSpecAfter({ + spec, + results, + executionState, + configState, + experimentalCoverageRecording + }); + } + ); +} + +init_esm_shims(); + +init_esm_shims(); +var debug19 = Debug17("cc:browser"); +function guessBrowser(browser, availableBrowsers = []) { + debug19( + "guessing browser from '%s', available browsers: %o", + browser, + availableBrowsers + ); + let result = availableBrowsers.find((b) => b.name === browser); + if (result) { + debug19("identified browser by name: %o", result); + return { + browserName: result.displayName, + browserVersion: result.version + }; + } + result = availableBrowsers.find((b) => b.path === browser); + if (result) { + debug19("identified browser by path: %o", result); + return { + browserName: result.displayName ?? result.name, + browserVersion: result.version + }; + } + warn("Unable to identify browser name and version"); + return { + browserName: "unknown", + browserVersion: "unknown" + }; +} + +init_esm_shims(); +var debug20 = Debug18("cc:platform"); +var getOsVersion = async () => { + if (platform() === "linux") { + try { + const linuxOs = await promisify(getos)(); + if ("dist" in linuxOs && "release" in linuxOs) { + return [linuxOs.dist, linuxOs.release].join(" - "); + } else { + return release(); + } + } catch { + return release(); + } + } + return release(); +}; +var getPlatformInfo = async () => { + const osVersion = await getOsVersion(); + const result = { + osName: platform(), + osVersion, + osCpus: cpus(), + osMemory: { + free: freemem(), + total: totalmem() + } + }; + debug20("platform info: %o", result); + return result; +}; + +async function getPlatform({ + browser, + config +}) { + return { + ...await getPlatformInfo(), + ...guessBrowser(browser ?? "electron", config.resolved?.browsers) + }; +} + +init_esm_shims(); +async function shutdown() { + await stopWSS(); +} + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); +function toArray(val) { + return val ? typeof val === "string" ? [val] : val : []; +} +function toPosix(file2, sep = path4.sep) { + return file2.split(sep).join(path4.posix.sep); +} + +var debug21 = Debug19("cc:specs"); +async function findSpecs({ + projectRoot, + testingType, + specPattern, + configSpecPattern, + excludeSpecPattern, + additionalIgnorePattern +}) { + configSpecPattern = toArray(configSpecPattern); + specPattern = toArray(specPattern); + excludeSpecPattern = toArray(excludeSpecPattern) || []; + additionalIgnorePattern = toArray(additionalIgnorePattern) || []; + debug21("exploring spec files for execution %O", { + testingType, + projectRoot, + specPattern, + configSpecPattern, + excludeSpecPattern, + additionalIgnorePattern + }); + if (!specPattern || !configSpecPattern) { + throw Error("Could not find glob patterns for exploring specs"); + } + let specAbsolutePaths = await getFilesByGlob(projectRoot, specPattern, { + absolute: true, + ignore: [...excludeSpecPattern, ...additionalIgnorePattern] + }); + if (!_11.isEqual(specPattern, configSpecPattern)) { + const defaultSpecAbsolutePaths = await getFilesByGlob( + projectRoot, + configSpecPattern, + { + absolute: true, + ignore: [...excludeSpecPattern, ...additionalIgnorePattern] + } + ); + specAbsolutePaths = _11.intersection( + specAbsolutePaths, + defaultSpecAbsolutePaths + ); + } + return matchedSpecs({ + projectRoot, + testingType, + specAbsolutePaths, + specPattern + }); +} +async function getFilesByGlob(projectRoot, glob, globOptions) { + const workingDirectoryPrefix = path5.join(projectRoot, path5.sep); + const globs = [].concat(glob).map( + (globPattern) => globPattern.startsWith("./") ? globPattern.replace("./", "") : globPattern + ).map((globPattern) => { + if (globPattern.startsWith(workingDirectoryPrefix)) { + return globPattern.replace(workingDirectoryPrefix, ""); + } + return globPattern; + }); + if (os.platform() === "win32") { + debug21("updating glob patterns to POSIX"); + for (const i in globs) { + const cur = globs[i]; + if (!cur) + throw new Error("undefined glob received"); + globs[i] = toPosix(cur); + } + } + try { + debug21("globbing pattern(s): %o", globs); + debug21("within directory: %s", projectRoot); + return matchGlobs(globs, { + onlyFiles: true, + absolute: true, + cwd: projectRoot, + ...globOptions, + ignore: (globOptions?.ignore ?? []).concat("**/node_modules/**") + }); + } catch (e) { + debug21("error in getFilesByGlob %o", e); + return []; + } +} +var matchGlobs = async (globs, globbyOptions) => { + return await globby(globs, globbyOptions); +}; +function matchedSpecs({ + projectRoot, + testingType, + specAbsolutePaths +}) { + debug21("found specs %o", specAbsolutePaths); + let commonRoot = ""; + if (specAbsolutePaths.length === 1) { + commonRoot = path5.dirname(specAbsolutePaths[0]); + } else { + commonRoot = commonPathPrefix(specAbsolutePaths); + } + return specAbsolutePaths.map( + (absolute) => transformSpec({ + projectRoot, + absolute, + testingType, + commonRoot, + platform: os.platform(), + sep: path5.sep + }) + ); +} +function transformSpec({ + projectRoot, + absolute, + testingType, + commonRoot, + platform: platform2, + sep +}) { + if (platform2 === "win32") { + absolute = toPosix(absolute, sep); + projectRoot = toPosix(projectRoot, sep); + } + const relative = path5.relative(projectRoot, absolute); + const parsedFile = path5.parse(absolute); + const fileExtension = path5.extname(absolute); + const specFileExtension = [".spec", ".test", "-spec", "-test", ".cy"].map((ext) => ext + fileExtension).find((ext) => absolute.endsWith(ext)) || fileExtension; + const parts = absolute.split(projectRoot); + let name = parts[parts.length - 1] || ""; + if (name.startsWith("/")) { + name = name.slice(1); + } + const LEADING_SLASH = /^\/|/g; + const relativeToCommonRoot = absolute.replace(commonRoot, "").replace(LEADING_SLASH, ""); + return { + fileExtension, + baseName: parsedFile.base, + fileName: parsedFile.base.replace(specFileExtension, ""), + specFileExtension, + relativeToCommonRoot, + specType: testingType === "component" ? "component" : "integration", + name, + relative, + absolute + }; +} + +var getSpecFiles = async ({ + config, + params +}) => { + const specPattern = getSpecPattern(config.specPattern, params.spec); + const specs = await findSpecs({ + projectRoot: params.project ?? config.projectRoot, + testingType: params.testingType, + specPattern, + configSpecPattern: config.specPattern, + excludeSpecPattern: config.excludeSpecPattern, + additionalIgnorePattern: config.additionalIgnorePattern + }); + if (specs.length === 0) { + warn( + "Found no spec files. Was looking for spec files that match both configSpecPattern and specPattern relative to projectRoot. Configuration: %O", + { + projectRoot: config.projectRoot, + specPattern, + configSpecPattern: config.specPattern, + excludeSpecPattern: [ + config.excludeSpecPattern, + config.additionalIgnorePattern + ].flat(2), + testingType: params.testingType + } + ); + } + return { specs, specPattern }; +}; +function getSpecPattern(configPattern, explicit) { + return explicit || configPattern; +} + +init_esm_shims(); + +init_esm_shims(); +var ConfigState = class { + constructor() { + this._config = void 0; + } + setConfig(c) { + this._config = c; + } + getConfig() { + return this._config; + } +}; + +init_esm_shims(); + +init_esm_shims(); +var SpecAfterToModuleAPIMapper = class _SpecAfterToModuleAPIMapper { + static getTestAttempt(attempt, screenshots) { + return { + ...attempt, + duration: attempt.wallClockDuration, + startedAt: attempt.wallClockStartedAt, + screenshots + }; + } + static getTest(t, screenshots) { + return { + ...t, + attempts: t.attempts.map( + (a, i) => _SpecAfterToModuleAPIMapper.getTestAttempt( + a, + screenshots.filter( + (s) => s.testId === t.testId && s.testAttemptIndex === i + ) + ) + ) + }; + } + static convert(specAfterResult, configState) { + const stats = { + duration: specAfterResult.stats.wallClockDuration, + endedAt: specAfterResult.stats.wallClockEndedAt, + startedAt: specAfterResult.stats.wallClockStartedAt, + failures: specAfterResult.stats.failures ?? 0, + passes: specAfterResult.stats.passes ?? 0, + pending: specAfterResult.stats.pending ?? 0, + skipped: specAfterResult.stats.skipped ?? 0, + suites: specAfterResult.stats.suites ?? 0, + tests: specAfterResult.stats.tests ?? 0 + }; + return { + status: "finished", + config: configState.getConfig(), + totalDuration: stats.duration, + totalSuites: stats.suites, + totalTests: stats.tests, + totalFailed: stats.failures, + totalPassed: stats.passes, + totalPending: stats.pending, + totalSkipped: stats.skipped ?? 0, + startedTestsAt: stats.startedAt, + endedTestsAt: stats.endedAt, + runs: [ + { + stats, + reporter: specAfterResult.reporter, + reporterStats: specAfterResult.reporterStats ?? null, + spec: specAfterResult.spec, + error: specAfterResult.error, + video: specAfterResult.video, + shouldUploadVideo: true, + hooks: specAfterResult.hooks, + tests: (specAfterResult.tests ?? []).map( + (t) => _SpecAfterToModuleAPIMapper.getTest(t, specAfterResult.screenshots) + ) + } + ] + }; + } + static backfillException(result) { + return { + ...result, + runs: result.runs.map(_SpecAfterToModuleAPIMapper.backfillExceptionRun) + }; + } + static backfillExceptionRun(run2) { + if (!run2.error) { + return run2; + } + return { + ...run2, + tests: [getFakeTestFromException(run2.error, run2.stats)] + }; + } +}; +function getFakeTestFromException(error2, stats) { + return { + title: ["Unknown"], + body: "", + displayError: error2.split("\n")[0], + state: "failed", + attempts: [ + { + state: "failed", + duration: 0, + error: { + name: "Error", + message: error2.split("\n")[0], + stack: error2, + codeFrame: null + }, + screenshots: [], + startedAt: stats.startedAt, + videoTimestamp: 0 + } + ] + }; +} + +import chalk2 from "chalk"; +import { default as Debug20, default as Debug22 } from "debug"; +import _12 from "lodash"; +import assert from "node:assert"; +import plur from "plur"; +var debug22 = Debug20("cc:state"); +var ExecutionState = class { + constructor() { + this.warnings = new Set(); + this.attemptsData = []; + this.screenshotsData = []; + this.state = {}; + } + getWarnings() { + return this.warnings; + } + addWarning(warning) { + this.warnings.add(warning); + } + getResults(configState) { + return Object.values(this.state).map( + (i) => this.getInstanceResults(configState, i.instanceId) + ); + } + getInstance(instanceId) { + return this.state[instanceId]; + } + getSpec(spec) { + return Object.values(this.state).find((i) => i.spec === spec); + } + initInstance({ + instanceId, + spec + }) { + debug22('Init execution state for "%s"', spec); + this.state[instanceId] = { + instanceId, + spec, + createdAt: new Date() + }; + } + setSpecBefore(spec) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + i.specBefore = new Date(); + } + setSpecCoverage(spec, coverageFilePath) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + debug22("Experimental: coverageFilePath was set"); + i.coverageFilePath = coverageFilePath; + } + setSpecAfter(spec, results) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + i.specAfter = new Date(); + i.specAfterResults = results; + } + setSpecOutput(spec, output) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + this.setInstanceOutput(i.instanceId, output); + } + setInstanceOutput(instanceId, output) { + const i = this.state[instanceId]; + if (!i) { + warn('Cannot find execution state for instance "%s"', instanceId); + return; + } + if (i.output) { + debug22('Instance "%s" already has output', instanceId); + return; + } + i.output = output; + } + setInstanceResult(instanceId, runResults) { + const i = this.state[instanceId]; + if (!i) { + warn('Cannot find execution state for instance "%s"', instanceId); + return; + } + i.runResults = { + ...runResults, + status: "finished" + }; + i.runResultsReportedAt = new Date(); + } + getInstanceResults(configState, instanceId) { + const i = this.getInstance(instanceId); + if (!i) { + error('Cannot find execution state for instance "%s"', instanceId); + return getFailedFakeInstanceResult(configState, { + specs: ["unknown"], + error: `[cc] Error while processing cypress results for instance ${instanceId}. See the console output for details.` + }); + } + if (i.specAfterResults) { + debug22('Using spec:after results for %s "%s"', instanceId, i.spec); + return SpecAfterToModuleAPIMapper.backfillException( + SpecAfterToModuleAPIMapper.convert(i.specAfterResults, configState) + ); + } + if (i.runResults) { + debug22('Using runResults for %s "%s"', instanceId, i.spec); + return SpecAfterToModuleAPIMapper.backfillException(i.runResults); + } + debug22('No results detected for "%s"', i.spec); + return getFailedFakeInstanceResult(configState, { + specs: [i.spec], + error: `No results detected for the spec file. That usually happens because of cypress crash. See the console output for details.` + }); + } + addAttemptsData(attemptDetails) { + this.attemptsData.push(attemptDetails); + } + getAttemptsData() { + return this.attemptsData; + } + addScreenshotsData(screenshotsData) { + this.screenshotsData.push(screenshotsData); + } + getScreenshotsData() { + return this.screenshotsData; + } + setCurrentTestID(testID) { + this.currentTestID = testID; + } + getCurrentTestID() { + return this.currentTestID; + } +}; + +init_esm_shims(); +function printWarnings2(executionState) { + const warnings = Array.from(executionState.getWarnings()); + if (warnings.length > 0) { + warn( + `${warnings.length} ${plur( + "Warning", + warnings.length + )} encountered during the execution: +${warnings.map( + (w, i) => ` +${chalk2.yellow(`[${i + 1}/${warnings.length}]`)} ${w}` + ).join("\n")}` + ); + } +} + +var debug23 = Debug21("cc:run"); +async function run(params = {}) { + const executionState = new ExecutionState(); + const configState = new ConfigState(); + activateDebug(params.cloudDebug); + debug23("run params %o", params); + params = preprocessParams(params); + debug23("params after preprocess %o", params); + if (isOffline(params)) { + info(`Skipping cloud orchestration because --record is set to false`); + return runBareCypress(params); + } + const validatedParams = await validateParams(params); + setAPIBaseUrl(validatedParams.cloudServiceUrl); + if (!isCc()) { + console.log(getLegalNotice()); + } + const { + recordKey, + projectId, + group, + parallel, + ciBuildId, + tag, + testingType, + batchSize, + autoCancelAfterFailures, + experimentalCoverageRecording + } = validatedParams; + const config = await getMergedConfig(validatedParams); + configState.setConfig(config?.resolved); + const { specs, specPattern } = await getSpecFiles({ + config, + params: validatedParams + }); + if (specs.length === 0) { + return; + } + const platform2 = await getPlatform({ + config, + browser: validatedParams.browser + }); + info(`@krivega/cc version: ${dim(_ccVersion)}`); + info(`Cypress version: ${dim(_cypressVersion)}`); + info("Discovered %d spec files", specs.length); + info( + `Tags: ${tag.length > 0 ? tag.join(",") : false}; Group: ${group ?? false}; Parallel: ${parallel ?? false}; Batch Size: ${batchSize}` + ); + info("Connecting to cloud orchestration service..."); + const run2 = await createRun({ + ci: getCI(ciBuildId), + specs: specs.map((spec) => spec.relative), + commit: await getGitInfo(config.projectRoot), + group, + platform: platform2, + parallel: parallel ?? false, + ciBuildId, + projectId, + recordKey, + specPattern: [specPattern].flat(2), + tags: tag, + testingType, + batchSize, + autoCancelAfterFailures, + coverageEnabled: experimentalCoverageRecording + }); + setRunId(run2.runId); + info("\u{1F3A5} Run URL:", bold(run2.runUrl)); + cutInitialOutput(); + await startWSS(); + listenToEvents( + configState, + executionState, + config.experimentalCoverageRecording + ); + await runTillDoneOrCancelled( + executionState, + configState, + { + runId: run2.runId, + groupId: run2.groupId, + machineId: run2.machineId, + platform: platform2, + specs + }, + validatedParams + ); + divider(); + await Promise.allSettled(reportTasks); + const _summary = summarizeExecution( + executionState.getResults(configState), + config + ); + title("white", "Cloud Run Finished"); + console.log(summaryTable(_summary)); + printWarnings2(executionState); + info("\n\u{1F3C1} Recorded Run:", bold(run2.runUrl)); + await shutdown(); + spacer(); + return { + ..._summary, + runUrl: run2.runUrl + }; +} + +init_esm_shims(); + +init_esm_shims(); + +init_esm_shims(); +var nestedObjectsInCurlyBracesRe = /\{(.+?)\}/g; +var nestedArraysInSquareBracketsRe = /\[(.+?)\]/g; +var everythingAfterFirstEqualRe = /=(.*)/; +var sanitizeAndConvertNestedArgs = (str, argName) => { + if (!str) { + return; + } + assert(_12.isString(argName) && argName.trim() !== ""); + try { + if (typeof str === "object") { + return str; + } + const parsed = tryJSONParse(str); + if (parsed) { + return parsed; + } + return _12.chain(str).replace(nestedObjectsInCurlyBracesRe, commasToPipes).replace(nestedArraysInSquareBracketsRe, commasToPipes).split(",").map((pair) => { + return pair.split(everythingAfterFirstEqualRe); + }).fromPairs().mapValues(JSONOrCoerce).value(); + } catch (err) { + error("could not parse CLI option '%s' value: %s", argName, str); + error("error %o", err); + return void 0; + } +}; +var tryJSONParse = (str) => { + try { + return JSON.parse(str) === Infinity ? null : JSON.parse(str); + } catch (err) { + return null; + } +}; +var commasToPipes = (match5) => { + return match5.split(",").join("|"); +}; +var pipesToCommas = (str) => { + return str.split("|").join(","); +}; +var JSONOrCoerce = (str) => { + const parsed = tryJSONParse(str); + if (parsed) { + return parsed; + } + str = pipesToCommas(str); + const parsed2 = tryJSONParse(str); + if (parsed2) { + return parsed2; + } + return coerce(str); +}; +var coerce = (value) => { + const num = _12.toNumber(value); + if (_12.invoke(num, "toString") === value) { + return num; + } + const bool = toBoolean(value); + if (_12.invoke(bool, "toString") === value) { + return bool; + } + const obj = tryJSONParse(value); + if (obj && typeof obj === "object") { + return obj; + } + const arr = _12.toArray(value); + if (_12.invoke(arr, "toString") === value) { + return arr; + } + return value; +}; +var toBoolean = (value) => { + switch (value) { + case "true": + return true; + case "false": + return false; + default: + return value; + } +}; + +init_esm_shims(); +var import_extra_typings = __toESM(require_extra_typings()); +var createProgram = (command = new import_extra_typings.Command()) => command.name("@krivega/cc").description( + ` +Run Cypress tests on CI using https://cc.dev or https://sorry-cypress.dev as an orchestration and reporting service + +${getLegalNotice()} + ` +).option( + "-b, --browser ", + "runs Cypress in the browser with the given name; if a filesystem path is supplied, Cypress will attempt to use the browser at that path" +).option( + "--ci-build-id ", + "the unique identifier for a run, this value is automatically detected for most CI providers" +).addOption( + new import_extra_typings.Option("--component", "runs Cypress component test").default(false).implies({ + e2e: false + }) +).option( + "-c, --config ", + "sets Cypress configuration values. separate multiple values with a comma. overrides any value in cypress.config.{js,ts,mjs,cjs}" +).option( + "-e, --env ", + "sets environment variables. separate multiple values with a comma. overrides any value in cypress.config.{js,ts,mjs,cjs} or cypress.env.json" +).option( + "-C, --config-file ", + 'specify Cypress config file, path to script file where Cypress configuration values are set. defaults to "cypress.config.{js,ts,mjs,cjs}"' +).addOption(new import_extra_typings.Option("--e2e", "runs end to end tests").default(true)).option("--group ", "a named group for recorded runs in Cc").addOption( + new import_extra_typings.Option( + "-k, --key ", + "your secret Record Key obtained from Cc. you can omit this if you set a CC_RECORD_KEY environment variable" + ).env("CC_RECORD_KEY") +).option( + "--parallel", + "enables concurrent runs and automatic load balancing of specs across multiple machines or processes", + false +).addOption( + new import_extra_typings.Option( + "-p, --port ", + "runs Cypress on a specific port. overrides any value in cypress.config.{js,ts,mjs,cjs}" + ).argParser((i) => parseInt(i, 10)) +).option( + "-P, --project ", + "path to your Cypress project root location - defaults to the current working directory" +).option("-q, --quiet", "suppress verbose output from Cypress").addOption( + new import_extra_typings.Option( + "--record [bool]", + "records the run and sends test results, screenshots and videos to Cc" + ).default(true).argParser((i) => i === "false" ? false : true) +).option( + "-r, --reporter ", + 'use a specific mocha reporter for Cypress, pass a path to use a custom reporter, defaults to "spec"' +).option( + "-o, --reporter-options ", + 'options for the mocha reporter. defaults to "null"' +).addOption( + new import_extra_typings.Option( + "-s, --spec ", + 'define specific glob pattern for running the spec file(s), Defaults to the "specMatch" entry from the "cypress.config.{js,ts,mjs,cjs}" file' + ).argParser(parseCommaSeparatedList) +).option( + "-t, --tag ", + "comma-separated tag(s) for recorded runs in Cc", + parseCommaSeparatedList +).addOption( + new import_extra_typings.Option( + "--auto-cancel-after-failures ", + "Automatically abort the run after the specified number of failed tests. Overrides the default project settings. If set, must be a positive integer or 'false' to disable (Cc-only)" + ).argParser(parseAutoCancelFailures) +).addOption( + new import_extra_typings.Option("--headed [bool]", "Run cypress in headed mode").default(false).argParser((i) => i === "false" ? false : true) +).addOption( + new import_extra_typings.Option( + "--cloud-config-file ", + "Specify the config file for @krivega/cc, defaults to 'cc.config.js' and will be searched in the project root, unless an aboslue path is provided" + ).default(void 0) +).addOption( + new import_extra_typings.Option( + `--cloud-debug [true | string]`, + `Enable debug mode for @krivega/cc, this will print out logs for troubleshooting. Values: [true | ${Object.values( + DebugMode + ).join( + " | " + )}]. Use comma to separate multiple values, e.g. --cloud-debug commit-info,cc` + ).argParser(parseCommaSeparatedList).default(void 0) +).addOption( + new import_extra_typings.Option( + `--experimental-coverage-recording [bool]`, + `Enable recording coverage results, specify the "coverageFile" Cypress environment variable for a custom coverage file, default is "./.nyc_output/out.json"` + ).default(void 0).argParser((i) => i === "false" ? false : true) +); +var program = createProgram(); +function parseCommaSeparatedList(value, previous = []) { + if (value) { + return previous.concat(value.split(",").map((t) => t.trim())); + } + return previous; +} +function parseAutoCancelFailures(value) { + if (value === "false") { + return false; + } + const parsedValue = parseInt(value, 10); + if (isNaN(parsedValue) || parsedValue < 1) { + throw new Error( + "Invalid argument provided. Must be a positive integer or 'false'." + ); + } + return parsedValue; +} + +var debug24 = Debug22("cc:cli"); +function parseCLIOptions(_program = program, ...args) { + const opts = _program.parse(...args).opts(); + activateDebug(opts.cloudDebug); + debug24("parsed CLI flags %o", opts); + const { e2e, component } = opts; + if (e2e && component) { + _program.error("Cannot use both e2e and component options"); + } + return getRunParametersFromCLI(opts); +} +function getRunParametersFromCLI(cliOptions) { + const { component, e2e, ...restOptions } = cliOptions; + const testingType = component ? "component" : "e2e"; + const result = { + ...restOptions, + config: sanitizeAndConvertNestedArgs(cliOptions.config, "config"), + env: sanitizeAndConvertNestedArgs(cliOptions.env, "env"), + reporterOptions: sanitizeAndConvertNestedArgs( + cliOptions.reporterOptions, + "reporterOptions" + ), + testingType, + recordKey: cliOptions.key + }; + debug24("parsed run params: %o", result); + return result; +} + +async function main() { + return run(parseCLIOptions()); +} +main().then((result) => { + if (!result) { + process.exit(1); + } + if (result.status === "failed") { + process.exit(1); + } + const overallFailed = result.totalFailed + result.totalSkipped; + if (overallFailed > 0) { + process.exit(overallFailed); + } + process.exit(0); +}).catch((err) => { + if (err instanceof ValidationError) { + program.error(withError(err.toString())); + } else { + console.error(err); + } + process.exit(1); +}); diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..e32e764 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,346 @@ +type TestState = "failed" | "passed" | "pending" | "skipped"; +type TestAttemptState = "failed" | "passed" | "pending"; +type TestingType$1 = "e2e" | "component"; +interface MochaError { + message: string; + name: string; + stack: string; + parsedStack: MochaParsedStackItem[]; + codeFrame: MochaCodeFrame; +} +interface MochaInvocationDetails { + function: string; + fileUrl: string; + originalFile: string; + relativeFile: string; + absoluteFile: string; + line: number; + column: number; + whitespace: string; + stack: string; +} +interface MochaCodeFrame { + line: number; + column: number; + originalFile: string; + relativeFile: string; + absoluteFile: string; + frame: string; + language: string; +} +interface MochaParsedStackItem { + message: string; + whitespace: string; + function?: string; + fileUrl?: string; + originalFile?: string; + relativeFile?: string; + absoluteFile?: string; + line?: number; + column?: number; +} +interface MochaHook { + title: string; + hookName: string; + hookId: string; + pending: boolean; + body: string; + type: string; + file: null | string; + invocationDetails: MochaInvocationDetails; + currentRetry: number; + retries: number; + _slow: number; +} +type TimingKey = "before each" | "after each" | "after all" | "before all"; +type Timing = { + [key in TimingKey]?: HookTimingItem; +} & { + lifecycle: number; + test: TimingItem; +}; +interface HookTimingItem extends TimingItem { + hookId: string; +} +interface TimingItem { + fnDuration: number; + afterFnDuration: number; +} + +declare namespace Cypress12 { + namespace SpecAfter { + interface Payload { + error: string | null; + hooks: Hooks[] | null; + reporter?: string; + reporterStats: ReporterStats | null; + screenshots: Screenshot[]; + spec: Spec; + stats: Stats; + tests: Test[] | null; + video: string | null; + } + interface Spec { + absolute: string; + baseName: string; + fileExtension: string; + fileName: string; + name: string; + relative: string; + relativeToCommonRoot: string; + specFileExtension: string; + specType: string; + } + interface Screenshot { + height: number; + name: string | null; + path: string; + screenshotId: string; + takenAt: string; + testAttemptIndex: number; + testId: string; + width: number; + } + interface ReporterStats { + suites: number; + tests: number; + passes: number; + pending: number; + failures: number; + start: string; + end: string; + duration: number; + } + interface Stats { + suites: number; + tests: number; + passes: number; + pending: number; + skipped: number; + failures: number; + wallClockStartedAt: string; + wallClockEndedAt: string; + wallClockDuration: number; + } + interface Test { + attempts: TestAttempt[]; + body: string; + displayError: string | null; + state: TestState; + title: string[]; + testId: string; + } + interface Hooks { + hookId: string; + hookName: "before each" | "after each" | "before all" | "after all"; + title: string[]; + body: string; + } + interface TestAttempt { + error: TestError | null; + failedFromHookId: string | null; + state: TestAttemptState; + timings: Timing | null; + videoTimestamp: number; + wallClockDuration: number; + wallClockStartedAt: string; + } + interface TestError { + message: string; + name: string; + stack: string; + codeFrame: CodeFrame | null; + } + interface CodeFrame { + line: number | null; + column: number | null; + originalFile: string | null; + relativeFile: string | null; + absoluteFile: string | null; + frame: string | null; + language: string | null; + } + } + namespace TestAfter { + interface Payload extends TestBefore.Payload { + duration: number; + err?: MochaError; + hooks: MochaHook[]; + timings: Timing; + } + } + namespace TestBefore { + interface Payload { + async: boolean; + body: string; + currentRetry: number; + fullTitle: string; + hooks?: MochaHook[]; + id: string; + invocationDetails?: MochaInvocationDetails; + order: number; + pending: boolean; + retries: number; + state: string; + sync: boolean; + timedOut: boolean; + timings: Pick; + title: string; + type: string; + wallClockStartedAt: string; + } + } + namespace ScreenshotAfter { + interface Payload { + testAttemptIndex: number; + size: number; + takenAt: string; + dimensions: { + width: number; + height: number; + }; + multipart: boolean; + specName: string; + name: string | null; + testFailure: boolean; + path: string; + scaled: boolean; + duration: number; + blackout: string[]; + } + } + namespace ModuleAPI { + type Result = CompletedResult | FailureResult; + interface FailureResult { + status: "failed"; + failures: number; + message: string; + } + interface CompletedResult { + browserName: string; + browserPath: string; + browserVersion: string; + config: Config; + cypressVersion: string; + endedTestsAt: string; + osName: string; + osVersion: string; + runs: Run[]; + startedTestsAt: string; + status: "finished" | "failed"; + totalDuration: number; + totalFailed: number; + totalPassed: number; + totalPending: number; + totalSkipped: number; + totalSuites: number; + totalTests: number; + } + interface Run { + error: SpecAfter.Payload["error"]; + hooks: SpecAfter.Payload["hooks"]; + reporter?: SpecAfter.Payload["reporter"]; + reporterStats: SpecAfter.Payload["reporterStats"]; + shouldUploadVideo: boolean; + spec: SpecAfter.Spec; + stats: Stats; + tests: Test[] | null; + video: string | null; + } + interface Test { + title: string[]; + state: TestState; + body: string; + displayError: string | null; + attempts: TestAttempt[]; + } + interface TestAttempt { + state: SpecAfter.TestAttempt["state"]; + error: SpecAfter.TestAttempt["error"]; + videoTimestamp: number; + duration: number | null; + startedAt: string; + screenshots: Screenshot[]; + } + interface Screenshot { + name: string | null; + takenAt: string; + path: string; + height: number; + width: number; + } + interface Stats { + duration: number; + endedAt: string; + failures: number; + passes: number; + pending: number; + skipped: number; + startedAt: string; + suites: number; + tests: number; + } + interface Config { + specPattern: string; + video: boolean; + videoUploadOnPasses: boolean; + version: string; + testingType: TestingType$1; + } + } +} + +type TestingType = Cypress.TestingType; +declare enum DebugMode { + None = "none", + All = "all", + Cc = "cc", + Cypress = "cypress", + CommitInfo = "commit-info" +} +type StrippedCypressModuleAPIOptions = Omit, "autoCancelAfterFailures" | "tag" | "spec" | "exit" | "headed" | "record" | "headless" | "noExit" | "parallel" | "key" | "tag" | "group" | "ciBuildId" | "cloudConfigFile">; +type CcRunParameters = StrippedCypressModuleAPIOptions & { + ciBuildId?: string; + batchSize?: number; + record?: boolean; + cloudServiceUrl?: string; + env?: object; + group?: string; + recordKey?: string; + parallel?: boolean; + projectId?: string; + spec?: string | string[]; + tag?: string | string[]; + testingType?: TestingType; + autoCancelAfterFailures?: number | false; + headed?: boolean; + cloudConfigFile?: string; + cloudDebug?: DebugMode | true | string | string[]; + experimentalCoverageRecording?: boolean; +}; +interface CcRunAPI extends CcRunParameters { +} + +declare function run(params?: CcRunAPI): Promise; + +export { CcRunAPI, run }; diff --git a/index.js b/index.js new file mode 100644 index 0000000..65ed250 --- /dev/null +++ b/index.js @@ -0,0 +1,3267 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +var cypress_cloud_exports = {}; +__export(cypress_cloud_exports, { + run: () => run2 +}); +module.exports = __toCommonJS(cypress_cloud_exports); + +var getImportMetaUrl = () => typeof document === "undefined" ? new URL("file:" + __filename).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href; +var importMetaUrl = getImportMetaUrl(); + +var import_register = require("source-map-support/register.js"); + +var import_module = require("module"); +var require2 = (0, import_module.createRequire)(importMetaUrl); + +var import_child_process = __toESM(require("child_process")); +var orginal = import_child_process.default.spawn; +import_child_process.default.spawn = function(command, args, options) { + if (command.match(/Cypress/)) { + const process2 = orginal(command, args, { + ...options, + stdio: ["pipe", "pipe", "pipe"] + }); + return process2; + } + return orginal(command, args, options); +}; + +var import_debug = __toESM(require("debug")); +var import_http = __toESM(require("http")); +var import_lil_http_terminator = __toESM(require("lil-http-terminator")); +var import_ts_pattern = require("ts-pattern"); +var WebSocket = __toESM(require("ws")); + +var Event = ((Event2) => { + Event2["RUN_CANCELLED"] = "run:cancelled"; + Event2["RUN_RESULT"] = "run:result"; + Event2["TEST_AFTER_RUN"] = "test:after:run"; + Event2["TEST_BEFORE_RUN"] = "test:before:run"; + Event2["AFTER_SCREENSHOT"] = "after:screenshot"; + Event2["AFTER_SPEC"] = "after:spec"; + return Event2; +})(Event || {}); +var allEvents = Object.values(Event); + +var import_events = __toESM(require("events")); +var _pubsub = null; +var getPubSub = () => { + if (!_pubsub) { + _pubsub = new import_events.default(); + } + return _pubsub; +}; + +var debug = (0, import_debug.default)("cc:ws"); +var server = null; +var wss = null; +var httpTerminator = null; +var getWSSPort = () => (0, import_ts_pattern.match)(server?.address()).with({ port: import_ts_pattern.P.number }, (address) => address.port).otherwise(() => 0); +var stopWSS = async () => { + debug("terminating wss server: %d", getWSSPort()); + if (!httpTerminator) { + debug("no wss server"); + return; + } + const { success, code, message, error: error2 } = await httpTerminator.terminate(); + if (!success) { + if (code === "TIMED_OUT") + error2(message); + if (code === "SERVER_ERROR") + error2(message, error2); + if (code === "INTERNAL_ERROR") + error2(message, error2); + } + debug("terminated wss server: %d", getWSSPort()); +}; +var startWSS = () => { + if (wss) { + return; + } + server = import_http.default.createServer().on("listening", () => { + if (!server) { + throw new Error("Server not initialized"); + } + wss = new WebSocket.WebSocketServer({ + server + }); + debug("starting wss on port %d", getWSSPort()); + wss.on("connection", function connection(ws) { + ws.on("message", function incoming(event) { + const message = JSON.parse(event.toString()); + getPubSub().emit(message.type, message.payload); + }); + }); + }).listen(); + httpTerminator = (0, import_lil_http_terminator.default)({ + server + }); +}; + +var import_debug2 = __toESM(require("debug")); +var debug2 = (0, import_debug2.default)("cc:capture"); +var _write = process.stdout.write; +var _log = process.log; +var restore = function() { + process.stdout.write = _write; + process.log = _log; +}; +var stdout = function() { + debug2("capturing stdout"); + let logs = []; + const { write } = process.stdout; + const { log: log2 } = process; + if (log2) { + process.log = function(str) { + logs.push(str); + return log2.apply(this, arguments); + }; + } + process.stdout.write = function(str) { + logs.push(str); + return write.apply(this, arguments); + }; + return { + toString() { + return logs.join(""); + }, + data: logs, + restore, + reset: () => { + debug2("resetting captured stdout"); + logs = []; + } + }; +}; +var initialOutput = ""; +var capturedOutput = null; +var initCapture = () => capturedOutput = stdout(); +var cutInitialOutput = () => { + if (!capturedOutput) + throw new Error("capturedOutput is null"); + initialOutput = capturedOutput.toString(); + capturedOutput.reset(); +}; +var resetCapture = () => { + if (!capturedOutput) + throw new Error("capturedOutput is null"); + capturedOutput.reset(); +}; +var getCapturedOutput = () => { + if (!capturedOutput) + throw new Error("capturedOutput is null"); + return capturedOutput.toString(); +}; +var getInitialOutput = () => initialOutput; + +var _runId = void 0; +var setRunId = (runId) => { + _runId = runId; +}; +var _cypressVersion = void 0; +var setCypressVersion = (cypressVersion) => { + _cypressVersion = cypressVersion; +}; +var _ccVersion = void 0; +var setCcVersion = (v) => { + _ccVersion = v; +}; + +var cypressPkg = require2("cypress/package.json"); +var pkg = require2("@krivega/cc/package.json"); +initCapture(); +setCypressVersion(cypressPkg.version); +setCcVersion(pkg.version); + +var import_debug25 = __toESM(require("debug")); + +function getLegalNotice() { + return ` +Copyright (C) ${(/* @__PURE__ */ new Date()).getFullYear()} cc +`; +} + +var import_axios = require("axios"); +var isRetriableError = (err) => { + if (err.code === "ECONNABORTED") { + return true; + } + if (err.code === "ECONNREFUSED") { + return true; + } + if (err.code === "ETIMEDOUT") { + return true; + } + if (!(0, import_axios.isAxiosError)(err)) { + return false; + } + return !!(err?.response?.status && 500 <= err.response.status && err.response.status < 600); +}; +var getDelay = (i) => [5 * 1e3, 10 * 1e3, 30 * 1e3][i - 1]; +var baseURL = "set baseURL"; +var getAPIBaseUrl = () => baseURL ?? "set baseURL"; +var setAPIBaseUrl = (url) => baseURL = url ?? "set baseURL"; + +var import_axios2 = __toESM(require("axios")); +var import_axios_retry = __toESM(require("axios-retry")); +var import_debug10 = __toESM(require("debug")); +var import_lodash5 = __toESM(require("lodash")); +var import_pretty_ms = __toESM(require("pretty-ms")); + +var import_debug7 = __toESM(require("debug")); +var import_ts_pattern3 = require("ts-pattern"); + +var import_cy2 = require("cy2"); +var import_debug6 = __toESM(require("debug")); +var import_execa = __toESM(require("execa")); +var import_fs = __toESM(require("fs")); + +var ValidationError = class extends Error { + constructor(message) { + super(message); + this.name = ""; + } +}; + +var import_tmp_promise = require("tmp-promise"); +var createTempFile = async () => { + const { path: path5 } = await (0, import_tmp_promise.file)(); + return path5; +}; + +var import_chalk = __toESM(require("chalk")); +var import_util = __toESM(require("util")); +var log = (...args) => console.log(import_util.default.format(...args)); +var info = log; +var format = import_util.default.format; +var withError = (msg) => import_chalk.default.bgRed.white(" ERROR ") + " " + msg; +var withWarning = (msg) => import_chalk.default.bgYellow.black(" WARNING ") + " " + msg; +var warn = (...args) => log(withWarning(import_util.default.format(...args))); +var error = (...args) => log(withError(import_util.default.format(...args)) + "\n"); +var title = (color, ...args) => info("\n " + import_chalk.default[color].bold(import_util.default.format(...args)) + " \n"); +var divider = () => console.log("\n" + import_chalk.default.gray(Array(100).fill("=").join("")) + "\n"); +var spacer = (n = 0) => console.log(Array(n).fill("").join("\n")); +var cyan = import_chalk.default.cyan; +var blue = import_chalk.default.blueBright; +var red = import_chalk.default.red; +var green = import_chalk.default.greenBright; +var gray = import_chalk.default.gray; +var white = import_chalk.default.white; +var magenta = import_chalk.default.magenta; +var bold = import_chalk.default.bold; +var dim = import_chalk.default.dim; + +var import_debug4 = __toESM(require("debug")); +var import_lodash = __toESM(require("lodash")); + +var import_debug3 = __toESM(require("debug")); +var import_ts_pattern2 = require("ts-pattern"); +function shouldEnablePluginDebug(param) { + return (0, import_ts_pattern2.match)(param).with(import_ts_pattern2.P.nullish, () => false).with("none", () => false).with(true, () => true).with("all", () => true).with("cc", () => true).with( + import_ts_pattern2.P.array(import_ts_pattern2.P.string), + (v) => v.includes("all") || v.includes("cc") + ).otherwise(() => false); +} +function activateDebug(mode) { + (0, import_ts_pattern2.match)(mode).with(import_ts_pattern2.P.instanceOf(Array), (i) => i.forEach(setDebugMode)).with(true, () => setDebugMode("all")).with( + import_ts_pattern2.P.union( + "all", + "cc", + "cypress", + "commit-info" + ), + (i) => setDebugMode(i) + ).otherwise(() => setDebugMode("none")); +} +function setDebugMode(mode) { + if (mode === "none") { + return; + } + const tokens = new Set(process.env.DEBUG ? process.env.DEBUG.split(",") : []); + (0, import_ts_pattern2.match)(mode).with("all", () => { + tokens.add("commit-info"); + tokens.add("cc:*"); + tokens.add("cypress:*"); + }).with("cc", () => tokens.add("cc:*")).with("cypress", () => tokens.add("cypress:*")).with("commit-info", () => tokens.add("commit-info")).otherwise(() => { + }); + import_debug3.default.enable(Array.from(tokens).join(",")); +} + +var import_bluebird = __toESM(require("bluebird")); +import_bluebird.default.Promise.config({ + cancellation: true +}); +var BPromise = import_bluebird.default.Promise; +var safe = (fn, ifFaled, ifSucceed) => async (...args) => { + try { + const r = await fn(...args); + ifSucceed(); + return r; + } catch (e) { + return ifFaled(e); + } +}; +var sortObjectKeys = (obj) => { + return Object.keys(obj).sort().reduce((acc, key) => { + acc[key] = obj[key]; + return acc; + }, {}); +}; + +var import_nanoid = require("nanoid"); +var getRandomString = (0, import_nanoid.customAlphabet)("abcdefghijklmnopqrstuvwxyz", 10); + +var debug4 = (0, import_debug4.default)("cc:boot"); +function getBootstrapArgs({ + params, + tempFilePath +}) { + return import_lodash.default.chain(getCypressCLIParams(params)).thru((opts) => ({ + ...opts, + env: { + ...opts.env ?? {}, + cc_marker: true, + cc_temp_file: tempFilePath, + cc_debug_enabled: shouldEnablePluginDebug(params.cloudDebug) + } + })).tap((opts) => { + debug4("cypress bootstrap params: %o", opts); + }).thru((opts) => ({ + ...opts, + env: sortObjectKeys(opts.env ?? {}) + })).thru(serializeOptions).tap((opts) => { + debug4("cypress bootstrap serialized params: %o", opts); + }).thru((args) => { + return [ + ...args, + "--spec", + getRandomString(), + params.testingType === "component" ? "--component" : "--e2e" + ]; + }).value(); +} +function getCypressCLIParams(params) { + const result = getCypressRunAPIParams(params); + const testingType = result.testingType === "component" ? { + component: true + } : {}; + return { + ...import_lodash.default.omit(result, "testingType"), + ...testingType + }; +} +function serializeOptions(options) { + return Object.entries(options).flatMap(([key, value]) => { + const _key = dashed(key); + if (typeof value === "boolean") { + return value === true ? [`--${_key}`] : [`--${_key}`, false]; + } + if (import_lodash.default.isObject(value)) { + return [`--${_key}`, serializeComplexParam(value)]; + } + return [`--${_key}`, value.toString()]; + }); +} +function serializeComplexParam(param) { + return JSON.stringify(param); +} +var dashed = (v) => v.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()); + +var debug5 = (0, import_debug6.default)("cc:boot"); +var bootCypress = async (params) => { + debug5("booting cypress..."); + const tempFilePath = await createTempFile(); + const cypressBin = await (0, import_cy2.getBinPath)(require2.resolve("cypress")); + debug5("cypress executable location: %s", cypressBin); + const args = getBootstrapArgs({ tempFilePath, params }); + debug5("booting cypress with args: %o", args); + const { stdout: stdout2, stderr } = await execCypress(cypressBin, args); + if (!import_fs.default.existsSync(tempFilePath)) { + throw new Error( + `Cannot resolve cypress configuration from ${tempFilePath}. Please report the issue.` + ); + } + try { + const f = import_fs.default.readFileSync(tempFilePath, "utf-8"); + if (!f) { + throw new Error("Is @krivega/cc/plugin installed?"); + } + debug5("cypress config '%s': '%s'", tempFilePath, f); + return JSON.parse(f); + } catch (err) { + debug5("read config temp file failed: %o", err); + info(bold("Cypress stdout:\n"), stdout2); + info(bold("Cypress stderr:\n"), stderr); + throw new ValidationError(`Unable to resolve cypress configuration +- make sure that '@krivega/cc/plugin' is installed +- report the issue together with cypress stdout and stderr +`); + } +}; +async function execCypress(cypressBin, args) { + let stdout2 = ""; + let stderr = ""; + try { + await (0, import_execa.default)(cypressBin, ["run", ...args], { + stdio: "pipe", + env: { + ...process.env, + CYPRESS_RECORD_KEY: void 0, + CYPRESS_PROJECT_ID: void 0 + } + }); + } catch (err) { + debug5("exec cypress failed (certain failures are expected): %o", err); + stdout2 = err.stdout; + stderr = err.stderr; + } + return { stdout: stdout2, stderr }; +} + +var import_is_absolute = __toESM(require("is-absolute")); +var import_lodash2 = __toESM(require("lodash")); +var import_path = __toESM(require("path")); +var defaultFilenames = [ + "cc.config.js", + "cc.config.cjs", + "cc.config.mjs" +]; +function getConfigFilePath(projectRoot = null, explicitConfigFilePath) { + const prefix = projectRoot ?? process.cwd(); + if (import_lodash2.default.isString(explicitConfigFilePath) && (0, import_is_absolute.default)(explicitConfigFilePath)) { + return [explicitConfigFilePath]; + } + if (import_lodash2.default.isString(explicitConfigFilePath)) { + return [normalizePath(prefix, explicitConfigFilePath)]; + } + return defaultFilenames.map((p) => normalizePath(prefix, p)); +} +function normalizePath(prefix, filename) { + return `file://${import_path.default.resolve(prefix, filename)}`; +} + +var debug6 = (0, import_debug7.default)("cc:config"); +var _config = null; +var defaultConfig = { + e2e: { + batchSize: 3 + }, + component: { + batchSize: 5 + }, + cloudServiceUrl: "set baseURL", + networkHeaders: void 0 +}; +async function getCcConfig(projectRoot, explicitConfigFilePath) { + if (_config) { + return _config; + } + const configFilePath = getConfigFilePath(projectRoot, explicitConfigFilePath); + for (const filepath of configFilePath) { + const config = (0, import_ts_pattern3.match)(await loadConfigFile(filepath)).with({ default: import_ts_pattern3.P.not(import_ts_pattern3.P.nullish) }, (c) => c.default).with(import_ts_pattern3.P.not(import_ts_pattern3.P.nullish), (c) => c).otherwise(() => null); + if (config) { + debug6("loaded cc config from '%s'\n%O", filepath, config); + info(`Using config file: ${dim(filepath)}`); + _config = { + ...defaultConfig, + ...config + }; + return _config; + } + } + warn( + "Failed to load config file, falling back to the default config. Attempted locations: %s", + configFilePath + ); + _config = defaultConfig; + return _config; +} +async function loadConfigFile(filepath) { + try { + debug6("loading cc config file from '%s'", filepath); + return await import(filepath); + } catch (e) { + debug6("failed loading config file from: %s", e); + return null; + } +} +async function getMergedConfig(params) { + debug6("resolving cypress config"); + const cypressResolvedConfig = await bootCypress(params); + debug6("cypress resolvedConfig: %O", cypressResolvedConfig); + const rawE2EPattern = cypressResolvedConfig.rawJson?.e2e?.specPattern; + let additionalIgnorePattern = []; + if (params.testingType === "component" && rawE2EPattern) { + additionalIgnorePattern = rawE2EPattern; + } + const result = { + projectRoot: cypressResolvedConfig?.projectRoot || process.cwd(), + projectId: params.projectId, + specPattern: cypressResolvedConfig?.specPattern || "**/*.*", + excludeSpecPattern: ( + cypressResolvedConfig?.resolved.excludeSpecPattern.value ?? [] + ), + additionalIgnorePattern, + resolved: cypressResolvedConfig, + experimentalCoverageRecording: params.experimentalCoverageRecording + }; + debug6("merged config: %O", result); + return result; +} + +var import_debug8 = __toESM(require("debug")); +var import_lodash3 = __toESM(require("lodash")); +var debug7 = (0, import_debug8.default)("cc:validateParams"); +async function resolveCcParams(params) { + const configFromFile = await getCcConfig( + params.project, + params.cloudConfigFile + ); + debug7("resolving cc params: %o", params); + debug7("resolving cc config file: %o", configFromFile); + const cloudServiceUrl = params.cloudServiceUrl ?? process.env.CC_API_URL ?? configFromFile.cloudServiceUrl; + const recordKey = params.recordKey ?? process.env.CC_RECORD_KEY ?? configFromFile.recordKey; + const projectId = params.projectId ?? process.env.CC_PROJECT_ID ?? configFromFile.projectId; + const testingType = params.testingType ?? "e2e"; + let batchSize = params.batchSize; + if (!batchSize) { + batchSize = testingType === "e2e" ? configFromFile.e2e.batchSize : configFromFile.component.batchSize; + } + return { + ...params, + cloudServiceUrl, + recordKey, + projectId, + batchSize, + testingType + }; +} +var projectIdError = `Cannot resolve projectId. Please use one of the following: +- provide it as a "projectId" property for "run" API method +- set CC_PROJECT_ID environment variable +- set "projectId" in "cc.config.{c}js" file`; +var cloudServiceUrlError = `Cannot resolve cloud service URL. Please use one of the following: +- provide it as a "cloudServiceUrl" property for "run" API method +- set CC_API_URL environment variable +- set "cloudServiceUrl" in "cc.config.{c}js" file`; +var cloudServiceInvalidUrlError = `Invalid cloud service URL provided`; +var recordKeyError = `Cannot resolve record key. Please use one of the following: + +- pass it as a CLI flag '-k, --key ' +- provide it as a "recordKey" property for "run" API method +- set CC_RECORD_KEY environment variable +- set "recordKey" in "cc.config.{c}js" file +`; +async function validateParams(_params) { + const params = await resolveCcParams(_params); + debug7("validating cc params: %o", params); + if (!params.cloudServiceUrl) { + throw new ValidationError(cloudServiceUrlError); + } + if (!params.projectId) { + throw new ValidationError(projectIdError); + } + if (!params.recordKey) { + throw new ValidationError(recordKeyError); + } + validateURL(params.cloudServiceUrl); + const requiredParameters = [ + "testingType", + "batchSize", + "projectId" + ]; + requiredParameters.forEach((key) => { + if (typeof params[key] === "undefined") { + error('Missing required parameter "%s"', key); + throw new Error("Missing required parameter"); + } + }); + params.tag = parseTags(params.tag); + params.autoCancelAfterFailures = getAutoCancelValue( + params.autoCancelAfterFailures + ); + debug7("validated cc params: %o", params); + return params; +} +function getAutoCancelValue(value) { + if (typeof value === "undefined") { + return void 0; + } + if (typeof value === "boolean") { + return value ? 1 : false; + } + if (typeof value === "number" && value > 0) { + return value; + } + throw new ValidationError( + `autoCancelAfterFailures: should be a positive integer or "false". Got: "${value}"` + ); +} +function isOffline(params) { + return params.record === false; +} +function parseTags(tagString) { + if (!tagString) { + return []; + } + if (Array.isArray(tagString)) { + return tagString.filter(Boolean); + } + return tagString.split(",").map((tag) => tag.trim()).filter(Boolean); +} +function validateURL(url) { + try { + new URL(url); + } catch (err) { + throw new ValidationError(`${cloudServiceInvalidUrlError}: "${url}"`); + } +} +function getCypressRunAPIParams(params) { + return { + ...import_lodash3.default.pickBy( + import_lodash3.default.omit(params, [ + "cloudDebug", + "cloudConfigFile", + "autoCancelAfterFailures", + "cloudServiceUrl", + "batchSize", + "projectId", + "key", + "recordKey", + "record", + "group", + "parallel", + "tag", + "ciBuildId", + "spec", + "exit", + "headless", + "experimentalCoverageRecording" + ]), + Boolean + ), + record: false, + env: { + ...params.env, + cc_debug_enabled: shouldEnablePluginDebug(params.cloudDebug) + } + }; +} +function preprocessParams(params) { + return { + ...params, + spec: processSpecParam(params.spec) + }; +} +function processSpecParam(spec) { + if (!spec) { + return void 0; + } + if (Array.isArray(spec)) { + return import_lodash3.default.flatten(spec.map((i) => i.split(","))); + } + return spec.split(","); +} + +var import_lodash4 = __toESM(require("lodash")); +function maybePrintErrors(err) { + if (!err.response?.data || !err.response?.status) { + return; + } + const { message, errors } = err.response.data; + switch (err.response.status) { + case 401: + warn("Received 401 Unauthorized"); + break; + case 422: + spacer(1); + warn(...formatGenericError(message, errors)); + spacer(1); + break; + default: + break; + } +} +function formatGenericError(message, errors) { + if (!import_lodash4.default.isString(message)) { + return ["Unexpected error from the cloud service"]; + } + if (errors?.length === 0) { + return [message]; + } + return [ + message, + ` +${(errors ?? []).map((e) => ` - ${e}`).join("\n")} +` + ]; +} + +var debug8 = (0, import_debug10.default)("cc:api"); +var MAX_RETRIES = 3; +var TIMEOUT_MS = 30 * 1e3; +var _client = null; +async function getClient() { + if (_client) { + return _client; + } + const ccConfig = await getCcConfig(); + _client = import_axios2.default.create({ + baseURL: getAPIBaseUrl(), + timeout: TIMEOUT_MS + }); + _client.interceptors.request.use((config) => { + const ccyVerson = _ccVersion ?? "0.0.0"; + const headers = { + ...config.headers, + "x-cypress-request-attempt": config["axios-retry"]?.retryCount ?? 0, + "x-cypress-version": _cypressVersion ?? "0.0.0", + "x-ccy-version": ccyVerson, + "User-Agent": `@krivega/cc/${ccyVerson}` + }; + if (_runId) { + headers["x-cypress-run-id"] = _runId; + } + if (!headers["Content-Type"]) { + headers["Content-Type"] = "application/json"; + } + if (ccConfig.networkHeaders) { + const filteredHeaders = import_lodash5.default.omit(ccConfig.networkHeaders, [ + "x-cypress-request-attempt", + "x-cypress-version", + "x-ccy-version", + "x-cypress-run-id", + "Content-Type" + ]); + debug8("using custom network headers: %o", filteredHeaders); + Object.assign(headers, filteredHeaders); + } + const req = { + ...config, + headers + }; + debug8("network request: %o", { + ...import_lodash5.default.pick(req, "method", "url", "headers"), + data: Buffer.isBuffer(req.data) ? "buffer" : req.data + }); + return req; + }); + (0, import_axios_retry.default)(_client, { + retries: MAX_RETRIES, + retryCondition: isRetriableError, + retryDelay: getDelay, + onRetry, + shouldResetTimeout: true + }); + return _client; +} +function onRetry(retryCount, err, config) { + warn( + "Network request '%s' failed: '%s'. Next attempt is in %s (%d/%d).", + `${config.method} ${config.url}`, + err.message, + (0, import_pretty_ms.default)(getDelay(retryCount)), + retryCount, + MAX_RETRIES + ); +} +var makeRequest = async (config) => { + return (await getClient())(config).then((res) => { + debug8("network response: %o", import_lodash5.default.omit(res, "request", "config")); + return res; + }).catch((error2) => { + maybePrintErrors(error2); + throw new ValidationError(error2.message); + }); +}; + +var import_lodash6 = __toESM(require("lodash")); +function printWarnings(warnings) { + warn("Notice from cloud service:"); + warnings.map((w) => { + spacer(1); + info(magenta.bold(w.message)); + Object.entries(import_lodash6.default.omit(w, "message")).map(([key, value]) => { + info("- %s: %s", key, value); + }); + spacer(1); + }); +} + +var createRun = async (payload) => { + const response = await makeRequest({ + method: "POST", + url: "/runs", + data: payload + }); + if ((response.data.warnings?.length ?? 0) > 0) { + printWarnings(response.data.warnings); + } + return response.data; +}; +var createInstance = async ({ + runId, + groupId, + machineId, + platform: platform2 +}) => { + const response = await makeRequest({ + method: "POST", + url: `runs/${runId}/instances`, + data: { + runId, + groupId, + machineId, + platform: platform2 + } + }); + return response.data; +}; +var createBatchedInstances = async (data) => { + const respone = await makeRequest({ + method: "POST", + url: `runs/${data.runId}/cy/instances`, + data + }); + return respone.data; +}; +var setInstanceTests = (instanceId, payload) => makeRequest({ + method: "POST", + url: `instances/${instanceId}/tests`, + data: payload +}).then((result) => result.data); +var updateInstanceResults = (instanceId, payload) => makeRequest({ + method: "POST", + url: `instances/${instanceId}/results`, + data: payload +}).then((result) => result.data); +var reportInstanceResultsMerged = (instanceId, payload) => makeRequest({ + method: "POST", + url: `instances/${instanceId}/cy/results`, + data: payload +}).then((result) => result.data); +var updateInstanceStdout = (instanceId, stdout2) => makeRequest({ + method: "PUT", + url: `instances/${instanceId}/stdout`, + data: { + stdout: stdout2 + } +}); + +var import_debug11 = __toESM(require("debug")); +var import_lodash7 = __toESM(require("lodash")); +var debug9 = (0, import_debug11.default)("cc:ci"); +var join = (char, ...pieces) => { + return import_lodash7.default.chain(pieces).compact().join(char).value(); +}; +var toCamelObject = (obj, key) => { + return import_lodash7.default.set(obj, import_lodash7.default.camelCase(key), process.env[key]); +}; +var extract = (envKeys) => { + return import_lodash7.default.transform(envKeys, toCamelObject, {}); +}; +var isTeamFoundation = () => { + return process.env.TF_BUILD && process.env.TF_BUILD_BUILDNUMBER; +}; +var isAzureCi = () => { + return process.env.TF_BUILD && process.env.AZURE_HTTP_USER_AGENT; +}; +var isAWSCodeBuild = () => { + return import_lodash7.default.some(process.env, (val, key) => { + return /^CODEBUILD_/.test(key); + }); +}; +var isBamboo = () => { + return process.env.bamboo_buildNumber; +}; +var isCodeshipBasic = () => { + return process.env.CI_NAME && process.env.CI_NAME === "codeship" && process.env.CODESHIP; +}; +var isCodeshipPro = () => { + return process.env.CI_NAME && process.env.CI_NAME === "codeship" && !process.env.CODESHIP; +}; +var isConcourse = () => { + return import_lodash7.default.some(process.env, (val, key) => { + return /^CONCOURSE_/.test(key); + }); +}; +var isGitlab = () => { + return process.env.GITLAB_CI || process.env.CI_SERVER_NAME && /^GitLab/.test(process.env.CI_SERVER_NAME); +}; +var isGoogleCloud = () => { + return process.env.GCP_PROJECT || process.env.GCLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT; +}; +var isJenkins = () => { + return process.env.JENKINS_URL || process.env.JENKINS_HOME || process.env.JENKINS_VERSION || process.env.HUDSON_URL || process.env.HUDSON_HOME; +}; +var isWercker = () => { + return process.env.WERCKER || process.env.WERCKER_MAIN_PIPELINE_STARTED; +}; +var CI_PROVIDERS = { + appveyor: "APPVEYOR", + azure: isAzureCi, + awsCodeBuild: isAWSCodeBuild, + bamboo: isBamboo, + bitbucket: "BITBUCKET_BUILD_NUMBER", + buildkite: "BUILDKITE", + circle: "CIRCLECI", + codeshipBasic: isCodeshipBasic, + codeshipPro: isCodeshipPro, + concourse: isConcourse, + codeFresh: "CF_BUILD_ID", + drone: "DRONE", + githubActions: "GITHUB_ACTIONS", + gitlab: isGitlab, + goCD: "GO_JOB_NAME", + googleCloud: isGoogleCloud, + jenkins: isJenkins, + semaphore: "SEMAPHORE", + shippable: "SHIPPABLE", + teamcity: "TEAMCITY_VERSION", + teamfoundation: isTeamFoundation, + travis: "TRAVIS", + wercker: isWercker, + netlify: "NETLIFY", + layerci: "LAYERCI" +}; +function _detectProviderName() { + const { env } = process; + return import_lodash7.default.findKey(CI_PROVIDERS, (value) => { + if (import_lodash7.default.isString(value)) { + return env[value]; + } + if (import_lodash7.default.isFunction(value)) { + return value(); + } + }); +} +var _providerCiParams = () => { + return { + appveyor: extract([ + "APPVEYOR_JOB_ID", + "APPVEYOR_ACCOUNT_NAME", + "APPVEYOR_PROJECT_SLUG", + "APPVEYOR_BUILD_NUMBER", + "APPVEYOR_BUILD_VERSION", + "APPVEYOR_PULL_REQUEST_NUMBER", + "APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH" + ]), + azure: extract([ + "BUILD_BUILDID", + "BUILD_BUILDNUMBER", + "BUILD_CONTAINERID", + "BUILD_REPOSITORY_URI" + ]), + awsCodeBuild: extract([ + "CODEBUILD_BUILD_ID", + "CODEBUILD_BUILD_NUMBER", + "CODEBUILD_RESOLVED_SOURCE_VERSION", + "CODEBUILD_SOURCE_REPO_URL", + "CODEBUILD_SOURCE_VERSION" + ]), + bamboo: extract([ + "bamboo_buildNumber", + "bamboo_buildResultsUrl", + "bamboo_planRepository_repositoryUrl", + "bamboo_buildKey" + ]), + bitbucket: extract([ + "BITBUCKET_REPO_SLUG", + "BITBUCKET_REPO_OWNER", + "BITBUCKET_BUILD_NUMBER", + "BITBUCKET_PARALLEL_STEP", + "BITBUCKET_STEP_RUN_NUMBER", + "BITBUCKET_PR_ID", + "BITBUCKET_PR_DESTINATION_BRANCH", + "BITBUCKET_PR_DESTINATION_COMMIT" + ]), + buildkite: extract([ + "BUILDKITE_REPO", + "BUILDKITE_SOURCE", + "BUILDKITE_JOB_ID", + "BUILDKITE_BUILD_ID", + "BUILDKITE_BUILD_URL", + "BUILDKITE_BUILD_NUMBER", + "BUILDKITE_PULL_REQUEST", + "BUILDKITE_PULL_REQUEST_REPO", + "BUILDKITE_PULL_REQUEST_BASE_BRANCH" + ]), + circle: extract([ + "CIRCLE_JOB", + "CIRCLE_BUILD_NUM", + "CIRCLE_BUILD_URL", + "CIRCLE_PR_NUMBER", + "CIRCLE_PR_REPONAME", + "CIRCLE_PR_USERNAME", + "CIRCLE_COMPARE_URL", + "CIRCLE_WORKFLOW_ID", + "CIRCLE_PULL_REQUEST", + "CIRCLE_REPOSITORY_URL", + "CI_PULL_REQUEST" + ]), + codeshipBasic: extract([ + "CI_BUILD_ID", + "CI_REPO_NAME", + "CI_BUILD_URL", + "CI_PROJECT_ID", + "CI_BUILD_NUMBER", + "CI_PULL_REQUEST" + ]), + codeshipPro: extract(["CI_BUILD_ID", "CI_REPO_NAME", "CI_PROJECT_ID"]), + concourse: extract([ + "BUILD_ID", + "BUILD_NAME", + "BUILD_JOB_NAME", + "BUILD_PIPELINE_NAME", + "BUILD_TEAM_NAME", + "ATC_EXTERNAL_URL" + ]), + codeFresh: extract([ + "CF_BUILD_ID", + "CF_BUILD_URL", + "CF_CURRENT_ATTEMPT", + "CF_STEP_NAME", + "CF_PIPELINE_NAME", + "CF_PIPELINE_TRIGGER_ID", + "CF_PULL_REQUEST_ID", + "CF_PULL_REQUEST_IS_FORK", + "CF_PULL_REQUEST_NUMBER", + "CF_PULL_REQUEST_TARGET" + ]), + drone: extract([ + "DRONE_JOB_NUMBER", + "DRONE_BUILD_LINK", + "DRONE_BUILD_NUMBER", + "DRONE_PULL_REQUEST" + ]), + githubActions: extract([ + "GITHUB_WORKFLOW", + "GITHUB_ACTION", + "GITHUB_EVENT_NAME", + "GITHUB_RUN_ID", + "GITHUB_RUN_ATTEMPT", + "GITHUB_REPOSITORY" + ]), + gitlab: extract([ + "CI_PIPELINE_ID", + "CI_PIPELINE_URL", + "CI_BUILD_ID", + "CI_JOB_ID", + "CI_JOB_URL", + "CI_JOB_NAME", + "GITLAB_HOST", + "CI_PROJECT_ID", + "CI_PROJECT_URL", + "CI_REPOSITORY_URL", + "CI_ENVIRONMENT_URL", + "CI_DEFAULT_BRANCH" + ]), + goCD: extract([ + "GO_SERVER_URL", + "GO_ENVIRONMENT_NAME", + "GO_PIPELINE_NAME", + "GO_PIPELINE_COUNTER", + "GO_PIPELINE_LABEL", + "GO_STAGE_NAME", + "GO_STAGE_COUNTER", + "GO_JOB_NAME", + "GO_TRIGGER_USER", + "GO_REVISION", + "GO_TO_REVISION", + "GO_FROM_REVISION", + "GO_MATERIAL_HAS_CHANGED" + ]), + googleCloud: extract([ + "BUILD_ID", + "PROJECT_ID", + "REPO_NAME", + "BRANCH_NAME", + "TAG_NAME", + "COMMIT_SHA", + "SHORT_SHA" + ]), + jenkins: extract(["BUILD_ID", "BUILD_URL", "BUILD_NUMBER", "ghprbPullId"]), + semaphore: extract([ + "SEMAPHORE_BRANCH_ID", + "SEMAPHORE_BUILD_NUMBER", + "SEMAPHORE_CURRENT_JOB", + "SEMAPHORE_CURRENT_THREAD", + "SEMAPHORE_EXECUTABLE_UUID", + "SEMAPHORE_GIT_BRANCH", + "SEMAPHORE_GIT_DIR", + "SEMAPHORE_GIT_REF", + "SEMAPHORE_GIT_REF_TYPE", + "SEMAPHORE_GIT_REPO_SLUG", + "SEMAPHORE_GIT_SHA", + "SEMAPHORE_GIT_URL", + "SEMAPHORE_JOB_COUNT", + "SEMAPHORE_JOB_ID", + "SEMAPHORE_JOB_NAME", + "SEMAPHORE_JOB_UUID", + "SEMAPHORE_PIPELINE_ID", + "SEMAPHORE_PLATFORM", + "SEMAPHORE_PROJECT_DIR", + "SEMAPHORE_PROJECT_HASH_ID", + "SEMAPHORE_PROJECT_ID", + "SEMAPHORE_PROJECT_NAME", + "SEMAPHORE_PROJECT_UUID", + "SEMAPHORE_REPO_SLUG", + "SEMAPHORE_TRIGGER_SOURCE", + "SEMAPHORE_WORKFLOW_ID", + "PULL_REQUEST_NUMBER" + ]), + shippable: extract([ + "SHIPPABLE_BUILD_ID", + "SHIPPABLE_BUILD_NUMBER", + "SHIPPABLE_COMMIT_RANGE", + "SHIPPABLE_CONTAINER_NAME", + "SHIPPABLE_JOB_ID", + "SHIPPABLE_JOB_NUMBER", + "SHIPPABLE_REPO_SLUG", + "IS_FORK", + "IS_GIT_TAG", + "IS_PRERELEASE", + "IS_RELEASE", + "REPOSITORY_URL", + "REPO_FULL_NAME", + "REPO_NAME", + "BUILD_URL", + "BASE_BRANCH", + "HEAD_BRANCH", + "IS_PULL_REQUEST", + "PULL_REQUEST", + "PULL_REQUEST_BASE_BRANCH", + "PULL_REQUEST_REPO_FULL_NAME" + ]), + teamcity: null, + teamfoundation: extract([ + "BUILD_BUILDID", + "BUILD_BUILDNUMBER", + "BUILD_CONTAINERID" + ]), + travis: extract([ + "TRAVIS_JOB_ID", + "TRAVIS_BUILD_ID", + "TRAVIS_BUILD_WEB_URL", + "TRAVIS_REPO_SLUG", + "TRAVIS_JOB_NUMBER", + "TRAVIS_EVENT_TYPE", + "TRAVIS_COMMIT_RANGE", + "TRAVIS_BUILD_NUMBER", + "TRAVIS_PULL_REQUEST", + "TRAVIS_PULL_REQUEST_BRANCH", + "TRAVIS_PULL_REQUEST_SHA" + ]), + wercker: null, + netlify: extract([ + "BUILD_ID", + "CONTEXT", + "URL", + "DEPLOY_URL", + "DEPLOY_PRIME_URL", + "DEPLOY_ID" + ]), + layerci: extract([ + "LAYERCI_JOB_ID", + "LAYERCI_RUNNER_ID", + "RETRY_INDEX", + "LAYERCI_PULL_REQUEST", + "LAYERCI_REPO_NAME", + "LAYERCI_REPO_OWNER", + "LAYERCI_BRANCH", + "GIT_TAG" + ]) + }; +}; +var _providerCommitParams = () => { + const { env } = process; + return { + appveyor: { + sha: env.APPVEYOR_REPO_COMMIT, + branch: env.APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH || env.APPVEYOR_REPO_BRANCH, + message: join( + "\n", + env.APPVEYOR_REPO_COMMIT_MESSAGE, + env.APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED + ), + authorName: env.APPVEYOR_REPO_COMMIT_AUTHOR, + authorEmail: env.APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL + }, + awsCodeBuild: { + sha: env.CODEBUILD_RESOLVED_SOURCE_VERSION, + remoteOrigin: env.CODEBUILD_SOURCE_REPO_URL + }, + azure: { + sha: env.BUILD_SOURCEVERSION, + branch: env.BUILD_SOURCEBRANCHNAME, + message: env.BUILD_SOURCEVERSIONMESSAGE, + authorName: env.BUILD_SOURCEVERSIONAUTHOR, + authorEmail: env.BUILD_REQUESTEDFOREMAIL + }, + bamboo: { + sha: env.bamboo_planRepository_revision, + branch: env.bamboo_planRepository_branch, + authorName: env.bamboo_planRepository_username, + remoteOrigin: env.bamboo_planRepository_repositoryURL + }, + bitbucket: { + sha: env.BITBUCKET_COMMIT, + branch: env.BITBUCKET_BRANCH + }, + buildkite: { + sha: env.BUILDKITE_COMMIT, + branch: env.BUILDKITE_BRANCH, + message: env.BUILDKITE_MESSAGE, + authorName: env.BUILDKITE_BUILD_CREATOR, + authorEmail: env.BUILDKITE_BUILD_CREATOR_EMAIL, + remoteOrigin: env.BUILDKITE_REPO, + defaultBranch: env.BUILDKITE_PIPELINE_DEFAULT_BRANCH + }, + circle: { + sha: env.CIRCLE_SHA1, + branch: env.CIRCLE_BRANCH, + authorName: env.CIRCLE_USERNAME, + remoteOrigin: env.CIRCLE_REPOSITORY_URL + }, + codeshipBasic: { + sha: env.CI_COMMIT_ID, + branch: env.CI_BRANCH, + message: env.CI_COMMIT_MESSAGE, + authorName: env.CI_COMMITTER_NAME, + authorEmail: env.CI_COMMITTER_EMAIL + }, + codeshipPro: { + sha: env.CI_COMMIT_ID, + branch: env.CI_BRANCH, + message: env.CI_COMMIT_MESSAGE, + authorName: env.CI_COMMITTER_NAME, + authorEmail: env.CI_COMMITTER_EMAIL + }, + codeFresh: { + sha: env.CF_REVISION, + branch: env.CF_BRANCH, + message: env.CF_COMMIT_MESSAGE, + authorName: env.CF_COMMIT_AUTHOR + }, + drone: { + sha: env.DRONE_COMMIT_SHA, + branch: env.DRONE_SOURCE_BRANCH, + message: env.DRONE_COMMIT_MESSAGE, + authorName: env.DRONE_COMMIT_AUTHOR, + authorEmail: env.DRONE_COMMIT_AUTHOR_EMAIL, + remoteOrigin: env.DRONE_GIT_HTTP_URL, + defaultBranch: env.DRONE_REPO_BRANCH + }, + githubActions: { + sha: env.GITHUB_SHA, + branch: env.GH_BRANCH || env.GITHUB_REF, + defaultBranch: env.GITHUB_BASE_REF, + remoteBranch: env.GITHUB_HEAD_REF, + runAttempt: env.GITHUB_RUN_ATTEMPT + }, + gitlab: { + sha: env.CI_COMMIT_SHA, + branch: env.CI_COMMIT_REF_NAME, + message: env.CI_COMMIT_MESSAGE, + authorName: env.GITLAB_USER_NAME, + authorEmail: env.GITLAB_USER_EMAIL, + remoteOrigin: env.CI_REPOSITORY_URL, + defaultBranch: env.CI_DEFAULT_BRANCH + }, + googleCloud: { + sha: env.COMMIT_SHA, + branch: env.BRANCH_NAME + }, + jenkins: { + sha: env.GIT_COMMIT, + branch: env.GIT_BRANCH + }, + semaphore: { + sha: env.SEMAPHORE_GIT_SHA, + branch: env.SEMAPHORE_GIT_BRANCH, + remoteOrigin: env.SEMAPHORE_GIT_REPO_SLUG + }, + shippable: { + sha: env.COMMIT, + branch: env.BRANCH, + message: env.COMMIT_MESSAGE, + authorName: env.COMMITTER + }, + snap: null, + teamcity: null, + teamfoundation: { + sha: env.BUILD_SOURCEVERSION, + branch: env.BUILD_SOURCEBRANCHNAME, + message: env.BUILD_SOURCEVERSIONMESSAGE, + authorName: env.BUILD_SOURCEVERSIONAUTHOR + }, + travis: { + sha: env.TRAVIS_PULL_REQUEST_SHA || env.TRAVIS_COMMIT, + branch: env.TRAVIS_PULL_REQUEST_BRANCH || env.TRAVIS_BRANCH, + message: env.TRAVIS_COMMIT_MESSAGE + }, + wercker: null, + netlify: { + sha: env.COMMIT_REF, + branch: env.BRANCH, + remoteOrigin: env.REPOSITORY_URL + }, + layerci: { + sha: env.GIT_COMMIT, + branch: env.LAYERCI_BRANCH, + message: env.GIT_COMMIT_TITLE + } + }; +}; +var _get = (fn) => { + const providerName = getCiProvider(); + if (!providerName) + return {}; + return import_lodash7.default.chain(fn()).get(providerName).value(); +}; +function checkForCiBuildFromCi(ciProvider) { + if (ciProvider && detectableCiBuildIdProviders().includes(ciProvider)) + return true; + throw new ValidationError( + `Could not determine CI build ID from the environment. Please provide a unique CI build ID using the --ci-build-id CLI flag or 'ciBuildId' parameter for 'run' method.` + ); +} +function detectableCiBuildIdProviders() { + return import_lodash7.default.chain(_providerCiParams()).omitBy(import_lodash7.default.isNull).keys().value(); +} +function getCiProvider() { + return _detectProviderName() || null; +} +function getCiParams() { + return _get(_providerCiParams); +} +function getCommitParams() { + return _get(_providerCommitParams); +} +function getCI(ciBuildId) { + const params = getCiParams(); + const provider = getCiProvider(); + if (!ciBuildId) + checkForCiBuildFromCi(provider); + debug9("detected CI provider: %s", provider); + debug9("detected CI params: %O", params); + return { + params, + provider + }; +} +function getCommitDefaults(existingInfo) { + debug9("git commit existing info"); + debug9(existingInfo); + const commitParamsObj = getCommitParams(); + debug9("commit info from provider environment variables: %O", commitParamsObj); + const combined = import_lodash7.default.transform( + existingInfo, + (memo, value, key) => { + return memo[key] = import_lodash7.default.defaultTo(value || commitParamsObj[key], null); + } + ); + debug9("combined git and environment variables from provider"); + debug9(combined); + return combined; +} + +var import_cypress = __toESM(require("cypress")); +var import_debug12 = __toESM(require("debug")); +var import_lodash8 = __toESM(require("lodash")); + +var import_date_fns2 = require("date-fns"); + +var import_date_fns = require("date-fns"); +var import_ts_pattern4 = require("ts-pattern"); +var SpecAfterResult = class _SpecAfterResult { + /** + * Combine standalone attempts and screenshots into standard result + * @param specResult - spec:after results + * @param executionState - ccy execution state + * @returns unified results, including attempts and screenshot details + */ + static getSpecAfterStandard(specAfterResults, executionState) { + return { + error: specAfterResults.error, + hooks: null, + reporter: specAfterResults.reporter, + reporterStats: specAfterResults.reporterStats, + spec: _SpecAfterResult.getSpecStandard(specAfterResults.spec), + tests: _SpecAfterResult.getTestStandard( + specAfterResults, + executionState.getAttemptsData() + ), + video: specAfterResults.video, + stats: _SpecAfterResult.getStatsStandard(specAfterResults.stats), + screenshots: _SpecAfterResult.getScreenshotsStandard( + specAfterResults.screenshots, + executionState.getScreenshotsData() + ) + }; + } + static getAttemptError(err) { + if (!err) { + return null; + } + return { + name: err.name, + message: err.message, + stack: err.stack, + codeFrame: err.codeFrame + }; + } + static getAttemptVideoTimestamp(attemptStartedAtMs, specStartedAtMs) { + return Math.max(attemptStartedAtMs - specStartedAtMs, 0); + } + static getSpecStartedAt(stats) { + if ("startedAt" in stats) { + return (0, import_date_fns.parseISO)(stats.startedAt); + } + if ("wallClockStartedAt" in stats) { + return (0, import_date_fns.parseISO)(stats.wallClockStartedAt); + } + warn("Cannot determine spec start date from stats: %o", stats); + return new Date(); + } + static getDummyTestAttemptError(attemptState) { + return (0, import_ts_pattern4.match)(attemptState).with("failed", () => ({ + name: "Error", + message: "[@krivega/cc] Could not get cypress attempt error details", + stack: "", + codeFrame: null + })).with("skipped", () => ({ + name: "Error", + message: "The test was skipped because of a hook failure", + stack: "", + codeFrame: null + })).otherwise(() => null); + } + static getTestAttemptStandard(mochaAttempt, cypressAttempt, specStartedAt) { + if (!mochaAttempt) { + const error2 = "error" in cypressAttempt ? cypressAttempt.error : null; + const duration = "wallClockDuration" in cypressAttempt ? cypressAttempt.wallClockDuration : null; + return { + state: cypressAttempt.state, + error: error2 ? error2 : _SpecAfterResult.getDummyTestAttemptError(cypressAttempt.state), + timings: "timings" in cypressAttempt ? cypressAttempt.timings : null, + wallClockStartedAt: "wallClockStartedAt" in cypressAttempt ? cypressAttempt.wallClockStartedAt : ( new Date()).toISOString(), + wallClockDuration: duration ? duration : 0, + failedFromHookId: "failedFromHookId" in cypressAttempt ? cypressAttempt.failedFromHookId : null, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : 0 + }; + } + return { + state: cypressAttempt.state, + error: "error" in cypressAttempt ? cypressAttempt.error : _SpecAfterResult.getAttemptError(mochaAttempt.err), + timings: "timings" in cypressAttempt ? cypressAttempt.timings : mochaAttempt.timings, + wallClockStartedAt: mochaAttempt.wallClockStartedAt ?? ( new Date()).toISOString(), + wallClockDuration: mochaAttempt.duration ?? -1, + failedFromHookId: "failedFromHookId" in cypressAttempt ? cypressAttempt.failedFromHookId : null, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : _SpecAfterResult.getAttemptVideoTimestamp( + (0, import_date_fns.parseISO)(mochaAttempt.wallClockStartedAt).getTime(), + specStartedAt.getTime() + ) + }; + } + static getTestStandard(specAfterResults, attempts) { + const standardTestList = (specAfterResults.tests ?? []).map((test, i) => { + const mochaAttempts = attempts.filter( + (attempt) => attempt.fullTitle === test.title.join(" ") + ); + const standardAttempts = (test.attempts ?? []).map( + (cypressAttempt, j) => { + const mochaAttempt = mochaAttempts.find( + (ma) => ma.currentRetry === j + ); + return _SpecAfterResult.getTestAttemptStandard( + mochaAttempt ?? null, + cypressAttempt, + _SpecAfterResult.getSpecStartedAt(specAfterResults.stats) + ); + } + ); + return { + body: "body" in test ? test.body : mochaAttempts[0]?.body ?? "", + testId: "testId" in test ? test.testId : mochaAttempts[0]?.id ?? `r${i}`, + title: test.title, + displayError: test.displayError, + state: test.state, + attempts: standardAttempts + }; + }); + return standardTestList; + } + static getSpecStandard(spec) { + return { + name: spec.name, + relative: spec.relative, + absolute: spec.absolute, + fileExtension: spec.fileExtension, + baseName: "baseName" in spec ? spec.baseName : "", + fileName: "fileName" in spec ? spec.fileName : "", + relativeToCommonRoot: "relativeToCommonRoot" in spec ? spec.relativeToCommonRoot : "", + specFileExtension: "specFileExtension" in spec ? spec.specFileExtension : "", + specType: "specType" in spec ? spec.specType : "" + }; + } + static getStatsStandard(stats) { + const result = { + skipped: stats.skipped, + suites: stats.suites, + tests: stats.tests, + passes: stats.passes, + pending: stats.pending, + failures: stats.failures, + wallClockStartedAt: "wallClockStartedAt" in stats ? stats.wallClockStartedAt : stats.startedAt, + wallClockEndedAt: "wallClockEndedAt" in stats ? stats.wallClockEndedAt : stats.endedAt, + wallClockDuration: "wallClockDuration" in stats ? stats.wallClockDuration : stats.duration ?? 0 + }; + result.tests = result.passes + result.failures + result.pending + result.skipped; + return result; + } + static getScreenshotsStandard(specAfterScreenshots, screenshotEvents) { + if (!specAfterScreenshots.length) { + return []; + } + return specAfterScreenshots.map((specScreenshot) => { + const es = screenshotEvents.find( + (screenshot) => screenshot.path === specScreenshot.path + ); + if (!es) { + warn( + 'Could not find details for screenshot at path "%s", skipping...', + specScreenshot.path + ); + } + return { + height: specScreenshot.height, + width: specScreenshot.width, + name: specScreenshot.name ?? es?.name ?? null, + path: specScreenshot.path, + takenAt: specScreenshot.takenAt, + testAttemptIndex: "testAttemptIndex" in specScreenshot ? specScreenshot.testAttemptIndex : es?.testAttemptIndex ?? -1, + testId: "testId" in specScreenshot ? specScreenshot.testId : es?.testId ?? "unknown", + screenshotId: "screenshotId" in specScreenshot ? specScreenshot.screenshotId : getRandomString() + }; + }); + } +}; + +var ModuleAPIResults = class _ModuleAPIResults { + static getRunScreenshots(run3) { + if ("screenshots" in run3) { + return run3.screenshots; + } + return (run3.tests ?? []).flatMap( + (t) => t.attempts.flatMap((a) => a.screenshots) + ); + } + static getTests(run3, executionState) { + const tests = run3.tests ?? []; + return tests.map((test, i) => { + const mochaAttempts = executionState.getAttemptsData().filter((attempt) => attempt.fullTitle === test.title.join(" ")); + const testId = "testId" in test ? test.testId : mochaAttempts[0]?.id ?? `r${i}`; + const runScreenshotPaths = _ModuleAPIResults.getRunScreenshots(run3).map( + (i2) => i2.path + ); + const testScreenshots = executionState.getScreenshotsData().filter((s) => runScreenshotPaths.includes(s.path)).filter((s) => s.testId === testId); + const standardAttempts = (test.attempts ?? []).map( + (cypressAttempt, j) => { + const mochaAttempt = mochaAttempts.find( + (ma) => ma.currentRetry === j + ); + const attemptScreenshots = testScreenshots.filter( + (t) => t.testAttemptIndex === j + ); + return _ModuleAPIResults.getTestAttempt( + mochaAttempt ?? null, + cypressAttempt, + attemptScreenshots, + SpecAfterResult.getSpecStartedAt(run3.stats) + ); + } + ); + return { + body: "body" in test ? test.body : mochaAttempts[0]?.body ?? "", + testId, + title: test.title, + displayError: test.displayError, + state: test.state, + attempts: standardAttempts + }; + }); + } + /** + * Convert version-specific attempt to a standard test attempt + */ + static getTestAttempt(mochaAttempt, cypressAttempt, screenshots, specStartedAt) { + if (!mochaAttempt) { + return { + state: cypressAttempt.state, + error: "error" in cypressAttempt ? cypressAttempt.error : SpecAfterResult.getDummyTestAttemptError(cypressAttempt.state), + startedAt: "startedAt" in cypressAttempt ? cypressAttempt.startedAt : ( new Date()).toISOString(), + duration: "duration" in cypressAttempt ? cypressAttempt.duration : 0, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : 0, + screenshots: "screenshots" in cypressAttempt ? cypressAttempt.screenshots : screenshots + }; + } + return { + state: cypressAttempt.state, + error: "error" in cypressAttempt ? cypressAttempt.error : SpecAfterResult.getAttemptError(mochaAttempt.err), + startedAt: "startedAt" in cypressAttempt ? cypressAttempt.startedAt : mochaAttempt.wallClockStartedAt ?? ( new Date()).toISOString(), + duration: "duration" in cypressAttempt ? cypressAttempt.duration : mochaAttempt.duration ?? -1, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : SpecAfterResult.getAttemptVideoTimestamp( + (0, import_date_fns2.parseISO)(mochaAttempt.wallClockStartedAt).getTime(), + specStartedAt.getTime() + ), + screenshots: "screenshots" in cypressAttempt ? cypressAttempt.screenshots : screenshots + }; + } + static getRun(run3, executionState) { + return { + ...run3, + tests: _ModuleAPIResults.getTests(run3, executionState), + spec: SpecAfterResult.getSpecStandard(run3.spec), + hooks: null, + shouldUploadVideo: "shouldUploadVideo" in run3 ? run3.shouldUploadVideo : true + }; + } + /** + * Converts different Cypress versions to standard form + */ + static getStandardResult(result, executionState) { + if (result.runs.length !== 1) { + throw new Error("Expected single run"); + } + const run3 = result.runs[0]; + const stats = SpecAfterResult.getStatsStandard(run3.stats); + return { + ...result, + runs: [_ModuleAPIResults.getRun(run3, executionState)], + totalSuites: 1, + totalDuration: stats.wallClockDuration, + totalTests: stats.tests, + totalFailed: stats.failures, + totalPassed: stats.passes, + totalPending: stats.pending, + totalSkipped: stats.skipped, + startedTestsAt: stats.wallClockStartedAt, + endedTestsAt: stats.wallClockEndedAt, + status: "finished" + }; + } + static isFailureResult(result) { + return "status" in result && result.status === "failed"; + } + static { + this.isSuccessResult = (result) => { + if ("status" in result) { + return result.status === "finished"; + } + return true; + }; + } + static getEmptyResult(config) { + return { + status: "finished", + totalDuration: 0, + totalSuites: 0, + totalPending: 0, + totalFailed: 0, + totalSkipped: 0, + totalPassed: 0, + totalTests: 0, + startedTestsAt: ( new Date()).toISOString(), + endedTestsAt: ( new Date()).toISOString(), + runs: [], + config + }; + } +}; + +var debug10 = (0, import_debug12.default)("cc:cypress"); +function runBareCypress(params = {}) { + const p = { + ...params, + ciBuildId: void 0, + tag: void 0, + parallel: void 0, + record: false, + group: void 0, + spec: import_lodash8.default.flatten(params.spec).join(",") + }; + debug10("Running bare Cypress with params %o", p); + return import_cypress.default.run(p); +} +async function runSpecFile({ spec }, cypressRunOptions) { + const runAPIOptions = getCypressRunAPIParams(cypressRunOptions); + const options = { + ...runAPIOptions, + config: { + ...runAPIOptions.config, + trashAssetsBeforeRuns: false + }, + env: { + ...runAPIOptions.env, + cc_ws: getWSSPort(), + cc_marker: true + }, + spec + }; + debug10("running cypress with options %o", options); + const result = await import_cypress.default.run(options); + if (ModuleAPIResults.isFailureResult(result)) { + warn('Cypress runner failed with message: "%s"', result.message); + warn( + "The following spec files will be marked as failed: %s", + spec.split(",").map((i) => ` + - ${i}`).join("") + ); + } + debug10("cypress run result %o", result); + return result; +} +var runSpecFileSafe = (spec, cypressRunOptions) => safe( + runSpecFile, + (error2) => { + const message = `Cypress runnner crashed with an error: +${error2.message} +${error2.stack}}`; + debug10("cypress run exception %o", error2); + warn('Cypress runner crashed: "%s"', message); + warn( + "The following spec files will be marked as failed: %s", + spec.spec.split(",").map((i) => ` + - ${i}`).join("") + ); + return { + status: "failed", + failures: 1, + message + }; + }, + () => { + } +)(spec, cypressRunOptions); + +var isCc = () => !!process.env.CC_ENFORCE_IS_CC || getAPIBaseUrl() === "set baseURL"; + +var import_commit_info = __toESM(require("@cypress/commit-info")); +var getGitInfo = async (projectRoot) => { + const commitInfo = await import_commit_info.default.commitInfo(projectRoot); + return getCommitDefaults({ + branch: commitInfo.branch, + remoteOrigin: commitInfo.remote, + authorEmail: commitInfo.email, + authorName: commitInfo.author, + message: commitInfo.message, + sha: commitInfo.sha + }); +}; + +var import_debug20 = __toESM(require("debug")); + +var import_debug19 = __toESM(require("debug")); + +var import_promises = __toESM(require("fs/promises")); +var import_path3 = require("path"); +var getCoverageFilePath = async (coverageFile = "./.nyc_output/out.json") => { + const path5 = (0, import_path3.join)(process.cwd(), coverageFile); + try { + await import_promises.default.access(path5); + return { + path: path5, + error: false + }; + } catch (error2) { + return { + path: path5, + error: error2 + }; + } +}; + +var import_debug18 = __toESM(require("debug")); + +var import_debug17 = __toESM(require("debug")); + +var import_lodash9 = __toESM(require("lodash")); + +var emptyStats = { + totalDuration: 0, + totalSuites: 0, + totalPending: 0, + totalFailed: 0, + totalSkipped: 0, + totalPassed: 0, + totalTests: 0 +}; +var getDummyFailedTest = (start, error2) => ({ + title: ["Unknown"], + state: "failed", + body: "// This test is automatically generated due to execution failure", + displayError: error2, + attempts: [ + { + state: "failed", + startedAt: start, + duration: 0, + videoTimestamp: 0, + screenshots: [], + error: { + name: "CypressExecutionError", + message: error2, + stack: "", + codeFrame: null + } + } + ] +}); +function getFailedFakeInstanceResult(configState, { + specs, + error: error2 +}) { + const start = ( new Date()).toISOString(); + const end = ( new Date()).toISOString(); + return { + config: configState.getConfig() ?? {}, + status: "finished", + startedTestsAt: ( new Date()).toISOString(), + endedTestsAt: ( new Date()).toISOString(), + totalDuration: 0, + totalSuites: 1, + totalFailed: 1, + totalPassed: 0, + totalPending: 0, + totalSkipped: 0, + totalTests: 1, + browserName: "unknown", + browserVersion: "unknown", + browserPath: "unknown", + osName: "unknown", + osVersion: "unknown", + cypressVersion: "unknown", + runs: specs.map((s) => ({ + stats: { + suites: 1, + tests: 1, + passes: 0, + pending: 0, + skipped: 0, + failures: 1, + startedAt: start, + endedAt: end, + duration: 0 + }, + reporter: "spec", + reporterStats: { + suites: 1, + tests: 1, + passes: 0, + pending: 0, + failures: 1, + start, + end, + duration: 0 + }, + hooks: [], + error: error2, + video: null, + spec: { + name: s, + relative: s, + absolute: s, + relativeToCommonRoot: s, + baseName: s, + specType: "integration", + fileExtension: "js", + fileName: s, + specFileExtension: "js" + }, + tests: [getDummyFailedTest(start, error2)], + shouldUploadVideo: false, + skippedSpec: false + })) + }; +} + +var summarizeExecution = (input, config) => { + if (!input.length) { + return ModuleAPIResults.getEmptyResult(config); + } + const overall = input.reduce( + (acc, { + totalDuration, + totalFailed, + totalPassed, + totalPending, + totalSkipped, + totalTests, + totalSuites + }) => ({ + totalDuration: acc.totalDuration + totalDuration, + totalSuites: acc.totalSuites + totalSuites, + totalPending: acc.totalPending + totalPending, + totalFailed: acc.totalFailed + totalFailed, + totalSkipped: acc.totalSkipped + totalSkipped, + totalPassed: acc.totalPassed + totalPassed, + totalTests: acc.totalTests + totalTests + }), + emptyStats + ); + const firstResult = input[0]; + const startItems = input.map((i) => i.startedTestsAt).sort(); + const endItems = input.map((i) => i.endedTestsAt).sort(); + const runs = input.map((i) => i.runs).flat(); + return { + ...overall, + runs, + startedTestsAt: import_lodash9.default.first(startItems), + endedTestsAt: import_lodash9.default.last(endItems), + ...import_lodash9.default.pick( + firstResult, + "browserName", + "browserVersion", + "browserPath", + "osName", + "osVersion", + "cypressVersion", + "config" + ), + status: "finished" + }; +}; + +var import_common_path_prefix = __toESM(require("common-path-prefix")); +var import_lodash10 = __toESM(require("lodash")); +var import_path4 = __toESM(require("path")); +var import_pretty_ms2 = __toESM(require("pretty-ms")); +var import_table = require("table"); +var failureIcon = red("\u2716"); +var successIcon = green("\u2714"); +var summaryTable = (r) => { + const overallSpecCount = r.runs.length; + const failedSpecsCount = import_lodash10.default.sum( + r.runs.filter((v) => v.stats.failures + v.stats.skipped > 0).map(() => 1) + ); + const hasFailed = failedSpecsCount > 0; + const verdict = hasFailed ? red(`${failedSpecsCount} of ${overallSpecCount} failed`) : overallSpecCount > 0 ? "All specs passed!" : "No specs executed"; + const specs = r.runs.map((r2) => r2.spec.relative); + const commonPath = getCommonPath(specs); + const data = r.runs.map((r2) => [ + r2.stats.failures + r2.stats.skipped > 0 ? failureIcon : successIcon, + stripCommonPath(r2.spec.relative, commonPath), + gray((0, import_pretty_ms2.default)(r2.stats.duration ?? 0)), + white(r2.stats.tests ?? 0), + r2.stats.passes ? green(r2.stats.passes) : gray("-"), + r2.stats.failures ? red(r2.stats.failures) : gray("-"), + r2.stats.pending ? cyan(r2.stats.pending) : gray("-"), + r2.stats.skipped ? red(r2.stats.skipped) : gray("-") + ]); + return (0, import_table.table)( + [ + [ + "", + gray("Spec"), + "", + gray("Tests"), + gray("Passing"), + gray("Failing"), + gray("Pending"), + gray("Skipped") + ], + ...data, + [ + hasFailed ? failureIcon : successIcon, + verdict, + gray((0, import_pretty_ms2.default)(r.totalDuration ?? 0)), + overallSpecCount > 0 ? white(r.totalTests ?? 0) : gray("-"), + r.totalPassed ? green(r.totalPassed) : gray("-"), + r.totalFailed ? red(r.totalFailed) : gray("-"), + r.totalPending ? cyan(r.totalPending) : gray("-"), + r.totalSkipped ? red(r.totalSkipped) : gray("-") + ] + ], + { + border, + columnDefault: { + width: 8 + }, + columns: [ + { alignment: "left", width: 2 }, + { alignment: "left", width: 30 }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" } + ], + drawHorizontalLine: (lineIndex, rowCount) => { + return lineIndex === 1 || lineIndex === 0 || lineIndex === rowCount - 1 || lineIndex === rowCount; + }, + drawVerticalLine: (lineIndex, rowCount) => { + return lineIndex === 0 || rowCount === lineIndex; + } + } + ); +}; +var border = import_lodash10.default.mapValues( + { + topBody: `\u2500`, + topJoin: `\u252C`, + topLeft: ` \u250C`, + topRight: `\u2510`, + bottomBody: `\u2500`, + bottomJoin: `\u2534`, + bottomLeft: ` \u2514`, + bottomRight: `\u2518`, + bodyLeft: ` \u2502`, + bodyRight: `\u2502`, + bodyJoin: `\u2502`, + joinBody: `\u2500`, + joinLeft: ` \u251C`, + joinRight: `\u2524`, + joinJoin: `\u253C` + }, + (v) => gray(v) +); +function getCommonPath(specs) { + if (specs.length === 0) { + return ""; + } + if (specs.length === 1) { + return import_path4.default.dirname(specs[0]) + import_path4.default.sep; + } + return (0, import_common_path_prefix.default)(specs); +} +function stripCommonPath(spec, commonPath) { + return spec.replace(commonPath, ""); +} + +var import_debug16 = __toESM(require("debug")); + +var import_debug14 = __toESM(require("debug")); + +var import_debug13 = __toESM(require("debug")); +var import_fs3 = __toESM(require("fs")); +var readFile = import_fs3.default.promises.readFile; +var debug11 = (0, import_debug13.default)("cc:upload"); +function uploadVideo(file2, url) { + return uploadFile(file2, url, "video/mp4"); +} +function uploadImage(file2, url) { + return uploadFile(file2, url, "image/png"); +} +function uploadJson(file2, url) { + return uploadFile(file2, url, "application/json"); +} +async function uploadFile(file2, url, type) { + debug11('uploading file "%s" to "%s"', file2, url); + const f = await readFile(file2); + await makeRequest({ + url, + method: "PUT", + data: f, + headers: { + "Content-Type": type, + "Content-Disposition": `inline` + } + }); +} + +var debug12 = (0, import_debug14.default)("cc:artifacts"); +async function uploadArtifacts({ + executionState, + videoPath, + videoUploadUrl, + screenshots, + screenshotUploadUrls, + coverageFilePath, + coverageUploadUrl +}) { + debug12("uploading artifacts: %o", { + videoPath, + videoUploadUrl, + screenshots, + screenshotUploadUrls, + coverageFilePath, + coverageUploadUrl + }); + const totalUploads = (videoPath ? 1 : 0) + screenshots.length + (coverageUploadUrl ? 1 : 0); + if (totalUploads === 0) { + return; + } + if (videoUploadUrl && videoPath) { + await safe( + uploadVideo, + (e) => { + debug12("failed uploading video %s. Error: %o", videoPath, e); + executionState.addWarning( + `Failed uploading video ${videoPath}. +${dim(e)}` + ); + }, + () => debug12("success uploading", videoPath) + )(videoPath, videoUploadUrl); + } + if (screenshotUploadUrls && screenshotUploadUrls.length) { + await Promise.all( + screenshots.map((screenshot) => { + const url = screenshotUploadUrls.find( + (urls) => urls.screenshotId === screenshot.screenshotId + )?.uploadUrl; + if (!url) { + debug12( + "No upload url for screenshot %o, screenshotUploadUrls: %o", + screenshot, + screenshotUploadUrls + ); + executionState.addWarning( + `No upload URL for screenshot ${screenshot.path}` + ); + return Promise.resolve(); + } + return safe( + uploadImage, + (e) => { + debug12( + "failed uploading screenshot %s. Error: %o", + screenshot.path, + e + ); + executionState.addWarning( + `Failed uploading screenshot ${screenshot.path}. +${dim(e)}` + ); + }, + () => debug12("success uploading", screenshot.path) + )(screenshot.path, url); + }) + ); + } + if (coverageUploadUrl && coverageFilePath) { + await safe( + uploadJson, + (e) => { + debug12( + "failed uploading coverage file %s. Error: %o", + coverageFilePath, + e + ); + executionState.addWarning( + `Failed uploading coverage file ${coverageFilePath}. +${dim(e)}` + ); + }, + () => debug12("success uploading", coverageFilePath) + )(coverageFilePath, coverageUploadUrl); + } +} +var uploadStdoutSafe = safe( + updateInstanceStdout, + () => { + }, + () => { + } +); + +var state = { + cancellationReason: null +}; +var setCancellationReason = (reason) => { + if (state.cancellationReason) { + return; + } + state.cancellationReason = reason; + getPubSub().emit("run:cancelled", reason); +}; + +var import_debug15 = __toESM(require("debug")); +var debug13 = (0, import_debug15.default)("cc:results"); +var getInstanceResultPayload = (runResult, coverageFilePath) => { + debug13("generating instance result payload from %o", runResult); + return { + stats: StandardResultsToAPIResults.getStats(runResult.stats), + reporterStats: runResult.reporterStats, + exception: runResult.error ?? null, + video: !!runResult.video, + screenshots: StandardResultsToAPIResults.getAllScreenshots(runResult), + hasCoverage: !!coverageFilePath, + tests: (runResult.tests ?? []).map( + StandardResultsToAPIResults.getTestForResults + ) + }; +}; +var getInstanceTestsPayload = (runResult, config) => { + return { + config: { + ...config.getConfig(), + videoUploadOnPasses: config.getConfig()?.videoUploadOnPasses ?? true + }, + tests: (runResult.tests ?? []).map( + StandardResultsToAPIResults.getTestForSetTests + ), + hooks: runResult.hooks + }; +}; +var StandardResultsToAPIResults = class _StandardResultsToAPIResults { + static getTestAttempt(attempt) { + return { + state: attempt.state, + error: attempt.error, + wallClockStartedAt: attempt.startedAt, + wallClockDuration: attempt.duration, + videoTimestamp: attempt.videoTimestamp + }; + } + static getTestForResults(test, index) { + return { + displayError: test.displayError, + state: test.state, + attempts: (test.attempts ?? []).map( + _StandardResultsToAPIResults.getTestAttempt + ), + clientId: `r${index}` + }; + } + static getTestForSetTests(test, index) { + return { + body: "redacted", + title: test.title, + clientId: `r${index}` + }; + } + static getAllScreenshots(run3) { + return (run3.tests ?? []).flatMap( + (t, i) => t.attempts.flatMap( + (a, j) => a.screenshots.map((s) => ({ + ...s, + testId: `r${i}`, + testAttemptIndex: j, + screenshotId: getRandomString() + })) + ) + ); + } + static getStats(stats) { + return { + ...stats, + wallClockDuration: stats.duration, + wallClockStartedAt: stats.startedAt, + wallClockEndedAt: stats.endedAt + }; + } +}; + +var debug14 = (0, import_debug16.default)("cc:results"); +async function getReportResultsTask(instanceId, executionState, configState, stdout2, coverageFilePath) { + const results = executionState.getInstanceResults(configState, instanceId); + const run3 = results.runs[0]; + if (!run3) { + throw new Error("No run found in Cypress results"); + } + const instanceResults = getInstanceResultPayload(run3, coverageFilePath); + const instanceTests = getInstanceTestsPayload(run3, configState); + const { videoUploadUrl, screenshotUploadUrls, coverageUploadUrl, cloud } = await reportResults(instanceId, instanceTests, instanceResults); + if (cloud?.shouldCancel) { + debug14("instance %s should cancel", instanceId); + setCancellationReason(cloud.shouldCancel); + } + debug14("instance %s artifact upload instructions %o", instanceId, { + videoUploadUrl, + screenshotUploadUrls, + coverageUploadUrl + }); + return Promise.all([ + uploadArtifacts({ + executionState, + videoUploadUrl, + videoPath: run3.video, + screenshotUploadUrls, + screenshots: instanceResults.screenshots, + coverageUploadUrl, + coverageFilePath + }), + uploadStdoutSafe(instanceId, getInitialOutput() + stdout2) + ]); +} +async function reportResults(instanceId, instanceTests, instanceResults) { + debug14("reporting instance %s results...", instanceId); + if (isCc()) { + return reportInstanceResultsMerged(instanceId, { + tests: instanceTests, + results: instanceResults + }); + } + await setInstanceTests(instanceId, instanceTests); + return updateInstanceResults(instanceId, instanceResults); +} + +var debug15 = (0, import_debug17.default)("cc:reportTask"); +var reportTasks = []; +var createReportTask = (configState, executionState, instanceId) => { + const instance = executionState.getInstance(instanceId); + if (!instance) { + error("Cannot find execution state for instance %s", instanceId); + return; + } + if (instance.reportStartedAt) { + debug15("Report task already created for instance %s", instanceId); + return; + } + instance.reportStartedAt = new Date(); + debug15("Creating report task for instanceId %s", instanceId); + reportTasks.push( + getReportResultsTask( + instanceId, + executionState, + configState, + instance.output ?? "no output captured", + instance.coverageFilePath + ).catch(error) + ); +}; +var createReportTaskSpec = (configState, executionState, spec) => { + const i = executionState.getSpec(spec); + if (!i) { + error("Cannot find execution state for spec %s", spec); + return; + } + debug15("Creating report task for spec %s", spec); + return createReportTask(configState, executionState, i.instanceId); +}; + +var debug16 = (0, import_debug18.default)("cc:runner"); +async function runTillDone(executionState, configState, { + runId, + groupId, + machineId, + platform: platform2, + specs: allSpecs +}, params) { + let hasMore = true; + while (hasMore) { + const newTasks = await runBatch(executionState, configState, { + runMeta: { + runId, + groupId, + machineId, + platform: platform2 + }, + allSpecs, + params + }); + if (!newTasks.length) { + debug16("No more tasks to run. Uploads queue: %d", reportTasks.length); + hasMore = false; + break; + } + newTasks.forEach( + (t) => createReportTask(configState, executionState, t.instanceId) + ); + } +} +async function runBatch(executionState, configState, { + runMeta, + params, + allSpecs +}) { + let batch = { + specs: [], + claimedInstances: 0, + totalInstances: 0 + }; + if (isCc()) { + debug16("Getting batched tasks: %d", params.batchSize); + batch = await createBatchedInstances({ + ...runMeta, + batchSize: params.batchSize + }); + debug16("Got batched tasks: %o", batch); + } else { + const response = await createInstance(runMeta); + if (response.spec !== null && response.instanceId !== null) { + batch.specs.push({ + spec: response.spec, + instanceId: response.instanceId + }); + } + batch.claimedInstances = response.claimedInstances; + batch.totalInstances = response.totalInstances; + } + if (batch.specs.length === 0) { + return []; + } + batch.specs.forEach((i) => executionState.initInstance(i)); + divider(); + info( + "Running: %s (%d/%d)", + batch.specs.map((s) => s.spec).join(", "), + batch.claimedInstances, + batch.totalInstances + ); + const batchedResult = await runSpecFileSafe( + { + spec: batch.specs.map((bs) => getSpecAbsolutePath(allSpecs, bs.spec)).join(",") + }, + params + ); + title("blue", "Reporting results and artifacts in background..."); + const output = getCapturedOutput(); + batch.specs.forEach((spec) => { + executionState.setInstanceOutput(spec.instanceId, output); + const singleSpecResult = getSingleSpecRunResult(spec.spec, batchedResult); + if (!singleSpecResult) { + return; + } + getPubSub().emit("run:result", { + specRelative: spec.spec, + instanceId: spec.instanceId, + runResult: singleSpecResult + }); + }); + resetCapture(); + return batch.specs; +} +function getSingleSpecRunResult(specRelative, batchedResult) { + if (!ModuleAPIResults.isSuccessResult(batchedResult)) { + return; + } + const run3 = batchedResult.runs.find((r) => r.spec.relative === specRelative); + if (!run3) { + return; + } + return { + ...batchedResult, + runs: [run3] + }; +} +function getSpecAbsolutePath(allSpecs, relative) { + const absolutePath = allSpecs.find((i) => i.relative === relative)?.absolute; + if (!absolutePath) { + warn( + 'Cannot find absolute path for spec. Spec: "%s", candidates: %o', + relative, + allSpecs + ); + throw new Error(`Cannot find absolute path for spec`); + } + return absolutePath; +} + +var cancellable = null; +function onRunCancelled(reason) { + warn( + `Run cancelled: %s. Waiting for uploads to complete and stopping execution...`, + reason + ); + cancellable?.cancel(); +} +async function runTillDoneOrCancelled(...args) { + return new Promise((_resolve, _reject) => { + cancellable = new BPromise((resolve, reject, onCancel) => { + if (!onCancel) { + _reject(new Error("BlueBird is misconfigured: onCancel is undefined")); + return; + } + onCancel(() => _resolve(void 0)); + runTillDone(...args).then( + () => { + resolve(); + _resolve(void 0); + }, + (error2) => { + reject(); + _reject(error2); + } + ); + }); + getPubSub().addListener("run:cancelled", onRunCancelled); + }).finally(() => { + getPubSub().removeListener("run:cancelled", onRunCancelled); + }); +} + +var debug17 = (0, import_debug19.default)("cc:events"); +function handleScreenshotEvent(screenshot, executionState) { + const data = { + ...screenshot, + testId: executionState.getCurrentTestID(), + height: screenshot.dimensions.height, + width: screenshot.dimensions.width + }; + executionState.addScreenshotsData(data); +} +function handleTestBefore(testAttempt, executionState) { + const parsed = JSON.parse(testAttempt); + executionState.setCurrentTestID(parsed.id); +} +function handleTestAfter(testAttempt, executionState) { + const test = JSON.parse(testAttempt); + executionState.addAttemptsData(test); +} +async function handleSpecAfter({ + executionState, + configState, + spec, + results, + experimentalCoverageRecording = false +}) { + debug17("after:spec %s %o", spec.relative, results); + executionState.setSpecAfter( + spec.relative, + SpecAfterResult.getSpecAfterStandard(results, executionState) + ); + executionState.setSpecOutput(spec.relative, getCapturedOutput()); + const config = configState.getConfig(); + if (experimentalCoverageRecording) { + const config2 = configState.getConfig(); + const { path: path5, error: error2 } = await getCoverageFilePath( + config2?.env?.coverageFile + ); + if (!error2) { + executionState.setSpecCoverage(spec.relative, path5); + } else { + executionState.addWarning( + `Error reading coverage file "${path5}". Coverage recording will be skipped. +${dim( + error2 + )}` + ); + } + } + createReportTaskSpec(configState, executionState, spec.relative); +} + +var debug18 = (0, import_debug20.default)("cc:events"); +function listenToEvents(configState, executionState, experimentalCoverageRecording = false) { + getPubSub().on( + "run:result", + ({ + instanceId, + runResult, + specRelative + }) => { + debug18("%s %s: %o", "run:result", instanceId, runResult); + executionState.setInstanceResult( + instanceId, + ModuleAPIResults.getStandardResult(runResult, executionState) + ); + } + ); + getPubSub().on("test:after:run", (payload) => { + debug18("%s %o", "test:after:run", payload); + handleTestAfter(payload, executionState); + }); + getPubSub().on("test:before:run", (payload) => { + debug18("%s %o", "test:before:run", payload); + handleTestBefore(payload, executionState); + }); + getPubSub().on( + "after:screenshot", + (screenshot) => { + debug18("%s %o", "after:screenshot", screenshot); + handleScreenshotEvent(screenshot, executionState); + } + ); + getPubSub().on( + "after:spec", + async ({ + spec, + results + }) => { + await handleSpecAfter({ + spec, + results, + executionState, + configState, + experimentalCoverageRecording + }); + } + ); +} + +var import_debug21 = __toESM(require("debug")); +var debug19 = (0, import_debug21.default)("cc:browser"); +function guessBrowser(browser, availableBrowsers = []) { + debug19( + "guessing browser from '%s', available browsers: %o", + browser, + availableBrowsers + ); + let result = availableBrowsers.find((b) => b.name === browser); + if (result) { + debug19("identified browser by name: %o", result); + return { + browserName: result.displayName, + browserVersion: result.version + }; + } + result = availableBrowsers.find((b) => b.path === browser); + if (result) { + debug19("identified browser by path: %o", result); + return { + browserName: result.displayName ?? result.name, + browserVersion: result.version + }; + } + warn("Unable to identify browser name and version"); + return { + browserName: "unknown", + browserVersion: "unknown" + }; +} + +var import_debug22 = __toESM(require("debug")); +var import_getos = __toESM(require("getos")); +var import_os = require("os"); +var import_util2 = require("util"); +var debug20 = (0, import_debug22.default)("cc:platform"); +var getOsVersion = async () => { + if ((0, import_os.platform)() === "linux") { + try { + const linuxOs = await (0, import_util2.promisify)(import_getos.default)(); + if ("dist" in linuxOs && "release" in linuxOs) { + return [linuxOs.dist, linuxOs.release].join(" - "); + } else { + return (0, import_os.release)(); + } + } catch { + return (0, import_os.release)(); + } + } + return (0, import_os.release)(); +}; +var getPlatformInfo = async () => { + const osVersion = await getOsVersion(); + const result = { + osName: (0, import_os.platform)(), + osVersion, + osCpus: (0, import_os.cpus)(), + osMemory: { + free: (0, import_os.freemem)(), + total: (0, import_os.totalmem)() + } + }; + debug20("platform info: %o", result); + return result; +}; + +async function getPlatform({ + browser, + config +}) { + return { + ...await getPlatformInfo(), + ...guessBrowser(browser ?? "electron", config.resolved?.browsers) + }; +} + +async function shutdown() { + await stopWSS(); +} + +var import_debug23 = __toESM(require("debug")); +var import_path6 = __toESM(require("path")); +var import_common_path_prefix2 = __toESM(require("common-path-prefix")); +var import_globby = __toESM(require("globby")); +var import_lodash11 = __toESM(require("lodash")); +var import_os2 = __toESM(require("os")); + +var import_path5 = __toESM(require("path")); +function toArray(val) { + return val ? typeof val === "string" ? [val] : val : []; +} +function toPosix(file2, sep = import_path5.default.sep) { + return file2.split(sep).join(import_path5.default.posix.sep); +} + +var debug21 = (0, import_debug23.default)("cc:specs"); +async function findSpecs({ + projectRoot, + testingType, + specPattern, + configSpecPattern, + excludeSpecPattern, + additionalIgnorePattern +}) { + configSpecPattern = toArray(configSpecPattern); + specPattern = toArray(specPattern); + excludeSpecPattern = toArray(excludeSpecPattern) || []; + additionalIgnorePattern = toArray(additionalIgnorePattern) || []; + debug21("exploring spec files for execution %O", { + testingType, + projectRoot, + specPattern, + configSpecPattern, + excludeSpecPattern, + additionalIgnorePattern + }); + if (!specPattern || !configSpecPattern) { + throw Error("Could not find glob patterns for exploring specs"); + } + let specAbsolutePaths = await getFilesByGlob(projectRoot, specPattern, { + absolute: true, + ignore: [...excludeSpecPattern, ...additionalIgnorePattern] + }); + if (!import_lodash11.default.isEqual(specPattern, configSpecPattern)) { + const defaultSpecAbsolutePaths = await getFilesByGlob( + projectRoot, + configSpecPattern, + { + absolute: true, + ignore: [...excludeSpecPattern, ...additionalIgnorePattern] + } + ); + specAbsolutePaths = import_lodash11.default.intersection( + specAbsolutePaths, + defaultSpecAbsolutePaths + ); + } + return matchedSpecs({ + projectRoot, + testingType, + specAbsolutePaths, + specPattern + }); +} +async function getFilesByGlob(projectRoot, glob, globOptions) { + const workingDirectoryPrefix = import_path6.default.join(projectRoot, import_path6.default.sep); + const globs = [].concat(glob).map( + (globPattern) => globPattern.startsWith("./") ? globPattern.replace("./", "") : globPattern + ).map((globPattern) => { + if (globPattern.startsWith(workingDirectoryPrefix)) { + return globPattern.replace(workingDirectoryPrefix, ""); + } + return globPattern; + }); + if (import_os2.default.platform() === "win32") { + debug21("updating glob patterns to POSIX"); + for (const i in globs) { + const cur = globs[i]; + if (!cur) + throw new Error("undefined glob received"); + globs[i] = toPosix(cur); + } + } + try { + debug21("globbing pattern(s): %o", globs); + debug21("within directory: %s", projectRoot); + return matchGlobs(globs, { + onlyFiles: true, + absolute: true, + cwd: projectRoot, + ...globOptions, + ignore: (globOptions?.ignore ?? []).concat("**/node_modules/**") + }); + } catch (e) { + debug21("error in getFilesByGlob %o", e); + return []; + } +} +var matchGlobs = async (globs, globbyOptions) => { + return await (0, import_globby.default)(globs, globbyOptions); +}; +function matchedSpecs({ + projectRoot, + testingType, + specAbsolutePaths +}) { + debug21("found specs %o", specAbsolutePaths); + let commonRoot = ""; + if (specAbsolutePaths.length === 1) { + commonRoot = import_path6.default.dirname(specAbsolutePaths[0]); + } else { + commonRoot = (0, import_common_path_prefix2.default)(specAbsolutePaths); + } + return specAbsolutePaths.map( + (absolute) => transformSpec({ + projectRoot, + absolute, + testingType, + commonRoot, + platform: import_os2.default.platform(), + sep: import_path6.default.sep + }) + ); +} +function transformSpec({ + projectRoot, + absolute, + testingType, + commonRoot, + platform: platform2, + sep +}) { + if (platform2 === "win32") { + absolute = toPosix(absolute, sep); + projectRoot = toPosix(projectRoot, sep); + } + const relative = import_path6.default.relative(projectRoot, absolute); + const parsedFile = import_path6.default.parse(absolute); + const fileExtension = import_path6.default.extname(absolute); + const specFileExtension = [".spec", ".test", "-spec", "-test", ".cy"].map((ext) => ext + fileExtension).find((ext) => absolute.endsWith(ext)) || fileExtension; + const parts = absolute.split(projectRoot); + let name = parts[parts.length - 1] || ""; + if (name.startsWith("/")) { + name = name.slice(1); + } + const LEADING_SLASH = /^\/|/g; + const relativeToCommonRoot = absolute.replace(commonRoot, "").replace(LEADING_SLASH, ""); + return { + fileExtension, + baseName: parsedFile.base, + fileName: parsedFile.base.replace(specFileExtension, ""), + specFileExtension, + relativeToCommonRoot, + specType: testingType === "component" ? "component" : "integration", + name, + relative, + absolute + }; +} + +var getSpecFiles = async ({ + config, + params +}) => { + const specPattern = getSpecPattern(config.specPattern, params.spec); + const specs = await findSpecs({ + projectRoot: params.project ?? config.projectRoot, + testingType: params.testingType, + specPattern, + configSpecPattern: config.specPattern, + excludeSpecPattern: config.excludeSpecPattern, + additionalIgnorePattern: config.additionalIgnorePattern + }); + if (specs.length === 0) { + warn( + "Found no spec files. Was looking for spec files that match both configSpecPattern and specPattern relative to projectRoot. Configuration: %O", + { + projectRoot: config.projectRoot, + specPattern, + configSpecPattern: config.specPattern, + excludeSpecPattern: [ + config.excludeSpecPattern, + config.additionalIgnorePattern + ].flat(2), + testingType: params.testingType + } + ); + } + return { specs, specPattern }; +}; +function getSpecPattern(configPattern, explicit) { + return explicit || configPattern; +} + +var ConfigState = class { + constructor() { + this._config = void 0; + } + setConfig(c) { + this._config = c; + } + getConfig() { + return this._config; + } +}; + +var SpecAfterToModuleAPIMapper = class _SpecAfterToModuleAPIMapper { + static getTestAttempt(attempt, screenshots) { + return { + ...attempt, + duration: attempt.wallClockDuration, + startedAt: attempt.wallClockStartedAt, + screenshots + }; + } + static getTest(t, screenshots) { + return { + ...t, + attempts: t.attempts.map( + (a, i) => _SpecAfterToModuleAPIMapper.getTestAttempt( + a, + screenshots.filter( + (s) => s.testId === t.testId && s.testAttemptIndex === i + ) + ) + ) + }; + } + static convert(specAfterResult, configState) { + const stats = { + duration: specAfterResult.stats.wallClockDuration, + endedAt: specAfterResult.stats.wallClockEndedAt, + startedAt: specAfterResult.stats.wallClockStartedAt, + failures: specAfterResult.stats.failures ?? 0, + passes: specAfterResult.stats.passes ?? 0, + pending: specAfterResult.stats.pending ?? 0, + skipped: specAfterResult.stats.skipped ?? 0, + suites: specAfterResult.stats.suites ?? 0, + tests: specAfterResult.stats.tests ?? 0 + }; + return { + status: "finished", + config: configState.getConfig(), + totalDuration: stats.duration, + totalSuites: stats.suites, + totalTests: stats.tests, + totalFailed: stats.failures, + totalPassed: stats.passes, + totalPending: stats.pending, + totalSkipped: stats.skipped ?? 0, + startedTestsAt: stats.startedAt, + endedTestsAt: stats.endedAt, + runs: [ + { + stats, + reporter: specAfterResult.reporter, + reporterStats: specAfterResult.reporterStats ?? null, + spec: specAfterResult.spec, + error: specAfterResult.error, + video: specAfterResult.video, + shouldUploadVideo: true, + hooks: specAfterResult.hooks, + tests: (specAfterResult.tests ?? []).map( + (t) => _SpecAfterToModuleAPIMapper.getTest(t, specAfterResult.screenshots) + ) + } + ] + }; + } + static backfillException(result) { + return { + ...result, + runs: result.runs.map(_SpecAfterToModuleAPIMapper.backfillExceptionRun) + }; + } + static backfillExceptionRun(run3) { + if (!run3.error) { + return run3; + } + return { + ...run3, + tests: [getFakeTestFromException(run3.error, run3.stats)] + }; + } +}; +function getFakeTestFromException(error2, stats) { + return { + title: ["Unknown"], + body: "", + displayError: error2.split("\n")[0], + state: "failed", + attempts: [ + { + state: "failed", + duration: 0, + error: { + name: "Error", + message: error2.split("\n")[0], + stack: error2, + codeFrame: null + }, + screenshots: [], + startedAt: stats.startedAt, + videoTimestamp: 0 + } + ] + }; +} + +var import_debug24 = __toESM(require("debug")); +var debug22 = (0, import_debug24.default)("cc:state"); +var ExecutionState = class { + constructor() { + this.warnings = new Set(); + this.attemptsData = []; + this.screenshotsData = []; + this.state = {}; + } + getWarnings() { + return this.warnings; + } + addWarning(warning) { + this.warnings.add(warning); + } + getResults(configState) { + return Object.values(this.state).map( + (i) => this.getInstanceResults(configState, i.instanceId) + ); + } + getInstance(instanceId) { + return this.state[instanceId]; + } + getSpec(spec) { + return Object.values(this.state).find((i) => i.spec === spec); + } + initInstance({ + instanceId, + spec + }) { + debug22('Init execution state for "%s"', spec); + this.state[instanceId] = { + instanceId, + spec, + createdAt: new Date() + }; + } + setSpecBefore(spec) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + i.specBefore = new Date(); + } + setSpecCoverage(spec, coverageFilePath) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + debug22("Experimental: coverageFilePath was set"); + i.coverageFilePath = coverageFilePath; + } + setSpecAfter(spec, results) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + i.specAfter = new Date(); + i.specAfterResults = results; + } + setSpecOutput(spec, output) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + this.setInstanceOutput(i.instanceId, output); + } + setInstanceOutput(instanceId, output) { + const i = this.state[instanceId]; + if (!i) { + warn('Cannot find execution state for instance "%s"', instanceId); + return; + } + if (i.output) { + debug22('Instance "%s" already has output', instanceId); + return; + } + i.output = output; + } + setInstanceResult(instanceId, runResults) { + const i = this.state[instanceId]; + if (!i) { + warn('Cannot find execution state for instance "%s"', instanceId); + return; + } + i.runResults = { + ...runResults, + status: "finished" + }; + i.runResultsReportedAt = new Date(); + } + getInstanceResults(configState, instanceId) { + const i = this.getInstance(instanceId); + if (!i) { + error('Cannot find execution state for instance "%s"', instanceId); + return getFailedFakeInstanceResult(configState, { + specs: ["unknown"], + error: `[cc] Error while processing cypress results for instance ${instanceId}. See the console output for details.` + }); + } + if (i.specAfterResults) { + debug22('Using spec:after results for %s "%s"', instanceId, i.spec); + return SpecAfterToModuleAPIMapper.backfillException( + SpecAfterToModuleAPIMapper.convert(i.specAfterResults, configState) + ); + } + if (i.runResults) { + debug22('Using runResults for %s "%s"', instanceId, i.spec); + return SpecAfterToModuleAPIMapper.backfillException(i.runResults); + } + debug22('No results detected for "%s"', i.spec); + return getFailedFakeInstanceResult(configState, { + specs: [i.spec], + error: `No results detected for the spec file. That usually happens because of cypress crash. See the console output for details.` + }); + } + addAttemptsData(attemptDetails) { + this.attemptsData.push(attemptDetails); + } + getAttemptsData() { + return this.attemptsData; + } + addScreenshotsData(screenshotsData) { + this.screenshotsData.push(screenshotsData); + } + getScreenshotsData() { + return this.screenshotsData; + } + setCurrentTestID(testID) { + this.currentTestID = testID; + } + getCurrentTestID() { + return this.currentTestID; + } +}; + +var import_chalk2 = __toESM(require("chalk")); +var import_plur = __toESM(require("plur")); +function printWarnings2(executionState) { + const warnings = Array.from(executionState.getWarnings()); + if (warnings.length > 0) { + warn( + `${warnings.length} ${(0, import_plur.default)( + "Warning", + warnings.length + )} encountered during the execution: +${warnings.map( + (w, i) => ` +${import_chalk2.default.yellow(`[${i + 1}/${warnings.length}]`)} ${w}` + ).join("\n")}` + ); + } +} + +var debug23 = (0, import_debug25.default)("cc:run"); +async function run(params = {}) { + const executionState = new ExecutionState(); + const configState = new ConfigState(); + activateDebug(params.cloudDebug); + debug23("run params %o", params); + params = preprocessParams(params); + debug23("params after preprocess %o", params); + if (isOffline(params)) { + info(`Skipping cloud orchestration because --record is set to false`); + return runBareCypress(params); + } + const validatedParams = await validateParams(params); + setAPIBaseUrl(validatedParams.cloudServiceUrl); + if (!isCc()) { + console.log(getLegalNotice()); + } + const { + recordKey, + projectId, + group, + parallel, + ciBuildId, + tag, + testingType, + batchSize, + autoCancelAfterFailures, + experimentalCoverageRecording + } = validatedParams; + const config = await getMergedConfig(validatedParams); + configState.setConfig(config?.resolved); + const { specs, specPattern } = await getSpecFiles({ + config, + params: validatedParams + }); + if (specs.length === 0) { + return; + } + const platform2 = await getPlatform({ + config, + browser: validatedParams.browser + }); + info(`@krivega/cc version: ${dim(_ccVersion)}`); + info(`Cypress version: ${dim(_cypressVersion)}`); + info("Discovered %d spec files", specs.length); + info( + `Tags: ${tag.length > 0 ? tag.join(",") : false}; Group: ${group ?? false}; Parallel: ${parallel ?? false}; Batch Size: ${batchSize}` + ); + info("Connecting to cloud orchestration service..."); + const run3 = await createRun({ + ci: getCI(ciBuildId), + specs: specs.map((spec) => spec.relative), + commit: await getGitInfo(config.projectRoot), + group, + platform: platform2, + parallel: parallel ?? false, + ciBuildId, + projectId, + recordKey, + specPattern: [specPattern].flat(2), + tags: tag, + testingType, + batchSize, + autoCancelAfterFailures, + coverageEnabled: experimentalCoverageRecording + }); + setRunId(run3.runId); + info("\u{1F3A5} Run URL:", bold(run3.runUrl)); + cutInitialOutput(); + await startWSS(); + listenToEvents( + configState, + executionState, + config.experimentalCoverageRecording + ); + await runTillDoneOrCancelled( + executionState, + configState, + { + runId: run3.runId, + groupId: run3.groupId, + machineId: run3.machineId, + platform: platform2, + specs + }, + validatedParams + ); + divider(); + await Promise.allSettled(reportTasks); + const _summary = summarizeExecution( + executionState.getResults(configState), + config + ); + title("white", "Cloud Run Finished"); + console.log(summaryTable(_summary)); + printWarnings2(executionState); + info("\n\u{1F3C1} Recorded Run:", bold(run3.runUrl)); + await shutdown(); + spacer(); + return { + ..._summary, + runUrl: run3.runUrl + }; +} + +function run2(params) { + return run(params); +} +0 && (module.exports = { + run +}); diff --git a/index.mjs b/index.mjs new file mode 100644 index 0000000..10a1f5c --- /dev/null +++ b/index.mjs @@ -0,0 +1,3233 @@ +import "source-map-support/register.js"; + +import { createRequire } from "module"; +var require2 = createRequire(import.meta.url); + +import cp from "child_process"; +var orginal = cp.spawn; +cp.spawn = function(command, args, options) { + if (command.match(/Cypress/)) { + const process2 = orginal(command, args, { + ...options, + stdio: ["pipe", "pipe", "pipe"] + }); + return process2; + } + return orginal(command, args, options); +}; + +import Debug from "debug"; +import http from "http"; +import HttpTerminator from "lil-http-terminator"; +import { match, P } from "ts-pattern"; +import * as WebSocket from "ws"; + +var Event = ((Event2) => { + Event2["RUN_CANCELLED"] = "run:cancelled"; + Event2["RUN_RESULT"] = "run:result"; + Event2["TEST_AFTER_RUN"] = "test:after:run"; + Event2["TEST_BEFORE_RUN"] = "test:before:run"; + Event2["AFTER_SCREENSHOT"] = "after:screenshot"; + Event2["AFTER_SPEC"] = "after:spec"; + return Event2; +})(Event || {}); +var allEvents = Object.values(Event); + +import EventEmitter from "events"; +var _pubsub = null; +var getPubSub = () => { + if (!_pubsub) { + _pubsub = new EventEmitter(); + } + return _pubsub; +}; + +var debug = Debug("cc:ws"); +var server = null; +var wss = null; +var httpTerminator = null; +var getWSSPort = () => match(server?.address()).with({ port: P.number }, (address) => address.port).otherwise(() => 0); +var stopWSS = async () => { + debug("terminating wss server: %d", getWSSPort()); + if (!httpTerminator) { + debug("no wss server"); + return; + } + const { success, code, message, error: error2 } = await httpTerminator.terminate(); + if (!success) { + if (code === "TIMED_OUT") + error2(message); + if (code === "SERVER_ERROR") + error2(message, error2); + if (code === "INTERNAL_ERROR") + error2(message, error2); + } + debug("terminated wss server: %d", getWSSPort()); +}; +var startWSS = () => { + if (wss) { + return; + } + server = http.createServer().on("listening", () => { + if (!server) { + throw new Error("Server not initialized"); + } + wss = new WebSocket.WebSocketServer({ + server + }); + debug("starting wss on port %d", getWSSPort()); + wss.on("connection", function connection(ws) { + ws.on("message", function incoming(event) { + const message = JSON.parse(event.toString()); + getPubSub().emit(message.type, message.payload); + }); + }); + }).listen(); + httpTerminator = HttpTerminator({ + server + }); +}; + +import Debug2 from "debug"; +var debug2 = Debug2("cc:capture"); +var _write = process.stdout.write; +var _log = process.log; +var restore = function() { + process.stdout.write = _write; + process.log = _log; +}; +var stdout = function() { + debug2("capturing stdout"); + let logs = []; + const { write } = process.stdout; + const { log: log2 } = process; + if (log2) { + process.log = function(str) { + logs.push(str); + return log2.apply(this, arguments); + }; + } + process.stdout.write = function(str) { + logs.push(str); + return write.apply(this, arguments); + }; + return { + toString() { + return logs.join(""); + }, + data: logs, + restore, + reset: () => { + debug2("resetting captured stdout"); + logs = []; + } + }; +}; +var initialOutput = ""; +var capturedOutput = null; +var initCapture = () => capturedOutput = stdout(); +var cutInitialOutput = () => { + if (!capturedOutput) + throw new Error("capturedOutput is null"); + initialOutput = capturedOutput.toString(); + capturedOutput.reset(); +}; +var resetCapture = () => { + if (!capturedOutput) + throw new Error("capturedOutput is null"); + capturedOutput.reset(); +}; +var getCapturedOutput = () => { + if (!capturedOutput) + throw new Error("capturedOutput is null"); + return capturedOutput.toString(); +}; +var getInitialOutput = () => initialOutput; + +var _runId = void 0; +var setRunId = (runId) => { + _runId = runId; +}; +var _cypressVersion = void 0; +var setCypressVersion = (cypressVersion) => { + _cypressVersion = cypressVersion; +}; +var _ccVersion = void 0; +var setCcVersion = (v) => { + _ccVersion = v; +}; + +var cypressPkg = require2("cypress/package.json"); +var pkg = require2("@krivega/cc/package.json"); +initCapture(); +setCypressVersion(cypressPkg.version); +setCcVersion(pkg.version); + +import Debug21 from "debug"; + +function getLegalNotice() { + return ` +Copyright (C) ${(/* @__PURE__ */ new Date()).getFullYear()} +`; +} + +import { isAxiosError } from "axios"; +var isRetriableError = (err) => { + if (err.code === "ECONNABORTED") { + return true; + } + if (err.code === "ECONNREFUSED") { + return true; + } + if (err.code === "ETIMEDOUT") { + return true; + } + if (!isAxiosError(err)) { + return false; + } + return !!(err?.response?.status && 500 <= err.response.status && err.response.status < 600); +}; +var getDelay = (i) => [5 * 1e3, 10 * 1e3, 30 * 1e3][i - 1]; +var baseURL = "set baseURL"; +var getAPIBaseUrl = () => baseURL ?? "set baseURL"; +var setAPIBaseUrl = (url) => baseURL = url ?? "set baseURL"; + +import axios from "axios"; +import axiosRetry from "axios-retry"; +import Debug7 from "debug"; +import _5 from "lodash"; +import prettyMilliseconds from "pretty-ms"; + +import Debug5 from "debug"; +import { match as match3, P as P3 } from "ts-pattern"; + +import { getBinPath } from "cy2"; +import Debug4 from "debug"; +import execa from "execa"; +import fs from "fs"; + +var ValidationError = class extends Error { + constructor(message) { + super(message); + this.name = ""; + } +}; + +import { file } from "tmp-promise"; +var createTempFile = async () => { + const { path: path5 } = await file(); + return path5; +}; + +import chalk from "chalk"; +import util from "util"; +var log = (...args) => console.log(util.format(...args)); +var info = log; +var format = util.format; +var withError = (msg) => chalk.bgRed.white(" ERROR ") + " " + msg; +var withWarning = (msg) => chalk.bgYellow.black(" WARNING ") + " " + msg; +var warn = (...args) => log(withWarning(util.format(...args))); +var error = (...args) => log(withError(util.format(...args)) + "\n"); +var title = (color, ...args) => info("\n " + chalk[color].bold(util.format(...args)) + " \n"); +var divider = () => console.log("\n" + chalk.gray(Array(100).fill("=").join("")) + "\n"); +var spacer = (n = 0) => console.log(Array(n).fill("").join("\n")); +var cyan = chalk.cyan; +var blue = chalk.blueBright; +var red = chalk.red; +var green = chalk.greenBright; +var gray = chalk.gray; +var white = chalk.white; +var magenta = chalk.magenta; +var bold = chalk.bold; +var dim = chalk.dim; + +import Debug3 from "debug"; +import _ from "lodash"; + +import debug3 from "debug"; +import { match as match2, P as P2 } from "ts-pattern"; +function shouldEnablePluginDebug(param) { + return match2(param).with(P2.nullish, () => false).with("none", () => false).with(true, () => true).with("all", () => true).with("cc", () => true).with( + P2.array(P2.string), + (v) => v.includes("all") || v.includes("cc") + ).otherwise(() => false); +} +function activateDebug(mode) { + match2(mode).with(P2.instanceOf(Array), (i) => i.forEach(setDebugMode)).with(true, () => setDebugMode("all")).with( + P2.union( + "all", + "cc", + "cypress", + "commit-info" + ), + (i) => setDebugMode(i) + ).otherwise(() => setDebugMode("none")); +} +function setDebugMode(mode) { + if (mode === "none") { + return; + } + const tokens = new Set(process.env.DEBUG ? process.env.DEBUG.split(",") : []); + match2(mode).with("all", () => { + tokens.add("commit-info"); + tokens.add("cc:*"); + tokens.add("cypress:*"); + }).with("cc", () => tokens.add("cc:*")).with("cypress", () => tokens.add("cypress:*")).with("commit-info", () => tokens.add("commit-info")).otherwise(() => { + }); + debug3.enable(Array.from(tokens).join(",")); +} + +import bluebird from "bluebird"; +bluebird.Promise.config({ + cancellation: true +}); +var BPromise = bluebird.Promise; +var safe = (fn, ifFaled, ifSucceed) => async (...args) => { + try { + const r = await fn(...args); + ifSucceed(); + return r; + } catch (e) { + return ifFaled(e); + } +}; +var sortObjectKeys = (obj) => { + return Object.keys(obj).sort().reduce((acc, key) => { + acc[key] = obj[key]; + return acc; + }, {}); +}; + +import { customAlphabet } from "nanoid"; +var getRandomString = customAlphabet("abcdefghijklmnopqrstuvwxyz", 10); + +var debug4 = Debug3("cc:boot"); +function getBootstrapArgs({ + params, + tempFilePath +}) { + return _.chain(getCypressCLIParams(params)).thru((opts) => ({ + ...opts, + env: { + ...opts.env ?? {}, + cc_marker: true, + cc_temp_file: tempFilePath, + cc_debug_enabled: shouldEnablePluginDebug(params.cloudDebug) + } + })).tap((opts) => { + debug4("cypress bootstrap params: %o", opts); + }).thru((opts) => ({ + ...opts, + env: sortObjectKeys(opts.env ?? {}) + })).thru(serializeOptions).tap((opts) => { + debug4("cypress bootstrap serialized params: %o", opts); + }).thru((args) => { + return [ + ...args, + "--spec", + getRandomString(), + params.testingType === "component" ? "--component" : "--e2e" + ]; + }).value(); +} +function getCypressCLIParams(params) { + const result = getCypressRunAPIParams(params); + const testingType = result.testingType === "component" ? { + component: true + } : {}; + return { + ..._.omit(result, "testingType"), + ...testingType + }; +} +function serializeOptions(options) { + return Object.entries(options).flatMap(([key, value]) => { + const _key = dashed(key); + if (typeof value === "boolean") { + return value === true ? [`--${_key}`] : [`--${_key}`, false]; + } + if (_.isObject(value)) { + return [`--${_key}`, serializeComplexParam(value)]; + } + return [`--${_key}`, value.toString()]; + }); +} +function serializeComplexParam(param) { + return JSON.stringify(param); +} +var dashed = (v) => v.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()); + +var debug5 = Debug4("cc:boot"); +var bootCypress = async (params) => { + debug5("booting cypress..."); + const tempFilePath = await createTempFile(); + const cypressBin = await getBinPath(require2.resolve("cypress")); + debug5("cypress executable location: %s", cypressBin); + const args = getBootstrapArgs({ tempFilePath, params }); + debug5("booting cypress with args: %o", args); + const { stdout: stdout2, stderr } = await execCypress(cypressBin, args); + if (!fs.existsSync(tempFilePath)) { + throw new Error( + `Cannot resolve cypress configuration from ${tempFilePath}. Please report the issue.` + ); + } + try { + const f = fs.readFileSync(tempFilePath, "utf-8"); + if (!f) { + throw new Error("Is @krivega/cc/plugin installed?"); + } + debug5("cypress config '%s': '%s'", tempFilePath, f); + return JSON.parse(f); + } catch (err) { + debug5("read config temp file failed: %o", err); + info(bold("Cypress stdout:\n"), stdout2); + info(bold("Cypress stderr:\n"), stderr); + throw new ValidationError(`Unable to resolve cypress configuration +- make sure that '@krivega/cc/plugin' is installed +- report the issue together with cypress stdout and stderr +`); + } +}; +async function execCypress(cypressBin, args) { + let stdout2 = ""; + let stderr = ""; + try { + await execa(cypressBin, ["run", ...args], { + stdio: "pipe", + env: { + ...process.env, + CYPRESS_RECORD_KEY: void 0, + CYPRESS_PROJECT_ID: void 0 + } + }); + } catch (err) { + debug5("exec cypress failed (certain failures are expected): %o", err); + stdout2 = err.stdout; + stderr = err.stderr; + } + return { stdout: stdout2, stderr }; +} + +import isAbsolute from "is-absolute"; +import _2 from "lodash"; +import path from "path"; +var defaultFilenames = [ + "cc.config.js", + "cc.config.cjs", + "cc.config.mjs" +]; +function getConfigFilePath(projectRoot = null, explicitConfigFilePath) { + const prefix = projectRoot ?? process.cwd(); + if (_2.isString(explicitConfigFilePath) && isAbsolute(explicitConfigFilePath)) { + return [explicitConfigFilePath]; + } + if (_2.isString(explicitConfigFilePath)) { + return [normalizePath(prefix, explicitConfigFilePath)]; + } + return defaultFilenames.map((p) => normalizePath(prefix, p)); +} +function normalizePath(prefix, filename) { + return `file://${path.resolve(prefix, filename)}`; +} + +var debug6 = Debug5("cc:config"); +var _config = null; +var defaultConfig = { + e2e: { + batchSize: 3 + }, + component: { + batchSize: 5 + }, + cloudServiceUrl: "set baseURL", + networkHeaders: void 0 +}; +async function getCcConfig(projectRoot, explicitConfigFilePath) { + if (_config) { + return _config; + } + const configFilePath = getConfigFilePath(projectRoot, explicitConfigFilePath); + for (const filepath of configFilePath) { + const config = match3(await loadConfigFile(filepath)).with({ default: P3.not(P3.nullish) }, (c) => c.default).with(P3.not(P3.nullish), (c) => c).otherwise(() => null); + if (config) { + debug6("loaded cc config from '%s'\n%O", filepath, config); + info(`Using config file: ${dim(filepath)}`); + _config = { + ...defaultConfig, + ...config + }; + return _config; + } + } + warn( + "Failed to load config file, falling back to the default config. Attempted locations: %s", + configFilePath + ); + _config = defaultConfig; + return _config; +} +async function loadConfigFile(filepath) { + try { + debug6("loading cc config file from '%s'", filepath); + return await import(filepath); + } catch (e) { + debug6("failed loading config file from: %s", e); + return null; + } +} +async function getMergedConfig(params) { + debug6("resolving cypress config"); + const cypressResolvedConfig = await bootCypress(params); + debug6("cypress resolvedConfig: %O", cypressResolvedConfig); + const rawE2EPattern = cypressResolvedConfig.rawJson?.e2e?.specPattern; + let additionalIgnorePattern = []; + if (params.testingType === "component" && rawE2EPattern) { + additionalIgnorePattern = rawE2EPattern; + } + const result = { + projectRoot: cypressResolvedConfig?.projectRoot || process.cwd(), + projectId: params.projectId, + specPattern: cypressResolvedConfig?.specPattern || "**/*.*", + excludeSpecPattern: ( + cypressResolvedConfig?.resolved.excludeSpecPattern.value ?? [] + ), + additionalIgnorePattern, + resolved: cypressResolvedConfig, + experimentalCoverageRecording: params.experimentalCoverageRecording + }; + debug6("merged config: %O", result); + return result; +} + +import Debug6 from "debug"; +import _3 from "lodash"; +var debug7 = Debug6("cc:validateParams"); +async function resolveCcParams(params) { + const configFromFile = await getCcConfig( + params.project, + params.cloudConfigFile + ); + debug7("resolving cc params: %o", params); + debug7("resolving cc config file: %o", configFromFile); + const cloudServiceUrl = params.cloudServiceUrl ?? process.env.CC_API_URL ?? configFromFile.cloudServiceUrl; + const recordKey = params.recordKey ?? process.env.CC_RECORD_KEY ?? configFromFile.recordKey; + const projectId = params.projectId ?? process.env.CC_PROJECT_ID ?? configFromFile.projectId; + const testingType = params.testingType ?? "e2e"; + let batchSize = params.batchSize; + if (!batchSize) { + batchSize = testingType === "e2e" ? configFromFile.e2e.batchSize : configFromFile.component.batchSize; + } + return { + ...params, + cloudServiceUrl, + recordKey, + projectId, + batchSize, + testingType + }; +} +var projectIdError = `Cannot resolve projectId. Please use one of the following: +- provide it as a "projectId" property for "run" API method +- set CC_PROJECT_ID environment variable +- set "projectId" in "cc.config.{c}js" file`; +var cloudServiceUrlError = `Cannot resolve cloud service URL. Please use one of the following: +- provide it as a "cloudServiceUrl" property for "run" API method +- set CC_API_URL environment variable +- set "cloudServiceUrl" in "cc.config.{c}js" file`; +var cloudServiceInvalidUrlError = `Invalid cloud service URL provided`; +var recordKeyError = `Cannot resolve record key. Please use one of the following: + +- pass it as a CLI flag '-k, --key ' +- provide it as a "recordKey" property for "run" API method +- set CC_RECORD_KEY environment variable +- set "recordKey" in "cc.config.{c}js" file +`; +async function validateParams(_params) { + const params = await resolveCcParams(_params); + debug7("validating cc params: %o", params); + if (!params.cloudServiceUrl) { + throw new ValidationError(cloudServiceUrlError); + } + if (!params.projectId) { + throw new ValidationError(projectIdError); + } + if (!params.recordKey) { + throw new ValidationError(recordKeyError); + } + validateURL(params.cloudServiceUrl); + const requiredParameters = [ + "testingType", + "batchSize", + "projectId" + ]; + requiredParameters.forEach((key) => { + if (typeof params[key] === "undefined") { + error('Missing required parameter "%s"', key); + throw new Error("Missing required parameter"); + } + }); + params.tag = parseTags(params.tag); + params.autoCancelAfterFailures = getAutoCancelValue( + params.autoCancelAfterFailures + ); + debug7("validated cc params: %o", params); + return params; +} +function getAutoCancelValue(value) { + if (typeof value === "undefined") { + return void 0; + } + if (typeof value === "boolean") { + return value ? 1 : false; + } + if (typeof value === "number" && value > 0) { + return value; + } + throw new ValidationError( + `autoCancelAfterFailures: should be a positive integer or "false". Got: "${value}"` + ); +} +function isOffline(params) { + return params.record === false; +} +function parseTags(tagString) { + if (!tagString) { + return []; + } + if (Array.isArray(tagString)) { + return tagString.filter(Boolean); + } + return tagString.split(",").map((tag) => tag.trim()).filter(Boolean); +} +function validateURL(url) { + try { + new URL(url); + } catch (err) { + throw new ValidationError(`${cloudServiceInvalidUrlError}: "${url}"`); + } +} +function getCypressRunAPIParams(params) { + return { + ..._3.pickBy( + _3.omit(params, [ + "cloudDebug", + "cloudConfigFile", + "autoCancelAfterFailures", + "cloudServiceUrl", + "batchSize", + "projectId", + "key", + "recordKey", + "record", + "group", + "parallel", + "tag", + "ciBuildId", + "spec", + "exit", + "headless", + "experimentalCoverageRecording" + ]), + Boolean + ), + record: false, + env: { + ...params.env, + cc_debug_enabled: shouldEnablePluginDebug(params.cloudDebug) + } + }; +} +function preprocessParams(params) { + return { + ...params, + spec: processSpecParam(params.spec) + }; +} +function processSpecParam(spec) { + if (!spec) { + return void 0; + } + if (Array.isArray(spec)) { + return _3.flatten(spec.map((i) => i.split(","))); + } + return spec.split(","); +} + +import _4 from "lodash"; +function maybePrintErrors(err) { + if (!err.response?.data || !err.response?.status) { + return; + } + const { message, errors } = err.response.data; + switch (err.response.status) { + case 401: + warn("Received 401 Unauthorized"); + break; + case 422: + spacer(1); + warn(...formatGenericError(message, errors)); + spacer(1); + break; + default: + break; + } +} +function formatGenericError(message, errors) { + if (!_4.isString(message)) { + return ["Unexpected error from the cloud service"]; + } + if (errors?.length === 0) { + return [message]; + } + return [ + message, + ` +${(errors ?? []).map((e) => ` - ${e}`).join("\n")} +` + ]; +} + +var debug8 = Debug7("cc:api"); +var MAX_RETRIES = 3; +var TIMEOUT_MS = 30 * 1e3; +var _client = null; +async function getClient() { + if (_client) { + return _client; + } + const ccConfig = await getCcConfig(); + _client = axios.create({ + baseURL: getAPIBaseUrl(), + timeout: TIMEOUT_MS + }); + _client.interceptors.request.use((config) => { + const ccyVerson = _ccVersion ?? "0.0.0"; + const headers = { + ...config.headers, + "x-cypress-request-attempt": config["axios-retry"]?.retryCount ?? 0, + "x-cypress-version": _cypressVersion ?? "0.0.0", + "x-ccy-version": ccyVerson, + "User-Agent": `@krivega/cc/${ccyVerson}` + }; + if (_runId) { + headers["x-cypress-run-id"] = _runId; + } + if (!headers["Content-Type"]) { + headers["Content-Type"] = "application/json"; + } + if (ccConfig.networkHeaders) { + const filteredHeaders = _5.omit(ccConfig.networkHeaders, [ + "x-cypress-request-attempt", + "x-cypress-version", + "x-ccy-version", + "x-cypress-run-id", + "Content-Type" + ]); + debug8("using custom network headers: %o", filteredHeaders); + Object.assign(headers, filteredHeaders); + } + const req = { + ...config, + headers + }; + debug8("network request: %o", { + ..._5.pick(req, "method", "url", "headers"), + data: Buffer.isBuffer(req.data) ? "buffer" : req.data + }); + return req; + }); + axiosRetry(_client, { + retries: MAX_RETRIES, + retryCondition: isRetriableError, + retryDelay: getDelay, + onRetry, + shouldResetTimeout: true + }); + return _client; +} +function onRetry(retryCount, err, config) { + warn( + "Network request '%s' failed: '%s'. Next attempt is in %s (%d/%d).", + `${config.method} ${config.url}`, + err.message, + prettyMilliseconds(getDelay(retryCount)), + retryCount, + MAX_RETRIES + ); +} +var makeRequest = async (config) => { + return (await getClient())(config).then((res) => { + debug8("network response: %o", _5.omit(res, "request", "config")); + return res; + }).catch((error2) => { + maybePrintErrors(error2); + throw new ValidationError(error2.message); + }); +}; + +import _6 from "lodash"; +function printWarnings(warnings) { + warn("Notice from cloud service:"); + warnings.map((w) => { + spacer(1); + info(magenta.bold(w.message)); + Object.entries(_6.omit(w, "message")).map(([key, value]) => { + info("- %s: %s", key, value); + }); + spacer(1); + }); +} + +var createRun = async (payload) => { + const response = await makeRequest({ + method: "POST", + url: "/runs", + data: payload + }); + if ((response.data.warnings?.length ?? 0) > 0) { + printWarnings(response.data.warnings); + } + return response.data; +}; +var createInstance = async ({ + runId, + groupId, + machineId, + platform: platform2 +}) => { + const response = await makeRequest({ + method: "POST", + url: `runs/${runId}/instances`, + data: { + runId, + groupId, + machineId, + platform: platform2 + } + }); + return response.data; +}; +var createBatchedInstances = async (data) => { + const respone = await makeRequest({ + method: "POST", + url: `runs/${data.runId}/cy/instances`, + data + }); + return respone.data; +}; +var setInstanceTests = (instanceId, payload) => makeRequest({ + method: "POST", + url: `instances/${instanceId}/tests`, + data: payload +}).then((result) => result.data); +var updateInstanceResults = (instanceId, payload) => makeRequest({ + method: "POST", + url: `instances/${instanceId}/results`, + data: payload +}).then((result) => result.data); +var reportInstanceResultsMerged = (instanceId, payload) => makeRequest({ + method: "POST", + url: `instances/${instanceId}/cy/results`, + data: payload +}).then((result) => result.data); +var updateInstanceStdout = (instanceId, stdout2) => makeRequest({ + method: "PUT", + url: `instances/${instanceId}/stdout`, + data: { + stdout: stdout2 + } +}); + +import debugFn from "debug"; +import _7 from "lodash"; +var debug9 = debugFn("cc:ci"); +var join = (char, ...pieces) => { + return _7.chain(pieces).compact().join(char).value(); +}; +var toCamelObject = (obj, key) => { + return _7.set(obj, _7.camelCase(key), process.env[key]); +}; +var extract = (envKeys) => { + return _7.transform(envKeys, toCamelObject, {}); +}; +var isTeamFoundation = () => { + return process.env.TF_BUILD && process.env.TF_BUILD_BUILDNUMBER; +}; +var isAzureCi = () => { + return process.env.TF_BUILD && process.env.AZURE_HTTP_USER_AGENT; +}; +var isAWSCodeBuild = () => { + return _7.some(process.env, (val, key) => { + return /^CODEBUILD_/.test(key); + }); +}; +var isBamboo = () => { + return process.env.bamboo_buildNumber; +}; +var isCodeshipBasic = () => { + return process.env.CI_NAME && process.env.CI_NAME === "codeship" && process.env.CODESHIP; +}; +var isCodeshipPro = () => { + return process.env.CI_NAME && process.env.CI_NAME === "codeship" && !process.env.CODESHIP; +}; +var isConcourse = () => { + return _7.some(process.env, (val, key) => { + return /^CONCOURSE_/.test(key); + }); +}; +var isGitlab = () => { + return process.env.GITLAB_CI || process.env.CI_SERVER_NAME && /^GitLab/.test(process.env.CI_SERVER_NAME); +}; +var isGoogleCloud = () => { + return process.env.GCP_PROJECT || process.env.GCLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT; +}; +var isJenkins = () => { + return process.env.JENKINS_URL || process.env.JENKINS_HOME || process.env.JENKINS_VERSION || process.env.HUDSON_URL || process.env.HUDSON_HOME; +}; +var isWercker = () => { + return process.env.WERCKER || process.env.WERCKER_MAIN_PIPELINE_STARTED; +}; +var CI_PROVIDERS = { + appveyor: "APPVEYOR", + azure: isAzureCi, + awsCodeBuild: isAWSCodeBuild, + bamboo: isBamboo, + bitbucket: "BITBUCKET_BUILD_NUMBER", + buildkite: "BUILDKITE", + circle: "CIRCLECI", + codeshipBasic: isCodeshipBasic, + codeshipPro: isCodeshipPro, + concourse: isConcourse, + codeFresh: "CF_BUILD_ID", + drone: "DRONE", + githubActions: "GITHUB_ACTIONS", + gitlab: isGitlab, + goCD: "GO_JOB_NAME", + googleCloud: isGoogleCloud, + jenkins: isJenkins, + semaphore: "SEMAPHORE", + shippable: "SHIPPABLE", + teamcity: "TEAMCITY_VERSION", + teamfoundation: isTeamFoundation, + travis: "TRAVIS", + wercker: isWercker, + netlify: "NETLIFY", + layerci: "LAYERCI" +}; +function _detectProviderName() { + const { env } = process; + return _7.findKey(CI_PROVIDERS, (value) => { + if (_7.isString(value)) { + return env[value]; + } + if (_7.isFunction(value)) { + return value(); + } + }); +} +var _providerCiParams = () => { + return { + appveyor: extract([ + "APPVEYOR_JOB_ID", + "APPVEYOR_ACCOUNT_NAME", + "APPVEYOR_PROJECT_SLUG", + "APPVEYOR_BUILD_NUMBER", + "APPVEYOR_BUILD_VERSION", + "APPVEYOR_PULL_REQUEST_NUMBER", + "APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH" + ]), + azure: extract([ + "BUILD_BUILDID", + "BUILD_BUILDNUMBER", + "BUILD_CONTAINERID", + "BUILD_REPOSITORY_URI" + ]), + awsCodeBuild: extract([ + "CODEBUILD_BUILD_ID", + "CODEBUILD_BUILD_NUMBER", + "CODEBUILD_RESOLVED_SOURCE_VERSION", + "CODEBUILD_SOURCE_REPO_URL", + "CODEBUILD_SOURCE_VERSION" + ]), + bamboo: extract([ + "bamboo_buildNumber", + "bamboo_buildResultsUrl", + "bamboo_planRepository_repositoryUrl", + "bamboo_buildKey" + ]), + bitbucket: extract([ + "BITBUCKET_REPO_SLUG", + "BITBUCKET_REPO_OWNER", + "BITBUCKET_BUILD_NUMBER", + "BITBUCKET_PARALLEL_STEP", + "BITBUCKET_STEP_RUN_NUMBER", + "BITBUCKET_PR_ID", + "BITBUCKET_PR_DESTINATION_BRANCH", + "BITBUCKET_PR_DESTINATION_COMMIT" + ]), + buildkite: extract([ + "BUILDKITE_REPO", + "BUILDKITE_SOURCE", + "BUILDKITE_JOB_ID", + "BUILDKITE_BUILD_ID", + "BUILDKITE_BUILD_URL", + "BUILDKITE_BUILD_NUMBER", + "BUILDKITE_PULL_REQUEST", + "BUILDKITE_PULL_REQUEST_REPO", + "BUILDKITE_PULL_REQUEST_BASE_BRANCH" + ]), + circle: extract([ + "CIRCLE_JOB", + "CIRCLE_BUILD_NUM", + "CIRCLE_BUILD_URL", + "CIRCLE_PR_NUMBER", + "CIRCLE_PR_REPONAME", + "CIRCLE_PR_USERNAME", + "CIRCLE_COMPARE_URL", + "CIRCLE_WORKFLOW_ID", + "CIRCLE_PULL_REQUEST", + "CIRCLE_REPOSITORY_URL", + "CI_PULL_REQUEST" + ]), + codeshipBasic: extract([ + "CI_BUILD_ID", + "CI_REPO_NAME", + "CI_BUILD_URL", + "CI_PROJECT_ID", + "CI_BUILD_NUMBER", + "CI_PULL_REQUEST" + ]), + codeshipPro: extract(["CI_BUILD_ID", "CI_REPO_NAME", "CI_PROJECT_ID"]), + concourse: extract([ + "BUILD_ID", + "BUILD_NAME", + "BUILD_JOB_NAME", + "BUILD_PIPELINE_NAME", + "BUILD_TEAM_NAME", + "ATC_EXTERNAL_URL" + ]), + codeFresh: extract([ + "CF_BUILD_ID", + "CF_BUILD_URL", + "CF_CURRENT_ATTEMPT", + "CF_STEP_NAME", + "CF_PIPELINE_NAME", + "CF_PIPELINE_TRIGGER_ID", + "CF_PULL_REQUEST_ID", + "CF_PULL_REQUEST_IS_FORK", + "CF_PULL_REQUEST_NUMBER", + "CF_PULL_REQUEST_TARGET" + ]), + drone: extract([ + "DRONE_JOB_NUMBER", + "DRONE_BUILD_LINK", + "DRONE_BUILD_NUMBER", + "DRONE_PULL_REQUEST" + ]), + githubActions: extract([ + "GITHUB_WORKFLOW", + "GITHUB_ACTION", + "GITHUB_EVENT_NAME", + "GITHUB_RUN_ID", + "GITHUB_RUN_ATTEMPT", + "GITHUB_REPOSITORY" + ]), + gitlab: extract([ + "CI_PIPELINE_ID", + "CI_PIPELINE_URL", + "CI_BUILD_ID", + "CI_JOB_ID", + "CI_JOB_URL", + "CI_JOB_NAME", + "GITLAB_HOST", + "CI_PROJECT_ID", + "CI_PROJECT_URL", + "CI_REPOSITORY_URL", + "CI_ENVIRONMENT_URL", + "CI_DEFAULT_BRANCH" + ]), + goCD: extract([ + "GO_SERVER_URL", + "GO_ENVIRONMENT_NAME", + "GO_PIPELINE_NAME", + "GO_PIPELINE_COUNTER", + "GO_PIPELINE_LABEL", + "GO_STAGE_NAME", + "GO_STAGE_COUNTER", + "GO_JOB_NAME", + "GO_TRIGGER_USER", + "GO_REVISION", + "GO_TO_REVISION", + "GO_FROM_REVISION", + "GO_MATERIAL_HAS_CHANGED" + ]), + googleCloud: extract([ + "BUILD_ID", + "PROJECT_ID", + "REPO_NAME", + "BRANCH_NAME", + "TAG_NAME", + "COMMIT_SHA", + "SHORT_SHA" + ]), + jenkins: extract(["BUILD_ID", "BUILD_URL", "BUILD_NUMBER", "ghprbPullId"]), + semaphore: extract([ + "SEMAPHORE_BRANCH_ID", + "SEMAPHORE_BUILD_NUMBER", + "SEMAPHORE_CURRENT_JOB", + "SEMAPHORE_CURRENT_THREAD", + "SEMAPHORE_EXECUTABLE_UUID", + "SEMAPHORE_GIT_BRANCH", + "SEMAPHORE_GIT_DIR", + "SEMAPHORE_GIT_REF", + "SEMAPHORE_GIT_REF_TYPE", + "SEMAPHORE_GIT_REPO_SLUG", + "SEMAPHORE_GIT_SHA", + "SEMAPHORE_GIT_URL", + "SEMAPHORE_JOB_COUNT", + "SEMAPHORE_JOB_ID", + "SEMAPHORE_JOB_NAME", + "SEMAPHORE_JOB_UUID", + "SEMAPHORE_PIPELINE_ID", + "SEMAPHORE_PLATFORM", + "SEMAPHORE_PROJECT_DIR", + "SEMAPHORE_PROJECT_HASH_ID", + "SEMAPHORE_PROJECT_ID", + "SEMAPHORE_PROJECT_NAME", + "SEMAPHORE_PROJECT_UUID", + "SEMAPHORE_REPO_SLUG", + "SEMAPHORE_TRIGGER_SOURCE", + "SEMAPHORE_WORKFLOW_ID", + "PULL_REQUEST_NUMBER" + ]), + shippable: extract([ + "SHIPPABLE_BUILD_ID", + "SHIPPABLE_BUILD_NUMBER", + "SHIPPABLE_COMMIT_RANGE", + "SHIPPABLE_CONTAINER_NAME", + "SHIPPABLE_JOB_ID", + "SHIPPABLE_JOB_NUMBER", + "SHIPPABLE_REPO_SLUG", + "IS_FORK", + "IS_GIT_TAG", + "IS_PRERELEASE", + "IS_RELEASE", + "REPOSITORY_URL", + "REPO_FULL_NAME", + "REPO_NAME", + "BUILD_URL", + "BASE_BRANCH", + "HEAD_BRANCH", + "IS_PULL_REQUEST", + "PULL_REQUEST", + "PULL_REQUEST_BASE_BRANCH", + "PULL_REQUEST_REPO_FULL_NAME" + ]), + teamcity: null, + teamfoundation: extract([ + "BUILD_BUILDID", + "BUILD_BUILDNUMBER", + "BUILD_CONTAINERID" + ]), + travis: extract([ + "TRAVIS_JOB_ID", + "TRAVIS_BUILD_ID", + "TRAVIS_BUILD_WEB_URL", + "TRAVIS_REPO_SLUG", + "TRAVIS_JOB_NUMBER", + "TRAVIS_EVENT_TYPE", + "TRAVIS_COMMIT_RANGE", + "TRAVIS_BUILD_NUMBER", + "TRAVIS_PULL_REQUEST", + "TRAVIS_PULL_REQUEST_BRANCH", + "TRAVIS_PULL_REQUEST_SHA" + ]), + wercker: null, + netlify: extract([ + "BUILD_ID", + "CONTEXT", + "URL", + "DEPLOY_URL", + "DEPLOY_PRIME_URL", + "DEPLOY_ID" + ]), + layerci: extract([ + "LAYERCI_JOB_ID", + "LAYERCI_RUNNER_ID", + "RETRY_INDEX", + "LAYERCI_PULL_REQUEST", + "LAYERCI_REPO_NAME", + "LAYERCI_REPO_OWNER", + "LAYERCI_BRANCH", + "GIT_TAG" + ]) + }; +}; +var _providerCommitParams = () => { + const { env } = process; + return { + appveyor: { + sha: env.APPVEYOR_REPO_COMMIT, + branch: env.APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH || env.APPVEYOR_REPO_BRANCH, + message: join( + "\n", + env.APPVEYOR_REPO_COMMIT_MESSAGE, + env.APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED + ), + authorName: env.APPVEYOR_REPO_COMMIT_AUTHOR, + authorEmail: env.APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL + }, + awsCodeBuild: { + sha: env.CODEBUILD_RESOLVED_SOURCE_VERSION, + remoteOrigin: env.CODEBUILD_SOURCE_REPO_URL + }, + azure: { + sha: env.BUILD_SOURCEVERSION, + branch: env.BUILD_SOURCEBRANCHNAME, + message: env.BUILD_SOURCEVERSIONMESSAGE, + authorName: env.BUILD_SOURCEVERSIONAUTHOR, + authorEmail: env.BUILD_REQUESTEDFOREMAIL + }, + bamboo: { + sha: env.bamboo_planRepository_revision, + branch: env.bamboo_planRepository_branch, + authorName: env.bamboo_planRepository_username, + remoteOrigin: env.bamboo_planRepository_repositoryURL + }, + bitbucket: { + sha: env.BITBUCKET_COMMIT, + branch: env.BITBUCKET_BRANCH + }, + buildkite: { + sha: env.BUILDKITE_COMMIT, + branch: env.BUILDKITE_BRANCH, + message: env.BUILDKITE_MESSAGE, + authorName: env.BUILDKITE_BUILD_CREATOR, + authorEmail: env.BUILDKITE_BUILD_CREATOR_EMAIL, + remoteOrigin: env.BUILDKITE_REPO, + defaultBranch: env.BUILDKITE_PIPELINE_DEFAULT_BRANCH + }, + circle: { + sha: env.CIRCLE_SHA1, + branch: env.CIRCLE_BRANCH, + authorName: env.CIRCLE_USERNAME, + remoteOrigin: env.CIRCLE_REPOSITORY_URL + }, + codeshipBasic: { + sha: env.CI_COMMIT_ID, + branch: env.CI_BRANCH, + message: env.CI_COMMIT_MESSAGE, + authorName: env.CI_COMMITTER_NAME, + authorEmail: env.CI_COMMITTER_EMAIL + }, + codeshipPro: { + sha: env.CI_COMMIT_ID, + branch: env.CI_BRANCH, + message: env.CI_COMMIT_MESSAGE, + authorName: env.CI_COMMITTER_NAME, + authorEmail: env.CI_COMMITTER_EMAIL + }, + codeFresh: { + sha: env.CF_REVISION, + branch: env.CF_BRANCH, + message: env.CF_COMMIT_MESSAGE, + authorName: env.CF_COMMIT_AUTHOR + }, + drone: { + sha: env.DRONE_COMMIT_SHA, + branch: env.DRONE_SOURCE_BRANCH, + message: env.DRONE_COMMIT_MESSAGE, + authorName: env.DRONE_COMMIT_AUTHOR, + authorEmail: env.DRONE_COMMIT_AUTHOR_EMAIL, + remoteOrigin: env.DRONE_GIT_HTTP_URL, + defaultBranch: env.DRONE_REPO_BRANCH + }, + githubActions: { + sha: env.GITHUB_SHA, + branch: env.GH_BRANCH || env.GITHUB_REF, + defaultBranch: env.GITHUB_BASE_REF, + remoteBranch: env.GITHUB_HEAD_REF, + runAttempt: env.GITHUB_RUN_ATTEMPT + }, + gitlab: { + sha: env.CI_COMMIT_SHA, + branch: env.CI_COMMIT_REF_NAME, + message: env.CI_COMMIT_MESSAGE, + authorName: env.GITLAB_USER_NAME, + authorEmail: env.GITLAB_USER_EMAIL, + remoteOrigin: env.CI_REPOSITORY_URL, + defaultBranch: env.CI_DEFAULT_BRANCH + }, + googleCloud: { + sha: env.COMMIT_SHA, + branch: env.BRANCH_NAME + }, + jenkins: { + sha: env.GIT_COMMIT, + branch: env.GIT_BRANCH + }, + semaphore: { + sha: env.SEMAPHORE_GIT_SHA, + branch: env.SEMAPHORE_GIT_BRANCH, + remoteOrigin: env.SEMAPHORE_GIT_REPO_SLUG + }, + shippable: { + sha: env.COMMIT, + branch: env.BRANCH, + message: env.COMMIT_MESSAGE, + authorName: env.COMMITTER + }, + snap: null, + teamcity: null, + teamfoundation: { + sha: env.BUILD_SOURCEVERSION, + branch: env.BUILD_SOURCEBRANCHNAME, + message: env.BUILD_SOURCEVERSIONMESSAGE, + authorName: env.BUILD_SOURCEVERSIONAUTHOR + }, + travis: { + sha: env.TRAVIS_PULL_REQUEST_SHA || env.TRAVIS_COMMIT, + branch: env.TRAVIS_PULL_REQUEST_BRANCH || env.TRAVIS_BRANCH, + message: env.TRAVIS_COMMIT_MESSAGE + }, + wercker: null, + netlify: { + sha: env.COMMIT_REF, + branch: env.BRANCH, + remoteOrigin: env.REPOSITORY_URL + }, + layerci: { + sha: env.GIT_COMMIT, + branch: env.LAYERCI_BRANCH, + message: env.GIT_COMMIT_TITLE + } + }; +}; +var _get = (fn) => { + const providerName = getCiProvider(); + if (!providerName) + return {}; + return _7.chain(fn()).get(providerName).value(); +}; +function checkForCiBuildFromCi(ciProvider) { + if (ciProvider && detectableCiBuildIdProviders().includes(ciProvider)) + return true; + throw new ValidationError( + `Could not determine CI build ID from the environment. Please provide a unique CI build ID using the --ci-build-id CLI flag or 'ciBuildId' parameter for 'run' method.` + ); +} +function detectableCiBuildIdProviders() { + return _7.chain(_providerCiParams()).omitBy(_7.isNull).keys().value(); +} +function getCiProvider() { + return _detectProviderName() || null; +} +function getCiParams() { + return _get(_providerCiParams); +} +function getCommitParams() { + return _get(_providerCommitParams); +} +function getCI(ciBuildId) { + const params = getCiParams(); + const provider = getCiProvider(); + if (!ciBuildId) + checkForCiBuildFromCi(provider); + debug9("detected CI provider: %s", provider); + debug9("detected CI params: %O", params); + return { + params, + provider + }; +} +function getCommitDefaults(existingInfo) { + debug9("git commit existing info"); + debug9(existingInfo); + const commitParamsObj = getCommitParams(); + debug9("commit info from provider environment variables: %O", commitParamsObj); + const combined = _7.transform( + existingInfo, + (memo, value, key) => { + return memo[key] = _7.defaultTo(value || commitParamsObj[key], null); + } + ); + debug9("combined git and environment variables from provider"); + debug9(combined); + return combined; +} + +import cypress from "cypress"; +import Debug8 from "debug"; +import _8 from "lodash"; + +import { parseISO as parseISO2 } from "date-fns"; + +import { parseISO } from "date-fns"; +import { match as match4 } from "ts-pattern"; +var SpecAfterResult = class _SpecAfterResult { + /** + * Combine standalone attempts and screenshots into standard result + * @param specResult - spec:after results + * @param executionState - ccy execution state + * @returns unified results, including attempts and screenshot details + */ + static getSpecAfterStandard(specAfterResults, executionState) { + return { + error: specAfterResults.error, + hooks: null, + reporter: specAfterResults.reporter, + reporterStats: specAfterResults.reporterStats, + spec: _SpecAfterResult.getSpecStandard(specAfterResults.spec), + tests: _SpecAfterResult.getTestStandard( + specAfterResults, + executionState.getAttemptsData() + ), + video: specAfterResults.video, + stats: _SpecAfterResult.getStatsStandard(specAfterResults.stats), + screenshots: _SpecAfterResult.getScreenshotsStandard( + specAfterResults.screenshots, + executionState.getScreenshotsData() + ) + }; + } + static getAttemptError(err) { + if (!err) { + return null; + } + return { + name: err.name, + message: err.message, + stack: err.stack, + codeFrame: err.codeFrame + }; + } + static getAttemptVideoTimestamp(attemptStartedAtMs, specStartedAtMs) { + return Math.max(attemptStartedAtMs - specStartedAtMs, 0); + } + static getSpecStartedAt(stats) { + if ("startedAt" in stats) { + return parseISO(stats.startedAt); + } + if ("wallClockStartedAt" in stats) { + return parseISO(stats.wallClockStartedAt); + } + warn("Cannot determine spec start date from stats: %o", stats); + return new Date(); + } + static getDummyTestAttemptError(attemptState) { + return match4(attemptState).with("failed", () => ({ + name: "Error", + message: "[@krivega/cc] Could not get cypress attempt error details", + stack: "", + codeFrame: null + })).with("skipped", () => ({ + name: "Error", + message: "The test was skipped because of a hook failure", + stack: "", + codeFrame: null + })).otherwise(() => null); + } + static getTestAttemptStandard(mochaAttempt, cypressAttempt, specStartedAt) { + if (!mochaAttempt) { + const error2 = "error" in cypressAttempt ? cypressAttempt.error : null; + const duration = "wallClockDuration" in cypressAttempt ? cypressAttempt.wallClockDuration : null; + return { + state: cypressAttempt.state, + error: error2 ? error2 : _SpecAfterResult.getDummyTestAttemptError(cypressAttempt.state), + timings: "timings" in cypressAttempt ? cypressAttempt.timings : null, + wallClockStartedAt: "wallClockStartedAt" in cypressAttempt ? cypressAttempt.wallClockStartedAt : ( new Date()).toISOString(), + wallClockDuration: duration ? duration : 0, + failedFromHookId: "failedFromHookId" in cypressAttempt ? cypressAttempt.failedFromHookId : null, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : 0 + }; + } + return { + state: cypressAttempt.state, + error: "error" in cypressAttempt ? cypressAttempt.error : _SpecAfterResult.getAttemptError(mochaAttempt.err), + timings: "timings" in cypressAttempt ? cypressAttempt.timings : mochaAttempt.timings, + wallClockStartedAt: mochaAttempt.wallClockStartedAt ?? ( new Date()).toISOString(), + wallClockDuration: mochaAttempt.duration ?? -1, + failedFromHookId: "failedFromHookId" in cypressAttempt ? cypressAttempt.failedFromHookId : null, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : _SpecAfterResult.getAttemptVideoTimestamp( + parseISO(mochaAttempt.wallClockStartedAt).getTime(), + specStartedAt.getTime() + ) + }; + } + static getTestStandard(specAfterResults, attempts) { + const standardTestList = (specAfterResults.tests ?? []).map((test, i) => { + const mochaAttempts = attempts.filter( + (attempt) => attempt.fullTitle === test.title.join(" ") + ); + const standardAttempts = (test.attempts ?? []).map( + (cypressAttempt, j) => { + const mochaAttempt = mochaAttempts.find( + (ma) => ma.currentRetry === j + ); + return _SpecAfterResult.getTestAttemptStandard( + mochaAttempt ?? null, + cypressAttempt, + _SpecAfterResult.getSpecStartedAt(specAfterResults.stats) + ); + } + ); + return { + body: "body" in test ? test.body : mochaAttempts[0]?.body ?? "", + testId: "testId" in test ? test.testId : mochaAttempts[0]?.id ?? `r${i}`, + title: test.title, + displayError: test.displayError, + state: test.state, + attempts: standardAttempts + }; + }); + return standardTestList; + } + static getSpecStandard(spec) { + return { + name: spec.name, + relative: spec.relative, + absolute: spec.absolute, + fileExtension: spec.fileExtension, + baseName: "baseName" in spec ? spec.baseName : "", + fileName: "fileName" in spec ? spec.fileName : "", + relativeToCommonRoot: "relativeToCommonRoot" in spec ? spec.relativeToCommonRoot : "", + specFileExtension: "specFileExtension" in spec ? spec.specFileExtension : "", + specType: "specType" in spec ? spec.specType : "" + }; + } + static getStatsStandard(stats) { + const result = { + skipped: stats.skipped, + suites: stats.suites, + tests: stats.tests, + passes: stats.passes, + pending: stats.pending, + failures: stats.failures, + wallClockStartedAt: "wallClockStartedAt" in stats ? stats.wallClockStartedAt : stats.startedAt, + wallClockEndedAt: "wallClockEndedAt" in stats ? stats.wallClockEndedAt : stats.endedAt, + wallClockDuration: "wallClockDuration" in stats ? stats.wallClockDuration : stats.duration ?? 0 + }; + result.tests = result.passes + result.failures + result.pending + result.skipped; + return result; + } + static getScreenshotsStandard(specAfterScreenshots, screenshotEvents) { + if (!specAfterScreenshots.length) { + return []; + } + return specAfterScreenshots.map((specScreenshot) => { + const es = screenshotEvents.find( + (screenshot) => screenshot.path === specScreenshot.path + ); + if (!es) { + warn( + 'Could not find details for screenshot at path "%s", skipping...', + specScreenshot.path + ); + } + return { + height: specScreenshot.height, + width: specScreenshot.width, + name: specScreenshot.name ?? es?.name ?? null, + path: specScreenshot.path, + takenAt: specScreenshot.takenAt, + testAttemptIndex: "testAttemptIndex" in specScreenshot ? specScreenshot.testAttemptIndex : es?.testAttemptIndex ?? -1, + testId: "testId" in specScreenshot ? specScreenshot.testId : es?.testId ?? "unknown", + screenshotId: "screenshotId" in specScreenshot ? specScreenshot.screenshotId : getRandomString() + }; + }); + } +}; + +var ModuleAPIResults = class _ModuleAPIResults { + static getRunScreenshots(run3) { + if ("screenshots" in run3) { + return run3.screenshots; + } + return (run3.tests ?? []).flatMap( + (t) => t.attempts.flatMap((a) => a.screenshots) + ); + } + static getTests(run3, executionState) { + const tests = run3.tests ?? []; + return tests.map((test, i) => { + const mochaAttempts = executionState.getAttemptsData().filter((attempt) => attempt.fullTitle === test.title.join(" ")); + const testId = "testId" in test ? test.testId : mochaAttempts[0]?.id ?? `r${i}`; + const runScreenshotPaths = _ModuleAPIResults.getRunScreenshots(run3).map( + (i2) => i2.path + ); + const testScreenshots = executionState.getScreenshotsData().filter((s) => runScreenshotPaths.includes(s.path)).filter((s) => s.testId === testId); + const standardAttempts = (test.attempts ?? []).map( + (cypressAttempt, j) => { + const mochaAttempt = mochaAttempts.find( + (ma) => ma.currentRetry === j + ); + const attemptScreenshots = testScreenshots.filter( + (t) => t.testAttemptIndex === j + ); + return _ModuleAPIResults.getTestAttempt( + mochaAttempt ?? null, + cypressAttempt, + attemptScreenshots, + SpecAfterResult.getSpecStartedAt(run3.stats) + ); + } + ); + return { + body: "body" in test ? test.body : mochaAttempts[0]?.body ?? "", + testId, + title: test.title, + displayError: test.displayError, + state: test.state, + attempts: standardAttempts + }; + }); + } + /** + * Convert version-specific attempt to a standard test attempt + */ + static getTestAttempt(mochaAttempt, cypressAttempt, screenshots, specStartedAt) { + if (!mochaAttempt) { + return { + state: cypressAttempt.state, + error: "error" in cypressAttempt ? cypressAttempt.error : SpecAfterResult.getDummyTestAttemptError(cypressAttempt.state), + startedAt: "startedAt" in cypressAttempt ? cypressAttempt.startedAt : ( new Date()).toISOString(), + duration: "duration" in cypressAttempt ? cypressAttempt.duration : 0, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : 0, + screenshots: "screenshots" in cypressAttempt ? cypressAttempt.screenshots : screenshots + }; + } + return { + state: cypressAttempt.state, + error: "error" in cypressAttempt ? cypressAttempt.error : SpecAfterResult.getAttemptError(mochaAttempt.err), + startedAt: "startedAt" in cypressAttempt ? cypressAttempt.startedAt : mochaAttempt.wallClockStartedAt ?? ( new Date()).toISOString(), + duration: "duration" in cypressAttempt ? cypressAttempt.duration : mochaAttempt.duration ?? -1, + videoTimestamp: "videoTimestamp" in cypressAttempt ? cypressAttempt.videoTimestamp : SpecAfterResult.getAttemptVideoTimestamp( + parseISO2(mochaAttempt.wallClockStartedAt).getTime(), + specStartedAt.getTime() + ), + screenshots: "screenshots" in cypressAttempt ? cypressAttempt.screenshots : screenshots + }; + } + static getRun(run3, executionState) { + return { + ...run3, + tests: _ModuleAPIResults.getTests(run3, executionState), + spec: SpecAfterResult.getSpecStandard(run3.spec), + hooks: null, + shouldUploadVideo: "shouldUploadVideo" in run3 ? run3.shouldUploadVideo : true + }; + } + /** + * Converts different Cypress versions to standard form + */ + static getStandardResult(result, executionState) { + if (result.runs.length !== 1) { + throw new Error("Expected single run"); + } + const run3 = result.runs[0]; + const stats = SpecAfterResult.getStatsStandard(run3.stats); + return { + ...result, + runs: [_ModuleAPIResults.getRun(run3, executionState)], + totalSuites: 1, + totalDuration: stats.wallClockDuration, + totalTests: stats.tests, + totalFailed: stats.failures, + totalPassed: stats.passes, + totalPending: stats.pending, + totalSkipped: stats.skipped, + startedTestsAt: stats.wallClockStartedAt, + endedTestsAt: stats.wallClockEndedAt, + status: "finished" + }; + } + static isFailureResult(result) { + return "status" in result && result.status === "failed"; + } + static { + this.isSuccessResult = (result) => { + if ("status" in result) { + return result.status === "finished"; + } + return true; + }; + } + static getEmptyResult(config) { + return { + status: "finished", + totalDuration: 0, + totalSuites: 0, + totalPending: 0, + totalFailed: 0, + totalSkipped: 0, + totalPassed: 0, + totalTests: 0, + startedTestsAt: ( new Date()).toISOString(), + endedTestsAt: ( new Date()).toISOString(), + runs: [], + config + }; + } +}; + +var debug10 = Debug8("cc:cypress"); +function runBareCypress(params = {}) { + const p = { + ...params, + ciBuildId: void 0, + tag: void 0, + parallel: void 0, + record: false, + group: void 0, + spec: _8.flatten(params.spec).join(",") + }; + debug10("Running bare Cypress with params %o", p); + return cypress.run(p); +} +async function runSpecFile({ spec }, cypressRunOptions) { + const runAPIOptions = getCypressRunAPIParams(cypressRunOptions); + const options = { + ...runAPIOptions, + config: { + ...runAPIOptions.config, + trashAssetsBeforeRuns: false + }, + env: { + ...runAPIOptions.env, + cc_ws: getWSSPort(), + cc_marker: true + }, + spec + }; + debug10("running cypress with options %o", options); + const result = await cypress.run(options); + if (ModuleAPIResults.isFailureResult(result)) { + warn('Cypress runner failed with message: "%s"', result.message); + warn( + "The following spec files will be marked as failed: %s", + spec.split(",").map((i) => ` + - ${i}`).join("") + ); + } + debug10("cypress run result %o", result); + return result; +} +var runSpecFileSafe = (spec, cypressRunOptions) => safe( + runSpecFile, + (error2) => { + const message = `Cypress runnner crashed with an error: +${error2.message} +${error2.stack}}`; + debug10("cypress run exception %o", error2); + warn('Cypress runner crashed: "%s"', message); + warn( + "The following spec files will be marked as failed: %s", + spec.spec.split(",").map((i) => ` + - ${i}`).join("") + ); + return { + status: "failed", + failures: 1, + message + }; + }, + () => { + } +)(spec, cypressRunOptions); + +var isCc = () => !!process.env.CC_ENFORCE_IS_CC || getAPIBaseUrl() === "set baseURL"; + +import git from "@cypress/commit-info"; +var getGitInfo = async (projectRoot) => { + const commitInfo = await git.commitInfo(projectRoot); + return getCommitDefaults({ + branch: commitInfo.branch, + remoteOrigin: commitInfo.remote, + authorEmail: commitInfo.email, + authorName: commitInfo.author, + message: commitInfo.message, + sha: commitInfo.sha + }); +}; + +import Debug16 from "debug"; + +import Debug15 from "debug"; + +import fs2 from "fs/promises"; +import { join as join2 } from "path"; +var getCoverageFilePath = async (coverageFile = "./.nyc_output/out.json") => { + const path5 = join2(process.cwd(), coverageFile); + try { + await fs2.access(path5); + return { + path: path5, + error: false + }; + } catch (error2) { + return { + path: path5, + error: error2 + }; + } +}; + +import Debug14 from "debug"; + +import Debug13 from "debug"; + +import _9 from "lodash"; + +var emptyStats = { + totalDuration: 0, + totalSuites: 0, + totalPending: 0, + totalFailed: 0, + totalSkipped: 0, + totalPassed: 0, + totalTests: 0 +}; +var getDummyFailedTest = (start, error2) => ({ + title: ["Unknown"], + state: "failed", + body: "// This test is automatically generated due to execution failure", + displayError: error2, + attempts: [ + { + state: "failed", + startedAt: start, + duration: 0, + videoTimestamp: 0, + screenshots: [], + error: { + name: "CypressExecutionError", + message: error2, + stack: "", + codeFrame: null + } + } + ] +}); +function getFailedFakeInstanceResult(configState, { + specs, + error: error2 +}) { + const start = ( new Date()).toISOString(); + const end = ( new Date()).toISOString(); + return { + config: configState.getConfig() ?? {}, + status: "finished", + startedTestsAt: ( new Date()).toISOString(), + endedTestsAt: ( new Date()).toISOString(), + totalDuration: 0, + totalSuites: 1, + totalFailed: 1, + totalPassed: 0, + totalPending: 0, + totalSkipped: 0, + totalTests: 1, + browserName: "unknown", + browserVersion: "unknown", + browserPath: "unknown", + osName: "unknown", + osVersion: "unknown", + cypressVersion: "unknown", + runs: specs.map((s) => ({ + stats: { + suites: 1, + tests: 1, + passes: 0, + pending: 0, + skipped: 0, + failures: 1, + startedAt: start, + endedAt: end, + duration: 0 + }, + reporter: "spec", + reporterStats: { + suites: 1, + tests: 1, + passes: 0, + pending: 0, + failures: 1, + start, + end, + duration: 0 + }, + hooks: [], + error: error2, + video: null, + spec: { + name: s, + relative: s, + absolute: s, + relativeToCommonRoot: s, + baseName: s, + specType: "integration", + fileExtension: "js", + fileName: s, + specFileExtension: "js" + }, + tests: [getDummyFailedTest(start, error2)], + shouldUploadVideo: false, + skippedSpec: false + })) + }; +} + +var summarizeExecution = (input, config) => { + if (!input.length) { + return ModuleAPIResults.getEmptyResult(config); + } + const overall = input.reduce( + (acc, { + totalDuration, + totalFailed, + totalPassed, + totalPending, + totalSkipped, + totalTests, + totalSuites + }) => ({ + totalDuration: acc.totalDuration + totalDuration, + totalSuites: acc.totalSuites + totalSuites, + totalPending: acc.totalPending + totalPending, + totalFailed: acc.totalFailed + totalFailed, + totalSkipped: acc.totalSkipped + totalSkipped, + totalPassed: acc.totalPassed + totalPassed, + totalTests: acc.totalTests + totalTests + }), + emptyStats + ); + const firstResult = input[0]; + const startItems = input.map((i) => i.startedTestsAt).sort(); + const endItems = input.map((i) => i.endedTestsAt).sort(); + const runs = input.map((i) => i.runs).flat(); + return { + ...overall, + runs, + startedTestsAt: _9.first(startItems), + endedTestsAt: _9.last(endItems), + ..._9.pick( + firstResult, + "browserName", + "browserVersion", + "browserPath", + "osName", + "osVersion", + "cypressVersion", + "config" + ), + status: "finished" + }; +}; + +import getCommonPathPrefix from "common-path-prefix"; +import _10 from "lodash"; +import path2 from "path"; +import prettyMS from "pretty-ms"; +import { table } from "table"; +var failureIcon = red("\u2716"); +var successIcon = green("\u2714"); +var summaryTable = (r) => { + const overallSpecCount = r.runs.length; + const failedSpecsCount = _10.sum( + r.runs.filter((v) => v.stats.failures + v.stats.skipped > 0).map(() => 1) + ); + const hasFailed = failedSpecsCount > 0; + const verdict = hasFailed ? red(`${failedSpecsCount} of ${overallSpecCount} failed`) : overallSpecCount > 0 ? "All specs passed!" : "No specs executed"; + const specs = r.runs.map((r2) => r2.spec.relative); + const commonPath = getCommonPath(specs); + const data = r.runs.map((r2) => [ + r2.stats.failures + r2.stats.skipped > 0 ? failureIcon : successIcon, + stripCommonPath(r2.spec.relative, commonPath), + gray(prettyMS(r2.stats.duration ?? 0)), + white(r2.stats.tests ?? 0), + r2.stats.passes ? green(r2.stats.passes) : gray("-"), + r2.stats.failures ? red(r2.stats.failures) : gray("-"), + r2.stats.pending ? cyan(r2.stats.pending) : gray("-"), + r2.stats.skipped ? red(r2.stats.skipped) : gray("-") + ]); + return table( + [ + [ + "", + gray("Spec"), + "", + gray("Tests"), + gray("Passing"), + gray("Failing"), + gray("Pending"), + gray("Skipped") + ], + ...data, + [ + hasFailed ? failureIcon : successIcon, + verdict, + gray(prettyMS(r.totalDuration ?? 0)), + overallSpecCount > 0 ? white(r.totalTests ?? 0) : gray("-"), + r.totalPassed ? green(r.totalPassed) : gray("-"), + r.totalFailed ? red(r.totalFailed) : gray("-"), + r.totalPending ? cyan(r.totalPending) : gray("-"), + r.totalSkipped ? red(r.totalSkipped) : gray("-") + ] + ], + { + border, + columnDefault: { + width: 8 + }, + columns: [ + { alignment: "left", width: 2 }, + { alignment: "left", width: 30 }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" }, + { alignment: "right" } + ], + drawHorizontalLine: (lineIndex, rowCount) => { + return lineIndex === 1 || lineIndex === 0 || lineIndex === rowCount - 1 || lineIndex === rowCount; + }, + drawVerticalLine: (lineIndex, rowCount) => { + return lineIndex === 0 || rowCount === lineIndex; + } + } + ); +}; +var border = _10.mapValues( + { + topBody: `\u2500`, + topJoin: `\u252C`, + topLeft: ` \u250C`, + topRight: `\u2510`, + bottomBody: `\u2500`, + bottomJoin: `\u2534`, + bottomLeft: ` \u2514`, + bottomRight: `\u2518`, + bodyLeft: ` \u2502`, + bodyRight: `\u2502`, + bodyJoin: `\u2502`, + joinBody: `\u2500`, + joinLeft: ` \u251C`, + joinRight: `\u2524`, + joinJoin: `\u253C` + }, + (v) => gray(v) +); +function getCommonPath(specs) { + if (specs.length === 0) { + return ""; + } + if (specs.length === 1) { + return path2.dirname(specs[0]) + path2.sep; + } + return getCommonPathPrefix(specs); +} +function stripCommonPath(spec, commonPath) { + return spec.replace(commonPath, ""); +} + +import Debug12 from "debug"; + +import Debug10 from "debug"; + +import Debug9 from "debug"; +import fs3 from "fs"; +var readFile = fs3.promises.readFile; +var debug11 = Debug9("cc:upload"); +function uploadVideo(file2, url) { + return uploadFile(file2, url, "video/mp4"); +} +function uploadImage(file2, url) { + return uploadFile(file2, url, "image/png"); +} +function uploadJson(file2, url) { + return uploadFile(file2, url, "application/json"); +} +async function uploadFile(file2, url, type) { + debug11('uploading file "%s" to "%s"', file2, url); + const f = await readFile(file2); + await makeRequest({ + url, + method: "PUT", + data: f, + headers: { + "Content-Type": type, + "Content-Disposition": `inline` + } + }); +} + +var debug12 = Debug10("cc:artifacts"); +async function uploadArtifacts({ + executionState, + videoPath, + videoUploadUrl, + screenshots, + screenshotUploadUrls, + coverageFilePath, + coverageUploadUrl +}) { + debug12("uploading artifacts: %o", { + videoPath, + videoUploadUrl, + screenshots, + screenshotUploadUrls, + coverageFilePath, + coverageUploadUrl + }); + const totalUploads = (videoPath ? 1 : 0) + screenshots.length + (coverageUploadUrl ? 1 : 0); + if (totalUploads === 0) { + return; + } + if (videoUploadUrl && videoPath) { + await safe( + uploadVideo, + (e) => { + debug12("failed uploading video %s. Error: %o", videoPath, e); + executionState.addWarning( + `Failed uploading video ${videoPath}. +${dim(e)}` + ); + }, + () => debug12("success uploading", videoPath) + )(videoPath, videoUploadUrl); + } + if (screenshotUploadUrls && screenshotUploadUrls.length) { + await Promise.all( + screenshots.map((screenshot) => { + const url = screenshotUploadUrls.find( + (urls) => urls.screenshotId === screenshot.screenshotId + )?.uploadUrl; + if (!url) { + debug12( + "No upload url for screenshot %o, screenshotUploadUrls: %o", + screenshot, + screenshotUploadUrls + ); + executionState.addWarning( + `No upload URL for screenshot ${screenshot.path}` + ); + return Promise.resolve(); + } + return safe( + uploadImage, + (e) => { + debug12( + "failed uploading screenshot %s. Error: %o", + screenshot.path, + e + ); + executionState.addWarning( + `Failed uploading screenshot ${screenshot.path}. +${dim(e)}` + ); + }, + () => debug12("success uploading", screenshot.path) + )(screenshot.path, url); + }) + ); + } + if (coverageUploadUrl && coverageFilePath) { + await safe( + uploadJson, + (e) => { + debug12( + "failed uploading coverage file %s. Error: %o", + coverageFilePath, + e + ); + executionState.addWarning( + `Failed uploading coverage file ${coverageFilePath}. +${dim(e)}` + ); + }, + () => debug12("success uploading", coverageFilePath) + )(coverageFilePath, coverageUploadUrl); + } +} +var uploadStdoutSafe = safe( + updateInstanceStdout, + () => { + }, + () => { + } +); + +var state = { + cancellationReason: null +}; +var setCancellationReason = (reason) => { + if (state.cancellationReason) { + return; + } + state.cancellationReason = reason; + getPubSub().emit("run:cancelled", reason); +}; + +import Debug11 from "debug"; +var debug13 = Debug11("cc:results"); +var getInstanceResultPayload = (runResult, coverageFilePath) => { + debug13("generating instance result payload from %o", runResult); + return { + stats: StandardResultsToAPIResults.getStats(runResult.stats), + reporterStats: runResult.reporterStats, + exception: runResult.error ?? null, + video: !!runResult.video, + screenshots: StandardResultsToAPIResults.getAllScreenshots(runResult), + hasCoverage: !!coverageFilePath, + tests: (runResult.tests ?? []).map( + StandardResultsToAPIResults.getTestForResults + ) + }; +}; +var getInstanceTestsPayload = (runResult, config) => { + return { + config: { + ...config.getConfig(), + videoUploadOnPasses: config.getConfig()?.videoUploadOnPasses ?? true + }, + tests: (runResult.tests ?? []).map( + StandardResultsToAPIResults.getTestForSetTests + ), + hooks: runResult.hooks + }; +}; +var StandardResultsToAPIResults = class _StandardResultsToAPIResults { + static getTestAttempt(attempt) { + return { + state: attempt.state, + error: attempt.error, + wallClockStartedAt: attempt.startedAt, + wallClockDuration: attempt.duration, + videoTimestamp: attempt.videoTimestamp + }; + } + static getTestForResults(test, index) { + return { + displayError: test.displayError, + state: test.state, + attempts: (test.attempts ?? []).map( + _StandardResultsToAPIResults.getTestAttempt + ), + clientId: `r${index}` + }; + } + static getTestForSetTests(test, index) { + return { + body: "redacted", + title: test.title, + clientId: `r${index}` + }; + } + static getAllScreenshots(run3) { + return (run3.tests ?? []).flatMap( + (t, i) => t.attempts.flatMap( + (a, j) => a.screenshots.map((s) => ({ + ...s, + testId: `r${i}`, + testAttemptIndex: j, + screenshotId: getRandomString() + })) + ) + ); + } + static getStats(stats) { + return { + ...stats, + wallClockDuration: stats.duration, + wallClockStartedAt: stats.startedAt, + wallClockEndedAt: stats.endedAt + }; + } +}; + +var debug14 = Debug12("cc:results"); +async function getReportResultsTask(instanceId, executionState, configState, stdout2, coverageFilePath) { + const results = executionState.getInstanceResults(configState, instanceId); + const run3 = results.runs[0]; + if (!run3) { + throw new Error("No run found in Cypress results"); + } + const instanceResults = getInstanceResultPayload(run3, coverageFilePath); + const instanceTests = getInstanceTestsPayload(run3, configState); + const { videoUploadUrl, screenshotUploadUrls, coverageUploadUrl, cloud } = await reportResults(instanceId, instanceTests, instanceResults); + if (cloud?.shouldCancel) { + debug14("instance %s should cancel", instanceId); + setCancellationReason(cloud.shouldCancel); + } + debug14("instance %s artifact upload instructions %o", instanceId, { + videoUploadUrl, + screenshotUploadUrls, + coverageUploadUrl + }); + return Promise.all([ + uploadArtifacts({ + executionState, + videoUploadUrl, + videoPath: run3.video, + screenshotUploadUrls, + screenshots: instanceResults.screenshots, + coverageUploadUrl, + coverageFilePath + }), + uploadStdoutSafe(instanceId, getInitialOutput() + stdout2) + ]); +} +async function reportResults(instanceId, instanceTests, instanceResults) { + debug14("reporting instance %s results...", instanceId); + if (isCc()) { + return reportInstanceResultsMerged(instanceId, { + tests: instanceTests, + results: instanceResults + }); + } + await setInstanceTests(instanceId, instanceTests); + return updateInstanceResults(instanceId, instanceResults); +} + +var debug15 = Debug13("cc:reportTask"); +var reportTasks = []; +var createReportTask = (configState, executionState, instanceId) => { + const instance = executionState.getInstance(instanceId); + if (!instance) { + error("Cannot find execution state for instance %s", instanceId); + return; + } + if (instance.reportStartedAt) { + debug15("Report task already created for instance %s", instanceId); + return; + } + instance.reportStartedAt = new Date(); + debug15("Creating report task for instanceId %s", instanceId); + reportTasks.push( + getReportResultsTask( + instanceId, + executionState, + configState, + instance.output ?? "no output captured", + instance.coverageFilePath + ).catch(error) + ); +}; +var createReportTaskSpec = (configState, executionState, spec) => { + const i = executionState.getSpec(spec); + if (!i) { + error("Cannot find execution state for spec %s", spec); + return; + } + debug15("Creating report task for spec %s", spec); + return createReportTask(configState, executionState, i.instanceId); +}; + +var debug16 = Debug14("cc:runner"); +async function runTillDone(executionState, configState, { + runId, + groupId, + machineId, + platform: platform2, + specs: allSpecs +}, params) { + let hasMore = true; + while (hasMore) { + const newTasks = await runBatch(executionState, configState, { + runMeta: { + runId, + groupId, + machineId, + platform: platform2 + }, + allSpecs, + params + }); + if (!newTasks.length) { + debug16("No more tasks to run. Uploads queue: %d", reportTasks.length); + hasMore = false; + break; + } + newTasks.forEach( + (t) => createReportTask(configState, executionState, t.instanceId) + ); + } +} +async function runBatch(executionState, configState, { + runMeta, + params, + allSpecs +}) { + let batch = { + specs: [], + claimedInstances: 0, + totalInstances: 0 + }; + if (isCc()) { + debug16("Getting batched tasks: %d", params.batchSize); + batch = await createBatchedInstances({ + ...runMeta, + batchSize: params.batchSize + }); + debug16("Got batched tasks: %o", batch); + } else { + const response = await createInstance(runMeta); + if (response.spec !== null && response.instanceId !== null) { + batch.specs.push({ + spec: response.spec, + instanceId: response.instanceId + }); + } + batch.claimedInstances = response.claimedInstances; + batch.totalInstances = response.totalInstances; + } + if (batch.specs.length === 0) { + return []; + } + batch.specs.forEach((i) => executionState.initInstance(i)); + divider(); + info( + "Running: %s (%d/%d)", + batch.specs.map((s) => s.spec).join(", "), + batch.claimedInstances, + batch.totalInstances + ); + const batchedResult = await runSpecFileSafe( + { + spec: batch.specs.map((bs) => getSpecAbsolutePath(allSpecs, bs.spec)).join(",") + }, + params + ); + title("blue", "Reporting results and artifacts in background..."); + const output = getCapturedOutput(); + batch.specs.forEach((spec) => { + executionState.setInstanceOutput(spec.instanceId, output); + const singleSpecResult = getSingleSpecRunResult(spec.spec, batchedResult); + if (!singleSpecResult) { + return; + } + getPubSub().emit("run:result", { + specRelative: spec.spec, + instanceId: spec.instanceId, + runResult: singleSpecResult + }); + }); + resetCapture(); + return batch.specs; +} +function getSingleSpecRunResult(specRelative, batchedResult) { + if (!ModuleAPIResults.isSuccessResult(batchedResult)) { + return; + } + const run3 = batchedResult.runs.find((r) => r.spec.relative === specRelative); + if (!run3) { + return; + } + return { + ...batchedResult, + runs: [run3] + }; +} +function getSpecAbsolutePath(allSpecs, relative) { + const absolutePath = allSpecs.find((i) => i.relative === relative)?.absolute; + if (!absolutePath) { + warn( + 'Cannot find absolute path for spec. Spec: "%s", candidates: %o', + relative, + allSpecs + ); + throw new Error(`Cannot find absolute path for spec`); + } + return absolutePath; +} + +var cancellable = null; +function onRunCancelled(reason) { + warn( + `Run cancelled: %s. Waiting for uploads to complete and stopping execution...`, + reason + ); + cancellable?.cancel(); +} +async function runTillDoneOrCancelled(...args) { + return new Promise((_resolve, _reject) => { + cancellable = new BPromise((resolve, reject, onCancel) => { + if (!onCancel) { + _reject(new Error("BlueBird is misconfigured: onCancel is undefined")); + return; + } + onCancel(() => _resolve(void 0)); + runTillDone(...args).then( + () => { + resolve(); + _resolve(void 0); + }, + (error2) => { + reject(); + _reject(error2); + } + ); + }); + getPubSub().addListener("run:cancelled", onRunCancelled); + }).finally(() => { + getPubSub().removeListener("run:cancelled", onRunCancelled); + }); +} + +var debug17 = Debug15("cc:events"); +function handleScreenshotEvent(screenshot, executionState) { + const data = { + ...screenshot, + testId: executionState.getCurrentTestID(), + height: screenshot.dimensions.height, + width: screenshot.dimensions.width + }; + executionState.addScreenshotsData(data); +} +function handleTestBefore(testAttempt, executionState) { + const parsed = JSON.parse(testAttempt); + executionState.setCurrentTestID(parsed.id); +} +function handleTestAfter(testAttempt, executionState) { + const test = JSON.parse(testAttempt); + executionState.addAttemptsData(test); +} +async function handleSpecAfter({ + executionState, + configState, + spec, + results, + experimentalCoverageRecording = false +}) { + debug17("after:spec %s %o", spec.relative, results); + executionState.setSpecAfter( + spec.relative, + SpecAfterResult.getSpecAfterStandard(results, executionState) + ); + executionState.setSpecOutput(spec.relative, getCapturedOutput()); + const config = configState.getConfig(); + if (experimentalCoverageRecording) { + const config2 = configState.getConfig(); + const { path: path5, error: error2 } = await getCoverageFilePath( + config2?.env?.coverageFile + ); + if (!error2) { + executionState.setSpecCoverage(spec.relative, path5); + } else { + executionState.addWarning( + `Error reading coverage file "${path5}". Coverage recording will be skipped. +${dim( + error2 + )}` + ); + } + } + createReportTaskSpec(configState, executionState, spec.relative); +} + +var debug18 = Debug16("cc:events"); +function listenToEvents(configState, executionState, experimentalCoverageRecording = false) { + getPubSub().on( + "run:result", + ({ + instanceId, + runResult, + specRelative + }) => { + debug18("%s %s: %o", "run:result", instanceId, runResult); + executionState.setInstanceResult( + instanceId, + ModuleAPIResults.getStandardResult(runResult, executionState) + ); + } + ); + getPubSub().on("test:after:run", (payload) => { + debug18("%s %o", "test:after:run", payload); + handleTestAfter(payload, executionState); + }); + getPubSub().on("test:before:run", (payload) => { + debug18("%s %o", "test:before:run", payload); + handleTestBefore(payload, executionState); + }); + getPubSub().on( + "after:screenshot", + (screenshot) => { + debug18("%s %o", "after:screenshot", screenshot); + handleScreenshotEvent(screenshot, executionState); + } + ); + getPubSub().on( + "after:spec", + async ({ + spec, + results + }) => { + await handleSpecAfter({ + spec, + results, + executionState, + configState, + experimentalCoverageRecording + }); + } + ); +} + +import Debug17 from "debug"; +var debug19 = Debug17("cc:browser"); +function guessBrowser(browser, availableBrowsers = []) { + debug19( + "guessing browser from '%s', available browsers: %o", + browser, + availableBrowsers + ); + let result = availableBrowsers.find((b) => b.name === browser); + if (result) { + debug19("identified browser by name: %o", result); + return { + browserName: result.displayName, + browserVersion: result.version + }; + } + result = availableBrowsers.find((b) => b.path === browser); + if (result) { + debug19("identified browser by path: %o", result); + return { + browserName: result.displayName ?? result.name, + browserVersion: result.version + }; + } + warn("Unable to identify browser name and version"); + return { + browserName: "unknown", + browserVersion: "unknown" + }; +} + +import Debug18 from "debug"; +import getos from "getos"; +import { cpus, freemem, platform, release, totalmem } from "os"; +import { promisify } from "util"; +var debug20 = Debug18("cc:platform"); +var getOsVersion = async () => { + if (platform() === "linux") { + try { + const linuxOs = await promisify(getos)(); + if ("dist" in linuxOs && "release" in linuxOs) { + return [linuxOs.dist, linuxOs.release].join(" - "); + } else { + return release(); + } + } catch { + return release(); + } + } + return release(); +}; +var getPlatformInfo = async () => { + const osVersion = await getOsVersion(); + const result = { + osName: platform(), + osVersion, + osCpus: cpus(), + osMemory: { + free: freemem(), + total: totalmem() + } + }; + debug20("platform info: %o", result); + return result; +}; + +async function getPlatform({ + browser, + config +}) { + return { + ...await getPlatformInfo(), + ...guessBrowser(browser ?? "electron", config.resolved?.browsers) + }; +} + +async function shutdown() { + await stopWSS(); +} + +import commonPathPrefix from "common-path-prefix"; +import Debug19 from "debug"; +import globby from "globby"; +import _11 from "lodash"; +import os from "os"; +import path4 from "path"; + +import path3 from "path"; +function toArray(val) { + return val ? typeof val === "string" ? [val] : val : []; +} +function toPosix(file2, sep = path3.sep) { + return file2.split(sep).join(path3.posix.sep); +} + +var debug21 = Debug19("cc:specs"); +async function findSpecs({ + projectRoot, + testingType, + specPattern, + configSpecPattern, + excludeSpecPattern, + additionalIgnorePattern +}) { + configSpecPattern = toArray(configSpecPattern); + specPattern = toArray(specPattern); + excludeSpecPattern = toArray(excludeSpecPattern) || []; + additionalIgnorePattern = toArray(additionalIgnorePattern) || []; + debug21("exploring spec files for execution %O", { + testingType, + projectRoot, + specPattern, + configSpecPattern, + excludeSpecPattern, + additionalIgnorePattern + }); + if (!specPattern || !configSpecPattern) { + throw Error("Could not find glob patterns for exploring specs"); + } + let specAbsolutePaths = await getFilesByGlob(projectRoot, specPattern, { + absolute: true, + ignore: [...excludeSpecPattern, ...additionalIgnorePattern] + }); + if (!_11.isEqual(specPattern, configSpecPattern)) { + const defaultSpecAbsolutePaths = await getFilesByGlob( + projectRoot, + configSpecPattern, + { + absolute: true, + ignore: [...excludeSpecPattern, ...additionalIgnorePattern] + } + ); + specAbsolutePaths = _11.intersection( + specAbsolutePaths, + defaultSpecAbsolutePaths + ); + } + return matchedSpecs({ + projectRoot, + testingType, + specAbsolutePaths, + specPattern + }); +} +async function getFilesByGlob(projectRoot, glob, globOptions) { + const workingDirectoryPrefix = path4.join(projectRoot, path4.sep); + const globs = [].concat(glob).map( + (globPattern) => globPattern.startsWith("./") ? globPattern.replace("./", "") : globPattern + ).map((globPattern) => { + if (globPattern.startsWith(workingDirectoryPrefix)) { + return globPattern.replace(workingDirectoryPrefix, ""); + } + return globPattern; + }); + if (os.platform() === "win32") { + debug21("updating glob patterns to POSIX"); + for (const i in globs) { + const cur = globs[i]; + if (!cur) + throw new Error("undefined glob received"); + globs[i] = toPosix(cur); + } + } + try { + debug21("globbing pattern(s): %o", globs); + debug21("within directory: %s", projectRoot); + return matchGlobs(globs, { + onlyFiles: true, + absolute: true, + cwd: projectRoot, + ...globOptions, + ignore: (globOptions?.ignore ?? []).concat("**/node_modules/**") + }); + } catch (e) { + debug21("error in getFilesByGlob %o", e); + return []; + } +} +var matchGlobs = async (globs, globbyOptions) => { + return await globby(globs, globbyOptions); +}; +function matchedSpecs({ + projectRoot, + testingType, + specAbsolutePaths +}) { + debug21("found specs %o", specAbsolutePaths); + let commonRoot = ""; + if (specAbsolutePaths.length === 1) { + commonRoot = path4.dirname(specAbsolutePaths[0]); + } else { + commonRoot = commonPathPrefix(specAbsolutePaths); + } + return specAbsolutePaths.map( + (absolute) => transformSpec({ + projectRoot, + absolute, + testingType, + commonRoot, + platform: os.platform(), + sep: path4.sep + }) + ); +} +function transformSpec({ + projectRoot, + absolute, + testingType, + commonRoot, + platform: platform2, + sep +}) { + if (platform2 === "win32") { + absolute = toPosix(absolute, sep); + projectRoot = toPosix(projectRoot, sep); + } + const relative = path4.relative(projectRoot, absolute); + const parsedFile = path4.parse(absolute); + const fileExtension = path4.extname(absolute); + const specFileExtension = [".spec", ".test", "-spec", "-test", ".cy"].map((ext) => ext + fileExtension).find((ext) => absolute.endsWith(ext)) || fileExtension; + const parts = absolute.split(projectRoot); + let name = parts[parts.length - 1] || ""; + if (name.startsWith("/")) { + name = name.slice(1); + } + const LEADING_SLASH = /^\/|/g; + const relativeToCommonRoot = absolute.replace(commonRoot, "").replace(LEADING_SLASH, ""); + return { + fileExtension, + baseName: parsedFile.base, + fileName: parsedFile.base.replace(specFileExtension, ""), + specFileExtension, + relativeToCommonRoot, + specType: testingType === "component" ? "component" : "integration", + name, + relative, + absolute + }; +} + +var getSpecFiles = async ({ + config, + params +}) => { + const specPattern = getSpecPattern(config.specPattern, params.spec); + const specs = await findSpecs({ + projectRoot: params.project ?? config.projectRoot, + testingType: params.testingType, + specPattern, + configSpecPattern: config.specPattern, + excludeSpecPattern: config.excludeSpecPattern, + additionalIgnorePattern: config.additionalIgnorePattern + }); + if (specs.length === 0) { + warn( + "Found no spec files. Was looking for spec files that match both configSpecPattern and specPattern relative to projectRoot. Configuration: %O", + { + projectRoot: config.projectRoot, + specPattern, + configSpecPattern: config.specPattern, + excludeSpecPattern: [ + config.excludeSpecPattern, + config.additionalIgnorePattern + ].flat(2), + testingType: params.testingType + } + ); + } + return { specs, specPattern }; +}; +function getSpecPattern(configPattern, explicit) { + return explicit || configPattern; +} + +var ConfigState = class { + constructor() { + this._config = void 0; + } + setConfig(c) { + this._config = c; + } + getConfig() { + return this._config; + } +}; + +var SpecAfterToModuleAPIMapper = class _SpecAfterToModuleAPIMapper { + static getTestAttempt(attempt, screenshots) { + return { + ...attempt, + duration: attempt.wallClockDuration, + startedAt: attempt.wallClockStartedAt, + screenshots + }; + } + static getTest(t, screenshots) { + return { + ...t, + attempts: t.attempts.map( + (a, i) => _SpecAfterToModuleAPIMapper.getTestAttempt( + a, + screenshots.filter( + (s) => s.testId === t.testId && s.testAttemptIndex === i + ) + ) + ) + }; + } + static convert(specAfterResult, configState) { + const stats = { + duration: specAfterResult.stats.wallClockDuration, + endedAt: specAfterResult.stats.wallClockEndedAt, + startedAt: specAfterResult.stats.wallClockStartedAt, + failures: specAfterResult.stats.failures ?? 0, + passes: specAfterResult.stats.passes ?? 0, + pending: specAfterResult.stats.pending ?? 0, + skipped: specAfterResult.stats.skipped ?? 0, + suites: specAfterResult.stats.suites ?? 0, + tests: specAfterResult.stats.tests ?? 0 + }; + return { + status: "finished", + config: configState.getConfig(), + totalDuration: stats.duration, + totalSuites: stats.suites, + totalTests: stats.tests, + totalFailed: stats.failures, + totalPassed: stats.passes, + totalPending: stats.pending, + totalSkipped: stats.skipped ?? 0, + startedTestsAt: stats.startedAt, + endedTestsAt: stats.endedAt, + runs: [ + { + stats, + reporter: specAfterResult.reporter, + reporterStats: specAfterResult.reporterStats ?? null, + spec: specAfterResult.spec, + error: specAfterResult.error, + video: specAfterResult.video, + shouldUploadVideo: true, + hooks: specAfterResult.hooks, + tests: (specAfterResult.tests ?? []).map( + (t) => _SpecAfterToModuleAPIMapper.getTest(t, specAfterResult.screenshots) + ) + } + ] + }; + } + static backfillException(result) { + return { + ...result, + runs: result.runs.map(_SpecAfterToModuleAPIMapper.backfillExceptionRun) + }; + } + static backfillExceptionRun(run3) { + if (!run3.error) { + return run3; + } + return { + ...run3, + tests: [getFakeTestFromException(run3.error, run3.stats)] + }; + } +}; +function getFakeTestFromException(error2, stats) { + return { + title: ["Unknown"], + body: "", + displayError: error2.split("\n")[0], + state: "failed", + attempts: [ + { + state: "failed", + duration: 0, + error: { + name: "Error", + message: error2.split("\n")[0], + stack: error2, + codeFrame: null + }, + screenshots: [], + startedAt: stats.startedAt, + videoTimestamp: 0 + } + ] + }; +} + +import Debug20 from "debug"; +var debug22 = Debug20("cc:state"); +var ExecutionState = class { + constructor() { + this.warnings = new Set(); + this.attemptsData = []; + this.screenshotsData = []; + this.state = {}; + } + getWarnings() { + return this.warnings; + } + addWarning(warning) { + this.warnings.add(warning); + } + getResults(configState) { + return Object.values(this.state).map( + (i) => this.getInstanceResults(configState, i.instanceId) + ); + } + getInstance(instanceId) { + return this.state[instanceId]; + } + getSpec(spec) { + return Object.values(this.state).find((i) => i.spec === spec); + } + initInstance({ + instanceId, + spec + }) { + debug22('Init execution state for "%s"', spec); + this.state[instanceId] = { + instanceId, + spec, + createdAt: new Date() + }; + } + setSpecBefore(spec) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + i.specBefore = new Date(); + } + setSpecCoverage(spec, coverageFilePath) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + debug22("Experimental: coverageFilePath was set"); + i.coverageFilePath = coverageFilePath; + } + setSpecAfter(spec, results) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + i.specAfter = new Date(); + i.specAfterResults = results; + } + setSpecOutput(spec, output) { + const i = this.getSpec(spec); + if (!i) { + warn('Cannot find execution state for spec "%s"', spec); + return; + } + this.setInstanceOutput(i.instanceId, output); + } + setInstanceOutput(instanceId, output) { + const i = this.state[instanceId]; + if (!i) { + warn('Cannot find execution state for instance "%s"', instanceId); + return; + } + if (i.output) { + debug22('Instance "%s" already has output', instanceId); + return; + } + i.output = output; + } + setInstanceResult(instanceId, runResults) { + const i = this.state[instanceId]; + if (!i) { + warn('Cannot find execution state for instance "%s"', instanceId); + return; + } + i.runResults = { + ...runResults, + status: "finished" + }; + i.runResultsReportedAt = new Date(); + } + getInstanceResults(configState, instanceId) { + const i = this.getInstance(instanceId); + if (!i) { + error('Cannot find execution state for instance "%s"', instanceId); + return getFailedFakeInstanceResult(configState, { + specs: ["unknown"], + error: `[cc] Error while processing cypress results for instance ${instanceId}. See the console output for details.` + }); + } + if (i.specAfterResults) { + debug22('Using spec:after results for %s "%s"', instanceId, i.spec); + return SpecAfterToModuleAPIMapper.backfillException( + SpecAfterToModuleAPIMapper.convert(i.specAfterResults, configState) + ); + } + if (i.runResults) { + debug22('Using runResults for %s "%s"', instanceId, i.spec); + return SpecAfterToModuleAPIMapper.backfillException(i.runResults); + } + debug22('No results detected for "%s"', i.spec); + return getFailedFakeInstanceResult(configState, { + specs: [i.spec], + error: `No results detected for the spec file. That usually happens because of cypress crash. See the console output for details.` + }); + } + addAttemptsData(attemptDetails) { + this.attemptsData.push(attemptDetails); + } + getAttemptsData() { + return this.attemptsData; + } + addScreenshotsData(screenshotsData) { + this.screenshotsData.push(screenshotsData); + } + getScreenshotsData() { + return this.screenshotsData; + } + setCurrentTestID(testID) { + this.currentTestID = testID; + } + getCurrentTestID() { + return this.currentTestID; + } +}; + +import chalk2 from "chalk"; +import plur from "plur"; +function printWarnings2(executionState) { + const warnings = Array.from(executionState.getWarnings()); + if (warnings.length > 0) { + warn( + `${warnings.length} ${plur( + "Warning", + warnings.length + )} encountered during the execution: +${warnings.map( + (w, i) => ` +${chalk2.yellow(`[${i + 1}/${warnings.length}]`)} ${w}` + ).join("\n")}` + ); + } +} + +var debug23 = Debug21("cc:run"); +async function run(params = {}) { + const executionState = new ExecutionState(); + const configState = new ConfigState(); + activateDebug(params.cloudDebug); + debug23("run params %o", params); + params = preprocessParams(params); + debug23("params after preprocess %o", params); + if (isOffline(params)) { + info(`Skipping cloud orchestration because --record is set to false`); + return runBareCypress(params); + } + const validatedParams = await validateParams(params); + setAPIBaseUrl(validatedParams.cloudServiceUrl); + if (!isCc()) { + console.log(getLegalNotice()); + } + const { + recordKey, + projectId, + group, + parallel, + ciBuildId, + tag, + testingType, + batchSize, + autoCancelAfterFailures, + experimentalCoverageRecording + } = validatedParams; + const config = await getMergedConfig(validatedParams); + configState.setConfig(config?.resolved); + const { specs, specPattern } = await getSpecFiles({ + config, + params: validatedParams + }); + if (specs.length === 0) { + return; + } + const platform2 = await getPlatform({ + config, + browser: validatedParams.browser + }); + info(`@krivega/cc version: ${dim(_ccVersion)}`); + info(`Cypress version: ${dim(_cypressVersion)}`); + info("Discovered %d spec files", specs.length); + info( + `Tags: ${tag.length > 0 ? tag.join(",") : false}; Group: ${group ?? false}; Parallel: ${parallel ?? false}; Batch Size: ${batchSize}` + ); + info("Connecting to cloud orchestration service..."); + const run3 = await createRun({ + ci: getCI(ciBuildId), + specs: specs.map((spec) => spec.relative), + commit: await getGitInfo(config.projectRoot), + group, + platform: platform2, + parallel: parallel ?? false, + ciBuildId, + projectId, + recordKey, + specPattern: [specPattern].flat(2), + tags: tag, + testingType, + batchSize, + autoCancelAfterFailures, + coverageEnabled: experimentalCoverageRecording + }); + setRunId(run3.runId); + info("\u{1F3A5} Run URL:", bold(run3.runUrl)); + cutInitialOutput(); + await startWSS(); + listenToEvents( + configState, + executionState, + config.experimentalCoverageRecording + ); + await runTillDoneOrCancelled( + executionState, + configState, + { + runId: run3.runId, + groupId: run3.groupId, + machineId: run3.machineId, + platform: platform2, + specs + }, + validatedParams + ); + divider(); + await Promise.allSettled(reportTasks); + const _summary = summarizeExecution( + executionState.getResults(configState), + config + ); + title("white", "Cloud Run Finished"); + console.log(summaryTable(_summary)); + printWarnings2(executionState); + info("\n\u{1F3C1} Recorded Run:", bold(run3.runUrl)); + await shutdown(); + spacer(); + return { + ..._summary, + runUrl: run3.runUrl + }; +} + +function run2(params) { + return run(params); +} +export { + run2 as run +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..e5c94c8 --- /dev/null +++ b/package.json @@ -0,0 +1,137 @@ +{ + "name": "@krivega/cc", + "version": "4.0.0", + "main": "./index.js", + "author": { + "name": "Krivega Dmitriy", + "email": "mr.krivega@gmail.com", + "url": "https://krivega.com" + }, + "license": "GPL-3.0-or-later", + "engines": { + "node": ">=14.7.0" + }, + "scripts": { + "rm": "rimraf out", + "lint": "TIMING=1 eslint \"**/*.ts*\"", + "test": "jest", + "test:watch": "jest --watch", + "release": "release-it", + "release:npm": "npm run build && ./publish.js", + "dev": "tsup-node --watch", + "build": "tsup-node --dts" + }, + "devDependencies": { + "@release-it/conventional-changelog": "^7.0.0", + "@swc/core": "^1.3.86", + "@swc/jest": "^0.2.29", + "@types/bluebird": "^3.5.38", + "@types/debug": "^4.1.7", + "@types/getos": "^3.0.1", + "@types/is-ci": "^3.0.0", + "@types/jest": "^29.2.4", + "@types/lodash": "^4.14.191", + "@types/ws": "^8.5.4", + "cypress": "^13.2.0", + "esbuild": "^0.16.5", + "eslint": "^7.32.0", + "eslint-config-custom": "latest", + "jest": "^29.3.1", + "nock": "^13.2.9", + "release-it": "^16.1.5", + "rimraf": "^3.0.2", + "tsconfig": "*", + "tsup": "^7.0.0", + "typescript": "^4.7.4" + }, + "dependencies": { + "@cypress/commit-info": "^2.2.0", + "axios": "^1.2.0", + "axios-retry": "^3.4.0", + "bluebird": "^3.7.2", + "chalk": "^4.1.2", + "commander": "^10.0.0", + "common-path-prefix": "^3.0.0", + "cy2": "^3.4.2", + "date-fns": "^2.30.0", + "debug": "^4.3.4", + "execa": "^5.1.1", + "fast-safe-stringify": "^2.1.1", + "getos": "^3.2.1", + "globby": "^11.1.0", + "is-absolute": "^1.0.0", + "lil-http-terminator": "^1.2.3", + "lodash": "^4.17.21", + "nanoid": "^3.3.4", + "plur": "^4.0.0", + "pretty-ms": "^7.0.1", + "source-map-support": "^0.5.21", + "table": "^6.8.1", + "tmp-promise": "^3.0.3", + "ts-pattern": "^4.3.0", + "ws": "^8.13.0" + }, + "bin": "./bin/cli.js", + "files": [ + "*" + ], + "tsup": { + "entry": [ + "./index.ts", + "./bin/*.ts", + "./plugin/*.ts", + "./support/*.ts" + ], + "external": [ + "cypress" + ], + "format": [ + "cjs", + "esm" + ], + "splitting": false, + "shims": true, + "clean": true, + "sourcemap": "inline", + "platform": "node", + "target": "esnext" + }, + "exports": { + ".": { + "import": "./index.mjs", + "require": "./index.js", + "types": "./index.d.ts" + }, + "./plugin": { + "import": "./plugin/index.js", + "require": "./plugin/index.js", + "types": "./plugin/index.d.ts" + }, + "./support": { + "import": "./support/index.js", + "require": "./support/index.js", + "types": "./support/index.d.ts" + }, + "./package.json": "./package.json" + }, + "release-it": { + "github": { + "release": true, + "releaseName": "v${version}" + }, + "npm": { + "publish": false + }, + "git": { + "requireCleanWorkingDir": false, + "commitMessage": "chore: release v${version}", + "tagName": "v${version}" + }, + "plugins": { + "@release-it/conventional-changelog": { + "preset": "angular", + "infile": "../../CHANGELOG.md" + } + } + } +} diff --git a/plugin/index.d.ts b/plugin/index.d.ts new file mode 100644 index 0000000..e30df49 --- /dev/null +++ b/plugin/index.d.ts @@ -0,0 +1,3 @@ +declare function cloudPlugin(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions): Promise; + +export { cloudPlugin, cloudPlugin as default }; diff --git a/plugin/index.js b/plugin/index.js new file mode 100644 index 0000000..ada2045 --- /dev/null +++ b/plugin/index.js @@ -0,0 +1,144 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +var plugin_exports = {}; +__export(plugin_exports, { + cloudPlugin: () => cloudPlugin, + default: () => plugin_default +}); +module.exports = __toCommonJS(plugin_exports); +var import_debug = __toESM(require("debug")); +var import_fs = __toESM(require("fs")); +var import_util2 = require("util"); +var import_ws = __toESM(require("ws")); + +var import_chalk = __toESM(require("chalk")); +var import_util = __toESM(require("util")); +var log = (...args) => console.log(import_util.default.format(...args)); +var format = import_util.default.format; +var withWarning = (msg) => import_chalk.default.bgYellow.black(" WARNING ") + " " + msg; +var warn = (...args) => log(withWarning(import_util.default.format(...args))); +var cyan = import_chalk.default.cyan; +var blue = import_chalk.default.blueBright; +var red = import_chalk.default.red; +var green = import_chalk.default.greenBright; +var gray = import_chalk.default.gray; +var white = import_chalk.default.white; +var magenta = import_chalk.default.magenta; +var bold = import_chalk.default.bold; +var dim = import_chalk.default.dim; + +var Event = ((Event2) => { + Event2["RUN_CANCELLED"] = "run:cancelled"; + Event2["RUN_RESULT"] = "run:result"; + Event2["TEST_AFTER_RUN"] = "test:after:run"; + Event2["TEST_BEFORE_RUN"] = "test:before:run"; + Event2["AFTER_SCREENSHOT"] = "after:screenshot"; + Event2["AFTER_SPEC"] = "after:spec"; + return Event2; +})(Event || {}); +var allEvents = Object.values(Event); + +var import_events = __toESM(require("events")); + +var _debug = (0, import_debug.default)("cc:plugin"); +async function cloudPlugin(on, config) { + function debug(...args) { + if (config.env.cc_debug_enabled) { + _debug((0, import_util2.format)(...args)); + } + } + if (!config.env.cc_marker) { + warn( + `Cc plugin is not installed properly - missing required variables in ${dim( + "cypress.env" + )}. Please refer to: self` + ); + } + let ws = null; + function sendToWS(message) { + if (ws) { + ws.send(JSON.stringify(message)); + } + } + if (config.env.cc_ws) { + debug("setting port to %s", config.env.cc_ws); + await new Promise((resolve) => { + ws = new import_ws.default(`ws://localhost:${config.env.cc_ws}`); + ws.on("open", () => { + resolve(null); + }); + }); + } + on("after:screenshot", (details) => { + sendToWS({ + type: "after:screenshot", + payload: details + }); + }); + on("task", { + "cc:test:after:run": (test) => { + debug("cc:test:after:run task received %o", test); + sendToWS({ + type: "test:after:run", + payload: test + }); + return null; + }, + "cc:test:before:run": (test) => { + debug("cc:test:before:run task received %o", test); + sendToWS({ + type: "test:before:run", + payload: test + }); + return null; + } + }); + on("before:spec", (spec) => { + debug("before:spec task received %o", spec); + sendToWS({ type: "before:spec", payload: { spec } }); + }); + on("after:spec", (spec, results) => { + debug("after:spec task received %o", spec); + sendToWS({ + type: "after:spec", + payload: { + spec, + results + } + }); + }); + debug("cc plugin loaded"); + if (config.env.cc_temp_file) { + debug("dumping config to '%s'", config.env.cc_temp_file); + import_fs.default.writeFileSync(config.env.cc_temp_file, JSON.stringify(config)); + debug("config is availabe at '%s'", config.env.cc_temp_file); + } + return config; +} +var plugin_default = cloudPlugin; +0 && (module.exports = { + cloudPlugin +}); diff --git a/plugin/index.mjs b/plugin/index.mjs new file mode 100644 index 0000000..94d4964 --- /dev/null +++ b/plugin/index.mjs @@ -0,0 +1,113 @@ +import Debug from "debug"; +import fs from "fs"; +import { format as format2 } from "util"; +import WebSocket from "ws"; + +import chalk from "chalk"; +import util from "util"; +var log = (...args) => console.log(util.format(...args)); +var format = util.format; +var withWarning = (msg) => chalk.bgYellow.black(" WARNING ") + " " + msg; +var warn = (...args) => log(withWarning(util.format(...args))); +var cyan = chalk.cyan; +var blue = chalk.blueBright; +var red = chalk.red; +var green = chalk.greenBright; +var gray = chalk.gray; +var white = chalk.white; +var magenta = chalk.magenta; +var bold = chalk.bold; +var dim = chalk.dim; + +var Event = ((Event2) => { + Event2["RUN_CANCELLED"] = "run:cancelled"; + Event2["RUN_RESULT"] = "run:result"; + Event2["TEST_AFTER_RUN"] = "test:after:run"; + Event2["TEST_BEFORE_RUN"] = "test:before:run"; + Event2["AFTER_SCREENSHOT"] = "after:screenshot"; + Event2["AFTER_SPEC"] = "after:spec"; + return Event2; +})(Event || {}); +var allEvents = Object.values(Event); + + +var _debug = Debug("cc:plugin"); +async function cloudPlugin(on, config) { + function debug(...args) { + if (config.env.cc_debug_enabled) { + _debug(format2(...args)); + } + } + if (!config.env.cc_marker) { + warn( + `Cc plugin is not installed properly - missing required variables in ${dim( + "cypress.env" + )}. Please refer to: self` + ); + } + let ws = null; + function sendToWS(message) { + if (ws) { + ws.send(JSON.stringify(message)); + } + } + if (config.env.cc_ws) { + debug("setting port to %s", config.env.cc_ws); + await new Promise((resolve) => { + ws = new WebSocket(`ws://localhost:${config.env.cc_ws}`); + ws.on("open", () => { + resolve(null); + }); + }); + } + on("after:screenshot", (details) => { + sendToWS({ + type: "after:screenshot", + payload: details + }); + }); + on("task", { + "cc:test:after:run": (test) => { + debug("cc:test:after:run task received %o", test); + sendToWS({ + type: "test:after:run", + payload: test + }); + return null; + }, + "cc:test:before:run": (test) => { + debug("cc:test:before:run task received %o", test); + sendToWS({ + type: "test:before:run", + payload: test + }); + return null; + } + }); + on("before:spec", (spec) => { + debug("before:spec task received %o", spec); + sendToWS({ type: "before:spec", payload: { spec } }); + }); + on("after:spec", (spec, results) => { + debug("after:spec task received %o", spec); + sendToWS({ + type: "after:spec", + payload: { + spec, + results + } + }); + }); + debug("cc plugin loaded"); + if (config.env.cc_temp_file) { + debug("dumping config to '%s'", config.env.cc_temp_file); + fs.writeFileSync(config.env.cc_temp_file, JSON.stringify(config)); + debug("config is availabe at '%s'", config.env.cc_temp_file); + } + return config; +} +var plugin_default = cloudPlugin; +export { + cloudPlugin, + plugin_default as default +}; diff --git a/support/index.d.ts b/support/index.d.ts new file mode 100644 index 0000000..c9247d4 --- /dev/null +++ b/support/index.d.ts @@ -0,0 +1,2 @@ + +export { } diff --git a/support/index.js b/support/index.js new file mode 100644 index 0000000..c68bc4e --- /dev/null +++ b/support/index.js @@ -0,0 +1,103 @@ +"use strict"; +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); + +// support/index.ts +var import_fast_safe_stringify = __toESM(require("fast-safe-stringify")); +var afterReportedTests = []; +var beforeReportedTests = []; +function pickTestData(test) { + return { + async: !!test.async, + body: test.body, + duration: test.duration, + // @ts-ignore + err: test.err, + // @ts-ignore + final: !!test.final, + // @ts-ignore + hooks: test.hooks, + // @ts-ignore + id: test.id, + // @ts-ignore + invocationDetails: test.invocationDetails, + // @ts-ignore + order: test.order, + pending: test.pending, + retries: test.retries(), + state: test.state, + sync: !!test.sync, + timedOut: test.timedOut, + // @ts-ignore + timings: test.timings, + // @ts-ignore + type: test.type, + // @ts-ignore + wallClockStartedAt: test.wallClockStartedAt, + title: test.title, + // @ts-ignore + currentRetry: test._currentRetry, + fullTitle: test.fullTitle() + }; +} +function sendTestAfterMetrics(test) { + if (test.pending || !test.state) { + return; + } + afterReportedTests.push(getTestHash(test)); + cy.task(`cc:test:after:run`, (0, import_fast_safe_stringify.default)(pickTestData(test)), { + log: false + }); +} +function sendTestBeforeMetrics(test) { + beforeReportedTests.push(getTestHash(test)); + cy.task(`cc:test:before:run`, (0, import_fast_safe_stringify.default)(pickTestData(test)), { + log: false + }); +} +function getTestHash(test) { + return `${test.fullTitle()}-${test._currentRetry}`; +} +function handleAfter(test) { + if (!afterReportedTests.includes(getTestHash(test))) { + sendTestAfterMetrics(test); + } +} +function handleBefore(test) { + if (!beforeReportedTests.includes(getTestHash(test))) { + sendTestBeforeMetrics(test); + } +} +afterEach(() => { + const currentTest = cy.state("ctx").currentTest; + if (currentTest) { + handleAfter(currentTest); + } +}); +beforeEach(() => { + const currentTest = cy.state("ctx").currentTest; + if (currentTest) { + handleBefore(currentTest); + } +}); +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3VwcG9ydC9pbmRleC50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLy8vIDxyZWZlcmVuY2UgdHlwZXM9XCJDeXByZXNzXCIgLz5cbmltcG9ydCBzYWZlU3RyaW5naWZ5IGZyb20gXCJmYXN0LXNhZmUtc3RyaW5naWZ5XCI7XG5cbmNvbnN0IGFmdGVyUmVwb3J0ZWRUZXN0czogc3RyaW5nW10gPSBbXTtcbmNvbnN0IGJlZm9yZVJlcG9ydGVkVGVzdHM6IHN0cmluZ1tdID0gW107XG5cbmZ1bmN0aW9uIHBpY2tUZXN0RGF0YSh0ZXN0OiBNb2NoYS5SdW5uYWJsZSkge1xuICByZXR1cm4ge1xuICAgIGFzeW5jOiAhIXRlc3QuYXN5bmMsXG4gICAgYm9keTogdGVzdC5ib2R5LFxuICAgIGR1cmF0aW9uOiB0ZXN0LmR1cmF0aW9uLFxuICAgIC8vIEB0cy1pZ25vcmVcbiAgICBlcnI6IHRlc3QuZXJyLFxuICAgIC8vIEB0cy1pZ25vcmVcbiAgICBmaW5hbDogISF0ZXN0LmZpbmFsLFxuICAgIC8vIEB0cy1pZ25vcmVcbiAgICBob29rczogdGVzdC5ob29rcyxcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgaWQ6IHRlc3QuaWQsXG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIGludm9jYXRpb25EZXRhaWxzOiB0ZXN0Lmludm9jYXRpb25EZXRhaWxzLFxuICAgIC8vIEB0cy1pZ25vcmVcbiAgICBvcmRlcjogdGVzdC5vcmRlcixcbiAgICBwZW5kaW5nOiB0ZXN0LnBlbmRpbmcsXG4gICAgcmV0cmllczogdGVzdC5yZXRyaWVzKCksXG4gICAgc3RhdGU6IHRlc3Quc3RhdGUsXG4gICAgc3luYzogISF0ZXN0LnN5bmMsXG4gICAgdGltZWRPdXQ6IHRlc3QudGltZWRPdXQsXG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHRpbWluZ3M6IHRlc3QudGltaW5ncyxcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgdHlwZTogdGVzdC50eXBlLFxuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB3YWxsQ2xvY2tTdGFydGVkQXQ6IHRlc3Qud2FsbENsb2NrU3RhcnRlZEF0LFxuICAgIHRpdGxlOiB0ZXN0LnRpdGxlLFxuICAgIC8vIEB0cy1pZ25vcmVcbiAgICBjdXJyZW50UmV0cnk6IHRlc3QuX2N1cnJlbnRSZXRyeSxcbiAgICBmdWxsVGl0bGU6IHRlc3QuZnVsbFRpdGxlKCksXG4gIH07XG59XG5mdW5jdGlvbiBzZW5kVGVzdEFmdGVyTWV0cmljcyh0ZXN0OiBNb2NoYS5SdW5uYWJsZSkge1xuICBpZiAodGVzdC5wZW5kaW5nIHx8ICF0ZXN0LnN0YXRlKSB7XG4gICAgLy8gVGVzdCBpcyBlaXRoZXIgc2tpcHBlZCBvciBoYXNuJ3QgcmFuIHlldC5cbiAgICAvLyBXZSBuZWVkIHRvIGNoZWNrIHRoaXMgYmVjYXVzZSBhbGwgdGVzdHMgd2lsbCBzaG93IHVwIGluIHRoZSBob29rIGV2ZXJ5IHRpbWVcbiAgICByZXR1cm47XG4gIH1cbiAgLy8gQHRzLWlnbm9yZVxuICBhZnRlclJlcG9ydGVkVGVzdHMucHVzaChnZXRUZXN0SGFzaCh0ZXN0KSk7XG4gIGN5LnRhc2soYGN1cnJlbnRzOnRlc3Q6YWZ0ZXI6cnVuYCwgc2FmZVN0cmluZ2lmeShwaWNrVGVzdERhdGEodGVzdCkpLCB7XG4gICAgbG9nOiBmYWxzZSxcbiAgfSk7XG59XG5cbmZ1bmN0aW9uIHNlbmRUZXN0QmVmb3JlTWV0cmljcyh0ZXN0OiBNb2NoYS5SdW5uYWJsZSkge1xuICBiZWZvcmVSZXBvcnRlZFRlc3RzLnB1c2goZ2V0VGVzdEhhc2godGVzdCkpO1xuICBjeS50YXNrKGBjdXJyZW50czp0ZXN0OmJlZm9yZTpydW5gLCBzYWZlU3RyaW5naWZ5KHBpY2tUZXN0RGF0YSh0ZXN0KSksIHtcbiAgICBsb2c6IGZhbHNlLFxuICB9KTtcbn1cblxuZnVuY3Rpb24gZ2V0VGVzdEhhc2godGVzdDogTW9jaGEuUnVubmFibGUpIHtcbiAgLy8gQHRzLWlnbm9yZVxuICByZXR1cm4gYCR7dGVzdC5mdWxsVGl0bGUoKX0tJHt0ZXN0Ll9jdXJyZW50UmV0cnl9YDtcbn1cblxuZnVuY3Rpb24gaGFuZGxlQWZ0ZXIodGVzdDogTW9jaGEuUnVubmFibGUpIHtcbiAgaWYgKCFhZnRlclJlcG9ydGVkVGVzdHMuaW5jbHVkZXMoZ2V0VGVzdEhhc2godGVzdCkpKSB7XG4gICAgc2VuZFRlc3RBZnRlck1ldHJpY3ModGVzdCk7XG4gIH1cbn1cbmZ1bmN0aW9uIGhhbmRsZUJlZm9yZSh0ZXN0OiBNb2NoYS5SdW5uYWJsZSkge1xuICBpZiAoIWJlZm9yZVJlcG9ydGVkVGVzdHMuaW5jbHVkZXMoZ2V0VGVzdEhhc2godGVzdCkpKSB7XG4gICAgc2VuZFRlc3RCZWZvcmVNZXRyaWNzKHRlc3QpO1xuICB9XG59XG5cbmFmdGVyRWFjaCgoKSA9PiB7XG4gIC8vIEB0cy1pZ25vcmVcbiAgY29uc3QgY3VycmVudFRlc3QgPSBjeS5zdGF0ZShcImN0eFwiKS5jdXJyZW50VGVzdDtcbiAgaWYgKGN1cnJlbnRUZXN0KSB7XG4gICAgaGFuZGxlQWZ0ZXIoY3VycmVudFRlc3QpO1xuICB9XG59KTtcblxuYmVmb3JlRWFjaCgoKSA9PiB7XG4gIC8vIEB0cy1pZ25vcmVcbiAgY29uc3QgY3VycmVudFRlc3QgPSBjeS5zdGF0ZShcImN0eFwiKS5jdXJyZW50VGVzdDtcblxuICBpZiAoY3VycmVudFRlc3QpIHtcbiAgICBoYW5kbGVCZWZvcmUoY3VycmVudFRlc3QpO1xuICB9XG59KTtcbiJdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFDQSxpQ0FBMEI7QUFFMUIsSUFBTSxxQkFBK0IsQ0FBQztBQUN0QyxJQUFNLHNCQUFnQyxDQUFDO0FBRXZDLFNBQVMsYUFBYSxNQUFzQjtBQUMxQyxTQUFPO0FBQUEsSUFDTCxPQUFPLENBQUMsQ0FBQyxLQUFLO0FBQUEsSUFDZCxNQUFNLEtBQUs7QUFBQSxJQUNYLFVBQVUsS0FBSztBQUFBO0FBQUEsSUFFZixLQUFLLEtBQUs7QUFBQTtBQUFBLElBRVYsT0FBTyxDQUFDLENBQUMsS0FBSztBQUFBO0FBQUEsSUFFZCxPQUFPLEtBQUs7QUFBQTtBQUFBLElBRVosSUFBSSxLQUFLO0FBQUE7QUFBQSxJQUVULG1CQUFtQixLQUFLO0FBQUE7QUFBQSxJQUV4QixPQUFPLEtBQUs7QUFBQSxJQUNaLFNBQVMsS0FBSztBQUFBLElBQ2QsU0FBUyxLQUFLLFFBQVE7QUFBQSxJQUN0QixPQUFPLEtBQUs7QUFBQSxJQUNaLE1BQU0sQ0FBQyxDQUFDLEtBQUs7QUFBQSxJQUNiLFVBQVUsS0FBSztBQUFBO0FBQUEsSUFFZixTQUFTLEtBQUs7QUFBQTtBQUFBLElBRWQsTUFBTSxLQUFLO0FBQUE7QUFBQSxJQUVYLG9CQUFvQixLQUFLO0FBQUEsSUFDekIsT0FBTyxLQUFLO0FBQUE7QUFBQSxJQUVaLGNBQWMsS0FBSztBQUFBLElBQ25CLFdBQVcsS0FBSyxVQUFVO0FBQUEsRUFDNUI7QUFDRjtBQUNBLFNBQVMscUJBQXFCLE1BQXNCO0FBQ2xELE1BQUksS0FBSyxXQUFXLENBQUMsS0FBSyxPQUFPO0FBRy9CO0FBQUEsRUFDRjtBQUVBLHFCQUFtQixLQUFLLFlBQVksSUFBSSxDQUFDO0FBQ3pDLEtBQUcsS0FBSywrQkFBMkIsMkJBQUFBLFNBQWMsYUFBYSxJQUFJLENBQUMsR0FBRztBQUFBLElBQ3BFLEtBQUs7QUFBQSxFQUNQLENBQUM7QUFDSDtBQUVBLFNBQVMsc0JBQXNCLE1BQXNCO0FBQ25ELHNCQUFvQixLQUFLLFlBQVksSUFBSSxDQUFDO0FBQzFDLEtBQUcsS0FBSyxnQ0FBNEIsMkJBQUFBLFNBQWMsYUFBYSxJQUFJLENBQUMsR0FBRztBQUFBLElBQ3JFLEtBQUs7QUFBQSxFQUNQLENBQUM7QUFDSDtBQUVBLFNBQVMsWUFBWSxNQUFzQjtBQUV6QyxTQUFPLEdBQUcsS0FBSyxVQUFVLEtBQUssS0FBSztBQUNyQztBQUVBLFNBQVMsWUFBWSxNQUFzQjtBQUN6QyxNQUFJLENBQUMsbUJBQW1CLFNBQVMsWUFBWSxJQUFJLENBQUMsR0FBRztBQUNuRCx5QkFBcUIsSUFBSTtBQUFBLEVBQzNCO0FBQ0Y7QUFDQSxTQUFTLGFBQWEsTUFBc0I7QUFDMUMsTUFBSSxDQUFDLG9CQUFvQixTQUFTLFlBQVksSUFBSSxDQUFDLEdBQUc7QUFDcEQsMEJBQXNCLElBQUk7QUFBQSxFQUM1QjtBQUNGO0FBRUEsVUFBVSxNQUFNO0FBRWQsUUFBTSxjQUFjLEdBQUcsTUFBTSxLQUFLLEVBQUU7QUFDcEMsTUFBSSxhQUFhO0FBQ2YsZ0JBQVksV0FBVztBQUFBLEVBQ3pCO0FBQ0YsQ0FBQztBQUVELFdBQVcsTUFBTTtBQUVmLFFBQU0sY0FBYyxHQUFHLE1BQU0sS0FBSyxFQUFFO0FBRXBDLE1BQUksYUFBYTtBQUNmLGlCQUFhLFdBQVc7QUFBQSxFQUMxQjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbInNhZmVTdHJpbmdpZnkiXQp9Cg== diff --git a/support/index.mjs b/support/index.mjs new file mode 100644 index 0000000..04457b2 --- /dev/null +++ b/support/index.mjs @@ -0,0 +1,79 @@ +// support/index.ts +import safeStringify from "fast-safe-stringify"; +var afterReportedTests = []; +var beforeReportedTests = []; +function pickTestData(test) { + return { + async: !!test.async, + body: test.body, + duration: test.duration, + // @ts-ignore + err: test.err, + // @ts-ignore + final: !!test.final, + // @ts-ignore + hooks: test.hooks, + // @ts-ignore + id: test.id, + // @ts-ignore + invocationDetails: test.invocationDetails, + // @ts-ignore + order: test.order, + pending: test.pending, + retries: test.retries(), + state: test.state, + sync: !!test.sync, + timedOut: test.timedOut, + // @ts-ignore + timings: test.timings, + // @ts-ignore + type: test.type, + // @ts-ignore + wallClockStartedAt: test.wallClockStartedAt, + title: test.title, + // @ts-ignore + currentRetry: test._currentRetry, + fullTitle: test.fullTitle() + }; +} +function sendTestAfterMetrics(test) { + if (test.pending || !test.state) { + return; + } + afterReportedTests.push(getTestHash(test)); + cy.task(`cc:test:after:run`, safeStringify(pickTestData(test)), { + log: false + }); +} +function sendTestBeforeMetrics(test) { + beforeReportedTests.push(getTestHash(test)); + cy.task(`cc:test:before:run`, safeStringify(pickTestData(test)), { + log: false + }); +} +function getTestHash(test) { + return `${test.fullTitle()}-${test._currentRetry}`; +} +function handleAfter(test) { + if (!afterReportedTests.includes(getTestHash(test))) { + sendTestAfterMetrics(test); + } +} +function handleBefore(test) { + if (!beforeReportedTests.includes(getTestHash(test))) { + sendTestBeforeMetrics(test); + } +} +afterEach(() => { + const currentTest = cy.state("ctx").currentTest; + if (currentTest) { + handleAfter(currentTest); + } +}); +beforeEach(() => { + const currentTest = cy.state("ctx").currentTest; + if (currentTest) { + handleBefore(currentTest); + } +}); +//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vc3VwcG9ydC9pbmRleC50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLy8vIDxyZWZlcmVuY2UgdHlwZXM9XCJDeXByZXNzXCIgLz5cbmltcG9ydCBzYWZlU3RyaW5naWZ5IGZyb20gXCJmYXN0LXNhZmUtc3RyaW5naWZ5XCI7XG5cbmNvbnN0IGFmdGVyUmVwb3J0ZWRUZXN0czogc3RyaW5nW10gPSBbXTtcbmNvbnN0IGJlZm9yZVJlcG9ydGVkVGVzdHM6IHN0cmluZ1tdID0gW107XG5cbmZ1bmN0aW9uIHBpY2tUZXN0RGF0YSh0ZXN0OiBNb2NoYS5SdW5uYWJsZSkge1xuICByZXR1cm4ge1xuICAgIGFzeW5jOiAhIXRlc3QuYXN5bmMsXG4gICAgYm9keTogdGVzdC5ib2R5LFxuICAgIGR1cmF0aW9uOiB0ZXN0LmR1cmF0aW9uLFxuICAgIC8vIEB0cy1pZ25vcmVcbiAgICBlcnI6IHRlc3QuZXJyLFxuICAgIC8vIEB0cy1pZ25vcmVcbiAgICBmaW5hbDogISF0ZXN0LmZpbmFsLFxuICAgIC8vIEB0cy1pZ25vcmVcbiAgICBob29rczogdGVzdC5ob29rcyxcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgaWQ6IHRlc3QuaWQsXG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIGludm9jYXRpb25EZXRhaWxzOiB0ZXN0Lmludm9jYXRpb25EZXRhaWxzLFxuICAgIC8vIEB0cy1pZ25vcmVcbiAgICBvcmRlcjogdGVzdC5vcmRlcixcbiAgICBwZW5kaW5nOiB0ZXN0LnBlbmRpbmcsXG4gICAgcmV0cmllczogdGVzdC5yZXRyaWVzKCksXG4gICAgc3RhdGU6IHRlc3Quc3RhdGUsXG4gICAgc3luYzogISF0ZXN0LnN5bmMsXG4gICAgdGltZWRPdXQ6IHRlc3QudGltZWRPdXQsXG4gICAgLy8gQHRzLWlnbm9yZVxuICAgIHRpbWluZ3M6IHRlc3QudGltaW5ncyxcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgdHlwZTogdGVzdC50eXBlLFxuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB3YWxsQ2xvY2tTdGFydGVkQXQ6IHRlc3Qud2FsbENsb2NrU3RhcnRlZEF0LFxuICAgIHRpdGxlOiB0ZXN0LnRpdGxlLFxuICAgIC8vIEB0cy1pZ25vcmVcbiAgICBjdXJyZW50UmV0cnk6IHRlc3QuX2N1cnJlbnRSZXRyeSxcbiAgICBmdWxsVGl0bGU6IHRlc3QuZnVsbFRpdGxlKCksXG4gIH07XG59XG5mdW5jdGlvbiBzZW5kVGVzdEFmdGVyTWV0cmljcyh0ZXN0OiBNb2NoYS5SdW5uYWJsZSkge1xuICBpZiAodGVzdC5wZW5kaW5nIHx8ICF0ZXN0LnN0YXRlKSB7XG4gICAgLy8gVGVzdCBpcyBlaXRoZXIgc2tpcHBlZCBvciBoYXNuJ3QgcmFuIHlldC5cbiAgICAvLyBXZSBuZWVkIHRvIGNoZWNrIHRoaXMgYmVjYXVzZSBhbGwgdGVzdHMgd2lsbCBzaG93IHVwIGluIHRoZSBob29rIGV2ZXJ5IHRpbWVcbiAgICByZXR1cm47XG4gIH1cbiAgLy8gQHRzLWlnbm9yZVxuICBhZnRlclJlcG9ydGVkVGVzdHMucHVzaChnZXRUZXN0SGFzaCh0ZXN0KSk7XG4gIGN5LnRhc2soYGN1cnJlbnRzOnRlc3Q6YWZ0ZXI6cnVuYCwgc2FmZVN0cmluZ2lmeShwaWNrVGVzdERhdGEodGVzdCkpLCB7XG4gICAgbG9nOiBmYWxzZSxcbiAgfSk7XG59XG5cbmZ1bmN0aW9uIHNlbmRUZXN0QmVmb3JlTWV0cmljcyh0ZXN0OiBNb2NoYS5SdW5uYWJsZSkge1xuICBiZWZvcmVSZXBvcnRlZFRlc3RzLnB1c2goZ2V0VGVzdEhhc2godGVzdCkpO1xuICBjeS50YXNrKGBjdXJyZW50czp0ZXN0OmJlZm9yZTpydW5gLCBzYWZlU3RyaW5naWZ5KHBpY2tUZXN0RGF0YSh0ZXN0KSksIHtcbiAgICBsb2c6IGZhbHNlLFxuICB9KTtcbn1cblxuZnVuY3Rpb24gZ2V0VGVzdEhhc2godGVzdDogTW9jaGEuUnVubmFibGUpIHtcbiAgLy8gQHRzLWlnbm9yZVxuICByZXR1cm4gYCR7dGVzdC5mdWxsVGl0bGUoKX0tJHt0ZXN0Ll9jdXJyZW50UmV0cnl9YDtcbn1cblxuZnVuY3Rpb24gaGFuZGxlQWZ0ZXIodGVzdDogTW9jaGEuUnVubmFibGUpIHtcbiAgaWYgKCFhZnRlclJlcG9ydGVkVGVzdHMuaW5jbHVkZXMoZ2V0VGVzdEhhc2godGVzdCkpKSB7XG4gICAgc2VuZFRlc3RBZnRlck1ldHJpY3ModGVzdCk7XG4gIH1cbn1cbmZ1bmN0aW9uIGhhbmRsZUJlZm9yZSh0ZXN0OiBNb2NoYS5SdW5uYWJsZSkge1xuICBpZiAoIWJlZm9yZVJlcG9ydGVkVGVzdHMuaW5jbHVkZXMoZ2V0VGVzdEhhc2godGVzdCkpKSB7XG4gICAgc2VuZFRlc3RCZWZvcmVNZXRyaWNzKHRlc3QpO1xuICB9XG59XG5cbmFmdGVyRWFjaCgoKSA9PiB7XG4gIC8vIEB0cy1pZ25vcmVcbiAgY29uc3QgY3VycmVudFRlc3QgPSBjeS5zdGF0ZShcImN0eFwiKS5jdXJyZW50VGVzdDtcbiAgaWYgKGN1cnJlbnRUZXN0KSB7XG4gICAgaGFuZGxlQWZ0ZXIoY3VycmVudFRlc3QpO1xuICB9XG59KTtcblxuYmVmb3JlRWFjaCgoKSA9PiB7XG4gIC8vIEB0cy1pZ25vcmVcbiAgY29uc3QgY3VycmVudFRlc3QgPSBjeS5zdGF0ZShcImN0eFwiKS5jdXJyZW50VGVzdDtcblxuICBpZiAoY3VycmVudFRlc3QpIHtcbiAgICBoYW5kbGVCZWZvcmUoY3VycmVudFRlc3QpO1xuICB9XG59KTtcbiJdLAogICJtYXBwaW5ncyI6ICI7QUFDQSxPQUFPLG1CQUFtQjtBQUUxQixJQUFNLHFCQUErQixDQUFDO0FBQ3RDLElBQU0sc0JBQWdDLENBQUM7QUFFdkMsU0FBUyxhQUFhLE1BQXNCO0FBQzFDLFNBQU87QUFBQSxJQUNMLE9BQU8sQ0FBQyxDQUFDLEtBQUs7QUFBQSxJQUNkLE1BQU0sS0FBSztBQUFBLElBQ1gsVUFBVSxLQUFLO0FBQUE7QUFBQSxJQUVmLEtBQUssS0FBSztBQUFBO0FBQUEsSUFFVixPQUFPLENBQUMsQ0FBQyxLQUFLO0FBQUE7QUFBQSxJQUVkLE9BQU8sS0FBSztBQUFBO0FBQUEsSUFFWixJQUFJLEtBQUs7QUFBQTtBQUFBLElBRVQsbUJBQW1CLEtBQUs7QUFBQTtBQUFBLElBRXhCLE9BQU8sS0FBSztBQUFBLElBQ1osU0FBUyxLQUFLO0FBQUEsSUFDZCxTQUFTLEtBQUssUUFBUTtBQUFBLElBQ3RCLE9BQU8sS0FBSztBQUFBLElBQ1osTUFBTSxDQUFDLENBQUMsS0FBSztBQUFBLElBQ2IsVUFBVSxLQUFLO0FBQUE7QUFBQSxJQUVmLFNBQVMsS0FBSztBQUFBO0FBQUEsSUFFZCxNQUFNLEtBQUs7QUFBQTtBQUFBLElBRVgsb0JBQW9CLEtBQUs7QUFBQSxJQUN6QixPQUFPLEtBQUs7QUFBQTtBQUFBLElBRVosY0FBYyxLQUFLO0FBQUEsSUFDbkIsV0FBVyxLQUFLLFVBQVU7QUFBQSxFQUM1QjtBQUNGO0FBQ0EsU0FBUyxxQkFBcUIsTUFBc0I7QUFDbEQsTUFBSSxLQUFLLFdBQVcsQ0FBQyxLQUFLLE9BQU87QUFHL0I7QUFBQSxFQUNGO0FBRUEscUJBQW1CLEtBQUssWUFBWSxJQUFJLENBQUM7QUFDekMsS0FBRyxLQUFLLDJCQUEyQixjQUFjLGFBQWEsSUFBSSxDQUFDLEdBQUc7QUFBQSxJQUNwRSxLQUFLO0FBQUEsRUFDUCxDQUFDO0FBQ0g7QUFFQSxTQUFTLHNCQUFzQixNQUFzQjtBQUNuRCxzQkFBb0IsS0FBSyxZQUFZLElBQUksQ0FBQztBQUMxQyxLQUFHLEtBQUssNEJBQTRCLGNBQWMsYUFBYSxJQUFJLENBQUMsR0FBRztBQUFBLElBQ3JFLEtBQUs7QUFBQSxFQUNQLENBQUM7QUFDSDtBQUVBLFNBQVMsWUFBWSxNQUFzQjtBQUV6QyxTQUFPLEdBQUcsS0FBSyxVQUFVLEtBQUssS0FBSztBQUNyQztBQUVBLFNBQVMsWUFBWSxNQUFzQjtBQUN6QyxNQUFJLENBQUMsbUJBQW1CLFNBQVMsWUFBWSxJQUFJLENBQUMsR0FBRztBQUNuRCx5QkFBcUIsSUFBSTtBQUFBLEVBQzNCO0FBQ0Y7QUFDQSxTQUFTLGFBQWEsTUFBc0I7QUFDMUMsTUFBSSxDQUFDLG9CQUFvQixTQUFTLFlBQVksSUFBSSxDQUFDLEdBQUc7QUFDcEQsMEJBQXNCLElBQUk7QUFBQSxFQUM1QjtBQUNGO0FBRUEsVUFBVSxNQUFNO0FBRWQsUUFBTSxjQUFjLEdBQUcsTUFBTSxLQUFLLEVBQUU7QUFDcEMsTUFBSSxhQUFhO0FBQ2YsZ0JBQVksV0FBVztBQUFBLEVBQ3pCO0FBQ0YsQ0FBQztBQUVELFdBQVcsTUFBTTtBQUVmLFFBQU0sY0FBYyxHQUFHLE1BQU0sS0FBSyxFQUFFO0FBRXBDLE1BQUksYUFBYTtBQUNmLGlCQUFhLFdBQVc7QUFBQSxFQUMxQjtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==