cypress
Version:
Cypress is a next generation front end testing tool built for the modern web
312 lines (311 loc) • 14.4 kB
JavaScript
;
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);
});
}