UNPKG

cypress

Version:

Cypress is a next generation front end testing tool built for the modern web

1,204 lines (1,151 loc) 55.5 kB
'use strict'; var xvfb = require('./xvfb-Csr_YzMk.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 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'); 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 logVersionMismatch(binaryVersion, binaryDir, packageVersion) { 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(); } function verifyBinary(installedVersion, binaryDir, options) { return xvfb.__awaiter(this, void 0, void 0, function* () { debug$1('running binary verification check', installedVersion); // 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(installedVersion)} `); } xvfb.loggerModule.log(); const verifyTaskRunner = 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); task.title = xvfb.util.titleize(chalk.green('Verified Cypress!'), chalk.gray(binaryDir)); }), }], { silentRendererCondition: () => xvfb.loggerModule.logLevel() === 'silent', }); yield verifyTaskRunner.run(); 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 with options %j', options); _.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 logVersionMismatch(binaryVersion, binaryDir, packageVersion); } const isVerified = options.force ? false : yield xvfb.stateModule.getBinaryVerifiedAsync(binaryDir); debug$1('is Verified ?', isVerified); if (!isVerified) { yield verifyBinary(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.PassThro