UNPKG

cypress

Version:

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

312 lines (311 loc) 14.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); 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.start = start; const lodash_1 = __importDefault(require("lodash")); const os_1 = __importDefault(require("os")); const child_process_1 = __importDefault(require("child_process")); const path_1 = __importDefault(require("path")); const bluebird_1 = __importDefault(require("bluebird")); const debug_1 = __importDefault(require("debug")); const util_1 = __importDefault(require("../util")); const state_1 = __importDefault(require("../tasks/state")); const xvfb_1 = __importDefault(require("./xvfb")); const verify_1 = require("../tasks/verify"); const errors_1 = require("../errors"); const readline_1 = __importDefault(require("readline")); const process_1 = require("process"); const debug = (0, debug_1.default)('cypress:cli'); const DBUS_ERROR_PATTERN = /ERROR:dbus\/(bus|object_proxy)\.cc/; function isPlatform(platform) { return os_1.default.platform() === platform; } function needsStderrPiped(needsXvfb) { return lodash_1.default.some([ isPlatform('darwin'), (needsXvfb && isPlatform('linux')), util_1.default.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 bluebird_1.default((resolve, reject) => { lodash_1.default.defaults(overrides, { onStderrData: false, }); const { onStderrData } = overrides; const envOverrides = util_1.default.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' startScriptPath = path_1.default.resolve(__dirname, '..', '..', '..', 'scripts', 'start.js'); debug('in dev mode the args became %o', args); } if (!options.dev && (0, verify_1.needsSandbox)()) { electronArgs.push('--no-sandbox'); } // strip dev out of child process options /** * @type {import('child_process').ForkOptions} */ let stdioOptions = lodash_1.default.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 = lodash_1.default.extend({}, stdioOptions.env, envOverrides); if (node11WindowsFix) { stdioOptions = lodash_1.default.extend({}, stdioOptions, { windowsHide: false }); } if (util_1.default.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_1.default.join(state_1.default.getBinaryPkgPath(path_1.default.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, lodash_1.default.omit(stdioOptions, 'env')); debug('spawning Cypress with executable: %s', executable); const child = child_process_1.default.spawn(executable, args, stdioOptions); function resolveOn(event) { return function (code, signal) { return __awaiter(this, void 0, void 0, function* () { debug('child event fired %o', { event, code, signal }); if (code === null) { const errorObject = errors_1.errors.childProcessKilled(event, signal); const err = yield (0, errors_1.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_1.default.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 __awaiter(this, void 0, void 0, function* () { const kill = (yield Promise.resolve().then(() => __importStar(require('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 __awaiter(this, void 0, void 0, function* () { try { yield xvfb_1.default.start(); const code = yield userFriendlySpawn(spawn); return code; } finally { yield xvfb_1.default.stop(); } }); } function userFriendlySpawn(spawn, linuxWithDisplayEnv) { return __awaiter(this, void 0, void 0, function* () { debug('spawning, should retry on display problem?', Boolean(linuxWithDisplayEnv)); let brokenGtkDisplay = false; const overrides = {}; if (linuxWithDisplayEnv) { lodash_1.default.extend(overrides, { electronLogging: true, onStderrData(str) { // if we receive a broken pipe anywhere // then we know that's why cypress exited early if (util_1.default.isBrokenGtkDisplay(str)) { brokenGtkDisplay = true; } }, }); } try { const code = yield spawn(overrides); if (code !== 0 && brokenGtkDisplay) { util_1.default.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 = (0, errors_1.throwFormErrorText)(errors_1.errors.unexpected); yield raiseErrorFn(error.message); } throw error; } }); } function start(args_1) { return __awaiter(this, arguments, void 0, function* (args, options = {}) { var _a, _b, _c, _d; let executable = util_1.default.getEnv('CYPRESS_RUN_BINARY') ? path_1.default.resolve(util_1.default.getEnv('CYPRESS_RUN_BINARY')) : state_1.default.getPathToExecutable(state_1.default.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_1.default.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 = util_1.default.isPossibleLinuxWithIncorrectDisplay(); return userFriendlySpawn(spawn, linuxWithDisplayEnv); }); }