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 ' - 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 };