Sorry, the diff of this file is too big to display
| '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 @@ |
+7
-4
| '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'); |
+7
-4
@@ -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 @@ |
+3
-3
| { | ||
| "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
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 20 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 3 instances in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 19 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 3 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
4464894
0.7%112352
0.66%548
1.48%