You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

cypress

Package Overview
Dependencies
Maintainers
2
Versions
277
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cypress - npm Package Compare versions

Comparing version
15.11.0
to
15.12.0
dist/cli-CMmiXnYu.js

Sorry, the diff of this file is too big to display

+104
'use strict';
var xvfb = require('./xvfb-D-nbLwPu.js');
var tmp = require('tmp');
var fs = require('fs-extra');
var cli$1 = require('./cli-CMmiXnYu.js');
/**
* Opens Cypress GUI
* @see https://on.cypress.io/module-api#cypress-open
*/
function open(options = {}) {
options = xvfb.util.normalizeModuleOptions(options);
return cli$1.openModule.start(options);
}
/**
* Runs Cypress tests in the current project
* @see https://on.cypress.io/module-api#cypress-run
*/
function run() {
return xvfb.__awaiter(this, arguments, void 0, function* (options = {}) {
if (!cli$1.runModule.isValidProject(options.project)) {
throw new Error(`Invalid project path parameter: ${options.project}`);
}
options = xvfb.util.normalizeModuleOptions(options);
tmp.setGracefulCleanup();
const outputPath = tmp.fileSync().name;
options.outputPath = outputPath;
const failedTests = yield cli$1.runModule.start(options);
const output = yield fs.readJson(outputPath, { throws: false });
if (!output) {
return {
status: 'failed',
failures: failedTests,
message: 'Could not find Cypress test run results',
};
}
return output;
});
}
const cli = {
/**
* Parses CLI arguments into an object that you can pass to "cypress.run"
* @example
* const cypress = require('cypress')
* const cli = ['cypress', 'run', '--browser', 'firefox']
* const options = await cypress.cli.parseRunArguments(cli)
* // options is {browser: 'firefox'}
* await cypress.run(options)
* @see https://on.cypress.io/module-api
*/
parseRunArguments(args) {
return cli$1.cliModule.parseRunCommand(args);
},
};
/**
* Provides automatic code completion for configuration in many popular code editors.
* While it's not strictly necessary for Cypress to parse your configuration, we
* recommend wrapping your config object with `defineConfig()`
* @example
* module.exports = defineConfig({
* viewportWith: 400
* })
*
* @see ../types/cypress-npm-api.d.ts
* @param {Cypress.ConfigOptions} config
* @returns {Cypress.ConfigOptions} the configuration passed in parameter
*/
function defineConfig(config) {
return config;
}
/**
* Provides automatic code completion for Component Frameworks Definitions.
* While it's not strictly necessary for Cypress to parse your configuration, we
* recommend wrapping your Component Framework Definition object with `defineComponentFramework()`
* @example
* module.exports = defineComponentFramework({
* type: 'cypress-ct-solid-js'
* // ...
* })
*
* @see ../types/cypress-npm-api.d.ts
* @param {Cypress.ThirdPartyComponentFrameworkDefinition} config
* @returns {Cypress.ThirdPartyComponentFrameworkDefinition} the configuration passed in parameter
*/
function defineComponentFramework(config) {
return config;
}
var cypress = /*#__PURE__*/Object.freeze({
__proto__: null,
cli: cli,
defineComponentFramework: defineComponentFramework,
defineConfig: defineConfig,
open: open,
run: run
});
exports.cli = cli;
exports.cypress = cypress;
exports.defineComponentFramework = defineComponentFramework;
exports.defineConfig = defineConfig;
exports.open = open;
exports.run = run;
'use strict';
var xvfb = require('./xvfb-D-nbLwPu.js');
var _ = require('lodash');
var os = require('os');
var cp = require('child_process');
var path = require('path');
var Debug = require('debug');
var chalk = require('chalk');
var listr2 = require('listr2');
var commonTags = require('common-tags');
var Bluebird = require('bluebird');
var logSymbols = require('log-symbols');
var figures = require('figures');
var cliCursor = require('cli-cursor');
var dayjs = require('dayjs');
var readline = require('readline');
var process$1 = require('process');
var require$$0 = require('stream');
var require$$1 = require('string_decoder');
var require$$1$1 = require('node:string_decoder');
// Vendored from @cypress/listr-verbose-renderer
const formattedLog = (options, output) => {
const timestamp = dayjs().format(options.dateFormat);
// eslint-disable-next-line no-console
console.log(`${chalk.dim(`[${timestamp}]`)} ${output}`);
};
const renderHelper = (task, event, options) => {
const log = formattedLog.bind(undefined, options);
if (event.type === 'STATE') {
const message = task.isPending() ? 'started' : task.state;
log(`${task.title} [${message}]`);
if (task.isSkipped() && task.output) {
log(`${figures.arrowRight} ${task.output}`);
}
}
else if (event.type === 'TITLE') {
log(`${task.title} [title changed]`);
}
};
const render = (tasks, options) => {
for (const task of tasks) {
task.subscribe((event) => {
if (event.type === 'SUBTASKS') {
render(task.subtasks, options);
return;
}
renderHelper(task, event, options);
}, (err) => {
// eslint-disable-next-line no-console
console.log(err);
});
}
};
class VerboseRenderer {
constructor(tasks, options) {
this._tasks = tasks;
this._options = Object.assign({
dateFormat: 'HH:mm:ss',
}, options);
}
static get nonTTY() {
return true;
}
render() {
cliCursor.hide();
render(this._tasks, this._options);
}
end() {
cliCursor.show();
}
}
const debug$1 = Debug('cypress:cli');
const verifyTestRunnerTimeoutMs = () => {
const verifyTimeout = +((xvfb.util === null || xvfb.util === void 0 ? void 0 : xvfb.util.getEnv('CYPRESS_VERIFY_TIMEOUT')) || 'NaN');
if (_.isNumber(verifyTimeout) && !_.isNaN(verifyTimeout)) {
return verifyTimeout;
}
return 30000;
};
const checkExecutable = (binaryDir) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
const executable = xvfb.stateModule.getPathToExecutable(binaryDir);
debug$1('checking if executable exists', executable);
try {
const isExecutable = yield xvfb.util.isExecutableAsync(executable);
debug$1('Binary is executable? :', isExecutable);
if (!isExecutable) {
return xvfb.throwFormErrorText(xvfb.errors.binaryNotExecutable(executable))();
}
}
catch (err) {
if (err.code === 'ENOENT') {
if (xvfb.util.isCi()) {
return xvfb.throwFormErrorText(xvfb.errors.notInstalledCI(executable))();
}
return xvfb.throwFormErrorText(xvfb.errors.missingApp(binaryDir))(commonTags.stripIndent `
Cypress executable not found at: ${chalk.cyan(executable)}
`);
}
throw err;
}
});
const runSmokeTest = (binaryDir, options) => {
let executable = xvfb.stateModule.getPathToExecutable(binaryDir);
const needsXvfb = xvfb.xvfb.isNeeded();
debug$1('needs Xvfb?', needsXvfb);
/**
* Spawn Cypress running smoke test to check if all operating system
* dependencies are good.
*/
const spawn = (linuxWithDisplayEnv) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
const random = _.random(0, 1000);
const args = ['--smoke-test', `--ping=${random}`];
if (needsSandbox()) {
// electron requires --no-sandbox to run as root
debug$1('disabling Electron sandbox');
args.unshift('--no-sandbox');
}
if (options.dev) {
executable = 'node';
const startScriptPath = xvfb.relativeToRepoRoot('scripts/start.js');
if (!startScriptPath) {
throw new Error(`Cypress start script (scripts/start.js) not found in parent directory of ${__dirname}`);
}
args.unshift(startScriptPath);
}
const smokeTestCommand = `${executable} ${args.join(' ')}`;
debug$1('running smoke test');
debug$1('using Cypress executable %s', executable);
debug$1('smoke test command:', smokeTestCommand);
debug$1('smoke test timeout %d ms', options.smokeTestTimeout);
const stdioOptions = _.extend({}, {
env: Object.assign(Object.assign({}, process.env), { FORCE_COLOR: '0' }),
timeout: options.smokeTestTimeout,
});
try {
const result = yield xvfb.util.exec(executable, args, stdioOptions);
// TODO: when execa > 1.1 is released
// change this to `result.all` for both stderr and stdout
// use lodash to be robust during tests against null result or missing stdout
const smokeTestStdout = _.get(result, 'stdout', '');
debug$1('smoke test stdout "%s"', smokeTestStdout);
if (!xvfb.util.stdoutLineMatches(String(random), smokeTestStdout)) {
debug$1('Smoke test failed because could not find %d in:', random, result);
const smokeTestStderr = _.get(result, 'stderr', '');
const errorText = smokeTestStderr || smokeTestStdout;
return xvfb.throwFormErrorText(xvfb.errors.smokeTestFailure(smokeTestCommand, false))(errorText);
}
}
catch (err) {
debug$1('Smoke test failed:', err);
let errMessage = err.stderr || err.message;
debug$1('error message:', errMessage);
if (err.timedOut) {
debug$1('error timedOut is true');
return xvfb.throwFormErrorText(xvfb.errors.smokeTestFailure(smokeTestCommand, true))(errMessage);
}
if (linuxWithDisplayEnv && xvfb.util.isBrokenGtkDisplay(errMessage)) {
xvfb.util.logBrokenGtkDisplayWarning();
return xvfb.throwFormErrorText(xvfb.errors.invalidSmokeTestDisplayError)(errMessage);
}
return xvfb.throwFormErrorText(xvfb.errors.missingDependency)(errMessage);
}
});
const spawnInXvfb = (linuxWithDisplayEnv) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
yield xvfb.xvfb.start();
return spawn(linuxWithDisplayEnv || false).finally(() => xvfb.__awaiter(void 0, void 0, void 0, function* () {
yield xvfb.xvfb.stop();
}));
});
const userFriendlySpawn = (linuxWithDisplayEnv) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
debug$1('spawning, should retry on display problem?', Boolean(linuxWithDisplayEnv));
try {
yield spawn(linuxWithDisplayEnv);
}
catch (err) {
if (err.code === 'INVALID_SMOKE_TEST_DISPLAY_ERROR') {
return spawnInXvfb(linuxWithDisplayEnv);
}
throw err;
}
});
if (needsXvfb) {
return spawnInXvfb();
}
// if we are on linux and there's already a DISPLAY
// set, then we may need to rerun cypress after
// spawning our own Xvfb server
const linuxWithDisplayEnv = xvfb.util.isPossibleLinuxWithIncorrectDisplay();
return userFriendlySpawn(linuxWithDisplayEnv);
};
function testBinary(version, binaryDir, options) {
debug$1('running binary verification check', version);
// if running from 'cypress verify', don't print this message
if (!options.force) {
xvfb.loggerModule.log(commonTags.stripIndent `
It looks like this is your first time using Cypress: ${chalk.cyan(version)}
`);
}
xvfb.loggerModule.log();
// if we are running in CI then use
// the verbose renderer else use
// the default
let renderer = xvfb.util.isCi() ? VerboseRenderer : 'default';
// NOTE: under test we set the listr renderer to 'silent' in order to get deterministic snapshots
if (xvfb.loggerModule.logLevel() === 'silent' || options.listrRenderer)
renderer = 'silent';
const rendererOptions = {
renderer,
};
const tasks = new listr2.Listr([
{
title: xvfb.util.titleize('Verifying Cypress can run', chalk.gray(binaryDir)),
task: (ctx, task) => xvfb.__awaiter(this, void 0, void 0, function* () {
debug$1('clearing out the verified version');
yield xvfb.stateModule.clearBinaryStateAsync(binaryDir);
yield Promise.all([
runSmokeTest(binaryDir, options),
Bluebird.delay(1500), // good user experience
]);
debug$1('write verified: true');
yield xvfb.stateModule.writeBinaryVerifiedAsync(true, binaryDir);
xvfb.util.setTaskTitle(task, xvfb.util.titleize(chalk.green('Verified Cypress!'), chalk.gray(binaryDir)), rendererOptions.renderer);
}),
},
], rendererOptions);
return tasks.run();
}
const maybeVerify = (installedVersion, binaryDir, options) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
const isVerified = yield xvfb.stateModule.getBinaryVerifiedAsync(binaryDir);
debug$1('is Verified ?', isVerified);
let shouldVerify = !isVerified;
// force verify if options.force
if (options.force) {
debug$1('force verify');
shouldVerify = true;
}
if (shouldVerify) {
yield testBinary(installedVersion, binaryDir, options);
if (options.welcomeMessage) {
xvfb.loggerModule.log();
xvfb.loggerModule.log('Opening Cypress...');
}
}
});
const start$1 = (...args_1) => xvfb.__awaiter(void 0, [...args_1], void 0, function* (options = {}) {
debug$1('verifying Cypress app');
_.defaults(options, {
dev: false,
force: false,
welcomeMessage: true,
smokeTestTimeout: verifyTestRunnerTimeoutMs(),
skipVerify: xvfb.util.getEnv('CYPRESS_SKIP_VERIFY') === 'true',
});
if (options.skipVerify) {
debug$1('skipping verification of the Cypress app');
return Promise.resolve();
}
const packageVersion = xvfb.util.pkgVersion();
let binaryDir = xvfb.stateModule.getBinaryDir(packageVersion);
if (options.dev) {
return runSmokeTest('', options);
}
const parseBinaryEnvVar = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
const envBinaryPath = xvfb.util.getEnv('CYPRESS_RUN_BINARY');
debug$1('CYPRESS_RUN_BINARY exists, =', envBinaryPath);
xvfb.loggerModule.log(commonTags.stripIndent `
${chalk.yellow('Note:')} You have set the environment variable:
${chalk.white('CYPRESS_RUN_BINARY=')}${chalk.cyan(envBinaryPath)}
This overrides the default Cypress binary path used.
`);
xvfb.loggerModule.log();
try {
const isExecutable = yield xvfb.util.isExecutableAsync(envBinaryPath);
debug$1('CYPRESS_RUN_BINARY is executable? :', isExecutable);
if (!isExecutable) {
return xvfb.throwFormErrorText(xvfb.errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(commonTags.stripIndent `
The supplied binary path is not executable
`);
}
const envBinaryDir = yield xvfb.stateModule.parseRealPlatformBinaryFolderAsync(envBinaryPath);
if (!envBinaryDir) {
return xvfb.throwFormErrorText(xvfb.errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))();
}
debug$1('CYPRESS_RUN_BINARY has binaryDir:', envBinaryDir);
binaryDir = envBinaryDir;
}
catch (err) {
if (err.code === 'ENOENT') {
return xvfb.throwFormErrorText(xvfb.errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(err.message);
}
throw err;
}
});
try {
debug$1('checking environment variables');
if (xvfb.util.getEnv('CYPRESS_RUN_BINARY')) {
yield parseBinaryEnvVar();
}
yield checkExecutable(binaryDir);
debug$1('binaryDir is ', binaryDir);
const pkg = yield xvfb.stateModule.getBinaryPkgAsync(binaryDir);
const binaryVersion = xvfb.stateModule.getBinaryPkgVersion(pkg);
if (!binaryVersion) {
debug$1('no Cypress binary found for cli version ', packageVersion);
return xvfb.throwFormErrorText(xvfb.errors.missingApp(binaryDir))(`
Cannot read binary version from: ${chalk.cyan(xvfb.stateModule.getBinaryPkgPath(binaryDir))}
`);
}
debug$1(`Found binary version ${chalk.green(binaryVersion)} installed in: ${chalk.cyan(binaryDir)}`);
if (binaryVersion !== packageVersion) {
// warn if we installed with CYPRESS_INSTALL_BINARY or changed version
// in the package.json
xvfb.loggerModule.log(`Found binary version ${chalk.green(binaryVersion)} installed in: ${chalk.cyan(binaryDir)}`);
xvfb.loggerModule.log();
xvfb.loggerModule.warn(commonTags.stripIndent `
${logSymbols.warning} Warning: Binary version ${chalk.green(binaryVersion)} does not match the expected package version ${chalk.green(packageVersion)}
These versions may not work properly together.
`);
xvfb.loggerModule.log();
}
yield maybeVerify(binaryVersion, binaryDir, options);
}
catch (err) {
if (err.known) {
throw err;
}
return xvfb.throwFormErrorText(xvfb.errors.unexpected)(err.stack);
}
});
const isLinuxLike = () => os.platform() !== 'win32';
/**
* Returns true if running on a system where Electron needs "--no-sandbox" flag.
* @see https://crbug.com/638180
*
* On Debian we had problems running in sandbox even for non-root users.
* @see https://github.com/cypress-io/cypress/issues/5434
* Seems there is a lot of discussion around this issue among Electron users
* @see https://github.com/electron/electron/issues/17972
*/
const needsSandbox = () => isLinuxLike();
var dist = {};
var FilterTaggedContent = {};
var LineDecoder = {};
var constants = {};
var hasRequiredConstants;
function requireConstants () {
if (hasRequiredConstants) return constants;
hasRequiredConstants = 1;
Object.defineProperty(constants, "__esModule", { value: true });
constants.DEBUG_PREFIX = constants.END_TAG = constants.START_TAG = void 0;
/**
* These tags are used to mark the beginning and end of error content that should
* be filtered from stderr output. The tags are designed to be unique and easily
* identifiable in log output.
*/
constants.START_TAG = '<<<CYPRESS.STDERR.START>>>';
/**
* Marks the end of error content that should be filtered from stderr output.
*/
constants.END_TAG = '<<<CYPRESS.STDERR.END>>>';
/**
* A regex that will match output from the 'debug' package
*/
// this regexp needs to match control characters
// eslint-disable-next-line no-control-regex
constants.DEBUG_PREFIX = /^\s+(?:\u001b\[[0-9;]*m)*((\S+):)+/u;
return constants;
}
var hasRequiredLineDecoder;
function requireLineDecoder () {
if (hasRequiredLineDecoder) return LineDecoder;
hasRequiredLineDecoder = 1;
/**
* Decodes incoming string chunks into complete lines, handling partial lines across chunk boundaries.
*
* This class buffers incoming string data and provides an iterator interface to yield complete
* lines. It handles the case where a line might be split across multiple chunks by maintaining
* an internal buffer. The end() method should be called to flush any remaining buffered content
* when processing is complete.
*/
var __importDefault = (LineDecoder && LineDecoder.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(LineDecoder, "__esModule", { value: true });
LineDecoder.LineDecoder = void 0;
const debug_1 = __importDefault(Debug);
const constants_1 = requireConstants();
const debugVerbose = (0, debug_1.default)(`cypress-verbose:stderr-filtering:LineDecoder:${process.pid}`);
let LineDecoder$1 = class LineDecoder {
constructor(overrideToken = constants_1.END_TAG) {
this.overrideToken = overrideToken;
this.buffer = '';
}
/**
* Adds a chunk of string data to the internal buffer.
*
* @param chunk The string chunk to add to the buffer
*/
write(chunk) {
debugVerbose('writing chunk to line decoder', { chunk });
this.buffer += chunk;
}
/**
* Iterates over complete lines in the current buffer.
*
* This generator yields complete lines from the buffer, splitting on newline characters.
* Any incomplete line at the end of the buffer is kept for the next iteration.
* Handles both Windows (\r\n) and Unix (\n) line endings.
*
* @yields Complete lines with newline characters preserved
*/
*[Symbol.iterator]() {
debugVerbose('iterating over lines in line decoder');
let nextLine = undefined;
do {
nextLine = this.nextLine();
if (nextLine) {
debugVerbose('yielding line:', nextLine);
debugVerbose('buffer size:', this.buffer.length);
yield nextLine;
}
} while (nextLine);
}
/**
* Flushes the remaining buffer content and yields all remaining lines.
*
* This method should be called when processing is complete to ensure all buffered
* content is yielded. It processes any remaining content in the buffer plus an
* optional final chunk. Handles both Windows (\r\n) and Unix (\n) line endings.
*
* @param chunk Optional final chunk to process along with the buffer
* @yields All remaining lines from the buffer and final chunk
*/
*end(chunk) {
this.buffer = `${this.buffer}${(chunk || '')}`;
let nextLine = undefined;
do {
nextLine = this.nextLine();
if (nextLine) {
yield nextLine;
}
} while (nextLine);
}
nextLine() {
const [newlineIndex, length] = [this.buffer.indexOf('\n'), 1];
const endsWithOverrideToken = newlineIndex < 0 ? this.buffer.endsWith(this.overrideToken) : false;
if (endsWithOverrideToken) {
debugVerbose('ends with override token');
const line = this.buffer;
this.buffer = '';
return line;
}
if (newlineIndex >= 0) {
debugVerbose('contains a newline');
const line = this.buffer.slice(0, newlineIndex + length);
this.buffer = this.buffer.slice(newlineIndex + length);
return line;
}
return undefined;
}
};
LineDecoder.LineDecoder = LineDecoder$1;
return LineDecoder;
}
var writeWithBackpressure = {};
var hasRequiredWriteWithBackpressure;
function requireWriteWithBackpressure () {
if (hasRequiredWriteWithBackpressure) return writeWithBackpressure;
hasRequiredWriteWithBackpressure = 1;
Object.defineProperty(writeWithBackpressure, "__esModule", { value: true });
writeWithBackpressure.writeWithBackpressure = writeWithBackpressure$1;
async function writeWithBackpressure$1(toStream, chunk) {
return new Promise((resolve, reject) => {
try {
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
const ret = toStream.write(buffer);
if (ret) {
resolve();
}
else {
toStream.once('drain', () => {
resolve();
});
}
}
catch (err) {
reject(err);
}
});
}
return writeWithBackpressure;
}
var hasRequiredFilterTaggedContent;
function requireFilterTaggedContent () {
if (hasRequiredFilterTaggedContent) return FilterTaggedContent;
hasRequiredFilterTaggedContent = 1;
var __importDefault = (FilterTaggedContent && FilterTaggedContent.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(FilterTaggedContent, "__esModule", { value: true });
FilterTaggedContent.FilterTaggedContent = void 0;
const stream_1 = require$$0;
const string_decoder_1 = require$$1;
const LineDecoder_1 = requireLineDecoder();
const debug_1 = __importDefault(Debug);
const writeWithBackpressure_1 = requireWriteWithBackpressure();
const debugVerbose = (0, debug_1.default)('cypress-verbose:stderr-filtering:FilterTaggedContent');
/**
* Filters content based on start and end tags, supporting multi-line tagged content.
*
* This transform stream processes incoming data and routes content between two output streams
* based on tag detection. Content between start and end tags is sent to the filtered stream,
* while content outside tags is sent to the main output stream. The class handles cases where
* tags span multiple lines by maintaining state across line boundaries.
*
* Example usage:
* ```typescript
* const filter = new FilterTaggedContent('<secret>', '</secret>', filteredStream)
* inputStream.pipe(filter).pipe(outputStream)
* ```
*/
let FilterTaggedContent$1 = class FilterTaggedContent extends stream_1.Transform {
/**
* Creates a new FilterTaggedContent instance.
*
* @param startTag The string that marks the beginning of content to filter
* @param endTag The string that marks the end of content to filter
* @param filtered The writable stream for filtered content
*/
constructor(startTag, endTag, wasteStream) {
super({
transform: (chunk, encoding, next) => this.transform(chunk, encoding, next),
flush: (callback) => this.flush(callback),
});
this.startTag = startTag;
this.endTag = endTag;
this.wasteStream = wasteStream;
this.inTaggedContent = false;
/**
* Processes incoming chunks and routes content based on tag detection.
*
* @param chunk The buffer chunk to process
* @param encoding The encoding of the chunk
* @param next Callback to call when processing is complete
*/
this.transform = async (chunk, encoding, next) => {
var _a, _b, _c;
try {
this.ensureDecoders(encoding);
const str = (_b = (_a = this.strDecoder) === null || _a === void 0 ? void 0 : _a.write(chunk)) !== null && _b !== void 0 ? _b : '';
(_c = this.lineDecoder) === null || _c === void 0 ? void 0 : _c.write(str);
debugVerbose('processing str for tags: "%s"', str);
for (const line of Array.from(this.lineDecoder || [])) {
await this.processLine(line);
}
next();
}
catch (err) {
next(err);
}
};
/**
* Flushes any remaining buffered content when the stream ends.
*
* @param callback Callback to call when flushing is complete
*/
this.flush = async (callback) => {
var _a;
debugVerbose('flushing');
this.ensureDecoders();
try {
for (const line of Array.from(((_a = this.lineDecoder) === null || _a === void 0 ? void 0 : _a.end()) || [])) {
await this.processLine(line);
}
callback();
}
catch (err) {
callback(err);
}
};
}
ensureDecoders(encoding) {
var _a;
const enc = (_a = (encoding === 'buffer' ? 'utf8' : encoding)) !== null && _a !== void 0 ? _a : 'utf8';
if (!this.lineDecoder) {
this.lineDecoder = new LineDecoder_1.LineDecoder();
}
if (!this.strDecoder) {
this.strDecoder = new string_decoder_1.StringDecoder(enc);
}
}
/**
* Processes a single line and routes content based on tag positions.
*
* This method handles the complex logic of detecting start and end tags within a line,
* maintaining state across lines, and routing content to the appropriate streams.
* It supports cases where both tags appear on the same line, only one tag appears,
* or no tags appear but the line is part of ongoing tagged content.
*
* @param line The line to process
*/
async processLine(line) {
const startPos = line.indexOf(this.startTag);
const endPos = line.lastIndexOf(this.endTag);
if (startPos >= 0 && endPos >= 0) {
// Both tags on same line
if (startPos > 0) {
await this.pass(line.slice(0, startPos));
}
await this.writeToWasteStream(line.slice(startPos + this.startTag.length, endPos));
if (endPos + this.endTag.length < line.length) {
await this.pass(line.slice(endPos + this.endTag.length));
}
}
else if (startPos >= 0) {
// Start tag found
if (startPos > 0) {
await this.pass(line.slice(0, startPos));
}
await this.writeToWasteStream(line.slice(startPos + this.startTag.length));
this.inTaggedContent = true;
}
else if (endPos >= 0) {
// End tag found
await this.writeToWasteStream(line.slice(0, endPos));
if (endPos + this.endTag.length < line.length) {
await this.pass(line.slice(endPos + this.endTag.length));
}
this.inTaggedContent = false;
}
else if (this.inTaggedContent) {
// Currently in tagged content
await this.writeToWasteStream(line);
}
else {
// Not in tagged content
await this.pass(line);
}
}
async writeToWasteStream(line, encoding) {
var _a;
debugVerbose('writing to waste stream: "%s"', line);
await (0, writeWithBackpressure_1.writeWithBackpressure)(this.wasteStream, Buffer.from(line, (_a = (encoding === 'buffer' ? 'utf8' : encoding)) !== null && _a !== void 0 ? _a : 'utf8'));
}
async pass(line, encoding) {
var _a;
debugVerbose('passing: "%s"', line);
this.push(Buffer.from(line, (_a = (encoding === 'buffer' ? 'utf8' : encoding)) !== null && _a !== void 0 ? _a : 'utf8'));
}
};
FilterTaggedContent.FilterTaggedContent = FilterTaggedContent$1;
return FilterTaggedContent;
}
var FilterPrefixedContent = {};
var hasRequiredFilterPrefixedContent;
function requireFilterPrefixedContent () {
if (hasRequiredFilterPrefixedContent) return FilterPrefixedContent;
hasRequiredFilterPrefixedContent = 1;
var __importDefault = (FilterPrefixedContent && FilterPrefixedContent.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(FilterPrefixedContent, "__esModule", { value: true });
FilterPrefixedContent.FilterPrefixedContent = void 0;
const stream_1 = require$$0;
const string_decoder_1 = require$$1;
const LineDecoder_1 = requireLineDecoder();
const debug_1 = __importDefault(Debug);
const debugStderr = (0, debug_1.default)('cypress:stderr');
const writeWithBackpressure_1 = requireWriteWithBackpressure();
/**
* Filters content based on a prefix pattern, routing matching lines to a filtered stream.
*
* This transform stream processes incoming data line by line and routes content between two
* output streams based on a regular expression prefix test. Lines that match the prefix pattern
* are sent to the filtered stream, while non-matching lines are sent to the main output stream.
*
* Example usage:
* ```typescript
* const errorStream = new Writable()
* const filter = new FilterPrefixedContent(/^ERROR:/, errorStream)
* inputStream.pipe(filter).pipe(outputStream)
* ```
*/
let FilterPrefixedContent$1 = class FilterPrefixedContent extends stream_1.Transform {
/**
* Creates a new FilterPrefixedContent instance.
*
* @param prefix The regular expression pattern to test against the beginning of each line
* @param filtered The writable stream for lines that match the prefix pattern
*/
constructor(prefix, wasteStream) {
super(({
transform: (chunk, encoding, next) => this.transform(chunk, encoding, next),
flush: (callback) => this.flush(callback),
}));
this.prefix = prefix;
this.wasteStream = wasteStream;
/**
* Processes incoming chunks and routes lines based on prefix matching.
*
* @param chunk The buffer chunk to process
* @param encoding The encoding of the chunk
* @param next Callback to call when processing is complete
*/
this.transform = async (chunk, encoding, next) => {
try {
if (!this.strDecoder) {
// @ts-expect-error type here is not correct, 'buffer' is not a valid encoding but it does get passed in
this.strDecoder = new string_decoder_1.StringDecoder(encoding === 'buffer' ? 'utf8' : encoding);
}
if (!this.lineDecoder) {
this.lineDecoder = new LineDecoder_1.LineDecoder();
}
const str = this.strDecoder.write(chunk);
this.lineDecoder.write(str);
for (const line of Array.from(this.lineDecoder || [])) {
await this.writeLine(line, encoding);
}
next();
}
catch (err) {
debugStderr('error in transform', err);
next(err);
}
};
/**
* Flushes any remaining buffered content when the stream ends.
*
* @param callback Callback to call when flushing is complete
*/
this.flush = async (callback) => {
var _a;
try {
if (!this.strDecoder) {
this.strDecoder = new string_decoder_1.StringDecoder();
}
if (!this.lineDecoder) {
this.lineDecoder = new LineDecoder_1.LineDecoder();
}
if (this.lineDecoder) {
for (const line of Array.from(((_a = this.lineDecoder) === null || _a === void 0 ? void 0 : _a.end()) || [])) {
await this.writeLine(line);
}
}
callback();
}
catch (err) {
callback(err);
}
};
}
/**
* Routes a single line to the appropriate stream based on prefix matching.
*
* Tests the line against the prefix regular expression and routes it to either
* the filtered stream (if it matches) or the main output stream (if it doesn't match).
*
* @param line The line to test and route
*/
async writeLine(line, encoding) {
var _a, _b;
if (this.prefix.test(line)) {
await (0, writeWithBackpressure_1.writeWithBackpressure)(this.wasteStream, Buffer.from(line, (_a = (encoding === 'buffer' ? 'utf8' : encoding)) !== null && _a !== void 0 ? _a : 'utf8'));
}
else {
const canWrite = this.push(Buffer.from(line, (_b = (encoding === 'buffer' ? 'utf8' : encoding)) !== null && _b !== void 0 ? _b : 'utf8'));
if (!canWrite) {
await new Promise((resolve) => this.once('drain', resolve));
}
}
}
};
FilterPrefixedContent.FilterPrefixedContent = FilterPrefixedContent$1;
return FilterPrefixedContent;
}
var TagStream = {};
var tagsDisabled = {};
var hasRequiredTagsDisabled;
function requireTagsDisabled () {
if (hasRequiredTagsDisabled) return tagsDisabled;
hasRequiredTagsDisabled = 1;
Object.defineProperty(tagsDisabled, "__esModule", { value: true });
tagsDisabled.tagsDisabled = tagsDisabled$1;
function tagsDisabled$1() {
return process.env.ELECTRON_ENABLE_LOGGING === '1' || process.env.CYPRESS_INTERNAL_ENV === 'development';
}
return tagsDisabled;
}
var hasRequiredTagStream;
function requireTagStream () {
if (hasRequiredTagStream) return TagStream;
hasRequiredTagStream = 1;
var __importDefault = (TagStream && TagStream.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(TagStream, "__esModule", { value: true });
TagStream.TagStream = void 0;
const stream_1 = require$$0;
const constants_1 = requireConstants();
const string_decoder_1 = require$$1;
const debug_1 = __importDefault(Debug);
const tagsDisabled_1 = requireTagsDisabled();
const debug = (0, debug_1.default)('cypress:stderr-filtering:TagStream');
const debugVerbose = (0, debug_1.default)('cypress-verbose:stderr-filtering:TagStream');
/**
* A Transform stream that wraps input data with start and end tags.
*
* This stream processes incoming chunks and wraps them with configurable
* start and end tags before passing them downstream. It handles both
* Buffer and string inputs, using a StringDecoder for proper encoding
* when processing Buffer chunks.
*
* By default, the start and end tags are the constants exported by this package:
* - START_TAG
* - END_TAG
*
* @example
* ```typescript
* const tagStream = new TagStream('[START]', '[END]');
* tagStream.pipe(process.stdout);
* tagStream.write('Hello World'); // Outputs: [START]Hello World[END]
* ```
*/
let TagStream$1 = class TagStream extends stream_1.Transform {
get initializedDecoder() {
debug('initializedDecoder', !!this.decoder);
if (!this.decoder) {
this.decoder = new string_decoder_1.StringDecoder();
}
return this.decoder;
}
/**
* Creates a new TagStream instance.
*
* @param startTag - The tag to prepend to each chunk. Defaults to START_TAG.
* @param endTag - The tag to append to each chunk. Defaults to END_TAG.
*/
constructor(startTag = constants_1.START_TAG, endTag = constants_1.END_TAG) {
super({
transform: (...args) => this.transform(...args),
});
this.startTag = startTag;
this.endTag = endTag;
}
/**
* Transforms incoming chunks by wrapping them with start and end tags.
*
* Processes the input chunk, handles both Buffer and string inputs,
* and wraps the result with the configured start and end tags.
* Implements backpressure handling by waiting for the 'drain' event
* when the downstream stream cannot accept more data.
*
* @param chunk - The input chunk to transform. Can be Buffer, string, or any other type.
* @param encoding - The encoding of the chunk (used by Transform stream).
* @param callback - Callback function to signal completion of transformation.
* @returns Promise that resolves when transformation is complete.
*/
async transform(chunk, encoding, callback) {
try {
const out = chunk instanceof Buffer ?
this.initializedDecoder.write(chunk) :
chunk;
const transformed = out ? this.tag(out) : Buffer.from('');
debugVerbose(`transformed: "${transformed.toString().replaceAll('\n', '\\n')}"`);
const canWrite = this.push(transformed);
if (!canWrite) {
debugVerbose('waiting for drain');
await new Promise((resolve) => this.once('drain', resolve));
}
callback();
}
catch (err) {
debug('error', err);
callback(err);
}
}
/**
* Flushes any remaining buffered data and wraps it with tags.
*
* Called when the stream is ending to process any remaining
* data in the StringDecoder buffer.
*
* @param callback - Callback function to signal completion of flush operation.
*/
flush(callback) {
debug('flushing');
const out = this.initializedDecoder.end();
callback(undefined, out ? this.tag(out) : Buffer.from(''));
}
tag(str) {
return (0, tagsDisabled_1.tagsDisabled)() ? Buffer.from(str) : Buffer.from(`${this.startTag}${str}${this.endTag}`);
}
};
TagStream.TagStream = TagStream$1;
return TagStream;
}
var WriteToDebug = {};
var hasRequiredWriteToDebug;
function requireWriteToDebug () {
if (hasRequiredWriteToDebug) return WriteToDebug;
hasRequiredWriteToDebug = 1;
Object.defineProperty(WriteToDebug, "__esModule", { value: true });
WriteToDebug.WriteToDebug = void 0;
const stream_1 = require$$0;
const node_string_decoder_1 = require$$1$1;
const LineDecoder_1 = requireLineDecoder();
/**
* A writable stream that routes incoming data to a debug logger.
*
* This class extends Writable to provide a stream interface that processes incoming
* data and forwards it to a debug logger. It handles line-by-line processing and
* automatically manages string decoding and line buffering. The stream is useful
* for debugging purposes where you want to log stream data with proper line handling.
*
* Example usage:
* ```typescript
* const debug = require('debug')('myapp:stream')
* const debugStream = new WriteToDebug(debug)
* someStream.pipe(debugStream)
* ```
*/
let WriteToDebug$1 = class WriteToDebug extends stream_1.Writable {
/**
* Creates a new WriteToDebug instance.
*
* @param debug The debug logger instance to write output to
*/
constructor(debug) {
super({
write: (chunk, encoding, next) => {
if (!this.strDecoder) {
// @ts-expect-error type here is not correct, 'buffer' is not a valid encoding but it does get passed in
this.strDecoder = new node_string_decoder_1.StringDecoder(encoding === 'buffer' ? 'utf8' : encoding);
}
if (!this.lineDecoder) {
this.lineDecoder = new LineDecoder_1.LineDecoder();
}
const str = this.strDecoder.write(chunk);
this.lineDecoder.write(str);
for (const line of this.lineDecoder) {
this.debugLine(line);
}
next();
},
final: (callback) => {
if (!this.strDecoder) {
this.strDecoder = new node_string_decoder_1.StringDecoder();
}
if (!this.lineDecoder) {
this.lineDecoder = new LineDecoder_1.LineDecoder();
}
for (const line of this.lineDecoder.end()) {
this.debugLine(line);
}
this.strDecoder = undefined;
this.lineDecoder = undefined;
callback();
},
});
this.debug = debug;
}
/**
* Processes a single line and sends it to the debug logger.
*
* This method cleans the line by removing trailing newlines while preserving
* intentional whitespace, then sends non-empty lines to the debug logger.
* Empty lines are filtered out to avoid cluttering the debug output.
*
* @param line The line to process and log
*/
debugLine(line) {
// Remove trailing newline but preserve intentional whitespace
const clean = line.endsWith('\n') ? line.slice(0, -1) : line;
if (clean) {
this.debug(clean);
}
}
};
WriteToDebug.WriteToDebug = WriteToDebug$1;
return WriteToDebug;
}
var Filter = {};
var hasRequiredFilter;
function requireFilter () {
if (hasRequiredFilter) return Filter;
hasRequiredFilter = 1;
Object.defineProperty(Filter, "__esModule", { value: true });
Filter.filter = filter;
const constants_1 = requireConstants();
const FilterPrefixedContent_1 = requireFilterPrefixedContent();
const FilterTaggedContent_1 = requireFilterTaggedContent();
const WriteToDebug_1 = requireWriteToDebug();
const tagsDisabled_1 = requireTagsDisabled();
function filter(stderr, debug, prefix) {
const prefixTx = new FilterPrefixedContent_1.FilterPrefixedContent(prefix, stderr);
const debugWriter = new WriteToDebug_1.WriteToDebug(debug);
if ((0, tagsDisabled_1.tagsDisabled)()) {
prefixTx.pipe(debugWriter);
}
else {
const tagTx = new FilterTaggedContent_1.FilterTaggedContent(constants_1.START_TAG, constants_1.END_TAG, stderr);
prefixTx.pipe(tagTx).pipe(debugWriter);
}
return prefixTx;
}
return Filter;
}
var logError = {};
var hasRequiredLogError;
function requireLogError () {
if (hasRequiredLogError) return logError;
hasRequiredLogError = 1;
Object.defineProperty(logError, "__esModule", { value: true });
logError.logError = void 0;
/**
* Standard error logging tags used for stderr filtering
*/
const constants_1 = requireConstants();
const tagsDisabled_1 = requireTagsDisabled();
/**
* Logs error messages with special tags for stderr filtering.
*
* This function wraps console.error calls with start and end tags that can be
* used by FilterTaggedContent to identify and filter error messages from stderr
* output. The tags allow for precise control over which error messages are
* filtered while preserving the original error content.
*
* @param args The arguments to log as an error message
*/
const logError$1 = (...args) => {
// When electron debug is enabled, the output will not be filtered, so
// these tags are not needed.
if ((0, tagsDisabled_1.tagsDisabled)()) {
// eslint-disable-next-line no-console
console.error(...args);
}
else {
// eslint-disable-next-line no-console
console.error(constants_1.START_TAG, ...args, constants_1.END_TAG);
}
};
logError.logError = logError$1;
return logError;
}
var hasRequiredDist;
function requireDist () {
if (hasRequiredDist) return dist;
hasRequiredDist = 1;
(function (exports$1) {
Object.defineProperty(exports$1, "__esModule", { value: true });
exports$1.DEBUG_PREFIX = exports$1.END_TAG = exports$1.START_TAG = exports$1.logError = exports$1.filter = exports$1.WriteToDebug = exports$1.TagStream = exports$1.FilterPrefixedContent = exports$1.FilterTaggedContent = void 0;
var FilterTaggedContent_1 = requireFilterTaggedContent();
Object.defineProperty(exports$1, "FilterTaggedContent", { enumerable: true, get: function () { return FilterTaggedContent_1.FilterTaggedContent; } });
var FilterPrefixedContent_1 = requireFilterPrefixedContent();
Object.defineProperty(exports$1, "FilterPrefixedContent", { enumerable: true, get: function () { return FilterPrefixedContent_1.FilterPrefixedContent; } });
var TagStream_1 = requireTagStream();
Object.defineProperty(exports$1, "TagStream", { enumerable: true, get: function () { return TagStream_1.TagStream; } });
var WriteToDebug_1 = requireWriteToDebug();
Object.defineProperty(exports$1, "WriteToDebug", { enumerable: true, get: function () { return WriteToDebug_1.WriteToDebug; } });
var Filter_1 = requireFilter();
Object.defineProperty(exports$1, "filter", { enumerable: true, get: function () { return Filter_1.filter; } });
var logError_1 = requireLogError();
Object.defineProperty(exports$1, "logError", { enumerable: true, get: function () { return logError_1.logError; } });
var constants_1 = requireConstants();
Object.defineProperty(exports$1, "START_TAG", { enumerable: true, get: function () { return constants_1.START_TAG; } });
Object.defineProperty(exports$1, "END_TAG", { enumerable: true, get: function () { return constants_1.END_TAG; } });
Object.defineProperty(exports$1, "DEBUG_PREFIX", { enumerable: true, get: function () { return constants_1.DEBUG_PREFIX; } });
} (dist));
return dist;
}
var distExports = requireDist();
const debug = Debug('cypress:cli');
const debugElectron = Debug('cypress:electron');
const debugStderr = Debug('cypress:internal-stderr');
function isPlatform(platform) {
return os.platform() === platform;
}
function needsStderrPiped(needsXvfb) {
return _.some([
isPlatform('darwin'),
(needsXvfb && isPlatform('linux')),
xvfb.util.isPossibleLinuxWithIncorrectDisplay(),
]);
}
function needsEverythingPipedDirectly() {
return isPlatform('win32');
}
function getStdioStrategy(needsXvfb) {
if (needsEverythingPipedDirectly()) {
return 'pipe';
}
// https://github.com/cypress-io/cypress/issues/921
// https://github.com/cypress-io/cypress/issues/1143
// https://github.com/cypress-io/cypress/issues/1745
if (needsStderrPiped(needsXvfb)) {
// returning pipe here so we can massage stderr
// and remove garbage from Xlib and libuv
// due to starting the Xvfb process on linux
return ['inherit', 'inherit', 'pipe'];
}
return 'inherit';
}
function createSpawnFunction(executable, args, options) {
return (overrides = {}) => {
return new Promise((resolve, reject) => xvfb.__awaiter(this, void 0, void 0, function* () {
var _a, _b;
_.defaults(overrides, {
onStderrData: false,
});
const { onStderrData } = overrides;
const envOverrides = xvfb.util.getEnvOverrides(options);
const electronArgs = [];
const node11WindowsFix = isPlatform('win32');
let startScriptPath;
if (options.dev) {
executable = 'node';
// if we're in dev then reset
// the launch cmd to be 'npm run dev'
// This path is correct in the build output, but not the source code. This file gets bundled into
// `dist/spawn-<hash>.js`, which makes this resolution appear incorrect at first glance.
startScriptPath = xvfb.relativeToRepoRoot('scripts/start.js');
if (!startScriptPath) {
throw new Error(`Cypress start script (scripts/start.js) not found in parent directory of ${__dirname}`);
}
}
if (!options.dev && needsSandbox()) {
electronArgs.push('--no-sandbox');
}
// strip dev out of child process options
/**
* @type {import('child_process').ForkOptions}
*/
let stdioOptions = _.pick(options, 'env', 'detached', 'stdio');
// figure out if we're going to be force enabling or disabling colors.
// also figure out whether we should force stdout and stderr into thinking
// it is a tty as opposed to a pipe.
stdioOptions.env = _.extend({}, stdioOptions.env, envOverrides);
if (node11WindowsFix) {
stdioOptions = _.extend({}, stdioOptions, { windowsHide: false });
}
if (xvfb.util.isPossibleLinuxWithIncorrectDisplay()) {
// make sure we use the latest DISPLAY variable if any
debug('passing DISPLAY', process.env.DISPLAY);
stdioOptions.env.DISPLAY = process.env.DISPLAY;
}
if (stdioOptions.env.ELECTRON_RUN_AS_NODE) {
// Since we are running electron as node, we need to add an entry point file.
startScriptPath = path.join(xvfb.stateModule.getBinaryPkgPath(path.dirname(executable)), '..', 'index.js');
}
else {
// Start arguments with "--" so Electron knows these are OUR
// arguments and does not try to sanitize them. Otherwise on Windows
// an url in one of the arguments crashes it :(
// https://github.com/cypress-io/cypress/issues/5466
args = [...electronArgs, '--', ...args];
}
if (startScriptPath) {
args.unshift(startScriptPath);
}
if (process.env.CYPRESS_INTERNAL_DEV_DEBUG) {
args.unshift(process.env.CYPRESS_INTERNAL_DEV_DEBUG);
}
debug('spawn args %o %o', args, _.omit(stdioOptions, 'env'));
debug('spawning Cypress with executable: %s', executable);
const platform = yield xvfb.util.getPlatformInfo().catch((e) => reject(e));
if (!platform) {
return;
}
function resolveOn(event) {
return function (code, signal) {
debug('child event fired %o', { event, code, signal });
if (code === null) {
const errorObject = xvfb.errors.childProcessKilled(event, signal);
errorObject.platform = platform;
const err = xvfb.getErrorSync(errorObject, platform);
reject(err);
return;
}
resolve(code);
};
}
const child = cp.spawn(executable, args, stdioOptions);
child.on('close', resolveOn('close'));
child.on('exit', resolveOn('exit'));
child.on('error', reject);
if (isPlatform('win32')) {
const rl = readline.createInterface({
input: process$1.stdin,
output: process$1.stdout,
});
// on windows, SIGINT does not propagate to the child process when ctrl+c is pressed
// this makes sure all nested processes are closed(ex: firefox inside the server)
rl.on('SIGINT', function () {
return xvfb.__awaiter(this, void 0, void 0, function* () {
const kill = (yield import('tree-kill')).default;
kill(child.pid, 'SIGINT');
});
});
}
// if stdio options is set to 'pipe', then
// we should set up pipes:
// process STDIN (read stream) => child STDIN (writeable)
// child STDOUT => process STDOUT
// child STDERR => process STDERR with additional filtering
if (child.stdin) {
debug('piping process STDIN into child STDIN');
process$1.stdin.pipe(child.stdin);
}
if (child.stdout) {
debug('piping child STDOUT to process STDOUT');
child.stdout.pipe(process$1.stdout);
}
// if this is defined then we are manually piping for linux
// to filter out the garbage
if (child.stderr) {
debug('piping child STDERR to process STDERR');
const sourceStream = new require$$0.PassThrough();
child.on('close', () => {
sourceStream.end();
});
child.stderr.on('data', (data) => {
const str = data.toString();
if (onStderrData && onStderrData(str)) {
return;
}
if (sourceStream.writable) {
sourceStream.write(data);
}
});
if (((_a = process.env.ELECTRON_ENABLE_LOGGING) !== null && _a !== void 0 ? _a : '') === '1' ||
debugElectron.enabled ||
((_b = process.env.CYPRESS_INTERNAL_ENV) !== null && _b !== void 0 ? _b : '') === 'development') {
sourceStream.pipe(process$1.stderr, { end: false });
}
else {
sourceStream.pipe(distExports.filter(process$1.stderr, debugStderr, distExports.DEBUG_PREFIX));
}
}
// https://github.com/cypress-io/cypress/issues/1841
// https://github.com/cypress-io/cypress/issues/5241
// In some versions of node, it will throw on windows
// when you close the parent process after piping
// into the child process. unpiping does not seem
// to have any effect. so we're just catching the
// error here and not doing anything.
process$1.stdin.on('error', (err) => {
if (['EPIPE', 'ENOTCONN'].includes(err.code)) {
return;
}
reject(err);
});
if (stdioOptions.detached) {
child.unref();
}
}));
};
}
function spawnInXvfb(spawn) {
return xvfb.__awaiter(this, void 0, void 0, function* () {
try {
yield xvfb.xvfb.start();
debug('xvfb started');
const code = yield userFriendlySpawn(spawn);
return code;
}
finally {
yield xvfb.xvfb.stop();
}
});
}
function userFriendlySpawn(spawn, linuxWithDisplayEnv) {
return xvfb.__awaiter(this, void 0, void 0, function* () {
debug('spawning, should retry on display problem?', Boolean(linuxWithDisplayEnv));
let brokenGtkDisplay = false;
const overrides = {};
if (linuxWithDisplayEnv) {
_.extend(overrides, {
electronLogging: true,
onStderrData(str) {
// if we receive a broken pipe anywhere
// then we know that's why cypress exited early
if (xvfb.util.isBrokenGtkDisplay(str)) {
brokenGtkDisplay = true;
}
},
});
}
try {
const code = yield spawn(overrides);
debug('tried spawning without xvfb, code', code, brokenGtkDisplay);
if (code !== 0 && brokenGtkDisplay) {
xvfb.util.logBrokenGtkDisplayWarning();
return spawnInXvfb(spawn);
}
return code;
}
catch (error) {
debug('error in userFriendlySpawn', error);
// we can format and handle an error message from the code above
// prevent wrapping error again by using "known: undefined" filter
if (error.known === undefined) {
const raiseErrorFn = xvfb.throwFormErrorText(xvfb.errors.unexpected);
yield raiseErrorFn(error.message);
}
throw error;
}
});
}
function start(args_1) {
return xvfb.__awaiter(this, arguments, void 0, function* (args, options = {}) {
var _a, _b, _c, _d;
let executable = xvfb.util.getEnv('CYPRESS_RUN_BINARY') ?
path.resolve(xvfb.util.getEnv('CYPRESS_RUN_BINARY')) :
xvfb.stateModule.getPathToExecutable(xvfb.stateModule.getBinaryDir());
// Always push cwd into the args
// which additionally acts as a signal to the
// binary that it was invoked through the NPM module
const baseArgs = args ? (typeof args === 'string' ? [args] : args) : [];
const decoratedArgs = baseArgs.concat([
'--cwd', process.cwd(),
'--userNodePath', process.execPath,
'--userNodeVersion', process.versions.node,
]);
const needsXvfb = xvfb.xvfb.isNeeded();
debug('needs to start own Xvfb?', needsXvfb);
const stdio = (_a = options.stdio) !== null && _a !== void 0 ? _a : getStdioStrategy(needsXvfb);
const dev = (_b = options.dev) !== null && _b !== void 0 ? _b : false;
const detached = (_c = options.detached) !== null && _c !== void 0 ? _c : false;
const env = (_d = options.env) !== null && _d !== void 0 ? _d : process.env;
const spawn = createSpawnFunction(executable, decoratedArgs, { stdio, dev, detached, env });
if (needsXvfb) {
debug('starting xvfb');
return spawnInXvfb(spawn);
}
// if we are on linux and there's already a DISPLAY
// set, then we may need to rerun cypress after
// spawning our own Xvfb server
const linuxWithDisplayEnv = xvfb.util.isPossibleLinuxWithIncorrectDisplay();
debug('linuxWithDisplayEnv', linuxWithDisplayEnv);
return userFriendlySpawn(spawn, linuxWithDisplayEnv);
});
}
exports.VerboseRenderer = VerboseRenderer;
exports.start = start$1;
exports.start$1 = start;
'use strict';
var os = require('os');
var Bluebird = require('bluebird');
var Xvfb = require('@cypress/xvfb');
var commonTags = require('common-tags');
var Debug = require('debug');
var chalk = require('chalk');
var _ = require('lodash');
var assert = require('assert');
var arch = require('arch');
var ospath = require('ospath');
var hasha = require('hasha');
var tty = require('tty');
var path = require('path');
var ciInfo = require('ci-info');
var execa = require('execa');
var si = require('systeminformation');
var cachedir = require('cachedir');
var logSymbols = require('log-symbols');
var executable = require('executable');
var process$1 = require('process');
var supportsColor = require('supports-color');
var isInstalledGlobally = require('is-installed-globally');
var fs$1 = require('fs-extra');
var fs = require('fs');
var untildify = require('untildify');
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
let logs = [];
const logLevel = () => {
return (process.env.npm_config_loglevel || 'notice');
};
const error = (...messages) => {
logs.push(messages.join(' '));
console.log(chalk.red(...messages)); // eslint-disable-line no-console
};
const warn = (...messages) => {
if (logLevel() === 'silent')
return;
logs.push(messages.join(' '));
console.log(chalk.yellow(...messages)); // eslint-disable-line no-console
};
const log = (...messages) => {
if (logLevel() === 'silent' || logLevel() === 'warn')
return;
logs.push(messages.join(' '));
console.log(...messages); // eslint-disable-line no-console
};
const always = (...messages) => {
logs.push(messages.join(' '));
console.log(...messages); // eslint-disable-line no-console
};
// splits long text into lines and calls log()
// on each one to allow easy unit testing for specific message
const logLines = (text) => {
const lines = text.split('\n');
for (const line of lines) {
log(line);
}
};
const print = () => {
return logs.join('\n');
};
const reset = () => {
logs = [];
};
const loggerModule = {
log,
warn,
error,
always,
logLines,
print,
reset,
logLevel,
};
function relativeToRepoRoot(targetPath) {
let currentDir = __dirname;
// Walk up the directory tree
while (currentDir !== path.dirname(currentDir)) {
const resolvedTargetPath = path.join(currentDir, targetPath);
const rootPackageJson = path.join(currentDir, 'package.json');
// Check if this is the `cypress` package.json
if (fs.existsSync(rootPackageJson)) {
try {
const pkg = JSON.parse(fs.readFileSync(rootPackageJson, 'utf8'));
const targetPathExists = fs.existsSync(resolvedTargetPath);
if (targetPathExists && pkg.name === 'cypress') {
return path.resolve(currentDir, targetPath);
}
}
catch (_a) {
// Ignore JSON parse errors
}
}
currentDir = path.dirname(currentDir);
}
return undefined;
}
const debug$2 = Debug('cypress:cli');
const issuesUrl = 'https://github.com/cypress-io/cypress/issues';
/**
* Returns SHA512 of a file
*/
const getFileChecksum = (filename) => {
assert.ok(_.isString(filename) && !_.isEmpty(filename), 'expected filename');
return hasha.fromFile(filename, { algorithm: 'sha512' });
};
const getFileSize = (filename) => __awaiter(void 0, void 0, void 0, function* () {
assert.ok(_.isString(filename) && !_.isEmpty(filename), 'expected filename');
const { size } = yield fs$1.stat(filename);
return size;
});
const isBrokenGtkDisplayRe = /Gtk: cannot open display/;
const stringify = (val) => {
return _.isObject(val) ? JSON.stringify(val) : val;
};
function normalizeModuleOptions(options = {}) {
return _.mapValues(options, stringify);
}
/**
* Returns true if the platform is Linux. We do a lot of different
* stuff on Linux (like Xvfb) and it helps to has readable code
*/
const isLinux = () => {
return os.platform() === 'linux';
};
/**
* If the DISPLAY variable is set incorrectly, when trying to spawn
* Cypress executable we get an error like this:
```
[1005:0509/184205.663837:WARNING:browser_main_loop.cc(258)] Gtk: cannot open display: 99
```
*/
const isBrokenGtkDisplay = (str) => {
return isBrokenGtkDisplayRe.test(str);
};
const isPossibleLinuxWithIncorrectDisplay = () => {
return isLinux() && !!process.env.DISPLAY;
};
const logBrokenGtkDisplayWarning = () => {
debug$2('Cypress exited due to a broken gtk display because of a potential invalid DISPLAY env... retrying after starting Xvfb');
// if we get this error, we are on Linux and DISPLAY is set
loggerModule.warn(commonTags.stripIndent `
${logSymbols.warning} Warning: Cypress failed to start.
This is likely due to a misconfigured DISPLAY environment variable.
DISPLAY was set to: "${process.env.DISPLAY}"
Cypress will attempt to fix the problem and rerun.
`);
loggerModule.warn();
};
function stdoutLineMatches(expectedLine, stdout) {
const lines = stdout.split('\n').map((val) => val.trim());
return lines.some((line) => line === expectedLine);
}
/**
* Confirms if given value is a valid CYPRESS_INTERNAL_ENV value. Undefined values
* are valid, because the system can set the default one.
*
* @param {string} value
* @example util.isValidCypressInternalEnvValue(process.env.CYPRESS_INTERNAL_ENV)
*/
function isValidCypressInternalEnvValue(value) {
if (_.isUndefined(value)) {
// will get default value
return true;
}
// names of config environments, see "packages/server/config/app.json"
const names = ['development', 'test', 'staging', 'production'];
return _.includes(names, value);
}
/**
* Confirms if given value is a non-production CYPRESS_INTERNAL_ENV value.
* Undefined values are valid, because the system can set the default one.
*
* @param {string} value
* @example util.isNonProductionCypressInternalEnvValue(process.env.CYPRESS_INTERNAL_ENV)
*/
function isNonProductionCypressInternalEnvValue(value) {
return !_.isUndefined(value) && value !== 'production';
}
/**
* Prints NODE_OPTIONS using debug() module, but only
* if DEBUG=cypress... is set
*/
function printNodeOptions(log = debug$2) {
if (!log.enabled) {
return;
}
if (process.env.NODE_OPTIONS) {
log('NODE_OPTIONS=%s', process.env.NODE_OPTIONS);
}
else {
log('NODE_OPTIONS is not set');
}
}
/**
* Removes double quote characters
* from the start and end of the given string IF they are both present
*
* @param {string} str Input string
* @returns {string} Trimmed string or the original string if there are no double quotes around it.
* @example
```
dequote('"foo"')
// returns string 'foo'
dequote('foo')
// returns string 'foo'
```
*/
const dequote = (str) => {
assert.ok(_.isString(str), 'expected a string to remove double quotes');
if (str.length > 1 && str[0] === '"' && str[str.length - 1] === '"') {
return str.substr(1, str.length - 2);
}
return str;
};
const parseOpts = (opts) => {
opts = _.pick(opts, 'autoCancelAfterFailures', 'browser', 'cachePath', 'cacheList', 'cacheClear', 'cachePrune', 'ciBuildId', 'ct', 'component', 'config', 'configFile', 'cypressVersion', 'destination', 'detached', 'dev', 'e2e', 'exit', 'env', 'expose', 'force', 'global', 'group', 'headed', 'headless', 'inspect', 'inspectBrk', 'key', 'path', 'parallel', 'passWithNoTests', 'port', 'posixExitCodes', 'project', 'quiet', 'reporter', 'reporterOptions', 'record', 'runnerUi', 'runProject', 'spec', 'tag');
if (opts.exit) {
opts = _.omit(opts, 'exit');
}
// some options might be quoted - which leads to unexpected results
// remove double quotes from certain options
const cleanOpts = Object.assign({}, opts);
const toDequote = ['group', 'ciBuildId'];
for (const prop of toDequote) {
if (_.has(opts, prop)) {
cleanOpts[prop] = dequote(opts[prop]);
}
}
debug$2('parsed cli options %o', cleanOpts);
return cleanOpts;
};
/**
* Copy of packages/server/lib/browsers/utils.ts
* because we need same functionality in CLI to show the path :(
*/
const getApplicationDataFolder = (...paths) => {
const { env } = process;
// allow overriding the app_data folder
let folder = env.CYPRESS_CONFIG_ENV || env.CYPRESS_INTERNAL_ENV || 'development';
// eslint-disable-next-line no-restricted-syntax
const pkg = JSON.parse(fs$1.readFileSync(relativeToRepoRoot('package.json'), 'utf8'));
const PRODUCT_NAME = pkg.productName || pkg.name;
const OS_DATA_PATH = ospath.data();
const ELECTRON_APP_DATA_PATH = path.join(OS_DATA_PATH, PRODUCT_NAME);
if (process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF) {
folder = `${folder}-e2e-test`;
}
const p = path.join(ELECTRON_APP_DATA_PATH, 'cy', folder, ...paths);
return p;
};
const util = {
normalizeModuleOptions,
parseOpts,
isValidCypressInternalEnvValue,
isNonProductionCypressInternalEnvValue,
printNodeOptions,
isCi() {
return ciInfo.isCI;
},
getEnvOverrides(options = {}) {
return _
.chain({})
.extend(this.getEnvColors())
.extend(this.getForceTty())
.omitBy(_.isUndefined) // remove undefined values
.mapValues((value) => {
return value ? '1' : '0';
})
.extend(this.getOriginalNodeOptions())
.value();
},
getOriginalNodeOptions() {
const opts = {};
if (process.env.NODE_OPTIONS) {
opts.ORIGINAL_NODE_OPTIONS = process.env.NODE_OPTIONS;
}
return opts;
},
getForceTty() {
return {
FORCE_STDIN_TTY: this.isTty(process.stdin.fd),
FORCE_STDOUT_TTY: this.isTty(process.stdout.fd),
FORCE_STDERR_TTY: this.isTty(process.stderr.fd),
};
},
getEnvColors() {
const sc = this.supportsColor();
return {
FORCE_COLOR: sc,
DEBUG_COLORS: sc,
MOCHA_COLORS: sc ? true : undefined,
};
},
isTty(fd) {
return tty.isatty(fd);
},
supportsColor() {
// if we've been explicitly told not to support
// color then turn this off
if (process.env.NO_COLOR) {
return false;
}
// https://github.com/cypress-io/cypress/issues/1747
// always return true in CI providers
if (process.env.CI) {
return true;
}
// ensure that both stdout and stderr support color
return Boolean(supportsColor.stdout) && Boolean(supportsColor.stderr);
},
cwd() {
return process$1.cwd();
},
pkgBuildInfo() {
// making this async would require many changes
// eslint-disable-next-line no-restricted-syntax
const pkgContent = fs$1.readFileSync(relativeToRepoRoot('package.json'), 'utf8');
return JSON.parse(pkgContent).buildInfo;
},
pkgVersion() {
// making this async would require many changes
// eslint-disable-next-line no-restricted-syntax
const pkgContent = fs$1.readFileSync(relativeToRepoRoot('package.json'), 'utf8');
return JSON.parse(pkgContent).version;
},
// TODO: remove this method
exit(code) {
process.exit(code);
},
logErrorExit1(err) {
loggerModule.error(err.message);
process.exit(1);
},
dequote,
titleize(...args) {
// prepend first arg with space
// and pad so that all messages line up
args[0] = _.padEnd(` ${args[0]}`, 24);
// get rid of any falsy values
args = _.compact(args);
return chalk.blue(...args);
},
calculateEta(percent, elapsed) {
// returns the number of seconds remaining
// if we're at 100% already just return 0
if (percent === 100) {
return 0;
}
// take the percentage and divide by one
// and multiple that against elapsed
// subtracting what's already elapsed
return elapsed * (1 / (percent / 100)) - elapsed;
},
convertPercentToPercentage(num) {
// convert a percent with values between 0 and 1
// with decimals, so that it is between 0 and 100
// and has no decimal places
return Math.round(_.isFinite(num) ? (num * 100) : 0);
},
secsRemaining(eta) {
// calculate the seconds reminaing with no decimal places
return (_.isFinite(eta) ? (eta / 1000) : 0).toFixed(0);
},
setTaskTitle(task, title, renderer) {
// only update the renderer title when not running in CI
if (renderer === 'default' && task.title !== title) {
task.title = title;
}
},
isInstalledGlobally() {
return isInstalledGlobally;
},
isSemver(str) {
return /^(\d+\.)?(\d+\.)?(\*|\d+)$/.test(str);
},
isExecutableAsync(filePath) {
return Promise.resolve(executable(filePath));
},
isLinux,
getOsVersionAsync() {
return __awaiter(this, void 0, void 0, function* () {
try {
const osInfo = yield si.osInfo();
if (osInfo.distro && osInfo.release) {
return `${osInfo.distro} - ${osInfo.release}`;
}
}
catch (err) {
return os.release();
}
return os.release();
});
},
getPlatformInfo() {
return __awaiter(this, void 0, void 0, function* () {
const [version, osArch] = yield Bluebird.all([
this.getOsVersionAsync(),
this.getRealArch(),
]);
return commonTags.stripIndent `
Platform: ${os.platform()}-${osArch} (${version})
Cypress Version: ${this.pkgVersion()}
`;
});
},
_cachedArch: undefined,
/**
* Attempt to return the real system arch (not process.arch, which is only the Node binary's arch)
*/
getRealArch() {
return __awaiter(this, void 0, void 0, function* () {
if (this._cachedArch)
return this._cachedArch;
function _getRealArch() {
return __awaiter(this, void 0, void 0, function* () {
const osPlatform = os.platform();
const osArch = os.arch();
debug$2('detecting arch %o', { osPlatform, osArch });
if (osArch === 'arm64')
return 'arm64';
if (osPlatform === 'darwin') {
// could possibly be x64 node on arm64 darwin, check if we are being translated by Rosetta
// https://stackoverflow.com/a/65347893/3474615
const { stdout } = yield execa('sysctl', ['-n', 'sysctl.proc_translated']).catch(() => ({ stdout: '' }));
debug$2('rosetta check result: %o', { stdout });
if (stdout === '1')
return 'arm64';
}
if (osPlatform === 'linux') {
// could possibly be x64 node on arm64 linux, check the "machine hardware name"
// list of names for reference: https://stackoverflow.com/a/45125525/3474615
const { stdout } = yield execa('uname', ['-m']).catch(() => ({ stdout: '' }));
debug$2('arm uname -m result: %o ', { stdout });
if (['aarch64_be', 'aarch64', 'armv8b', 'armv8l'].includes(stdout))
return 'arm64';
}
const pkgArch = arch();
if (pkgArch === 'x86')
return 'ia32';
return pkgArch;
});
}
return (this._cachedArch = yield _getRealArch());
});
},
// attention:
// when passing relative path to NPM post install hook, the current working
// directory is set to the `node_modules/cypress` folder
// the user is probably passing relative path with respect to root package folder
formAbsolutePath(filename) {
if (path.isAbsolute(filename)) {
return filename;
}
return path.join(process$1.cwd(), '..', '..', filename);
},
getEnv(varName, trim) {
assert.ok(_.isString(varName) && !_.isEmpty(varName), 'expected environment variable name, not');
const configVarName = `npm_config_${varName}`;
const configVarNameLower = configVarName.toLowerCase();
const packageConfigVarName = `npm_package_config_${varName}`;
let result;
if (process.env.hasOwnProperty(varName)) {
debug$2(`Using ${varName} from environment variable`);
result = process.env[varName];
}
else if (process.env.hasOwnProperty(configVarName)) {
debug$2(`Using ${varName} from npm config`);
result = process.env[configVarName];
}
else if (process.env.hasOwnProperty(configVarNameLower)) {
debug$2(`Using ${varName.toLowerCase()} from npm config`);
result = process.env[configVarNameLower];
}
else if (process.env.hasOwnProperty(packageConfigVarName)) {
debug$2(`Using ${varName} from package.json config`);
result = process.env[packageConfigVarName];
}
// environment variables are often set double quotes to escape characters
// and on Windows it can lead to weird things: for example
// set FOO="C:\foo.txt" && node -e "console.log('>>>%s<<<', process.env.FOO)"
// will print
// >>>"C:\foo.txt" <<<
// see https://github.com/cypress-io/cypress/issues/4506#issuecomment-506029942
// so for sanity sake we should first trim whitespace characters and remove
// double quotes around environment strings if the caller is expected to
// use this environment string as a file path
return trim && (result !== null && result !== undefined) ? dequote(_.trim(result)) : result;
},
getCacheDir() {
return cachedir('Cypress');
},
isPostInstall() {
return process.env.npm_lifecycle_event === 'postinstall';
},
exec: execa,
stdoutLineMatches,
issuesUrl,
isBrokenGtkDisplay,
logBrokenGtkDisplayWarning,
isPossibleLinuxWithIncorrectDisplay,
getGitHubIssueUrl(number) {
assert.ok(_.isInteger(number), 'github issue should be an integer');
assert.ok(number > 0, 'github issue should be a positive number');
return `${issuesUrl}/${number}`;
},
getFileChecksum,
getFileSize,
getApplicationDataFolder,
};
const debug$1 = Debug('cypress:cli');
const getPlatformExecutable = () => {
const platform = os.platform();
switch (platform) {
case 'darwin': return 'Contents/MacOS/Cypress';
case 'linux': return 'Cypress';
case 'win32': return 'Cypress.exe';
// TODO handle this error using our standard
default: throw new Error(`Platform: "${platform}" is not supported.`);
}
};
const getPlatFormBinaryFolder = () => {
const platform = os.platform();
switch (platform) {
case 'darwin': return 'Cypress.app';
case 'linux': return 'Cypress';
case 'win32': return 'Cypress';
// TODO handle this error using our standard
default: throw new Error(`Platform: "${platform}" is not supported.`);
}
};
const getBinaryPkgPath = (binaryDir) => {
const platform = os.platform();
switch (platform) {
case 'darwin': return path.join(binaryDir, 'Contents', 'Resources', 'app', 'package.json');
case 'linux': return path.join(binaryDir, 'resources', 'app', 'package.json');
case 'win32': return path.join(binaryDir, 'resources', 'app', 'package.json');
// TODO handle this error using our standard
default: throw new Error(`Platform: "${platform}" is not supported.`);
}
};
/**
* Get path to binary directory
*/
const getBinaryDir = (version = util.pkgVersion()) => {
return path.join(getVersionDir(version), getPlatFormBinaryFolder());
};
const getVersionDir = (version = util.pkgVersion(), buildInfo = util.pkgBuildInfo()) => {
if (buildInfo && !buildInfo.stable) {
version = ['beta', version, buildInfo.commitBranch, buildInfo.commitSha.slice(0, 8)].join('-');
}
return path.join(getCacheDir(), version);
};
/**
* When executing "npm postinstall" hook, the working directory is set to
* "<current folder>/node_modules/cypress", which can be surprising when using relative paths.
*/
const isInstallingFromPostinstallHook = () => {
// individual folders
const cwdFolders = process$1.cwd().split(path.sep);
const length = cwdFolders.length;
return cwdFolders[length - 2] === 'node_modules' && cwdFolders[length - 1] === 'cypress';
};
const getCacheDir = () => {
let cache_directory = util.getCacheDir();
if (util.getEnv('CYPRESS_CACHE_FOLDER')) {
const envVarCacheDir = untildify(util.getEnv('CYPRESS_CACHE_FOLDER'));
debug$1('using environment variable CYPRESS_CACHE_FOLDER %s', envVarCacheDir);
if (!path.isAbsolute(envVarCacheDir) && isInstallingFromPostinstallHook()) {
const packageRootFolder = path.join('..', '..', envVarCacheDir);
cache_directory = path.resolve(packageRootFolder);
debug$1('installing from postinstall hook, original root folder is %s', packageRootFolder);
debug$1('and resolved cache directory is %s', cache_directory);
}
else {
cache_directory = path.resolve(envVarCacheDir);
}
}
return cache_directory;
};
const parseRealPlatformBinaryFolderAsync = (binaryPath) => __awaiter(void 0, void 0, void 0, function* () {
const realPath = yield fs$1.realpath(binaryPath);
debug$1('CYPRESS_RUN_BINARY has realpath:', realPath);
if (!realPath.toString().endsWith(getPlatformExecutable())) {
return false;
}
if (os.platform() === 'darwin') {
return path.resolve(realPath, '..', '..', '..');
}
return path.resolve(realPath, '..');
});
const getDistDir = () => {
return path.join(__dirname, '..', '..', 'dist');
};
/**
* Returns full filename to the file that keeps the Test Runner verification state as JSON text.
* Note: the binary state file will be stored one level up from the given binary folder.
* @param {string} binaryDir - full path to the folder holding the binary.
*/
const getBinaryStatePath = (binaryDir) => {
return path.join(binaryDir, '..', 'binary_state.json');
};
const getBinaryStateContentsAsync = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
const fullPath = getBinaryStatePath(binaryDir);
try {
const contents = yield fs$1.readJson(fullPath);
debug$1('binary_state.json contents:', contents);
return contents;
}
catch (error) {
if (error.code === 'ENOENT' || error instanceof SyntaxError) {
debug$1('could not read binary_state.json file at "%s"', fullPath);
return {};
}
throw error;
}
});
const getBinaryVerifiedAsync = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
const contents = yield getBinaryStateContentsAsync(binaryDir);
return contents.verified;
});
const clearBinaryStateAsync = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
yield fs$1.remove(getBinaryStatePath(binaryDir));
});
/**
* Writes the new binary status.
* @param {boolean} verified The new test runner state after smoke test
* @param {string} binaryDir Folder holding the binary
* @returns {Promise<void>} returns a promise
*/
const writeBinaryVerifiedAsync = (verified, binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
const contents = yield getBinaryStateContentsAsync(binaryDir);
yield fs$1.outputJson(getBinaryStatePath(binaryDir), _.extend(contents, { verified }), { spaces: 2 });
});
const getPathToExecutable = (binaryDir) => {
return path.join(binaryDir, getPlatformExecutable());
};
/**
* Resolves with an object read from the binary app package.json file.
* If the file does not exist resolves with null
*/
const getBinaryPkgAsync = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
const pathToPackageJson = getBinaryPkgPath(binaryDir);
debug$1('Reading binary package.json from:', pathToPackageJson);
const exists = yield fs$1.pathExists(pathToPackageJson);
if (!exists) {
return null;
}
return fs$1.readJson(pathToPackageJson);
});
const getBinaryPkgVersion = (o) => _.get(o, 'version', null);
const getBinaryElectronVersion = (o) => _.get(o, 'electronVersion', null);
const getBinaryElectronNodeVersion = (o) => _.get(o, 'electronNodeVersion', null);
const stateModule = {
getPathToExecutable,
getPlatformExecutable,
// those names start to sound like Java
getBinaryElectronNodeVersion,
getBinaryElectronVersion,
getBinaryPkgVersion,
getBinaryVerifiedAsync,
getBinaryPkgAsync,
getBinaryPkgPath,
getBinaryDir,
getCacheDir,
clearBinaryStateAsync,
writeBinaryVerifiedAsync,
parseRealPlatformBinaryFolderAsync,
getDistDir,
getVersionDir,
};
const docsUrl = 'https://on.cypress.io';
const requiredDependenciesUrl = `${docsUrl}/required-dependencies`;
const runDocumentationUrl = `${docsUrl}/cypress-run`;
// TODO it would be nice if all error objects could be enforced via types
// to only have description + solution properties
const hr = '----------';
const genericErrorSolution = commonTags.stripIndent `
Search for an existing issue or open a GitHub issue at
${chalk.blue(util.issuesUrl)}
`;
// common errors Cypress application can encounter
const unknownError = {
description: 'Unknown Cypress CLI error',
solution: genericErrorSolution,
};
const invalidRunProjectPath = {
description: 'Invalid --project path',
solution: commonTags.stripIndent `
Please provide a valid project path.
Learn more about ${chalk.cyan('cypress run')} at:
${chalk.blue(runDocumentationUrl)}
`,
};
const invalidOS = {
description: 'The Cypress App could not be installed. Your machine does not meet the operating system requirements.',
solution: commonTags.stripIndent `
${chalk.blue('https://on.cypress.io/app/get-started/install-cypress#System-requirements')}`,
};
const failedDownload = {
description: 'The Cypress App could not be downloaded.',
solution: commonTags.stripIndent `
Does your workplace require a proxy to be used to access the Internet? If so, you must configure the HTTP_PROXY environment variable before downloading Cypress. Read more: https://on.cypress.io/proxy-configuration
Otherwise, please check network connectivity and try again:`,
};
const failedUnzip = {
description: 'The Cypress App could not be unzipped.',
solution: genericErrorSolution,
};
const failedUnzipWindowsMaxPathLength = {
description: 'The Cypress App could not be unzipped.',
solution: `This is most likely because the maximum path length is being exceeded on your system.
Read here for solutions to this problem: https://on.cypress.io/win-max-path-length-error`,
};
const missingApp = (binaryDir) => {
return {
description: `No version of Cypress is installed in: ${chalk.cyan(binaryDir)}`,
solution: commonTags.stripIndent `
\nPlease reinstall Cypress by running: ${chalk.cyan('cypress install')}
`,
};
};
const binaryNotExecutable = (executable) => {
return {
description: `Cypress cannot run because this binary file does not have executable permissions here:\n\n${executable}`,
solution: commonTags.stripIndent `\n
Reasons this may happen:
- node was installed as 'root' or with 'sudo'
- the cypress npm package as 'root' or with 'sudo'
Please check that you have the appropriate user permissions.
You can also try clearing the cache with 'cypress cache clear' and reinstalling.
`,
};
};
const notInstalledCI = (executable) => {
return {
description: 'The cypress npm package is installed, but the Cypress binary is missing.',
solution: commonTags.stripIndent `\n
We expected the binary to be installed here: ${chalk.cyan(executable)}
Reasons it may be missing:
- You're caching 'node_modules' but are not caching this path: ${util.getCacheDir()}
- You ran 'npm install' at an earlier build step but did not persist: ${util.getCacheDir()}
Properly caching the binary will fix this error and avoid downloading and unzipping Cypress.
Alternatively, you can run 'cypress install' to download the binary again.
${chalk.blue('https://on.cypress.io/not-installed-ci-error')}
`,
};
};
const nonZeroExitCodeXvfb = {
description: 'Xvfb exited with a non zero exit code.',
solution: commonTags.stripIndent `
There was a problem spawning Xvfb.
This is likely a problem with your system, permissions, or installation of Xvfb.
`,
};
const missingXvfb = {
description: 'Your system is missing the dependency: Xvfb',
solution: commonTags.stripIndent `
Install Xvfb and run Cypress again.
Read our documentation on dependencies for more information:
${chalk.blue(requiredDependenciesUrl)}
If you are using Docker, we provide containers with all required dependencies installed.
`,
};
const smokeTestFailure = (smokeTestCommand, timedOut) => {
return {
description: `Cypress verification ${timedOut ? 'timed out' : 'failed'}.`,
solution: commonTags.stripIndent `
This command failed with the following output:
${smokeTestCommand}
`,
};
};
const invalidSmokeTestDisplayError = {
code: 'INVALID_SMOKE_TEST_DISPLAY_ERROR',
description: 'Cypress verification failed.',
solution(msg) {
return commonTags.stripIndent `
Cypress failed to start after spawning a new Xvfb server.
The error logs we received were:
${hr}
${msg}
${hr}
This may be due to a missing library or dependency. ${chalk.blue(requiredDependenciesUrl)}
Please refer to the error above for more detail.
`;
},
};
const missingDependency = {
description: 'Cypress failed to start.',
// this message is too Linux specific
solution: commonTags.stripIndent `
This may be due to a missing library or dependency. ${chalk.blue(requiredDependenciesUrl)}
Please refer to the error below for more details.
`,
};
const invalidCacheDirectory = {
description: 'Cypress cannot write to the cache directory due to file permissions',
solution: commonTags.stripIndent `
See discussion and possible solutions at
${chalk.blue(util.getGitHubIssueUrl(1281))}
`,
};
const versionMismatch = {
description: 'Installed version does not match package version.',
solution: 'Install Cypress and verify app again',
};
const incompatibleHeadlessFlags = {
description: '`--headed` and `--headless` cannot both be passed.',
solution: 'Either pass `--headed` or `--headless`, but not both.',
};
const solutionUnknown = commonTags.stripIndent `
Please search Cypress documentation for possible solutions:
${chalk.blue(docsUrl)}
Check if there is a GitHub issue describing this crash:
${chalk.blue(util.issuesUrl)}
Consider opening a new issue.
`;
const unexpected = {
description: 'An unexpected error occurred while verifying the Cypress executable.',
solution: solutionUnknown,
};
const invalidCypressEnv = {
description: chalk.red('The environment variable with the reserved name "CYPRESS_INTERNAL_ENV" is set.'),
solution: chalk.red('Unset the "CYPRESS_INTERNAL_ENV" environment variable and run Cypress again.'),
exitCode: 11,
};
const invalidTestingType = {
description: 'Invalid testingType',
solution: `Please provide a valid testingType. Valid test types are ${chalk.cyan('\'e2e\'')} and ${chalk.cyan('\'component\'')}.`,
};
const incompatibleTestTypeFlags = {
description: '`--e2e` and `--component` cannot both be passed.',
solution: 'Either pass `--e2e` or `--component`, but not both.',
};
const incompatibleTestingTypeAndFlag = {
description: 'Set a `testingType` and also passed `--e2e` or `--component` flags.',
solution: 'Either set `testingType` or pass a testing type flag, but not both.',
};
const invalidConfigFile = {
description: '`--config-file` cannot be false.',
solution: 'Either pass a relative path to a valid Cypress config file or remove this option.',
};
/**
* This error happens when CLI detects that the child Test Runner process
* was killed with a signal, like SIGBUS
* @see https://github.com/cypress-io/cypress/issues/5808
* @param {'close'|'event'} eventName Child close event name
* @param {string} signal Signal that closed the child process, like "SIGBUS"
*/
const childProcessKilled = (eventName, signal) => {
return {
description: `The Test Runner unexpectedly exited via a ${chalk.cyan(eventName)} event with signal ${chalk.cyan(signal)}`,
solution: solutionUnknown,
};
};
const CYPRESS_RUN_BINARY = {
notValid: (value) => {
const properFormat = `**/${stateModule.getPlatformExecutable()}`;
return {
description: `Could not run binary set by environment variable: CYPRESS_RUN_BINARY=${value}`,
solution: `Ensure the environment variable is a path to the Cypress binary, matching ${properFormat}`,
};
},
};
function getErrorSync(errorObject, platform) {
const errorMessage = syncFormErrorText(Object.assign(Object.assign({}, errorObject), { platform }));
const err = new Error(errorMessage);
err.known = true;
return err;
}
function formErrorText(info, msg, prevMessage) {
return __awaiter(this, void 0, void 0, function* () {
const platform = yield util.getPlatformInfo();
return syncFormErrorText(Object.assign(Object.assign({}, info), { platform }), msg, prevMessage);
});
}
function syncFormErrorText(info, msg, prevMessage) {
const formatted = [];
function add(msg) {
formatted.push(commonTags.stripIndents(msg));
}
assert.ok(_.isString(info.description) && !_.isEmpty(info.description), 'expected error description to be text.');
// assuming that if there the solution is a function it will handle
// error message and (optional previous error message)
if (_.isFunction(info.solution)) {
const text = info.solution(msg, prevMessage);
assert.ok(_.isString(text) && !_.isEmpty(text), 'expected solution to be text.');
add(`
${info.description}
${text}
`);
}
else {
assert.ok(_.isString(info.solution) && !_.isEmpty(info.solution), 'expected error solution to be text.');
add(`
${info.description}
${info.solution}
`);
if (msg) {
add(`
${hr}
${msg}
`);
}
}
add(`
${hr}
${info.platform}
`);
if (info.footer) {
add(`
${hr}
${info.footer}
`);
}
return formatted.join('\n\n');
}
const raise = (info) => {
return (text) => {
const err = new Error(text);
if (info.code) {
err.code = info.code;
}
err.known = true;
throw err;
};
};
const throwFormErrorText = (info) => {
return (msg, prevMessage) => __awaiter(void 0, void 0, void 0, function* () {
const errorText = yield formErrorText(info, msg, prevMessage);
raise(info)(errorText);
});
};
/**
* Forms full error message with error and OS details, prints to the error output
* and then exits the process.
* @param {ErrorInformation} info Error information {description, solution}
* @example return exitWithError(errors.invalidCypressEnv)('foo')
*/
const exitWithError = (info) => {
return (msg) => __awaiter(void 0, void 0, void 0, function* () {
const text = yield formErrorText(info, msg);
console.error(text);
process.exit(info.exitCode || 1);
});
};
const errors = {
unknownError,
nonZeroExitCodeXvfb,
missingXvfb,
missingApp,
notInstalledCI,
missingDependency,
invalidOS,
invalidSmokeTestDisplayError,
versionMismatch,
binaryNotExecutable,
unexpected,
failedDownload,
failedUnzip,
failedUnzipWindowsMaxPathLength,
invalidCypressEnv,
invalidCacheDirectory,
CYPRESS_RUN_BINARY,
smokeTestFailure,
childProcessKilled,
incompatibleHeadlessFlags,
invalidRunProjectPath,
invalidTestingType,
incompatibleTestTypeFlags,
incompatibleTestingTypeAndFlag,
invalidConfigFile,
};
const debug = Debug('cypress:cli');
const debugXvfb = Debug('cypress:xvfb');
debug.Debug = debugXvfb.Debug = Debug;
const xvfbOptions = {
displayNum: process.env.XVFB_DISPLAY_NUM,
timeout: 30000, // milliseconds
// need to explicitly define screen otherwise electron will crash
// https://github.com/cypress-io/cypress/issues/6184
xvfb_args: ['-screen', '0', '1280x1024x24'],
onStderrData(data) {
if (debugXvfb.enabled) {
debugXvfb(data.toString());
}
},
};
const xvfb = Bluebird.promisifyAll(new Xvfb(xvfbOptions));
const _debugXvfb = debugXvfb;
const _xvfb = xvfb;
const _xvfbOptions = xvfbOptions;
function start() {
return __awaiter(this, void 0, void 0, function* () {
debug('Starting Xvfb');
try {
yield xvfb.startAsync();
return null;
}
catch (e) {
if (e.nonZeroExitCode === true) {
const raiseErrorFn = throwFormErrorText(errors.nonZeroExitCodeXvfb);
yield raiseErrorFn(e);
}
if (e.known) {
throw e;
}
const raiseErrorFn = throwFormErrorText(errors.missingXvfb);
yield raiseErrorFn(e);
}
});
}
function stop() {
return __awaiter(this, void 0, void 0, function* () {
debug('Stopping Xvfb');
try {
yield xvfb.stopAsync();
return null;
}
catch (e) {
return null;
}
});
}
function isNeeded() {
if (process.env.ELECTRON_RUN_AS_NODE) {
debug('Environment variable ELECTRON_RUN_AS_NODE detected, xvfb is not needed');
return false; // xvfb required for electron processes only.
}
if (os.platform() !== 'linux') {
return false;
}
if (process.env.DISPLAY) {
const issueUrl = util.getGitHubIssueUrl(4034);
const message = commonTags.stripIndent `
DISPLAY environment variable is set to ${process.env.DISPLAY} on Linux
Assuming this DISPLAY points at working X11 server,
Cypress will not spawn own Xvfb
NOTE: if the X11 server is NOT working, Cypress will exit without explanation,
see ${issueUrl}
Solution: Unset the DISPLAY variable and try again:
DISPLAY= npx cypress run ...
`;
debug(message);
return false;
}
debug('undefined DISPLAY environment variable');
debug('Cypress will spawn its own Xvfb');
return true;
}
// async method, resolved with Boolean
function verify() {
return __awaiter(this, void 0, void 0, function* () {
try {
yield xvfb.startAsync();
return true;
}
catch (err) {
debug('Could not verify xvfb: %s', err.message);
return false;
}
finally {
yield xvfb.stopAsync();
}
});
}
var xvfb$1 = {
_debugXvfb,
_xvfb,
_xvfbOptions,
start,
stop,
isNeeded,
verify,
};
exports.__awaiter = __awaiter;
exports._debugXvfb = _debugXvfb;
exports._xvfb = _xvfb;
exports._xvfbOptions = _xvfbOptions;
exports.errors = errors;
exports.exitWithError = exitWithError;
exports.getErrorSync = getErrorSync;
exports.isNeeded = isNeeded;
exports.loggerModule = loggerModule;
exports.relativeToRepoRoot = relativeToRepoRoot;
exports.start = start;
exports.stateModule = stateModule;
exports.stop = stop;
exports.throwFormErrorText = throwFormErrorText;
exports.util = util;
exports.verify = verify;
exports.xvfb = xvfb$1;
+6
-3

@@ -5,3 +5,3 @@ 'use strict';

require('./xvfb-D9xcxM5q.js');
require('./xvfb-D-nbLwPu.js');
require('lodash');

@@ -12,4 +12,4 @@ require('commander');

require('debug');
var cli = require('./cli-BOVvrUqJ.js');
require('./spawn-Bjv8F3GP.js');
var cli = require('./cli-CMmiXnYu.js');
require('./spawn-Cbp8Y4I3.js');
require('os');

@@ -52,2 +52,5 @@ require('bluebird');

require('cli-cursor');
require('stream');
require('string_decoder');
require('node:string_decoder');

@@ -54,0 +57,0 @@

'use strict';
require('./xvfb-D9xcxM5q.js');
require('./xvfb-D-nbLwPu.js');
require('tmp');
require('fs-extra');
require('./cli-BOVvrUqJ.js');
var cypress = require('./cypress-Dzgf-C6F.js');
require('./cli-CMmiXnYu.js');
var cypress = require('./cypress-CtKCB-k6.js');
require('os');

@@ -36,3 +36,3 @@ require('bluebird');

require('dayjs/plugin/relativeTime');
require('./spawn-Bjv8F3GP.js');
require('./spawn-Cbp8Y4I3.js');
require('child_process');

@@ -43,2 +43,5 @@ require('listr2');

require('readline');
require('stream');
require('string_decoder');
require('node:string_decoder');
require('timers/promises');

@@ -45,0 +48,0 @@ require('fs/promises');

'use strict';
require('../xvfb-D9xcxM5q.js');
require('../xvfb-D-nbLwPu.js');
require('lodash');

@@ -9,5 +9,6 @@ require('os');

require('debug');
var spawn = require('../spawn-Bjv8F3GP.js');
var spawn = require('../spawn-Cbp8Y4I3.js');
require('readline');
require('process');
require('stream');
require('bluebird');

@@ -37,2 +38,4 @@ require('@cypress/xvfb');

require('dayjs');
require('string_decoder');
require('node:string_decoder');

@@ -39,0 +42,0 @@

@@ -5,3 +5,3 @@ 'use strict';

var xvfb = require('../xvfb-D9xcxM5q.js');
var xvfb = require('../xvfb-D-nbLwPu.js');
require('os');

@@ -8,0 +8,0 @@ require('bluebird');

@@ -5,8 +5,8 @@ 'use strict';

var xvfb = require('./xvfb-D9xcxM5q.js');
var xvfb = require('./xvfb-D-nbLwPu.js');
var minimist = require('minimist');
var Debug = require('debug');
var cli$1 = require('./cli-BOVvrUqJ.js');
var spawn = require('./spawn-Bjv8F3GP.js');
var cypress = require('./cypress-Dzgf-C6F.js');
var cli$1 = require('./cli-CMmiXnYu.js');
var spawn = require('./spawn-Cbp8Y4I3.js');
var cypress = require('./cypress-CtKCB-k6.js');
require('os');

@@ -53,2 +53,5 @@ require('bluebird');

require('cli-cursor');
require('stream');
require('string_decoder');
require('node:string_decoder');
require('tmp');

@@ -55,0 +58,0 @@

{
"name": "cypress",
"version": "15.11.0",
"version": "15.12.0",
"main": "dist/index.js",

@@ -145,4 +145,4 @@ "scripts": {

"commitBranch": "develop",
"commitSha": "94ff6dbcf34843aff50d05b6f85bbf48ca0d38d4",
"commitDate": "2026-02-24T20:53:13.000Z",
"commitSha": "dbb806af7cb2e657ad659675bc9b38a65a3d4a18",
"commitDate": "2026-03-10T14:34:58.000Z",
"stable": true

@@ -149,0 +149,0 @@ },

Sorry, the diff of this file is too big to display

'use strict';
var xvfb = require('./xvfb-D9xcxM5q.js');
var tmp = require('tmp');
var fs = require('fs-extra');
var cli$1 = require('./cli-BOVvrUqJ.js');
/**
* Opens Cypress GUI
* @see https://on.cypress.io/module-api#cypress-open
*/
function open(options = {}) {
options = xvfb.util.normalizeModuleOptions(options);
return cli$1.openModule.start(options);
}
/**
* Runs Cypress tests in the current project
* @see https://on.cypress.io/module-api#cypress-run
*/
function run() {
return xvfb.__awaiter(this, arguments, void 0, function* (options = {}) {
if (!cli$1.runModule.isValidProject(options.project)) {
throw new Error(`Invalid project path parameter: ${options.project}`);
}
options = xvfb.util.normalizeModuleOptions(options);
tmp.setGracefulCleanup();
const outputPath = tmp.fileSync().name;
options.outputPath = outputPath;
const failedTests = yield cli$1.runModule.start(options);
const output = yield fs.readJson(outputPath, { throws: false });
if (!output) {
return {
status: 'failed',
failures: failedTests,
message: 'Could not find Cypress test run results',
};
}
return output;
});
}
const cli = {
/**
* Parses CLI arguments into an object that you can pass to "cypress.run"
* @example
* const cypress = require('cypress')
* const cli = ['cypress', 'run', '--browser', 'firefox']
* const options = await cypress.cli.parseRunArguments(cli)
* // options is {browser: 'firefox'}
* await cypress.run(options)
* @see https://on.cypress.io/module-api
*/
parseRunArguments(args) {
return cli$1.cliModule.parseRunCommand(args);
},
};
/**
* Provides automatic code completion for configuration in many popular code editors.
* While it's not strictly necessary for Cypress to parse your configuration, we
* recommend wrapping your config object with `defineConfig()`
* @example
* module.exports = defineConfig({
* viewportWith: 400
* })
*
* @see ../types/cypress-npm-api.d.ts
* @param {Cypress.ConfigOptions} config
* @returns {Cypress.ConfigOptions} the configuration passed in parameter
*/
function defineConfig(config) {
return config;
}
/**
* Provides automatic code completion for Component Frameworks Definitions.
* While it's not strictly necessary for Cypress to parse your configuration, we
* recommend wrapping your Component Framework Definition object with `defineComponentFramework()`
* @example
* module.exports = defineComponentFramework({
* type: 'cypress-ct-solid-js'
* // ...
* })
*
* @see ../types/cypress-npm-api.d.ts
* @param {Cypress.ThirdPartyComponentFrameworkDefinition} config
* @returns {Cypress.ThirdPartyComponentFrameworkDefinition} the configuration passed in parameter
*/
function defineComponentFramework(config) {
return config;
}
var cypress = /*#__PURE__*/Object.freeze({
__proto__: null,
cli: cli,
defineComponentFramework: defineComponentFramework,
defineConfig: defineConfig,
open: open,
run: run
});
exports.cli = cli;
exports.cypress = cypress;
exports.defineComponentFramework = defineComponentFramework;
exports.defineConfig = defineConfig;
exports.open = open;
exports.run = run;
'use strict';
var xvfb = require('./xvfb-D9xcxM5q.js');
var _ = require('lodash');
var os = require('os');
var cp = require('child_process');
var path = require('path');
var Debug = require('debug');
var chalk = require('chalk');
var listr2 = require('listr2');
var commonTags = require('common-tags');
var Bluebird = require('bluebird');
var logSymbols = require('log-symbols');
var figures = require('figures');
var cliCursor = require('cli-cursor');
var dayjs = require('dayjs');
var readline = require('readline');
var process$1 = require('process');
// Vendored from @cypress/listr-verbose-renderer
const formattedLog = (options, output) => {
const timestamp = dayjs().format(options.dateFormat);
// eslint-disable-next-line no-console
console.log(`${chalk.dim(`[${timestamp}]`)} ${output}`);
};
const renderHelper = (task, event, options) => {
const log = formattedLog.bind(undefined, options);
if (event.type === 'STATE') {
const message = task.isPending() ? 'started' : task.state;
log(`${task.title} [${message}]`);
if (task.isSkipped() && task.output) {
log(`${figures.arrowRight} ${task.output}`);
}
}
else if (event.type === 'TITLE') {
log(`${task.title} [title changed]`);
}
};
const render = (tasks, options) => {
for (const task of tasks) {
task.subscribe((event) => {
if (event.type === 'SUBTASKS') {
render(task.subtasks, options);
return;
}
renderHelper(task, event, options);
}, (err) => {
// eslint-disable-next-line no-console
console.log(err);
});
}
};
class VerboseRenderer {
constructor(tasks, options) {
this._tasks = tasks;
this._options = Object.assign({
dateFormat: 'HH:mm:ss',
}, options);
}
static get nonTTY() {
return true;
}
render() {
cliCursor.hide();
render(this._tasks, this._options);
}
end() {
cliCursor.show();
}
}
const debug$1 = Debug('cypress:cli');
const verifyTestRunnerTimeoutMs = () => {
const verifyTimeout = +((xvfb.util === null || xvfb.util === void 0 ? void 0 : xvfb.util.getEnv('CYPRESS_VERIFY_TIMEOUT')) || 'NaN');
if (_.isNumber(verifyTimeout) && !_.isNaN(verifyTimeout)) {
return verifyTimeout;
}
return 30000;
};
const checkExecutable = (binaryDir) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
const executable = xvfb.stateModule.getPathToExecutable(binaryDir);
debug$1('checking if executable exists', executable);
try {
const isExecutable = yield xvfb.util.isExecutableAsync(executable);
debug$1('Binary is executable? :', isExecutable);
if (!isExecutable) {
return xvfb.throwFormErrorText(xvfb.errors.binaryNotExecutable(executable))();
}
}
catch (err) {
if (err.code === 'ENOENT') {
if (xvfb.util.isCi()) {
return xvfb.throwFormErrorText(xvfb.errors.notInstalledCI(executable))();
}
return xvfb.throwFormErrorText(xvfb.errors.missingApp(binaryDir))(commonTags.stripIndent `
Cypress executable not found at: ${chalk.cyan(executable)}
`);
}
throw err;
}
});
const runSmokeTest = (binaryDir, options) => {
let executable = xvfb.stateModule.getPathToExecutable(binaryDir);
const needsXvfb = xvfb.xvfb.isNeeded();
debug$1('needs Xvfb?', needsXvfb);
/**
* Spawn Cypress running smoke test to check if all operating system
* dependencies are good.
*/
const spawn = (linuxWithDisplayEnv) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
const random = _.random(0, 1000);
const args = ['--smoke-test', `--ping=${random}`];
if (needsSandbox()) {
// electron requires --no-sandbox to run as root
debug$1('disabling Electron sandbox');
args.unshift('--no-sandbox');
}
if (options.dev) {
executable = 'node';
const startScriptPath = xvfb.relativeToRepoRoot('scripts/start.js');
if (!startScriptPath) {
throw new Error(`Cypress start script (scripts/start.js) not found in parent directory of ${__dirname}`);
}
args.unshift(startScriptPath);
}
const smokeTestCommand = `${executable} ${args.join(' ')}`;
debug$1('running smoke test');
debug$1('using Cypress executable %s', executable);
debug$1('smoke test command:', smokeTestCommand);
debug$1('smoke test timeout %d ms', options.smokeTestTimeout);
const stdioOptions = _.extend({}, {
env: Object.assign(Object.assign({}, process.env), { FORCE_COLOR: '0' }),
timeout: options.smokeTestTimeout,
});
try {
const result = yield xvfb.util.exec(executable, args, stdioOptions);
// TODO: when execa > 1.1 is released
// change this to `result.all` for both stderr and stdout
// use lodash to be robust during tests against null result or missing stdout
const smokeTestStdout = _.get(result, 'stdout', '');
debug$1('smoke test stdout "%s"', smokeTestStdout);
if (!xvfb.util.stdoutLineMatches(String(random), smokeTestStdout)) {
debug$1('Smoke test failed because could not find %d in:', random, result);
const smokeTestStderr = _.get(result, 'stderr', '');
const errorText = smokeTestStderr || smokeTestStdout;
return xvfb.throwFormErrorText(xvfb.errors.smokeTestFailure(smokeTestCommand, false))(errorText);
}
}
catch (err) {
debug$1('Smoke test failed:', err);
let errMessage = err.stderr || err.message;
debug$1('error message:', errMessage);
if (err.timedOut) {
debug$1('error timedOut is true');
return xvfb.throwFormErrorText(xvfb.errors.smokeTestFailure(smokeTestCommand, true))(errMessage);
}
if (linuxWithDisplayEnv && xvfb.util.isBrokenGtkDisplay(errMessage)) {
xvfb.util.logBrokenGtkDisplayWarning();
return xvfb.throwFormErrorText(xvfb.errors.invalidSmokeTestDisplayError)(errMessage);
}
return xvfb.throwFormErrorText(xvfb.errors.missingDependency)(errMessage);
}
});
const spawnInXvfb = (linuxWithDisplayEnv) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
yield xvfb.xvfb.start();
return spawn(linuxWithDisplayEnv || false).finally(() => xvfb.__awaiter(void 0, void 0, void 0, function* () {
yield xvfb.xvfb.stop();
}));
});
const userFriendlySpawn = (linuxWithDisplayEnv) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
debug$1('spawning, should retry on display problem?', Boolean(linuxWithDisplayEnv));
try {
yield spawn(linuxWithDisplayEnv);
}
catch (err) {
if (err.code === 'INVALID_SMOKE_TEST_DISPLAY_ERROR') {
return spawnInXvfb(linuxWithDisplayEnv);
}
throw err;
}
});
if (needsXvfb) {
return spawnInXvfb();
}
// if we are on linux and there's already a DISPLAY
// set, then we may need to rerun cypress after
// spawning our own Xvfb server
const linuxWithDisplayEnv = xvfb.util.isPossibleLinuxWithIncorrectDisplay();
return userFriendlySpawn(linuxWithDisplayEnv);
};
function testBinary(version, binaryDir, options) {
debug$1('running binary verification check', version);
// if running from 'cypress verify', don't print this message
if (!options.force) {
xvfb.loggerModule.log(commonTags.stripIndent `
It looks like this is your first time using Cypress: ${chalk.cyan(version)}
`);
}
xvfb.loggerModule.log();
// if we are running in CI then use
// the verbose renderer else use
// the default
let renderer = xvfb.util.isCi() ? VerboseRenderer : 'default';
// NOTE: under test we set the listr renderer to 'silent' in order to get deterministic snapshots
if (xvfb.loggerModule.logLevel() === 'silent' || options.listrRenderer)
renderer = 'silent';
const rendererOptions = {
renderer,
};
const tasks = new listr2.Listr([
{
title: xvfb.util.titleize('Verifying Cypress can run', chalk.gray(binaryDir)),
task: (ctx, task) => xvfb.__awaiter(this, void 0, void 0, function* () {
debug$1('clearing out the verified version');
yield xvfb.stateModule.clearBinaryStateAsync(binaryDir);
yield Promise.all([
runSmokeTest(binaryDir, options),
Bluebird.delay(1500), // good user experience
]);
debug$1('write verified: true');
yield xvfb.stateModule.writeBinaryVerifiedAsync(true, binaryDir);
xvfb.util.setTaskTitle(task, xvfb.util.titleize(chalk.green('Verified Cypress!'), chalk.gray(binaryDir)), rendererOptions.renderer);
}),
},
], rendererOptions);
return tasks.run();
}
const maybeVerify = (installedVersion, binaryDir, options) => xvfb.__awaiter(void 0, void 0, void 0, function* () {
const isVerified = yield xvfb.stateModule.getBinaryVerifiedAsync(binaryDir);
debug$1('is Verified ?', isVerified);
let shouldVerify = !isVerified;
// force verify if options.force
if (options.force) {
debug$1('force verify');
shouldVerify = true;
}
if (shouldVerify) {
yield testBinary(installedVersion, binaryDir, options);
if (options.welcomeMessage) {
xvfb.loggerModule.log();
xvfb.loggerModule.log('Opening Cypress...');
}
}
});
const start$1 = (...args_1) => xvfb.__awaiter(void 0, [...args_1], void 0, function* (options = {}) {
debug$1('verifying Cypress app');
_.defaults(options, {
dev: false,
force: false,
welcomeMessage: true,
smokeTestTimeout: verifyTestRunnerTimeoutMs(),
skipVerify: xvfb.util.getEnv('CYPRESS_SKIP_VERIFY') === 'true',
});
if (options.skipVerify) {
debug$1('skipping verification of the Cypress app');
return Promise.resolve();
}
const packageVersion = xvfb.util.pkgVersion();
let binaryDir = xvfb.stateModule.getBinaryDir(packageVersion);
if (options.dev) {
return runSmokeTest('', options);
}
const parseBinaryEnvVar = () => xvfb.__awaiter(void 0, void 0, void 0, function* () {
const envBinaryPath = xvfb.util.getEnv('CYPRESS_RUN_BINARY');
debug$1('CYPRESS_RUN_BINARY exists, =', envBinaryPath);
xvfb.loggerModule.log(commonTags.stripIndent `
${chalk.yellow('Note:')} You have set the environment variable:
${chalk.white('CYPRESS_RUN_BINARY=')}${chalk.cyan(envBinaryPath)}
This overrides the default Cypress binary path used.
`);
xvfb.loggerModule.log();
try {
const isExecutable = yield xvfb.util.isExecutableAsync(envBinaryPath);
debug$1('CYPRESS_RUN_BINARY is executable? :', isExecutable);
if (!isExecutable) {
return xvfb.throwFormErrorText(xvfb.errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(commonTags.stripIndent `
The supplied binary path is not executable
`);
}
const envBinaryDir = yield xvfb.stateModule.parseRealPlatformBinaryFolderAsync(envBinaryPath);
if (!envBinaryDir) {
return xvfb.throwFormErrorText(xvfb.errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))();
}
debug$1('CYPRESS_RUN_BINARY has binaryDir:', envBinaryDir);
binaryDir = envBinaryDir;
}
catch (err) {
if (err.code === 'ENOENT') {
return xvfb.throwFormErrorText(xvfb.errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(err.message);
}
throw err;
}
});
try {
debug$1('checking environment variables');
if (xvfb.util.getEnv('CYPRESS_RUN_BINARY')) {
yield parseBinaryEnvVar();
}
yield checkExecutable(binaryDir);
debug$1('binaryDir is ', binaryDir);
const pkg = yield xvfb.stateModule.getBinaryPkgAsync(binaryDir);
const binaryVersion = xvfb.stateModule.getBinaryPkgVersion(pkg);
if (!binaryVersion) {
debug$1('no Cypress binary found for cli version ', packageVersion);
return xvfb.throwFormErrorText(xvfb.errors.missingApp(binaryDir))(`
Cannot read binary version from: ${chalk.cyan(xvfb.stateModule.getBinaryPkgPath(binaryDir))}
`);
}
debug$1(`Found binary version ${chalk.green(binaryVersion)} installed in: ${chalk.cyan(binaryDir)}`);
if (binaryVersion !== packageVersion) {
// warn if we installed with CYPRESS_INSTALL_BINARY or changed version
// in the package.json
xvfb.loggerModule.log(`Found binary version ${chalk.green(binaryVersion)} installed in: ${chalk.cyan(binaryDir)}`);
xvfb.loggerModule.log();
xvfb.loggerModule.warn(commonTags.stripIndent `
${logSymbols.warning} Warning: Binary version ${chalk.green(binaryVersion)} does not match the expected package version ${chalk.green(packageVersion)}
These versions may not work properly together.
`);
xvfb.loggerModule.log();
}
yield maybeVerify(binaryVersion, binaryDir, options);
}
catch (err) {
if (err.known) {
throw err;
}
return xvfb.throwFormErrorText(xvfb.errors.unexpected)(err.stack);
}
});
const isLinuxLike = () => os.platform() !== 'win32';
/**
* Returns true if running on a system where Electron needs "--no-sandbox" flag.
* @see https://crbug.com/638180
*
* On Debian we had problems running in sandbox even for non-root users.
* @see https://github.com/cypress-io/cypress/issues/5434
* Seems there is a lot of discussion around this issue among Electron users
* @see https://github.com/electron/electron/issues/17972
*/
const needsSandbox = () => isLinuxLike();
const debug = Debug('cypress:cli');
const DBUS_ERROR_PATTERN = /ERROR:dbus\/(bus|object_proxy)\.cc/;
function isPlatform(platform) {
return os.platform() === platform;
}
function needsStderrPiped(needsXvfb) {
return _.some([
isPlatform('darwin'),
(needsXvfb && isPlatform('linux')),
xvfb.util.isPossibleLinuxWithIncorrectDisplay(),
]);
}
function needsEverythingPipedDirectly() {
return isPlatform('win32');
}
function getStdioStrategy(needsXvfb) {
if (needsEverythingPipedDirectly()) {
return 'pipe';
}
// https://github.com/cypress-io/cypress/issues/921
// https://github.com/cypress-io/cypress/issues/1143
// https://github.com/cypress-io/cypress/issues/1745
if (needsStderrPiped(needsXvfb)) {
// returning pipe here so we can massage stderr
// and remove garbage from Xlib and libuv
// due to starting the Xvfb process on linux
return ['inherit', 'inherit', 'pipe'];
}
return 'inherit';
}
function createSpawnFunction(executable, args, options) {
return (overrides = {}) => {
return new Promise((resolve, reject) => {
_.defaults(overrides, {
onStderrData: false,
});
const { onStderrData } = overrides;
const envOverrides = xvfb.util.getEnvOverrides(options);
const electronArgs = [];
const node11WindowsFix = isPlatform('win32');
let startScriptPath;
if (options.dev) {
executable = 'node';
// if we're in dev then reset
// the launch cmd to be 'npm run dev'
// This path is correct in the build output, but not the source code. This file gets bundled into
// `dist/spawn-<hash>.js`, which makes this resolution appear incorrect at first glance.
startScriptPath = xvfb.relativeToRepoRoot('scripts/start.js');
if (!startScriptPath) {
throw new Error(`Cypress start script (scripts/start.js) not found in parent directory of ${__dirname}`);
}
}
if (!options.dev && needsSandbox()) {
electronArgs.push('--no-sandbox');
}
// strip dev out of child process options
/**
* @type {import('child_process').ForkOptions}
*/
let stdioOptions = _.pick(options, 'env', 'detached', 'stdio');
// figure out if we're going to be force enabling or disabling colors.
// also figure out whether we should force stdout and stderr into thinking
// it is a tty as opposed to a pipe.
stdioOptions.env = _.extend({}, stdioOptions.env, envOverrides);
if (node11WindowsFix) {
stdioOptions = _.extend({}, stdioOptions, { windowsHide: false });
}
if (xvfb.util.isPossibleLinuxWithIncorrectDisplay()) {
// make sure we use the latest DISPLAY variable if any
debug('passing DISPLAY', process.env.DISPLAY);
stdioOptions.env.DISPLAY = process.env.DISPLAY;
}
if (stdioOptions.env.ELECTRON_RUN_AS_NODE) {
// Since we are running electron as node, we need to add an entry point file.
startScriptPath = path.join(xvfb.stateModule.getBinaryPkgPath(path.dirname(executable)), '..', 'index.js');
}
else {
// Start arguments with "--" so Electron knows these are OUR
// arguments and does not try to sanitize them. Otherwise on Windows
// an url in one of the arguments crashes it :(
// https://github.com/cypress-io/cypress/issues/5466
args = [...electronArgs, '--', ...args];
}
if (startScriptPath) {
args.unshift(startScriptPath);
}
if (process.env.CYPRESS_INTERNAL_DEV_DEBUG) {
args.unshift(process.env.CYPRESS_INTERNAL_DEV_DEBUG);
}
debug('spawn args %o %o', args, _.omit(stdioOptions, 'env'));
debug('spawning Cypress with executable: %s', executable);
const child = cp.spawn(executable, args, stdioOptions);
function resolveOn(event) {
return function (code, signal) {
return xvfb.__awaiter(this, void 0, void 0, function* () {
debug('child event fired %o', { event, code, signal });
if (code === null) {
const errorObject = xvfb.errors.childProcessKilled(event, signal);
const err = yield xvfb.getError(errorObject);
return reject(err);
}
resolve(code);
});
};
}
child.on('close', resolveOn('close'));
child.on('exit', resolveOn('exit'));
child.on('error', reject);
if (isPlatform('win32')) {
const rl = readline.createInterface({
input: process$1.stdin,
output: process$1.stdout,
});
// on windows, SIGINT does not propagate to the child process when ctrl+c is pressed
// this makes sure all nested processes are closed(ex: firefox inside the server)
rl.on('SIGINT', function () {
return xvfb.__awaiter(this, void 0, void 0, function* () {
const kill = (yield import('tree-kill')).default;
kill(child.pid, 'SIGINT');
});
});
}
// if stdio options is set to 'pipe', then
// we should set up pipes:
// process STDIN (read stream) => child STDIN (writeable)
// child STDOUT => process STDOUT
// child STDERR => process STDERR with additional filtering
if (child.stdin) {
debug('piping process STDIN into child STDIN');
process$1.stdin.pipe(child.stdin);
}
if (child.stdout) {
debug('piping child STDOUT to process STDOUT');
child.stdout.pipe(process$1.stdout);
}
// if this is defined then we are manually piping for linux
// to filter out the garbage
if (child.stderr) {
debug('piping child STDERR to process STDERR');
child.stderr.on('data', (data) => {
const str = data.toString();
// if we have a callback and this explicitly returns
// false then bail
if (onStderrData && onStderrData(str)) {
return;
}
if (str.match(DBUS_ERROR_PATTERN)) {
debug(str);
}
else {
// else pass it along!
process$1.stderr.write(data);
}
});
}
// https://github.com/cypress-io/cypress/issues/1841
// https://github.com/cypress-io/cypress/issues/5241
// In some versions of node, it will throw on windows
// when you close the parent process after piping
// into the child process. unpiping does not seem
// to have any effect. so we're just catching the
// error here and not doing anything.
process$1.stdin.on('error', (err) => {
if (['EPIPE', 'ENOTCONN'].includes(err.code)) {
return;
}
throw err;
});
if (stdioOptions.detached) {
child.unref();
}
});
};
}
function spawnInXvfb(spawn) {
return xvfb.__awaiter(this, void 0, void 0, function* () {
try {
yield xvfb.xvfb.start();
const code = yield userFriendlySpawn(spawn);
return code;
}
finally {
yield xvfb.xvfb.stop();
}
});
}
function userFriendlySpawn(spawn, linuxWithDisplayEnv) {
return xvfb.__awaiter(this, void 0, void 0, function* () {
debug('spawning, should retry on display problem?', Boolean(linuxWithDisplayEnv));
let brokenGtkDisplay = false;
const overrides = {};
if (linuxWithDisplayEnv) {
_.extend(overrides, {
electronLogging: true,
onStderrData(str) {
// if we receive a broken pipe anywhere
// then we know that's why cypress exited early
if (xvfb.util.isBrokenGtkDisplay(str)) {
brokenGtkDisplay = true;
}
},
});
}
try {
const code = yield spawn(overrides);
if (code !== 0 && brokenGtkDisplay) {
xvfb.util.logBrokenGtkDisplayWarning();
return spawnInXvfb(spawn);
}
return code;
}
catch (error) {
// we can format and handle an error message from the code above
// prevent wrapping error again by using "known: undefined" filter
if (error.known === undefined) {
const raiseErrorFn = xvfb.throwFormErrorText(xvfb.errors.unexpected);
yield raiseErrorFn(error.message);
}
throw error;
}
});
}
function start(args_1) {
return xvfb.__awaiter(this, arguments, void 0, function* (args, options = {}) {
var _a, _b, _c, _d;
let executable = xvfb.util.getEnv('CYPRESS_RUN_BINARY') ?
path.resolve(xvfb.util.getEnv('CYPRESS_RUN_BINARY')) :
xvfb.stateModule.getPathToExecutable(xvfb.stateModule.getBinaryDir());
// Always push cwd into the args
// which additionally acts as a signal to the
// binary that it was invoked through the NPM module
const baseArgs = args ? (typeof args === 'string' ? [args] : args) : [];
const decoratedArgs = baseArgs.concat([
'--cwd', process.cwd(),
'--userNodePath', process.execPath,
'--userNodeVersion', process.versions.node,
]);
const needsXvfb = xvfb.xvfb.isNeeded();
debug('needs to start own Xvfb?', needsXvfb);
const stdio = (_a = options.stdio) !== null && _a !== void 0 ? _a : getStdioStrategy(needsXvfb);
const dev = (_b = options.dev) !== null && _b !== void 0 ? _b : false;
const detached = (_c = options.detached) !== null && _c !== void 0 ? _c : false;
const env = (_d = options.env) !== null && _d !== void 0 ? _d : process.env;
const spawn = createSpawnFunction(executable, decoratedArgs, { stdio, dev, detached, env });
if (needsXvfb) {
return spawnInXvfb(spawn);
}
// if we are on linux and there's already a DISPLAY
// set, then we may need to rerun cypress after
// spawning our own Xvfb server
const linuxWithDisplayEnv = xvfb.util.isPossibleLinuxWithIncorrectDisplay();
return userFriendlySpawn(spawn, linuxWithDisplayEnv);
});
}
exports.VerboseRenderer = VerboseRenderer;
exports.start = start$1;
exports.start$1 = start;
'use strict';
var os = require('os');
var Bluebird = require('bluebird');
var Xvfb = require('@cypress/xvfb');
var commonTags = require('common-tags');
var Debug = require('debug');
var chalk = require('chalk');
var _ = require('lodash');
var assert = require('assert');
var arch = require('arch');
var ospath = require('ospath');
var hasha = require('hasha');
var tty = require('tty');
var path = require('path');
var ciInfo = require('ci-info');
var execa = require('execa');
var si = require('systeminformation');
var cachedir = require('cachedir');
var logSymbols = require('log-symbols');
var executable = require('executable');
var process$1 = require('process');
var supportsColor = require('supports-color');
var isInstalledGlobally = require('is-installed-globally');
var fs$1 = require('fs-extra');
var fs = require('fs');
var untildify = require('untildify');
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
let logs = [];
const logLevel = () => {
return (process.env.npm_config_loglevel || 'notice');
};
const error = (...messages) => {
logs.push(messages.join(' '));
console.log(chalk.red(...messages)); // eslint-disable-line no-console
};
const warn = (...messages) => {
if (logLevel() === 'silent')
return;
logs.push(messages.join(' '));
console.log(chalk.yellow(...messages)); // eslint-disable-line no-console
};
const log = (...messages) => {
if (logLevel() === 'silent' || logLevel() === 'warn')
return;
logs.push(messages.join(' '));
console.log(...messages); // eslint-disable-line no-console
};
const always = (...messages) => {
logs.push(messages.join(' '));
console.log(...messages); // eslint-disable-line no-console
};
// splits long text into lines and calls log()
// on each one to allow easy unit testing for specific message
const logLines = (text) => {
const lines = text.split('\n');
for (const line of lines) {
log(line);
}
};
const print = () => {
return logs.join('\n');
};
const reset = () => {
logs = [];
};
const loggerModule = {
log,
warn,
error,
always,
logLines,
print,
reset,
logLevel,
};
function relativeToRepoRoot(targetPath) {
let currentDir = __dirname;
// Walk up the directory tree
while (currentDir !== path.dirname(currentDir)) {
const resolvedTargetPath = path.join(currentDir, targetPath);
const rootPackageJson = path.join(currentDir, 'package.json');
// Check if this is the `cypress` package.json
if (fs.existsSync(rootPackageJson)) {
try {
const pkg = JSON.parse(fs.readFileSync(rootPackageJson, 'utf8'));
const targetPathExists = fs.existsSync(resolvedTargetPath);
if (targetPathExists && pkg.name === 'cypress') {
return path.resolve(currentDir, targetPath);
}
}
catch (_a) {
// Ignore JSON parse errors
}
}
currentDir = path.dirname(currentDir);
}
return undefined;
}
const debug$2 = Debug('cypress:cli');
const issuesUrl = 'https://github.com/cypress-io/cypress/issues';
/**
* Returns SHA512 of a file
*/
const getFileChecksum = (filename) => {
assert.ok(_.isString(filename) && !_.isEmpty(filename), 'expected filename');
return hasha.fromFile(filename, { algorithm: 'sha512' });
};
const getFileSize = (filename) => __awaiter(void 0, void 0, void 0, function* () {
assert.ok(_.isString(filename) && !_.isEmpty(filename), 'expected filename');
const { size } = yield fs$1.stat(filename);
return size;
});
const isBrokenGtkDisplayRe = /Gtk: cannot open display/;
const stringify = (val) => {
return _.isObject(val) ? JSON.stringify(val) : val;
};
function normalizeModuleOptions(options = {}) {
return _.mapValues(options, stringify);
}
/**
* Returns true if the platform is Linux. We do a lot of different
* stuff on Linux (like Xvfb) and it helps to has readable code
*/
const isLinux = () => {
return os.platform() === 'linux';
};
/**
* If the DISPLAY variable is set incorrectly, when trying to spawn
* Cypress executable we get an error like this:
```
[1005:0509/184205.663837:WARNING:browser_main_loop.cc(258)] Gtk: cannot open display: 99
```
*/
const isBrokenGtkDisplay = (str) => {
return isBrokenGtkDisplayRe.test(str);
};
const isPossibleLinuxWithIncorrectDisplay = () => {
return isLinux() && !!process.env.DISPLAY;
};
const logBrokenGtkDisplayWarning = () => {
debug$2('Cypress exited due to a broken gtk display because of a potential invalid DISPLAY env... retrying after starting Xvfb');
// if we get this error, we are on Linux and DISPLAY is set
loggerModule.warn(commonTags.stripIndent `
${logSymbols.warning} Warning: Cypress failed to start.
This is likely due to a misconfigured DISPLAY environment variable.
DISPLAY was set to: "${process.env.DISPLAY}"
Cypress will attempt to fix the problem and rerun.
`);
loggerModule.warn();
};
function stdoutLineMatches(expectedLine, stdout) {
const lines = stdout.split('\n').map((val) => val.trim());
return lines.some((line) => line === expectedLine);
}
/**
* Confirms if given value is a valid CYPRESS_INTERNAL_ENV value. Undefined values
* are valid, because the system can set the default one.
*
* @param {string} value
* @example util.isValidCypressInternalEnvValue(process.env.CYPRESS_INTERNAL_ENV)
*/
function isValidCypressInternalEnvValue(value) {
if (_.isUndefined(value)) {
// will get default value
return true;
}
// names of config environments, see "packages/server/config/app.json"
const names = ['development', 'test', 'staging', 'production'];
return _.includes(names, value);
}
/**
* Confirms if given value is a non-production CYPRESS_INTERNAL_ENV value.
* Undefined values are valid, because the system can set the default one.
*
* @param {string} value
* @example util.isNonProductionCypressInternalEnvValue(process.env.CYPRESS_INTERNAL_ENV)
*/
function isNonProductionCypressInternalEnvValue(value) {
return !_.isUndefined(value) && value !== 'production';
}
/**
* Prints NODE_OPTIONS using debug() module, but only
* if DEBUG=cypress... is set
*/
function printNodeOptions(log = debug$2) {
if (!log.enabled) {
return;
}
if (process.env.NODE_OPTIONS) {
log('NODE_OPTIONS=%s', process.env.NODE_OPTIONS);
}
else {
log('NODE_OPTIONS is not set');
}
}
/**
* Removes double quote characters
* from the start and end of the given string IF they are both present
*
* @param {string} str Input string
* @returns {string} Trimmed string or the original string if there are no double quotes around it.
* @example
```
dequote('"foo"')
// returns string 'foo'
dequote('foo')
// returns string 'foo'
```
*/
const dequote = (str) => {
assert.ok(_.isString(str), 'expected a string to remove double quotes');
if (str.length > 1 && str[0] === '"' && str[str.length - 1] === '"') {
return str.substr(1, str.length - 2);
}
return str;
};
const parseOpts = (opts) => {
opts = _.pick(opts, 'autoCancelAfterFailures', 'browser', 'cachePath', 'cacheList', 'cacheClear', 'cachePrune', 'ciBuildId', 'ct', 'component', 'config', 'configFile', 'cypressVersion', 'destination', 'detached', 'dev', 'e2e', 'exit', 'env', 'expose', 'force', 'global', 'group', 'headed', 'headless', 'inspect', 'inspectBrk', 'key', 'path', 'parallel', 'passWithNoTests', 'port', 'posixExitCodes', 'project', 'quiet', 'reporter', 'reporterOptions', 'record', 'runnerUi', 'runProject', 'spec', 'tag');
if (opts.exit) {
opts = _.omit(opts, 'exit');
}
// some options might be quoted - which leads to unexpected results
// remove double quotes from certain options
const cleanOpts = Object.assign({}, opts);
const toDequote = ['group', 'ciBuildId'];
for (const prop of toDequote) {
if (_.has(opts, prop)) {
cleanOpts[prop] = dequote(opts[prop]);
}
}
debug$2('parsed cli options %o', cleanOpts);
return cleanOpts;
};
/**
* Copy of packages/server/lib/browsers/utils.ts
* because we need same functionality in CLI to show the path :(
*/
const getApplicationDataFolder = (...paths) => {
const { env } = process;
// allow overriding the app_data folder
let folder = env.CYPRESS_CONFIG_ENV || env.CYPRESS_INTERNAL_ENV || 'development';
// eslint-disable-next-line no-restricted-syntax
const pkg = JSON.parse(fs$1.readFileSync(relativeToRepoRoot('package.json'), 'utf8'));
const PRODUCT_NAME = pkg.productName || pkg.name;
const OS_DATA_PATH = ospath.data();
const ELECTRON_APP_DATA_PATH = path.join(OS_DATA_PATH, PRODUCT_NAME);
if (process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF) {
folder = `${folder}-e2e-test`;
}
const p = path.join(ELECTRON_APP_DATA_PATH, 'cy', folder, ...paths);
return p;
};
const util = {
normalizeModuleOptions,
parseOpts,
isValidCypressInternalEnvValue,
isNonProductionCypressInternalEnvValue,
printNodeOptions,
isCi() {
return ciInfo.isCI;
},
getEnvOverrides(options = {}) {
return _
.chain({})
.extend(this.getEnvColors())
.extend(this.getForceTty())
.omitBy(_.isUndefined) // remove undefined values
.mapValues((value) => {
return value ? '1' : '0';
})
.extend(this.getOriginalNodeOptions())
.value();
},
getOriginalNodeOptions() {
const opts = {};
if (process.env.NODE_OPTIONS) {
opts.ORIGINAL_NODE_OPTIONS = process.env.NODE_OPTIONS;
}
return opts;
},
getForceTty() {
return {
FORCE_STDIN_TTY: this.isTty(process.stdin.fd),
FORCE_STDOUT_TTY: this.isTty(process.stdout.fd),
FORCE_STDERR_TTY: this.isTty(process.stderr.fd),
};
},
getEnvColors() {
const sc = this.supportsColor();
return {
FORCE_COLOR: sc,
DEBUG_COLORS: sc,
MOCHA_COLORS: sc ? true : undefined,
};
},
isTty(fd) {
return tty.isatty(fd);
},
supportsColor() {
// if we've been explicitly told not to support
// color then turn this off
if (process.env.NO_COLOR) {
return false;
}
// https://github.com/cypress-io/cypress/issues/1747
// always return true in CI providers
if (process.env.CI) {
return true;
}
// ensure that both stdout and stderr support color
return Boolean(supportsColor.stdout) && Boolean(supportsColor.stderr);
},
cwd() {
return process$1.cwd();
},
pkgBuildInfo() {
// making this async would require many changes
// eslint-disable-next-line no-restricted-syntax
const pkgContent = fs$1.readFileSync(relativeToRepoRoot('package.json'), 'utf8');
return JSON.parse(pkgContent).buildInfo;
},
pkgVersion() {
// making this async would require many changes
// eslint-disable-next-line no-restricted-syntax
const pkgContent = fs$1.readFileSync(relativeToRepoRoot('package.json'), 'utf8');
return JSON.parse(pkgContent).version;
},
// TODO: remove this method
exit(code) {
process.exit(code);
},
logErrorExit1(err) {
loggerModule.error(err.message);
process.exit(1);
},
dequote,
titleize(...args) {
// prepend first arg with space
// and pad so that all messages line up
args[0] = _.padEnd(` ${args[0]}`, 24);
// get rid of any falsy values
args = _.compact(args);
return chalk.blue(...args);
},
calculateEta(percent, elapsed) {
// returns the number of seconds remaining
// if we're at 100% already just return 0
if (percent === 100) {
return 0;
}
// take the percentage and divide by one
// and multiple that against elapsed
// subtracting what's already elapsed
return elapsed * (1 / (percent / 100)) - elapsed;
},
convertPercentToPercentage(num) {
// convert a percent with values between 0 and 1
// with decimals, so that it is between 0 and 100
// and has no decimal places
return Math.round(_.isFinite(num) ? (num * 100) : 0);
},
secsRemaining(eta) {
// calculate the seconds reminaing with no decimal places
return (_.isFinite(eta) ? (eta / 1000) : 0).toFixed(0);
},
setTaskTitle(task, title, renderer) {
// only update the renderer title when not running in CI
if (renderer === 'default' && task.title !== title) {
task.title = title;
}
},
isInstalledGlobally() {
return isInstalledGlobally;
},
isSemver(str) {
return /^(\d+\.)?(\d+\.)?(\*|\d+)$/.test(str);
},
isExecutableAsync(filePath) {
return Promise.resolve(executable(filePath));
},
isLinux,
getOsVersionAsync() {
return __awaiter(this, void 0, void 0, function* () {
try {
const osInfo = yield si.osInfo();
if (osInfo.distro && osInfo.release) {
return `${osInfo.distro} - ${osInfo.release}`;
}
}
catch (err) {
return os.release();
}
return os.release();
});
},
getPlatformInfo() {
return __awaiter(this, void 0, void 0, function* () {
const [version, osArch] = yield Bluebird.all([
this.getOsVersionAsync(),
this.getRealArch(),
]);
return commonTags.stripIndent `
Platform: ${os.platform()}-${osArch} (${version})
Cypress Version: ${this.pkgVersion()}
`;
});
},
_cachedArch: undefined,
/**
* Attempt to return the real system arch (not process.arch, which is only the Node binary's arch)
*/
getRealArch() {
return __awaiter(this, void 0, void 0, function* () {
if (this._cachedArch)
return this._cachedArch;
function _getRealArch() {
return __awaiter(this, void 0, void 0, function* () {
const osPlatform = os.platform();
const osArch = os.arch();
debug$2('detecting arch %o', { osPlatform, osArch });
if (osArch === 'arm64')
return 'arm64';
if (osPlatform === 'darwin') {
// could possibly be x64 node on arm64 darwin, check if we are being translated by Rosetta
// https://stackoverflow.com/a/65347893/3474615
const { stdout } = yield execa('sysctl', ['-n', 'sysctl.proc_translated']).catch(() => ({ stdout: '' }));
debug$2('rosetta check result: %o', { stdout });
if (stdout === '1')
return 'arm64';
}
if (osPlatform === 'linux') {
// could possibly be x64 node on arm64 linux, check the "machine hardware name"
// list of names for reference: https://stackoverflow.com/a/45125525/3474615
const { stdout } = yield execa('uname', ['-m']).catch(() => ({ stdout: '' }));
debug$2('arm uname -m result: %o ', { stdout });
if (['aarch64_be', 'aarch64', 'armv8b', 'armv8l'].includes(stdout))
return 'arm64';
}
const pkgArch = arch();
if (pkgArch === 'x86')
return 'ia32';
return pkgArch;
});
}
return (this._cachedArch = yield _getRealArch());
});
},
// attention:
// when passing relative path to NPM post install hook, the current working
// directory is set to the `node_modules/cypress` folder
// the user is probably passing relative path with respect to root package folder
formAbsolutePath(filename) {
if (path.isAbsolute(filename)) {
return filename;
}
return path.join(process$1.cwd(), '..', '..', filename);
},
getEnv(varName, trim) {
assert.ok(_.isString(varName) && !_.isEmpty(varName), 'expected environment variable name, not');
const configVarName = `npm_config_${varName}`;
const configVarNameLower = configVarName.toLowerCase();
const packageConfigVarName = `npm_package_config_${varName}`;
let result;
if (process.env.hasOwnProperty(varName)) {
debug$2(`Using ${varName} from environment variable`);
result = process.env[varName];
}
else if (process.env.hasOwnProperty(configVarName)) {
debug$2(`Using ${varName} from npm config`);
result = process.env[configVarName];
}
else if (process.env.hasOwnProperty(configVarNameLower)) {
debug$2(`Using ${varName.toLowerCase()} from npm config`);
result = process.env[configVarNameLower];
}
else if (process.env.hasOwnProperty(packageConfigVarName)) {
debug$2(`Using ${varName} from package.json config`);
result = process.env[packageConfigVarName];
}
// environment variables are often set double quotes to escape characters
// and on Windows it can lead to weird things: for example
// set FOO="C:\foo.txt" && node -e "console.log('>>>%s<<<', process.env.FOO)"
// will print
// >>>"C:\foo.txt" <<<
// see https://github.com/cypress-io/cypress/issues/4506#issuecomment-506029942
// so for sanity sake we should first trim whitespace characters and remove
// double quotes around environment strings if the caller is expected to
// use this environment string as a file path
return trim && (result !== null && result !== undefined) ? dequote(_.trim(result)) : result;
},
getCacheDir() {
return cachedir('Cypress');
},
isPostInstall() {
return process.env.npm_lifecycle_event === 'postinstall';
},
exec: execa,
stdoutLineMatches,
issuesUrl,
isBrokenGtkDisplay,
logBrokenGtkDisplayWarning,
isPossibleLinuxWithIncorrectDisplay,
getGitHubIssueUrl(number) {
assert.ok(_.isInteger(number), 'github issue should be an integer');
assert.ok(number > 0, 'github issue should be a positive number');
return `${issuesUrl}/${number}`;
},
getFileChecksum,
getFileSize,
getApplicationDataFolder,
};
const debug$1 = Debug('cypress:cli');
const getPlatformExecutable = () => {
const platform = os.platform();
switch (platform) {
case 'darwin': return 'Contents/MacOS/Cypress';
case 'linux': return 'Cypress';
case 'win32': return 'Cypress.exe';
// TODO handle this error using our standard
default: throw new Error(`Platform: "${platform}" is not supported.`);
}
};
const getPlatFormBinaryFolder = () => {
const platform = os.platform();
switch (platform) {
case 'darwin': return 'Cypress.app';
case 'linux': return 'Cypress';
case 'win32': return 'Cypress';
// TODO handle this error using our standard
default: throw new Error(`Platform: "${platform}" is not supported.`);
}
};
const getBinaryPkgPath = (binaryDir) => {
const platform = os.platform();
switch (platform) {
case 'darwin': return path.join(binaryDir, 'Contents', 'Resources', 'app', 'package.json');
case 'linux': return path.join(binaryDir, 'resources', 'app', 'package.json');
case 'win32': return path.join(binaryDir, 'resources', 'app', 'package.json');
// TODO handle this error using our standard
default: throw new Error(`Platform: "${platform}" is not supported.`);
}
};
/**
* Get path to binary directory
*/
const getBinaryDir = (version = util.pkgVersion()) => {
return path.join(getVersionDir(version), getPlatFormBinaryFolder());
};
const getVersionDir = (version = util.pkgVersion(), buildInfo = util.pkgBuildInfo()) => {
if (buildInfo && !buildInfo.stable) {
version = ['beta', version, buildInfo.commitBranch, buildInfo.commitSha.slice(0, 8)].join('-');
}
return path.join(getCacheDir(), version);
};
/**
* When executing "npm postinstall" hook, the working directory is set to
* "<current folder>/node_modules/cypress", which can be surprising when using relative paths.
*/
const isInstallingFromPostinstallHook = () => {
// individual folders
const cwdFolders = process$1.cwd().split(path.sep);
const length = cwdFolders.length;
return cwdFolders[length - 2] === 'node_modules' && cwdFolders[length - 1] === 'cypress';
};
const getCacheDir = () => {
let cache_directory = util.getCacheDir();
if (util.getEnv('CYPRESS_CACHE_FOLDER')) {
const envVarCacheDir = untildify(util.getEnv('CYPRESS_CACHE_FOLDER'));
debug$1('using environment variable CYPRESS_CACHE_FOLDER %s', envVarCacheDir);
if (!path.isAbsolute(envVarCacheDir) && isInstallingFromPostinstallHook()) {
const packageRootFolder = path.join('..', '..', envVarCacheDir);
cache_directory = path.resolve(packageRootFolder);
debug$1('installing from postinstall hook, original root folder is %s', packageRootFolder);
debug$1('and resolved cache directory is %s', cache_directory);
}
else {
cache_directory = path.resolve(envVarCacheDir);
}
}
return cache_directory;
};
const parseRealPlatformBinaryFolderAsync = (binaryPath) => __awaiter(void 0, void 0, void 0, function* () {
const realPath = yield fs$1.realpath(binaryPath);
debug$1('CYPRESS_RUN_BINARY has realpath:', realPath);
if (!realPath.toString().endsWith(getPlatformExecutable())) {
return false;
}
if (os.platform() === 'darwin') {
return path.resolve(realPath, '..', '..', '..');
}
return path.resolve(realPath, '..');
});
const getDistDir = () => {
return path.join(__dirname, '..', '..', 'dist');
};
/**
* Returns full filename to the file that keeps the Test Runner verification state as JSON text.
* Note: the binary state file will be stored one level up from the given binary folder.
* @param {string} binaryDir - full path to the folder holding the binary.
*/
const getBinaryStatePath = (binaryDir) => {
return path.join(binaryDir, '..', 'binary_state.json');
};
const getBinaryStateContentsAsync = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
const fullPath = getBinaryStatePath(binaryDir);
try {
const contents = yield fs$1.readJson(fullPath);
debug$1('binary_state.json contents:', contents);
return contents;
}
catch (error) {
if (error.code === 'ENOENT' || error instanceof SyntaxError) {
debug$1('could not read binary_state.json file at "%s"', fullPath);
return {};
}
throw error;
}
});
const getBinaryVerifiedAsync = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
const contents = yield getBinaryStateContentsAsync(binaryDir);
return contents.verified;
});
const clearBinaryStateAsync = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
yield fs$1.remove(getBinaryStatePath(binaryDir));
});
/**
* Writes the new binary status.
* @param {boolean} verified The new test runner state after smoke test
* @param {string} binaryDir Folder holding the binary
* @returns {Promise<void>} returns a promise
*/
const writeBinaryVerifiedAsync = (verified, binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
const contents = yield getBinaryStateContentsAsync(binaryDir);
yield fs$1.outputJson(getBinaryStatePath(binaryDir), _.extend(contents, { verified }), { spaces: 2 });
});
const getPathToExecutable = (binaryDir) => {
return path.join(binaryDir, getPlatformExecutable());
};
/**
* Resolves with an object read from the binary app package.json file.
* If the file does not exist resolves with null
*/
const getBinaryPkgAsync = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
const pathToPackageJson = getBinaryPkgPath(binaryDir);
debug$1('Reading binary package.json from:', pathToPackageJson);
const exists = yield fs$1.pathExists(pathToPackageJson);
if (!exists) {
return null;
}
return fs$1.readJson(pathToPackageJson);
});
const getBinaryPkgVersion = (o) => _.get(o, 'version', null);
const getBinaryElectronVersion = (o) => _.get(o, 'electronVersion', null);
const getBinaryElectronNodeVersion = (o) => _.get(o, 'electronNodeVersion', null);
const stateModule = {
getPathToExecutable,
getPlatformExecutable,
// those names start to sound like Java
getBinaryElectronNodeVersion,
getBinaryElectronVersion,
getBinaryPkgVersion,
getBinaryVerifiedAsync,
getBinaryPkgAsync,
getBinaryPkgPath,
getBinaryDir,
getCacheDir,
clearBinaryStateAsync,
writeBinaryVerifiedAsync,
parseRealPlatformBinaryFolderAsync,
getDistDir,
getVersionDir,
};
const docsUrl = 'https://on.cypress.io';
const requiredDependenciesUrl = `${docsUrl}/required-dependencies`;
const runDocumentationUrl = `${docsUrl}/cypress-run`;
// TODO it would be nice if all error objects could be enforced via types
// to only have description + solution properties
const hr = '----------';
const genericErrorSolution = commonTags.stripIndent `
Search for an existing issue or open a GitHub issue at
${chalk.blue(util.issuesUrl)}
`;
// common errors Cypress application can encounter
const unknownError = {
description: 'Unknown Cypress CLI error',
solution: genericErrorSolution,
};
const invalidRunProjectPath = {
description: 'Invalid --project path',
solution: commonTags.stripIndent `
Please provide a valid project path.
Learn more about ${chalk.cyan('cypress run')} at:
${chalk.blue(runDocumentationUrl)}
`,
};
const invalidOS = {
description: 'The Cypress App could not be installed. Your machine does not meet the operating system requirements.',
solution: commonTags.stripIndent `
${chalk.blue('https://on.cypress.io/app/get-started/install-cypress#System-requirements')}`,
};
const failedDownload = {
description: 'The Cypress App could not be downloaded.',
solution: commonTags.stripIndent `
Does your workplace require a proxy to be used to access the Internet? If so, you must configure the HTTP_PROXY environment variable before downloading Cypress. Read more: https://on.cypress.io/proxy-configuration
Otherwise, please check network connectivity and try again:`,
};
const failedUnzip = {
description: 'The Cypress App could not be unzipped.',
solution: genericErrorSolution,
};
const failedUnzipWindowsMaxPathLength = {
description: 'The Cypress App could not be unzipped.',
solution: `This is most likely because the maximum path length is being exceeded on your system.
Read here for solutions to this problem: https://on.cypress.io/win-max-path-length-error`,
};
const missingApp = (binaryDir) => {
return {
description: `No version of Cypress is installed in: ${chalk.cyan(binaryDir)}`,
solution: commonTags.stripIndent `
\nPlease reinstall Cypress by running: ${chalk.cyan('cypress install')}
`,
};
};
const binaryNotExecutable = (executable) => {
return {
description: `Cypress cannot run because this binary file does not have executable permissions here:\n\n${executable}`,
solution: commonTags.stripIndent `\n
Reasons this may happen:
- node was installed as 'root' or with 'sudo'
- the cypress npm package as 'root' or with 'sudo'
Please check that you have the appropriate user permissions.
You can also try clearing the cache with 'cypress cache clear' and reinstalling.
`,
};
};
const notInstalledCI = (executable) => {
return {
description: 'The cypress npm package is installed, but the Cypress binary is missing.',
solution: commonTags.stripIndent `\n
We expected the binary to be installed here: ${chalk.cyan(executable)}
Reasons it may be missing:
- You're caching 'node_modules' but are not caching this path: ${util.getCacheDir()}
- You ran 'npm install' at an earlier build step but did not persist: ${util.getCacheDir()}
Properly caching the binary will fix this error and avoid downloading and unzipping Cypress.
Alternatively, you can run 'cypress install' to download the binary again.
${chalk.blue('https://on.cypress.io/not-installed-ci-error')}
`,
};
};
const nonZeroExitCodeXvfb = {
description: 'Xvfb exited with a non zero exit code.',
solution: commonTags.stripIndent `
There was a problem spawning Xvfb.
This is likely a problem with your system, permissions, or installation of Xvfb.
`,
};
const missingXvfb = {
description: 'Your system is missing the dependency: Xvfb',
solution: commonTags.stripIndent `
Install Xvfb and run Cypress again.
Read our documentation on dependencies for more information:
${chalk.blue(requiredDependenciesUrl)}
If you are using Docker, we provide containers with all required dependencies installed.
`,
};
const smokeTestFailure = (smokeTestCommand, timedOut) => {
return {
description: `Cypress verification ${timedOut ? 'timed out' : 'failed'}.`,
solution: commonTags.stripIndent `
This command failed with the following output:
${smokeTestCommand}
`,
};
};
const invalidSmokeTestDisplayError = {
code: 'INVALID_SMOKE_TEST_DISPLAY_ERROR',
description: 'Cypress verification failed.',
solution(msg) {
return commonTags.stripIndent `
Cypress failed to start after spawning a new Xvfb server.
The error logs we received were:
${hr}
${msg}
${hr}
This may be due to a missing library or dependency. ${chalk.blue(requiredDependenciesUrl)}
Please refer to the error above for more detail.
`;
},
};
const missingDependency = {
description: 'Cypress failed to start.',
// this message is too Linux specific
solution: commonTags.stripIndent `
This may be due to a missing library or dependency. ${chalk.blue(requiredDependenciesUrl)}
Please refer to the error below for more details.
`,
};
const invalidCacheDirectory = {
description: 'Cypress cannot write to the cache directory due to file permissions',
solution: commonTags.stripIndent `
See discussion and possible solutions at
${chalk.blue(util.getGitHubIssueUrl(1281))}
`,
};
const versionMismatch = {
description: 'Installed version does not match package version.',
solution: 'Install Cypress and verify app again',
};
const incompatibleHeadlessFlags = {
description: '`--headed` and `--headless` cannot both be passed.',
solution: 'Either pass `--headed` or `--headless`, but not both.',
};
const solutionUnknown = commonTags.stripIndent `
Please search Cypress documentation for possible solutions:
${chalk.blue(docsUrl)}
Check if there is a GitHub issue describing this crash:
${chalk.blue(util.issuesUrl)}
Consider opening a new issue.
`;
const unexpected = {
description: 'An unexpected error occurred while verifying the Cypress executable.',
solution: solutionUnknown,
};
const invalidCypressEnv = {
description: chalk.red('The environment variable with the reserved name "CYPRESS_INTERNAL_ENV" is set.'),
solution: chalk.red('Unset the "CYPRESS_INTERNAL_ENV" environment variable and run Cypress again.'),
exitCode: 11,
};
const invalidTestingType = {
description: 'Invalid testingType',
solution: `Please provide a valid testingType. Valid test types are ${chalk.cyan('\'e2e\'')} and ${chalk.cyan('\'component\'')}.`,
};
const incompatibleTestTypeFlags = {
description: '`--e2e` and `--component` cannot both be passed.',
solution: 'Either pass `--e2e` or `--component`, but not both.',
};
const incompatibleTestingTypeAndFlag = {
description: 'Set a `testingType` and also passed `--e2e` or `--component` flags.',
solution: 'Either set `testingType` or pass a testing type flag, but not both.',
};
const invalidConfigFile = {
description: '`--config-file` cannot be false.',
solution: 'Either pass a relative path to a valid Cypress config file or remove this option.',
};
/**
* This error happens when CLI detects that the child Test Runner process
* was killed with a signal, like SIGBUS
* @see https://github.com/cypress-io/cypress/issues/5808
* @param {'close'|'event'} eventName Child close event name
* @param {string} signal Signal that closed the child process, like "SIGBUS"
*/
const childProcessKilled = (eventName, signal) => {
return {
description: `The Test Runner unexpectedly exited via a ${chalk.cyan(eventName)} event with signal ${chalk.cyan(signal)}`,
solution: solutionUnknown,
};
};
const CYPRESS_RUN_BINARY = {
notValid: (value) => {
const properFormat = `**/${stateModule.getPlatformExecutable()}`;
return {
description: `Could not run binary set by environment variable: CYPRESS_RUN_BINARY=${value}`,
solution: `Ensure the environment variable is a path to the Cypress binary, matching ${properFormat}`,
};
},
};
function addPlatformInformation(info) {
return __awaiter(this, void 0, void 0, function* () {
const platform = yield util.getPlatformInfo();
return Object.assign(Object.assign({}, info), { platform });
});
}
/**
* Given an error object (see the errors above), forms error message text with details,
* then resolves with Error instance you can throw or reject with.
* @param {object} errorObject
* @returns {Promise<Error>} resolves with an Error
* @example
```js
// inside a Promise with "resolve" and "reject"
const errorObject = childProcessKilled('exit', 'SIGKILL')
return getError(errorObject).then(reject)
```
*/
function getError(errorObject) {
return __awaiter(this, void 0, void 0, function* () {
const errorMessage = yield formErrorText(errorObject);
const err = new Error(errorMessage);
err.known = true;
return err;
});
}
/**
* Forms nice error message with error and platform information,
* and if possible a way to solve it. Resolves with a string.
*/
function formErrorText(info, msg, prevMessage) {
return __awaiter(this, void 0, void 0, function* () {
const infoWithPlatform = yield addPlatformInformation(info);
const formatted = [];
function add(msg) {
formatted.push(commonTags.stripIndents(msg));
}
assert.ok(_.isString(infoWithPlatform.description) && !_.isEmpty(infoWithPlatform.description), 'expected error description to be text.');
// assuming that if there the solution is a function it will handle
// error message and (optional previous error message)
if (_.isFunction(infoWithPlatform.solution)) {
const text = infoWithPlatform.solution(msg, prevMessage);
assert.ok(_.isString(text) && !_.isEmpty(text), 'expected solution to be text.');
add(`
${infoWithPlatform.description}
${text}
`);
}
else {
assert.ok(_.isString(infoWithPlatform.solution) && !_.isEmpty(infoWithPlatform.solution), 'expected error solution to be text.');
add(`
${infoWithPlatform.description}
${infoWithPlatform.solution}
`);
if (msg) {
add(`
${hr}
${msg}
`);
}
}
add(`
${hr}
${infoWithPlatform.platform}
`);
if (infoWithPlatform.footer) {
add(`
${hr}
${infoWithPlatform.footer}
`);
}
return formatted.join('\n\n');
});
}
const raise = (info) => {
return (text) => {
const err = new Error(text);
if (info.code) {
err.code = info.code;
}
err.known = true;
throw err;
};
};
const throwFormErrorText = (info) => {
return (msg, prevMessage) => __awaiter(void 0, void 0, void 0, function* () {
const errorText = yield formErrorText(info, msg, prevMessage);
raise(info)(errorText);
});
};
/**
* Forms full error message with error and OS details, prints to the error output
* and then exits the process.
* @param {ErrorInformation} info Error information {description, solution}
* @example return exitWithError(errors.invalidCypressEnv)('foo')
*/
const exitWithError = (info) => {
return (msg) => __awaiter(void 0, void 0, void 0, function* () {
const text = yield formErrorText(info, msg);
console.error(text);
process.exit(info.exitCode || 1);
});
};
const errors = {
unknownError,
nonZeroExitCodeXvfb,
missingXvfb,
missingApp,
notInstalledCI,
missingDependency,
invalidOS,
invalidSmokeTestDisplayError,
versionMismatch,
binaryNotExecutable,
unexpected,
failedDownload,
failedUnzip,
failedUnzipWindowsMaxPathLength,
invalidCypressEnv,
invalidCacheDirectory,
CYPRESS_RUN_BINARY,
smokeTestFailure,
childProcessKilled,
incompatibleHeadlessFlags,
invalidRunProjectPath,
invalidTestingType,
incompatibleTestTypeFlags,
incompatibleTestingTypeAndFlag,
invalidConfigFile,
};
const debug = Debug('cypress:cli');
const debugXvfb = Debug('cypress:xvfb');
debug.Debug = debugXvfb.Debug = Debug;
const xvfbOptions = {
displayNum: process.env.XVFB_DISPLAY_NUM,
timeout: 30000, // milliseconds
// need to explicitly define screen otherwise electron will crash
// https://github.com/cypress-io/cypress/issues/6184
xvfb_args: ['-screen', '0', '1280x1024x24'],
onStderrData(data) {
if (debugXvfb.enabled) {
debugXvfb(data.toString());
}
},
};
const xvfb = Bluebird.promisifyAll(new Xvfb(xvfbOptions));
const _debugXvfb = debugXvfb;
const _xvfb = xvfb;
const _xvfbOptions = xvfbOptions;
function start() {
return __awaiter(this, void 0, void 0, function* () {
debug('Starting Xvfb');
try {
yield xvfb.startAsync();
return null;
}
catch (e) {
if (e.nonZeroExitCode === true) {
const raiseErrorFn = throwFormErrorText(errors.nonZeroExitCodeXvfb);
yield raiseErrorFn(e);
}
if (e.known) {
throw e;
}
const raiseErrorFn = throwFormErrorText(errors.missingXvfb);
yield raiseErrorFn(e);
}
});
}
function stop() {
return __awaiter(this, void 0, void 0, function* () {
debug('Stopping Xvfb');
try {
yield xvfb.stopAsync();
return null;
}
catch (e) {
return null;
}
});
}
function isNeeded() {
if (process.env.ELECTRON_RUN_AS_NODE) {
debug('Environment variable ELECTRON_RUN_AS_NODE detected, xvfb is not needed');
return false; // xvfb required for electron processes only.
}
if (os.platform() !== 'linux') {
return false;
}
if (process.env.DISPLAY) {
const issueUrl = util.getGitHubIssueUrl(4034);
const message = commonTags.stripIndent `
DISPLAY environment variable is set to ${process.env.DISPLAY} on Linux
Assuming this DISPLAY points at working X11 server,
Cypress will not spawn own Xvfb
NOTE: if the X11 server is NOT working, Cypress will exit without explanation,
see ${issueUrl}
Solution: Unset the DISPLAY variable and try again:
DISPLAY= npx cypress run ...
`;
debug(message);
return false;
}
debug('undefined DISPLAY environment variable');
debug('Cypress will spawn its own Xvfb');
return true;
}
// async method, resolved with Boolean
function verify() {
return __awaiter(this, void 0, void 0, function* () {
try {
yield xvfb.startAsync();
return true;
}
catch (err) {
debug('Could not verify xvfb: %s', err.message);
return false;
}
finally {
yield xvfb.stopAsync();
}
});
}
var xvfb$1 = {
_debugXvfb,
_xvfb,
_xvfbOptions,
start,
stop,
isNeeded,
verify,
};
exports.__awaiter = __awaiter;
exports._debugXvfb = _debugXvfb;
exports._xvfb = _xvfb;
exports._xvfbOptions = _xvfbOptions;
exports.errors = errors;
exports.exitWithError = exitWithError;
exports.getError = getError;
exports.isNeeded = isNeeded;
exports.loggerModule = loggerModule;
exports.relativeToRepoRoot = relativeToRepoRoot;
exports.start = start;
exports.stateModule = stateModule;
exports.stop = stop;
exports.throwFormErrorText = throwFormErrorText;
exports.util = util;
exports.verify = verify;
exports.xvfb = xvfb$1;

Sorry, the diff of this file is not supported yet