Files
cycloud/index.mjs
2024-11-20 14:40:39 +01:00

3234 lines
90 KiB
JavaScript

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("@bart/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 @bart/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 '@bart/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 <record-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": `@bart/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: "[@bart/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(`@bart/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
};