UNPKG

cypress

Version:

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

304 lines (298 loc) 14.6 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.needsSandbox = exports.start = exports.verifyTestRunnerTimeoutMs = void 0; const lodash_1 = __importDefault(require("lodash")); const chalk_1 = __importDefault(require("chalk")); const listr2_1 = require("listr2"); const debug_1 = __importDefault(require("debug")); const common_tags_1 = require("common-tags"); const bluebird_1 = __importDefault(require("bluebird")); const log_symbols_1 = __importDefault(require("log-symbols")); const path_1 = __importDefault(require("path")); const os_1 = __importDefault(require("os")); const VerboseRenderer_1 = __importDefault(require("../VerboseRenderer")); const errors_1 = require("../errors"); const util_1 = __importDefault(require("../util")); const logger_1 = __importDefault(require("../logger")); const xvfb_1 = __importDefault(require("../exec/xvfb")); const state_1 = __importDefault(require("./state")); const debug = (0, debug_1.default)('cypress:cli'); const verifyTestRunnerTimeoutMs = () => { const verifyTimeout = +((util_1.default === null || util_1.default === void 0 ? void 0 : util_1.default.getEnv('CYPRESS_VERIFY_TIMEOUT')) || 'NaN'); if (lodash_1.default.isNumber(verifyTimeout) && !lodash_1.default.isNaN(verifyTimeout)) { return verifyTimeout; } return 30000; }; exports.verifyTestRunnerTimeoutMs = verifyTestRunnerTimeoutMs; const checkExecutable = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () { const executable = state_1.default.getPathToExecutable(binaryDir); debug('checking if executable exists', executable); try { const isExecutable = yield util_1.default.isExecutableAsync(executable); debug('Binary is executable? :', isExecutable); if (!isExecutable) { return (0, errors_1.throwFormErrorText)(errors_1.errors.binaryNotExecutable(executable))(); } } catch (err) { if (err.code === 'ENOENT') { if (util_1.default.isCi()) { return (0, errors_1.throwFormErrorText)(errors_1.errors.notInstalledCI(executable))(); } return (0, errors_1.throwFormErrorText)(errors_1.errors.missingApp(binaryDir))((0, common_tags_1.stripIndent) ` Cypress executable not found at: ${chalk_1.default.cyan(executable)} `); } throw err; } }); const runSmokeTest = (binaryDir, options) => { let executable = state_1.default.getPathToExecutable(binaryDir); const needsXvfb = xvfb_1.default.isNeeded(); debug('needs Xvfb?', needsXvfb); /** * Spawn Cypress running smoke test to check if all operating system * dependencies are good. */ const spawn = (linuxWithDisplayEnv) => __awaiter(void 0, void 0, void 0, function* () { const random = lodash_1.default.random(0, 1000); const args = ['--smoke-test', `--ping=${random}`]; if ((0, exports.needsSandbox)()) { // electron requires --no-sandbox to run as root debug('disabling Electron sandbox'); args.unshift('--no-sandbox'); } if (options.dev) { executable = 'node'; args.unshift(path_1.default.resolve(__dirname, '..', '..', '..', 'scripts', 'start.js')); } const smokeTestCommand = `${executable} ${args.join(' ')}`; debug('running smoke test'); debug('using Cypress executable %s', executable); debug('smoke test command:', smokeTestCommand); debug('smoke test timeout %d ms', options.smokeTestTimeout); const stdioOptions = lodash_1.default.extend({}, { env: Object.assign(Object.assign({}, process.env), { FORCE_COLOR: '0' }), timeout: options.smokeTestTimeout, }); try { const result = yield util_1.default.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 = lodash_1.default.get(result, 'stdout', ''); debug('smoke test stdout "%s"', smokeTestStdout); if (!util_1.default.stdoutLineMatches(String(random), smokeTestStdout)) { debug('Smoke test failed because could not find %d in:', random, result); const smokeTestStderr = lodash_1.default.get(result, 'stderr', ''); const errorText = smokeTestStderr || smokeTestStdout; return (0, errors_1.throwFormErrorText)(errors_1.errors.smokeTestFailure(smokeTestCommand, false))(errorText); } } catch (err) { debug('Smoke test failed:', err); let errMessage = err.stderr || err.message; debug('error message:', errMessage); if (err.timedOut) { debug('error timedOut is true'); return (0, errors_1.throwFormErrorText)(errors_1.errors.smokeTestFailure(smokeTestCommand, true))(errMessage); } if (linuxWithDisplayEnv && util_1.default.isBrokenGtkDisplay(errMessage)) { util_1.default.logBrokenGtkDisplayWarning(); return (0, errors_1.throwFormErrorText)(errors_1.errors.invalidSmokeTestDisplayError)(errMessage); } return (0, errors_1.throwFormErrorText)(errors_1.errors.missingDependency)(errMessage); } }); const spawnInXvfb = (linuxWithDisplayEnv) => __awaiter(void 0, void 0, void 0, function* () { yield xvfb_1.default.start(); return spawn(linuxWithDisplayEnv || false).finally(() => __awaiter(void 0, void 0, void 0, function* () { yield xvfb_1.default.stop(); })); }); const userFriendlySpawn = (linuxWithDisplayEnv) => __awaiter(void 0, void 0, void 0, function* () { debug('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 = util_1.default.isPossibleLinuxWithIncorrectDisplay(); return userFriendlySpawn(linuxWithDisplayEnv); }; function testBinary(version, binaryDir, options) { debug('running binary verification check', version); // if running from 'cypress verify', don't print this message if (!options.force) { logger_1.default.log((0, common_tags_1.stripIndent) ` It looks like this is your first time using Cypress: ${chalk_1.default.cyan(version)} `); } logger_1.default.log(); // if we are running in CI then use // the verbose renderer else use // the default let renderer = util_1.default.isCi() ? VerboseRenderer_1.default : 'default'; // NOTE: under test we set the listr renderer to 'silent' in order to get deterministic snapshots if (logger_1.default.logLevel() === 'silent' || options.listrRenderer) renderer = 'silent'; const rendererOptions = { renderer, }; const tasks = new listr2_1.Listr([ { title: util_1.default.titleize('Verifying Cypress can run', chalk_1.default.gray(binaryDir)), task: (ctx, task) => __awaiter(this, void 0, void 0, function* () { debug('clearing out the verified version'); yield state_1.default.clearBinaryStateAsync(binaryDir); yield Promise.all([ runSmokeTest(binaryDir, options), bluebird_1.default.delay(1500), // good user experience ]); debug('write verified: true'); yield state_1.default.writeBinaryVerifiedAsync(true, binaryDir); util_1.default.setTaskTitle(task, util_1.default.titleize(chalk_1.default.green('Verified Cypress!'), chalk_1.default.gray(binaryDir)), rendererOptions.renderer); }), }, ], rendererOptions); return tasks.run(); } const maybeVerify = (installedVersion, binaryDir, options) => __awaiter(void 0, void 0, void 0, function* () { const isVerified = yield state_1.default.getBinaryVerifiedAsync(binaryDir); debug('is Verified ?', isVerified); let shouldVerify = !isVerified; // force verify if options.force if (options.force) { debug('force verify'); shouldVerify = true; } if (shouldVerify) { yield testBinary(installedVersion, binaryDir, options); if (options.welcomeMessage) { logger_1.default.log(); logger_1.default.log('Opening Cypress...'); } } }); const start = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (options = {}) { debug('verifying Cypress app'); const packageVersion = util_1.default.pkgVersion(); let binaryDir = state_1.default.getBinaryDir(packageVersion); lodash_1.default.defaults(options, { dev: false, force: false, welcomeMessage: true, smokeTestTimeout: (0, exports.verifyTestRunnerTimeoutMs)(), skipVerify: util_1.default.getEnv('CYPRESS_SKIP_VERIFY') === 'true', }); if (options.skipVerify) { debug('skipping verification of the Cypress app'); return Promise.resolve(); } if (options.dev) { return runSmokeTest('', options); } const parseBinaryEnvVar = () => __awaiter(void 0, void 0, void 0, function* () { const envBinaryPath = util_1.default.getEnv('CYPRESS_RUN_BINARY'); debug('CYPRESS_RUN_BINARY exists, =', envBinaryPath); logger_1.default.log((0, common_tags_1.stripIndent) ` ${chalk_1.default.yellow('Note:')} You have set the environment variable: ${chalk_1.default.white('CYPRESS_RUN_BINARY=')}${chalk_1.default.cyan(envBinaryPath)} This overrides the default Cypress binary path used. `); logger_1.default.log(); try { const isExecutable = yield util_1.default.isExecutableAsync(envBinaryPath); debug('CYPRESS_RUN_BINARY is executable? :', isExecutable); if (!isExecutable) { return (0, errors_1.throwFormErrorText)(errors_1.errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))((0, common_tags_1.stripIndent) ` The supplied binary path is not executable `); } const envBinaryDir = yield state_1.default.parseRealPlatformBinaryFolderAsync(envBinaryPath); if (!envBinaryDir) { return (0, errors_1.throwFormErrorText)(errors_1.errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(); } debug('CYPRESS_RUN_BINARY has binaryDir:', envBinaryDir); binaryDir = envBinaryDir; } catch (err) { if (err.code === 'ENOENT') { return (0, errors_1.throwFormErrorText)(errors_1.errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(err.message); } throw err; } }); try { debug('checking environment variables'); if (util_1.default.getEnv('CYPRESS_RUN_BINARY')) { yield parseBinaryEnvVar(); } yield checkExecutable(binaryDir); debug('binaryDir is ', binaryDir); const pkg = yield state_1.default.getBinaryPkgAsync(binaryDir); const binaryVersion = state_1.default.getBinaryPkgVersion(pkg); if (!binaryVersion) { debug('no Cypress binary found for cli version ', packageVersion); return (0, errors_1.throwFormErrorText)(errors_1.errors.missingApp(binaryDir))(` Cannot read binary version from: ${chalk_1.default.cyan(state_1.default.getBinaryPkgPath(binaryDir))} `); } debug(`Found binary version ${chalk_1.default.green(binaryVersion)} installed in: ${chalk_1.default.cyan(binaryDir)}`); if (binaryVersion !== packageVersion) { // warn if we installed with CYPRESS_INSTALL_BINARY or changed version // in the package.json logger_1.default.log(`Found binary version ${chalk_1.default.green(binaryVersion)} installed in: ${chalk_1.default.cyan(binaryDir)}`); logger_1.default.log(); logger_1.default.warn((0, common_tags_1.stripIndent) ` ${log_symbols_1.default.warning} Warning: Binary version ${chalk_1.default.green(binaryVersion)} does not match the expected package version ${chalk_1.default.green(packageVersion)} These versions may not work properly together. `); logger_1.default.log(); } yield maybeVerify(binaryVersion, binaryDir, options); } catch (err) { if (err.known) { throw err; } return (0, errors_1.throwFormErrorText)(errors_1.errors.unexpected)(err.stack); } }); exports.start = start; const isLinuxLike = () => os_1.default.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(); exports.needsSandbox = needsSandbox;