initial status
This commit is contained in:
72
node_modules/cypress/lib/VerboseRenderer.js
generated
vendored
Normal file
72
node_modules/cypress/lib/VerboseRenderer.js
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
"use strict";
|
||||
|
||||
// Vendored from @cypress/listr-verbose-renderer
|
||||
const figures = require('figures');
|
||||
|
||||
const cliCursor = require('cli-cursor');
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
const dayjs = require('dayjs');
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = VerboseRenderer;
|
426
node_modules/cypress/lib/cli.js
generated
vendored
Normal file
426
node_modules/cypress/lib/cli.js
generated
vendored
Normal file
@@ -0,0 +1,426 @@
|
||||
"use strict";
|
||||
|
||||
// @ts-check
|
||||
const _ = require('lodash');
|
||||
|
||||
const R = require('ramda');
|
||||
|
||||
const commander = require('commander');
|
||||
|
||||
const {
|
||||
stripIndent
|
||||
} = require('common-tags');
|
||||
|
||||
const logSymbols = require('log-symbols');
|
||||
|
||||
const debug = require('debug')('cypress:cli:cli');
|
||||
|
||||
const util = require('./util');
|
||||
|
||||
const logger = require('./logger');
|
||||
|
||||
const errors = require('./errors');
|
||||
|
||||
const cache = require('./tasks/cache'); // patch "commander" method called when a user passed an unknown option
|
||||
// we want to print help for the current command and exit with an error
|
||||
|
||||
|
||||
function unknownOption(flag, type = 'option') {
|
||||
if (this._allowUnknownOption) return;
|
||||
logger.error();
|
||||
logger.error(` error: unknown ${type}:`, flag);
|
||||
logger.error();
|
||||
this.outputHelp();
|
||||
util.exit(1);
|
||||
}
|
||||
|
||||
commander.Command.prototype.unknownOption = unknownOption;
|
||||
|
||||
const coerceFalseOrString = arg => {
|
||||
return arg !== 'false' ? arg : false;
|
||||
};
|
||||
|
||||
const coerceFalse = arg => {
|
||||
return arg !== 'false';
|
||||
};
|
||||
|
||||
const coerceAnyStringToInt = arg => {
|
||||
return typeof arg === 'string' ? parseInt(arg) : arg;
|
||||
};
|
||||
|
||||
const spaceDelimitedArgsMsg = (flag, args) => {
|
||||
let msg = `
|
||||
${logSymbols.warning} Warning: It looks like you're passing --${flag} a space-separated list of arguments:
|
||||
|
||||
"${args.join(' ')}"
|
||||
|
||||
This will work, but it's not recommended.
|
||||
|
||||
If you are trying to pass multiple arguments, separate them with commas instead:
|
||||
cypress run --${flag} arg1,arg2,arg3
|
||||
`;
|
||||
|
||||
if (flag === 'spec') {
|
||||
msg += `
|
||||
The most common cause of this warning is using an unescaped glob pattern. If you are
|
||||
trying to pass a glob pattern, escape it using quotes:
|
||||
cypress run --spec "**/*.spec.js"
|
||||
`;
|
||||
}
|
||||
|
||||
logger.log();
|
||||
logger.warn(stripIndent(msg));
|
||||
logger.log();
|
||||
};
|
||||
|
||||
const parseVariableOpts = (fnArgs, args) => {
|
||||
const [opts, unknownArgs] = fnArgs;
|
||||
|
||||
if (unknownArgs && unknownArgs.length && (opts.spec || opts.tag)) {
|
||||
// this will capture space-delimited args after
|
||||
// flags that could have possible multiple args
|
||||
// but before the next option
|
||||
// --spec spec1 spec2 or --tag foo bar
|
||||
const multiArgFlags = _.compact([opts.spec ? 'spec' : opts.spec, opts.tag ? 'tag' : opts.tag]);
|
||||
|
||||
_.forEach(multiArgFlags, flag => {
|
||||
const argIndex = _.indexOf(args, `--${flag}`) + 2;
|
||||
|
||||
const nextOptOffset = _.findIndex(_.slice(args, argIndex), arg => {
|
||||
return _.startsWith(arg, '--');
|
||||
});
|
||||
|
||||
const endIndex = nextOptOffset !== -1 ? argIndex + nextOptOffset : args.length;
|
||||
|
||||
const maybeArgs = _.slice(args, argIndex, endIndex);
|
||||
|
||||
const extraArgs = _.intersection(maybeArgs, unknownArgs);
|
||||
|
||||
if (extraArgs.length) {
|
||||
opts[flag] = [opts[flag]].concat(extraArgs);
|
||||
spaceDelimitedArgsMsg(flag, opts[flag]);
|
||||
opts[flag] = opts[flag].join(',');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
debug('variable-length opts parsed %o', {
|
||||
args,
|
||||
opts
|
||||
});
|
||||
return util.parseOpts(opts);
|
||||
};
|
||||
|
||||
const descriptions = {
|
||||
browserOpenMode: 'path to a custom browser to be added to the list of available browsers in Cypress',
|
||||
browserRunMode: 'runs Cypress in the browser with the given name. if a filesystem path is supplied, Cypress will attempt to use the browser at that path.',
|
||||
cacheClear: 'delete all cached binaries',
|
||||
cachePrune: 'deletes all cached binaries except for the version currently in use',
|
||||
cacheList: 'list cached binary versions',
|
||||
cachePath: 'print the path to the binary cache',
|
||||
cacheSize: 'Used with the list command to show the sizes of the cached folders',
|
||||
ciBuildId: 'the unique identifier for a run on your CI provider. typically a "BUILD_ID" env var. this value is automatically detected for most CI providers',
|
||||
config: 'sets configuration values. separate multiple values with a comma. overrides any value in cypress.json.',
|
||||
configFile: 'path to JSON file where configuration values are set. defaults to "cypress.json". pass "false" to disable.',
|
||||
detached: 'runs Cypress application in detached mode',
|
||||
dev: 'runs cypress in development and bypasses binary check',
|
||||
env: 'sets environment variables. separate multiple values with a comma. overrides any value in cypress.json or cypress.env.json',
|
||||
exit: 'keep the browser open after tests finish',
|
||||
forceInstall: 'force install the Cypress binary',
|
||||
global: 'force Cypress into global mode as if its globally installed',
|
||||
group: 'a named group for recorded runs in the Cypress Dashboard',
|
||||
headed: 'displays the browser instead of running headlessly',
|
||||
headless: 'hide the browser instead of running headed (default for cypress run)',
|
||||
key: 'your secret Record Key. you can omit this if you set a CYPRESS_RECORD_KEY environment variable.',
|
||||
parallel: 'enables concurrent runs and automatic load balancing of specs across multiple machines or processes',
|
||||
port: 'runs Cypress on a specific port. overrides any value in cypress.json.',
|
||||
project: 'path to the project',
|
||||
quiet: 'run quietly, using only the configured reporter',
|
||||
record: 'records the run. sends test results, screenshots and videos to your Cypress Dashboard.',
|
||||
reporter: 'runs a specific mocha reporter. pass a path to use a custom reporter. defaults to "spec"',
|
||||
reporterOptions: 'options for the mocha reporter. defaults to "null"',
|
||||
spec: 'runs specific spec file(s). defaults to "all"',
|
||||
tag: 'named tag(s) for recorded runs in the Cypress Dashboard',
|
||||
version: 'prints Cypress version'
|
||||
};
|
||||
const knownCommands = ['cache', 'help', '-h', '--help', 'install', 'open', 'run', 'open-ct', 'run-ct', 'verify', '-v', '--version', 'version', 'info'];
|
||||
|
||||
const text = description => {
|
||||
if (!descriptions[description]) {
|
||||
throw new Error(`Could not find description for: ${description}`);
|
||||
}
|
||||
|
||||
return descriptions[description];
|
||||
};
|
||||
|
||||
function includesVersion(args) {
|
||||
return _.includes(args, 'version') || _.includes(args, '--version') || _.includes(args, '-v');
|
||||
}
|
||||
|
||||
function showVersions(args) {
|
||||
debug('printing Cypress version');
|
||||
debug('additional arguments %o', args);
|
||||
const versionParser = commander.option('--component <package|binary|electron|node>', 'component to report version for').allowUnknownOption(true);
|
||||
const parsed = versionParser.parse(args);
|
||||
const parsedOptions = {
|
||||
component: parsed.component
|
||||
};
|
||||
debug('parsed version arguments %o', parsedOptions);
|
||||
|
||||
const reportAllVersions = versions => {
|
||||
logger.always('Cypress package version:', versions.package);
|
||||
logger.always('Cypress binary version:', versions.binary);
|
||||
logger.always('Electron version:', versions.electronVersion);
|
||||
logger.always('Bundled Node version:', versions.electronNodeVersion);
|
||||
};
|
||||
|
||||
const reportComponentVersion = (componentName, versions) => {
|
||||
const names = {
|
||||
package: 'package',
|
||||
binary: 'binary',
|
||||
electron: 'electronVersion',
|
||||
node: 'electronNodeVersion'
|
||||
};
|
||||
|
||||
if (!names[componentName]) {
|
||||
throw new Error(`Unknown component name "${componentName}"`);
|
||||
}
|
||||
|
||||
const name = names[componentName];
|
||||
|
||||
if (!versions[name]) {
|
||||
throw new Error(`Cannot find version for component "${componentName}" under property "${name}"`);
|
||||
}
|
||||
|
||||
const version = versions[name];
|
||||
logger.always(version);
|
||||
};
|
||||
|
||||
const defaultVersions = {
|
||||
package: undefined,
|
||||
binary: undefined,
|
||||
electronVersion: undefined,
|
||||
electronNodeVersion: undefined
|
||||
};
|
||||
return require('./exec/versions').getVersions().then((versions = defaultVersions) => {
|
||||
if (parsedOptions.component) {
|
||||
reportComponentVersion(parsedOptions.component, versions);
|
||||
} else {
|
||||
reportAllVersions(versions);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}).catch(util.logErrorExit1);
|
||||
}
|
||||
|
||||
const createProgram = () => {
|
||||
const program = new commander.Command(); // bug in commander not printing name
|
||||
// in usage help docs
|
||||
|
||||
program._name = 'cypress';
|
||||
program.usage('<command> [options]');
|
||||
return program;
|
||||
};
|
||||
|
||||
const addCypressRunCommand = program => {
|
||||
return program.command('run').usage('[options]').description('Runs Cypress tests from the CLI without the GUI').option('-b, --browser <browser-name-or-path>', text('browserRunMode')).option('--ci-build-id <id>', text('ciBuildId')).option('-c, --config <config>', text('config')).option('-C, --config-file <config-file>', text('configFile')).option('-e, --env <env>', text('env')).option('--group <name>', text('group')).option('-k, --key <record-key>', text('key')).option('--headed', text('headed')).option('--headless', text('headless')).option('--no-exit', text('exit')).option('--parallel', text('parallel')).option('-p, --port <port>', text('port')).option('-P, --project <project-path>', text('project')).option('-q, --quiet', text('quiet')).option('--record [bool]', text('record'), coerceFalse).option('-r, --reporter <reporter>', text('reporter')).option('-o, --reporter-options <reporter-options>', text('reporterOptions')).option('-s, --spec <spec>', text('spec')).option('-t, --tag <tag>', text('tag')).option('--dev', text('dev'), coerceFalse);
|
||||
};
|
||||
/**
|
||||
* Casts known command line options for "cypress run" to their intended type.
|
||||
* For example if the user passes "--port 5005" the ".port" property should be
|
||||
* a number 5005 and not a string "5005".
|
||||
*
|
||||
* Returns a clone of the original object.
|
||||
*/
|
||||
|
||||
|
||||
const castCypressRunOptions = opts => {
|
||||
// only properties that have type "string | false" in our TS definition
|
||||
// require special handling, because CLI parsing takes care of purely
|
||||
// boolean arguments
|
||||
const result = R.evolve({
|
||||
port: coerceAnyStringToInt,
|
||||
configFile: coerceFalseOrString
|
||||
})(opts);
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Parses `cypress run` command line option array into an object
|
||||
* with options that you can feed into a `cypress.run()` module API call.
|
||||
* @example
|
||||
* const options = parseRunCommand(['cypress', 'run', '--browser', 'chrome'])
|
||||
* // options is {browser: 'chrome'}
|
||||
*/
|
||||
parseRunCommand(args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!Array.isArray(args)) {
|
||||
return reject(new Error('Expected array of arguments'));
|
||||
} // make a copy of the input arguments array
|
||||
// and add placeholders where "node ..." would usually be
|
||||
// also remove "cypress" keyword at the start if present
|
||||
|
||||
|
||||
const cliArgs = args[0] === 'cypress' ? [...args.slice(1)] : [...args];
|
||||
cliArgs.unshift(null, null);
|
||||
debug('creating program parser');
|
||||
const program = createProgram();
|
||||
addCypressRunCommand(program).action((...fnArgs) => {
|
||||
debug('parsed Cypress run %o', fnArgs);
|
||||
const options = parseVariableOpts(fnArgs, cliArgs);
|
||||
debug('parsed options %o', options);
|
||||
const casted = castCypressRunOptions(options);
|
||||
debug('casted options %o', casted);
|
||||
resolve(casted);
|
||||
});
|
||||
debug('parsing args: %o', cliArgs);
|
||||
program.parse(cliArgs);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Parses the command line and kicks off Cypress process.
|
||||
*/
|
||||
init(args) {
|
||||
if (!args) {
|
||||
args = process.argv;
|
||||
}
|
||||
|
||||
const {
|
||||
CYPRESS_INTERNAL_ENV
|
||||
} = process.env;
|
||||
|
||||
if (!util.isValidCypressInternalEnvValue(CYPRESS_INTERNAL_ENV)) {
|
||||
debug('invalid CYPRESS_INTERNAL_ENV value', CYPRESS_INTERNAL_ENV);
|
||||
return errors.exitWithError(errors.errors.invalidCypressEnv)(`CYPRESS_INTERNAL_ENV=${CYPRESS_INTERNAL_ENV}`);
|
||||
}
|
||||
|
||||
if (util.isNonProductionCypressInternalEnvValue(CYPRESS_INTERNAL_ENV)) {
|
||||
debug('non-production CYPRESS_INTERNAL_ENV value', CYPRESS_INTERNAL_ENV);
|
||||
let msg = `
|
||||
${logSymbols.warning} Warning: It looks like you're passing CYPRESS_INTERNAL_ENV=${CYPRESS_INTERNAL_ENV}
|
||||
|
||||
The environment variable "CYPRESS_INTERNAL_ENV" is reserved and should only be used internally.
|
||||
|
||||
Unset the "CYPRESS_INTERNAL_ENV" environment variable and run Cypress again.
|
||||
`;
|
||||
logger.log();
|
||||
logger.warn(stripIndent(msg));
|
||||
logger.log();
|
||||
}
|
||||
|
||||
const program = createProgram();
|
||||
program.command('help').description('Shows CLI help and exits').action(() => {
|
||||
program.help();
|
||||
});
|
||||
program.option('-v, --version', text('version')).command('version').description(text('version')).action(() => {
|
||||
showVersions(args);
|
||||
});
|
||||
program.command('open').usage('[options]').description('Opens Cypress in the interactive GUI.').option('-b, --browser <browser-path>', text('browserOpenMode')).option('-c, --config <config>', text('config')).option('-C, --config-file <config-file>', text('configFile')).option('-d, --detached [bool]', text('detached'), coerceFalse).option('-e, --env <env>', text('env')).option('--global', text('global')).option('-p, --port <port>', text('port')).option('-P, --project <project-path>', text('project')).option('--dev', text('dev'), coerceFalse).action(opts => {
|
||||
debug('opening Cypress');
|
||||
|
||||
require('./exec/open').start(util.parseOpts(opts)).catch(util.logErrorExit1);
|
||||
});
|
||||
addCypressRunCommand(program).action((...fnArgs) => {
|
||||
debug('running Cypress with args %o', fnArgs);
|
||||
|
||||
require('./exec/run').start(parseVariableOpts(fnArgs, args)).then(util.exit).catch(util.logErrorExit1);
|
||||
});
|
||||
program.command('open-ct').usage('[options]').description('Opens Cypress component testing interactive mode.').option('-b, --browser <browser-path>', text('browserOpenMode')).option('-c, --config <config>', text('config')).option('-C, --config-file <config-file>', text('configFile')).option('-d, --detached [bool]', text('detached'), coerceFalse).option('-e, --env <env>', text('env')).option('--global', text('global')).option('-p, --port <port>', text('port')).option('-P, --project <project-path>', text('project')).option('--dev', text('dev'), coerceFalse).action(opts => {
|
||||
debug('opening Cypress');
|
||||
|
||||
require('./exec/open').start({ ...util.parseOpts(opts),
|
||||
testingType: 'component'
|
||||
}).catch(util.logErrorExit1);
|
||||
});
|
||||
program.command('run-ct').usage('[options]').description('Runs all Cypress Component Testing suites').option('-b, --browser <browser-name-or-path>', text('browserRunMode')).option('--ci-build-id <id>', text('ciBuildId')).option('-c, --config <config>', text('config')).option('-C, --config-file <config-file>', text('configFile')).option('-e, --env <env>', text('env')).option('--group <name>', text('group')).option('-k, --key <record-key>', text('key')).option('--headed', text('headed')).option('--headless', text('headless')).option('--no-exit', text('exit')).option('--parallel', text('parallel')).option('-p, --port <port>', text('port')).option('-P, --project <project-path>', text('project')).option('-q, --quiet', text('quiet')).option('--record [bool]', text('record'), coerceFalse).option('-r, --reporter <reporter>', text('reporter')).option('-o, --reporter-options <reporter-options>', text('reporterOptions')).option('-s, --spec <spec>', text('spec')).option('-t, --tag <tag>', text('tag')).option('--dev', text('dev'), coerceFalse).action(opts => {
|
||||
debug('running Cypress run-ct');
|
||||
|
||||
require('./exec/run').start({ ...util.parseOpts(opts),
|
||||
testingType: 'component'
|
||||
}).then(util.exit).catch(util.logErrorExit1);
|
||||
});
|
||||
program.command('install').usage('[options]').description('Installs the Cypress executable matching this package\'s version').option('-f, --force', text('forceInstall')).action(opts => {
|
||||
require('./tasks/install').start(util.parseOpts(opts)).catch(util.logErrorExit1);
|
||||
});
|
||||
program.command('verify').usage('[options]').description('Verifies that Cypress is installed correctly and executable').option('--dev', text('dev'), coerceFalse).action(opts => {
|
||||
const defaultOpts = {
|
||||
force: true,
|
||||
welcomeMessage: false
|
||||
};
|
||||
const parsedOpts = util.parseOpts(opts);
|
||||
|
||||
const options = _.extend(parsedOpts, defaultOpts);
|
||||
|
||||
require('./tasks/verify').start(options).catch(util.logErrorExit1);
|
||||
});
|
||||
program.command('cache').usage('[command]').description('Manages the Cypress binary cache').option('list', text('cacheList')).option('path', text('cachePath')).option('clear', text('cacheClear')).option('prune', text('cachePrune')).option('--size', text('cacheSize')).action(function (opts, args) {
|
||||
if (!args || !args.length) {
|
||||
this.outputHelp();
|
||||
util.exit(1);
|
||||
}
|
||||
|
||||
const [command] = args;
|
||||
|
||||
if (!_.includes(['list', 'path', 'clear', 'prune'], command)) {
|
||||
unknownOption.call(this, `cache ${command}`, 'command');
|
||||
}
|
||||
|
||||
if (command === 'list') {
|
||||
debug('cache command %o', {
|
||||
command,
|
||||
size: opts.size
|
||||
});
|
||||
return cache.list(opts.size).catch({
|
||||
code: 'ENOENT'
|
||||
}, () => {
|
||||
logger.always('No cached binary versions were found.');
|
||||
process.exit(0);
|
||||
}).catch(e => {
|
||||
debug('cache list command failed with "%s"', e.message);
|
||||
util.logErrorExit1(e);
|
||||
});
|
||||
}
|
||||
|
||||
cache[command]();
|
||||
});
|
||||
program.command('info').usage('[command]').description('Prints Cypress and system information').option('--dev', text('dev'), coerceFalse).action(opts => {
|
||||
require('./exec/info').start(opts).then(util.exit).catch(util.logErrorExit1);
|
||||
});
|
||||
debug('cli starts with arguments %j', args);
|
||||
util.printNodeOptions(); // if there are no arguments
|
||||
|
||||
if (args.length <= 2) {
|
||||
debug('printing help');
|
||||
program.help(); // exits
|
||||
}
|
||||
|
||||
const firstCommand = args[2];
|
||||
|
||||
if (!_.includes(knownCommands, firstCommand)) {
|
||||
debug('unknown command %s', firstCommand);
|
||||
logger.error('Unknown command', `"${firstCommand}"`);
|
||||
program.outputHelp();
|
||||
return util.exit(1);
|
||||
}
|
||||
|
||||
if (includesVersion(args)) {
|
||||
// commander 2.11.0 changes behavior
|
||||
// and now does not understand top level options
|
||||
// .option('-v, --version').command('version')
|
||||
// so we have to manually catch '-v, --version'
|
||||
return showVersions(args);
|
||||
}
|
||||
|
||||
debug('program parsing arguments');
|
||||
return program.parse(args);
|
||||
}
|
||||
|
||||
}; // @ts-ignore
|
||||
|
||||
if (!module.parent) {
|
||||
logger.error('This CLI module should be required from another Node module');
|
||||
logger.error('and not executed directly');
|
||||
process.exit(-1);
|
||||
}
|
75
node_modules/cypress/lib/cypress.js
generated
vendored
Normal file
75
node_modules/cypress/lib/cypress.js
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
"use strict";
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/316
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const tmp = Promise.promisifyAll(require('tmp'));
|
||||
|
||||
const fs = require('./fs');
|
||||
|
||||
const open = require('./exec/open');
|
||||
|
||||
const run = require('./exec/run');
|
||||
|
||||
const util = require('./util');
|
||||
|
||||
const cli = require('./cli');
|
||||
|
||||
const cypressModuleApi = {
|
||||
/**
|
||||
* Opens Cypress GUI
|
||||
* @see https://on.cypress.io/module-api#cypress-open
|
||||
*/
|
||||
open(options = {}) {
|
||||
options = util.normalizeModuleOptions(options);
|
||||
return open.start(options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Runs Cypress tests in the current project
|
||||
* @see https://on.cypress.io/module-api#cypress-run
|
||||
*/
|
||||
run(options = {}) {
|
||||
if (!run.isValidProject(options.project)) {
|
||||
return Promise.reject(new Error(`Invalid project path parameter: ${options.project}`));
|
||||
}
|
||||
|
||||
options = util.normalizeModuleOptions(options);
|
||||
return tmp.fileAsync().then(outputPath => {
|
||||
options.outputPath = outputPath;
|
||||
return run.start(options).then(failedTests => {
|
||||
return fs.readJsonAsync(outputPath, {
|
||||
throws: false
|
||||
}).then(output => {
|
||||
if (!output) {
|
||||
return {
|
||||
status: 'failed',
|
||||
failures: failedTests,
|
||||
message: 'Could not find Cypress test run results'
|
||||
};
|
||||
}
|
||||
|
||||
return output;
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
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.parseRunCommand(args);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
module.exports = cypressModuleApi;
|
392
node_modules/cypress/lib/errors.js
generated
vendored
Normal file
392
node_modules/cypress/lib/errors.js
generated
vendored
Normal file
@@ -0,0 +1,392 @@
|
||||
"use strict";
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
const {
|
||||
stripIndent,
|
||||
stripIndents
|
||||
} = require('common-tags');
|
||||
|
||||
const {
|
||||
merge
|
||||
} = require('ramda');
|
||||
|
||||
const la = require('lazy-ass');
|
||||
|
||||
const is = require('check-more-types');
|
||||
|
||||
const util = require('./util');
|
||||
|
||||
const state = require('./tasks/state');
|
||||
|
||||
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 = 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: stripIndent`
|
||||
Please provide a valid project path.
|
||||
|
||||
Learn more about ${chalk.cyan('cypress run')} at:
|
||||
|
||||
${chalk.blue(runDocumentationUrl)}
|
||||
`
|
||||
};
|
||||
const failedDownload = {
|
||||
description: 'The Cypress App could not be downloaded.',
|
||||
solution: 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 missingApp = binaryDir => {
|
||||
return {
|
||||
description: `No version of Cypress is installed in: ${chalk.cyan(binaryDir)}`,
|
||||
solution: 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: 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: 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: 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: 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: stripIndent`
|
||||
This command failed with the following output:
|
||||
|
||||
${smokeTestCommand}
|
||||
|
||||
`
|
||||
};
|
||||
};
|
||||
|
||||
const invalidSmokeTestDisplayError = {
|
||||
code: 'INVALID_SMOKE_TEST_DISPLAY_ERROR',
|
||||
description: 'Cypress verification failed.',
|
||||
|
||||
solution(msg) {
|
||||
return 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: 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: 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 = 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\'')}.`
|
||||
};
|
||||
/**
|
||||
* 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 = `**/${state.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 util.getPlatformInfo().then(platform => {
|
||||
return merge(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 formErrorText(errorObject).then(errorMessage => {
|
||||
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 addPlatformInformation(info).then(obj => {
|
||||
const formatted = [];
|
||||
|
||||
function add(msg) {
|
||||
formatted.push(stripIndents(msg));
|
||||
}
|
||||
|
||||
la(is.unemptyString(obj.description), 'expected error description to be text', obj.description); // assuming that if there the solution is a function it will handle
|
||||
// error message and (optional previous error message)
|
||||
|
||||
if (is.fn(obj.solution)) {
|
||||
const text = obj.solution(msg, prevMessage);
|
||||
la(is.unemptyString(text), 'expected solution to be text', text);
|
||||
add(`
|
||||
${obj.description}
|
||||
|
||||
${text}
|
||||
|
||||
`);
|
||||
} else {
|
||||
la(is.unemptyString(obj.solution), 'expected error solution to be text', obj.solution);
|
||||
add(`
|
||||
${obj.description}
|
||||
|
||||
${obj.solution}
|
||||
|
||||
`);
|
||||
|
||||
if (msg) {
|
||||
add(`
|
||||
${hr}
|
||||
|
||||
${msg}
|
||||
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
add(`
|
||||
${hr}
|
||||
|
||||
${obj.platform}
|
||||
`);
|
||||
|
||||
if (obj.footer) {
|
||||
add(`
|
||||
|
||||
${hr}
|
||||
|
||||
${obj.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) => {
|
||||
return formErrorText(info, msg, prevMessage).then(raise(info));
|
||||
};
|
||||
};
|
||||
/**
|
||||
* 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 => {
|
||||
return formErrorText(info, msg).then(text => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(text);
|
||||
process.exit(info.exitCode || 1);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
raise,
|
||||
exitWithError,
|
||||
// formError,
|
||||
formErrorText,
|
||||
throwFormErrorText,
|
||||
getError,
|
||||
hr,
|
||||
errors: {
|
||||
unknownError,
|
||||
nonZeroExitCodeXvfb,
|
||||
missingXvfb,
|
||||
missingApp,
|
||||
notInstalledCI,
|
||||
missingDependency,
|
||||
invalidSmokeTestDisplayError,
|
||||
versionMismatch,
|
||||
binaryNotExecutable,
|
||||
unexpected,
|
||||
failedDownload,
|
||||
failedUnzip,
|
||||
invalidCypressEnv,
|
||||
invalidCacheDirectory,
|
||||
CYPRESS_RUN_BINARY,
|
||||
smokeTestFailure,
|
||||
childProcessKilled,
|
||||
incompatibleHeadlessFlags,
|
||||
invalidRunProjectPath,
|
||||
invalidTestingType
|
||||
}
|
||||
};
|
96
node_modules/cypress/lib/exec/info.js
generated
vendored
Normal file
96
node_modules/cypress/lib/exec/info.js
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
"use strict";
|
||||
|
||||
/* eslint-disable no-console */
|
||||
const spawn = require('./spawn');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const state = require('../tasks/state');
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
const prettyBytes = require('pretty-bytes');
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const R = require('ramda'); // color for numbers and show values
|
||||
|
||||
|
||||
const g = chalk.green; // color for paths
|
||||
|
||||
const p = chalk.cyan; // urls
|
||||
|
||||
const link = chalk.blue.underline; // to be exported
|
||||
|
||||
const methods = {};
|
||||
|
||||
methods.findProxyEnvironmentVariables = () => {
|
||||
return _.pick(process.env, ['HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY']);
|
||||
};
|
||||
|
||||
const maskSensitiveVariables = R.evolve({
|
||||
CYPRESS_RECORD_KEY: R.always('<redacted>')
|
||||
});
|
||||
|
||||
methods.findCypressEnvironmentVariables = () => {
|
||||
const isCyVariable = (val, key) => key.startsWith('CYPRESS_');
|
||||
|
||||
return R.pickBy(isCyVariable)(process.env);
|
||||
};
|
||||
|
||||
const formatCypressVariables = () => {
|
||||
const vars = methods.findCypressEnvironmentVariables();
|
||||
return maskSensitiveVariables(vars);
|
||||
};
|
||||
|
||||
methods.start = (options = {}) => {
|
||||
const args = ['--mode=info'];
|
||||
return spawn.start(args, {
|
||||
dev: options.dev
|
||||
}).then(() => {
|
||||
console.log();
|
||||
const proxyVars = methods.findProxyEnvironmentVariables();
|
||||
|
||||
if (_.isEmpty(proxyVars)) {
|
||||
console.log('Proxy Settings: none detected');
|
||||
} else {
|
||||
console.log('Proxy Settings:');
|
||||
|
||||
_.forEach(proxyVars, (value, key) => {
|
||||
console.log('%s: %s', key, g(value));
|
||||
});
|
||||
|
||||
console.log();
|
||||
console.log('Learn More: %s', link('https://on.cypress.io/proxy-configuration'));
|
||||
console.log();
|
||||
}
|
||||
}).then(() => {
|
||||
const cyVars = formatCypressVariables();
|
||||
|
||||
if (_.isEmpty(cyVars)) {
|
||||
console.log('Environment Variables: none detected');
|
||||
} else {
|
||||
console.log('Environment Variables:');
|
||||
|
||||
_.forEach(cyVars, (value, key) => {
|
||||
console.log('%s: %s', key, g(value));
|
||||
});
|
||||
}
|
||||
}).then(() => {
|
||||
console.log();
|
||||
console.log('Application Data:', p(util.getApplicationDataFolder()));
|
||||
console.log('Browser Profiles:', p(util.getApplicationDataFolder('browsers')));
|
||||
console.log('Binary Caches: %s', p(state.getCacheDir()));
|
||||
}).then(() => {
|
||||
console.log();
|
||||
return util.getOsVersionAsync().then(osVersion => {
|
||||
console.log('Cypress Version: %s', g(util.pkgVersion()));
|
||||
console.log('System Platform: %s (%s)', g(os.platform()), g(osVersion));
|
||||
console.log('System Memory: %s free %s', g(prettyBytes(os.totalmem())), g(prettyBytes(os.freemem())));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = methods;
|
66
node_modules/cypress/lib/exec/open.js
generated
vendored
Normal file
66
node_modules/cypress/lib/exec/open.js
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
"use strict";
|
||||
|
||||
const debug = require('debug')('cypress:cli');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const spawn = require('./spawn');
|
||||
|
||||
const verify = require('../tasks/verify');
|
||||
|
||||
const {
|
||||
processTestingType
|
||||
} = require('./shared');
|
||||
|
||||
module.exports = {
|
||||
start(options = {}) {
|
||||
if (!util.isInstalledGlobally() && !options.global && !options.project) {
|
||||
options.project = process.cwd();
|
||||
}
|
||||
|
||||
const args = [];
|
||||
|
||||
if (options.config) {
|
||||
args.push('--config', options.config);
|
||||
}
|
||||
|
||||
if (options.configFile !== undefined) {
|
||||
args.push('--config-file', options.configFile);
|
||||
}
|
||||
|
||||
if (options.browser) {
|
||||
args.push('--browser', options.browser);
|
||||
}
|
||||
|
||||
if (options.env) {
|
||||
args.push('--env', options.env);
|
||||
}
|
||||
|
||||
if (options.port) {
|
||||
args.push('--port', options.port);
|
||||
}
|
||||
|
||||
if (options.project) {
|
||||
args.push('--project', options.project);
|
||||
}
|
||||
|
||||
args.push(...processTestingType(options.testingType));
|
||||
debug('opening from options %j', options);
|
||||
debug('command line arguments %j', args);
|
||||
|
||||
function open() {
|
||||
return spawn.start(args, {
|
||||
dev: options.dev,
|
||||
detached: Boolean(options.detached),
|
||||
stdio: 'inherit'
|
||||
});
|
||||
}
|
||||
|
||||
if (options.dev) {
|
||||
return open();
|
||||
}
|
||||
|
||||
return verify.start().then(open);
|
||||
}
|
||||
|
||||
};
|
200
node_modules/cypress/lib/exec/run.js
generated
vendored
Normal file
200
node_modules/cypress/lib/exec/run.js
generated
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
"use strict";
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const debug = require('debug')('cypress:cli:run');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const spawn = require('./spawn');
|
||||
|
||||
const verify = require('../tasks/verify');
|
||||
|
||||
const {
|
||||
exitWithError,
|
||||
errors
|
||||
} = require('../errors');
|
||||
|
||||
const {
|
||||
processTestingType,
|
||||
throwInvalidOptionError
|
||||
} = require('./shared');
|
||||
/**
|
||||
* Typically a user passes a string path to the project.
|
||||
* But "cypress open" allows using `false` to open in global mode,
|
||||
* and the user can accidentally execute `cypress run --project false`
|
||||
* which should be invalid.
|
||||
*/
|
||||
|
||||
|
||||
const isValidProject = v => {
|
||||
if (typeof v === 'boolean') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (v === '' || v === 'false' || v === 'true') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
/**
|
||||
* Maps options collected by the CLI
|
||||
* and forms list of CLI arguments to the server.
|
||||
*
|
||||
* Note: there is lightweight validation, with errors
|
||||
* thrown synchronously.
|
||||
*
|
||||
* @returns {string[]} list of CLI arguments
|
||||
*/
|
||||
|
||||
|
||||
const processRunOptions = (options = {}) => {
|
||||
debug('processing run options %o', options);
|
||||
|
||||
if (!isValidProject(options.project)) {
|
||||
debug('invalid project option %o', {
|
||||
project: options.project
|
||||
});
|
||||
return throwInvalidOptionError(errors.invalidRunProjectPath);
|
||||
}
|
||||
|
||||
const args = ['--run-project', options.project];
|
||||
|
||||
if (options.browser) {
|
||||
args.push('--browser', options.browser);
|
||||
}
|
||||
|
||||
if (options.ciBuildId) {
|
||||
args.push('--ci-build-id', options.ciBuildId);
|
||||
}
|
||||
|
||||
if (options.config) {
|
||||
args.push('--config', options.config);
|
||||
}
|
||||
|
||||
if (options.configFile !== undefined) {
|
||||
args.push('--config-file', options.configFile);
|
||||
}
|
||||
|
||||
if (options.env) {
|
||||
args.push('--env', options.env);
|
||||
}
|
||||
|
||||
if (options.exit === false) {
|
||||
args.push('--no-exit');
|
||||
}
|
||||
|
||||
if (options.group) {
|
||||
args.push('--group', options.group);
|
||||
}
|
||||
|
||||
if (options.headed) {
|
||||
args.push('--headed', options.headed);
|
||||
}
|
||||
|
||||
if (options.headless) {
|
||||
if (options.headed) {
|
||||
return throwInvalidOptionError(errors.incompatibleHeadlessFlags);
|
||||
}
|
||||
|
||||
args.push('--headed', !options.headless);
|
||||
} // if key is set use that - else attempt to find it by environment variable
|
||||
|
||||
|
||||
if (options.key == null) {
|
||||
debug('--key is not set, looking up environment variable CYPRESS_RECORD_KEY');
|
||||
options.key = util.getEnv('CYPRESS_RECORD_KEY');
|
||||
} // if we have a key assume we're in record mode
|
||||
|
||||
|
||||
if (options.key) {
|
||||
args.push('--key', options.key);
|
||||
}
|
||||
|
||||
if (options.outputPath) {
|
||||
args.push('--output-path', options.outputPath);
|
||||
}
|
||||
|
||||
if (options.parallel) {
|
||||
args.push('--parallel');
|
||||
}
|
||||
|
||||
if (options.port) {
|
||||
args.push('--port', options.port);
|
||||
}
|
||||
|
||||
if (options.quiet) {
|
||||
args.push('--quiet');
|
||||
} // if record is defined and we're not
|
||||
// already in ci mode, then send it up
|
||||
|
||||
|
||||
if (options.record != null) {
|
||||
args.push('--record', options.record);
|
||||
} // if we have a specific reporter push that into the args
|
||||
|
||||
|
||||
if (options.reporter) {
|
||||
args.push('--reporter', options.reporter);
|
||||
} // if we have a specific reporter push that into the args
|
||||
|
||||
|
||||
if (options.reporterOptions) {
|
||||
args.push('--reporter-options', options.reporterOptions);
|
||||
} // if we have specific spec(s) push that into the args
|
||||
|
||||
|
||||
if (options.spec) {
|
||||
args.push('--spec', options.spec);
|
||||
}
|
||||
|
||||
if (options.tag) {
|
||||
args.push('--tag', options.tag);
|
||||
}
|
||||
|
||||
args.push(...processTestingType(options.testingType));
|
||||
return args;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
processRunOptions,
|
||||
isValidProject,
|
||||
|
||||
// resolves with the number of failed tests
|
||||
start(options = {}) {
|
||||
_.defaults(options, {
|
||||
key: null,
|
||||
spec: null,
|
||||
reporter: null,
|
||||
reporterOptions: null,
|
||||
project: process.cwd()
|
||||
});
|
||||
|
||||
function run() {
|
||||
let args;
|
||||
|
||||
try {
|
||||
args = processRunOptions(options);
|
||||
} catch (err) {
|
||||
if (err.details) {
|
||||
return exitWithError(err.details)();
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
debug('run to spawn.start args %j', args);
|
||||
return spawn.start(args, {
|
||||
dev: options.dev
|
||||
});
|
||||
}
|
||||
|
||||
if (options.dev) {
|
||||
return run();
|
||||
}
|
||||
|
||||
return verify.start().then(run);
|
||||
}
|
||||
|
||||
};
|
50
node_modules/cypress/lib/exec/shared.js
generated
vendored
Normal file
50
node_modules/cypress/lib/exec/shared.js
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
errors
|
||||
} = require('../errors');
|
||||
/**
|
||||
* Throws an error with "details" property from
|
||||
* "errors" object.
|
||||
* @param {Object} details - Error details
|
||||
*/
|
||||
|
||||
|
||||
const throwInvalidOptionError = details => {
|
||||
if (!details) {
|
||||
details = errors.unknownError;
|
||||
} // throw this error synchronously, it will be caught later on and
|
||||
// the details will be propagated to the promise chain
|
||||
|
||||
|
||||
const err = new Error();
|
||||
err.details = details;
|
||||
throw err;
|
||||
};
|
||||
/**
|
||||
* Selects exec args based on the configured `testingType`
|
||||
* @param {string} testingType The type of tests being executed
|
||||
* @returns {string[]} The array of new exec arguments
|
||||
*/
|
||||
|
||||
|
||||
const processTestingType = testingType => {
|
||||
if (testingType) {
|
||||
if (testingType === 'e2e') {
|
||||
return ['--testing-type', 'e2e'];
|
||||
}
|
||||
|
||||
if (testingType === 'component') {
|
||||
return ['--testing-type', 'component'];
|
||||
}
|
||||
|
||||
return throwInvalidOptionError(errors.invalidTestingType);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
throwInvalidOptionError,
|
||||
processTestingType
|
||||
};
|
299
node_modules/cypress/lib/exec/spawn.js
generated
vendored
Normal file
299
node_modules/cypress/lib/exec/spawn.js
generated
vendored
Normal file
@@ -0,0 +1,299 @@
|
||||
"use strict";
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const cp = require('child_process');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const debug = require('debug')('cypress:cli');
|
||||
|
||||
const debugElectron = require('debug')('cypress:electron');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const state = require('../tasks/state');
|
||||
|
||||
const xvfb = require('./xvfb');
|
||||
|
||||
const verify = require('../tasks/verify');
|
||||
|
||||
const errors = require('../errors');
|
||||
|
||||
const isXlibOrLibudevRe = /^(?:Xlib|libudev)/;
|
||||
const isHighSierraWarningRe = /\*\*\* WARNING/;
|
||||
const isRenderWorkerRe = /\.RenderWorker-/;
|
||||
const GARBAGE_WARNINGS = [isXlibOrLibudevRe, isHighSierraWarningRe, isRenderWorkerRe];
|
||||
|
||||
const isGarbageLineWarning = str => {
|
||||
return _.some(GARBAGE_WARNINGS, re => {
|
||||
return re.test(str);
|
||||
});
|
||||
};
|
||||
|
||||
function isPlatform(platform) {
|
||||
return os.platform() === platform;
|
||||
}
|
||||
|
||||
function needsStderrPiped(needsXvfb) {
|
||||
return _.some([isPlatform('darwin'), needsXvfb && isPlatform('linux'), util.isPossibleLinuxWithIncorrectDisplay()]);
|
||||
}
|
||||
|
||||
function needsEverythingPipedDirectly() {
|
||||
return isPlatform('win32');
|
||||
}
|
||||
|
||||
function getStdio(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';
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isGarbageLineWarning,
|
||||
|
||||
start(args, options = {}) {
|
||||
const needsXvfb = xvfb.isNeeded();
|
||||
let executable = state.getPathToExecutable(state.getBinaryDir());
|
||||
|
||||
if (util.getEnv('CYPRESS_RUN_BINARY')) {
|
||||
executable = path.resolve(util.getEnv('CYPRESS_RUN_BINARY'));
|
||||
}
|
||||
|
||||
debug('needs to start own Xvfb?', needsXvfb); // Always push cwd into the args
|
||||
// which additionally acts as a signal to the
|
||||
// binary that it was invoked through the NPM module
|
||||
|
||||
args = args || [];
|
||||
|
||||
if (typeof args === 'string') {
|
||||
args = [args];
|
||||
}
|
||||
|
||||
args = [...args, '--cwd', process.cwd()];
|
||||
|
||||
_.defaults(options, {
|
||||
dev: false,
|
||||
env: process.env,
|
||||
detached: false,
|
||||
stdio: getStdio(needsXvfb)
|
||||
});
|
||||
|
||||
const spawn = (overrides = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
_.defaults(overrides, {
|
||||
onStderrData: false,
|
||||
electronLogging: false
|
||||
});
|
||||
|
||||
const {
|
||||
onStderrData,
|
||||
electronLogging
|
||||
} = overrides;
|
||||
const envOverrides = util.getEnvOverrides(options);
|
||||
const electronArgs = [];
|
||||
const node11WindowsFix = isPlatform('win32');
|
||||
|
||||
if (options.dev) {
|
||||
// if we're in dev then reset
|
||||
// the launch cmd to be 'npm run dev'
|
||||
executable = 'node';
|
||||
electronArgs.unshift(path.resolve(__dirname, '..', '..', '..', 'scripts', 'start.js'));
|
||||
debug('in dev mode the args became %o', args);
|
||||
}
|
||||
|
||||
if (!options.dev && verify.needsSandbox()) {
|
||||
electronArgs.push('--no-sandbox');
|
||||
} // strip dev out of child process options
|
||||
|
||||
|
||||
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 (electronLogging) {
|
||||
stdioOptions.env.ELECTRON_ENABLE_LOGGING = true;
|
||||
}
|
||||
|
||||
if (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.
|
||||
const serverEntryPoint = path.join(state.getBinaryPkgPath(path.dirname(executable)), '..', 'index.js');
|
||||
args = [serverEntryPoint, ...args];
|
||||
} 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];
|
||||
}
|
||||
|
||||
debug('spawning Cypress with executable: %s', executable);
|
||||
debug('spawn args %o %o', args, _.omit(stdioOptions, 'env'));
|
||||
const child = cp.spawn(executable, args, stdioOptions);
|
||||
|
||||
function resolveOn(event) {
|
||||
return function (code, signal) {
|
||||
debug('child event fired %o', {
|
||||
event,
|
||||
code,
|
||||
signal
|
||||
});
|
||||
|
||||
if (code === null) {
|
||||
const errorObject = errors.errors.childProcessKilled(event, signal);
|
||||
return errors.getError(errorObject).then(reject);
|
||||
}
|
||||
|
||||
resolve(code);
|
||||
};
|
||||
}
|
||||
|
||||
child.on('close', resolveOn('close'));
|
||||
child.on('exit', resolveOn('exit'));
|
||||
child.on('error', reject); // 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.stdin.pipe(child.stdin);
|
||||
}
|
||||
|
||||
if (child.stdout) {
|
||||
debug('piping child STDOUT to process STDOUT');
|
||||
child.stdout.pipe(process.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(); // bail if this is warning line garbage
|
||||
|
||||
if (isGarbageLineWarning(str)) {
|
||||
return;
|
||||
} // if we have a callback and this explictly returns
|
||||
// false then bail
|
||||
|
||||
|
||||
if (onStderrData && onStderrData(str) === false) {
|
||||
return;
|
||||
} // else pass it along!
|
||||
|
||||
|
||||
process.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.stdin.on('error', err => {
|
||||
if (['EPIPE', 'ENOTCONN'].includes(err.code)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
|
||||
if (stdioOptions.detached) {
|
||||
child.unref();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const spawnInXvfb = () => {
|
||||
return xvfb.start().then(userFriendlySpawn).finally(xvfb.stop);
|
||||
};
|
||||
|
||||
const userFriendlySpawn = linuxWithDisplayEnv => {
|
||||
debug('spawning, should retry on display problem?', Boolean(linuxWithDisplayEnv));
|
||||
let brokenGtkDisplay;
|
||||
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 (util.isBrokenGtkDisplay(str)) {
|
||||
brokenGtkDisplay = true;
|
||||
} // we should attempt to always slurp up
|
||||
// the stderr logs unless we've explicitly
|
||||
// enabled the electron debug logging
|
||||
|
||||
|
||||
if (!debugElectron.enabled) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return spawn(overrides).then(code => {
|
||||
if (code !== 0 && brokenGtkDisplay) {
|
||||
util.logBrokenGtkDisplayWarning();
|
||||
return spawnInXvfb();
|
||||
}
|
||||
|
||||
return code;
|
||||
}) // we can format and handle an error message from the code above
|
||||
// prevent wrapping error again by using "known: undefined" filter
|
||||
.catch({
|
||||
known: undefined
|
||||
}, errors.throwFormErrorText(errors.errors.unexpected));
|
||||
};
|
||||
|
||||
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.isPossibleLinuxWithIncorrectDisplay();
|
||||
return userFriendlySpawn(linuxWithDisplayEnv);
|
||||
}
|
||||
|
||||
};
|
59
node_modules/cypress/lib/exec/versions.js
generated
vendored
Normal file
59
node_modules/cypress/lib/exec/versions.js
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
"use strict";
|
||||
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const debug = require('debug')('cypress:cli');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const state = require('../tasks/state');
|
||||
|
||||
const {
|
||||
throwFormErrorText,
|
||||
errors
|
||||
} = require('../errors');
|
||||
|
||||
const getVersions = () => {
|
||||
return Promise.try(() => {
|
||||
if (util.getEnv('CYPRESS_RUN_BINARY')) {
|
||||
let envBinaryPath = path.resolve(util.getEnv('CYPRESS_RUN_BINARY'));
|
||||
return state.parseRealPlatformBinaryFolderAsync(envBinaryPath).then(envBinaryDir => {
|
||||
if (!envBinaryDir) {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))();
|
||||
}
|
||||
|
||||
debug('CYPRESS_RUN_BINARY has binaryDir:', envBinaryDir);
|
||||
return envBinaryDir;
|
||||
}).catch({
|
||||
code: 'ENOENT'
|
||||
}, err => {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(err.message);
|
||||
});
|
||||
}
|
||||
|
||||
return state.getBinaryDir();
|
||||
}).then(state.getBinaryPkgAsync).then(pkg => {
|
||||
const versions = {
|
||||
binary: state.getBinaryPkgVersion(pkg),
|
||||
electronVersion: state.getBinaryElectronVersion(pkg),
|
||||
electronNodeVersion: state.getBinaryElectronNodeVersion(pkg)
|
||||
};
|
||||
debug('binary versions %o', versions);
|
||||
return versions;
|
||||
}).then(binaryVersions => {
|
||||
const versions = {
|
||||
package: util.pkgVersion(),
|
||||
binary: binaryVersions.binary || 'not installed',
|
||||
electronVersion: binaryVersions.electronVersion || 'not found',
|
||||
electronNodeVersion: binaryVersions.electronNodeVersion || 'not found'
|
||||
};
|
||||
debug('combined versions %o', versions);
|
||||
return versions;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getVersions
|
||||
};
|
106
node_modules/cypress/lib/exec/xvfb.js
generated
vendored
Normal file
106
node_modules/cypress/lib/exec/xvfb.js
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
"use strict";
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const Xvfb = require('@cypress/xvfb');
|
||||
|
||||
const {
|
||||
stripIndent
|
||||
} = require('common-tags');
|
||||
|
||||
const Debug = require('debug');
|
||||
|
||||
const {
|
||||
throwFormErrorText,
|
||||
errors
|
||||
} = require('../errors');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const debug = Debug('cypress:cli');
|
||||
const debugXvfb = Debug('cypress:xvfb');
|
||||
debug.Debug = debugXvfb.Debug = Debug;
|
||||
const xvfbOptions = {
|
||||
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 = Promise.promisifyAll(new Xvfb(xvfbOptions));
|
||||
module.exports = {
|
||||
_debugXvfb: debugXvfb,
|
||||
// expose for testing
|
||||
_xvfb: xvfb,
|
||||
// expose for testing
|
||||
_xvfbOptions: xvfbOptions,
|
||||
|
||||
// expose for testing
|
||||
start() {
|
||||
debug('Starting Xvfb');
|
||||
return xvfb.startAsync().return(null).catch({
|
||||
nonZeroExitCode: true
|
||||
}, throwFormErrorText(errors.nonZeroExitCodeXvfb)).catch(err => {
|
||||
if (err.known) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return throwFormErrorText(errors.missingXvfb)(err);
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
debug('Stopping Xvfb');
|
||||
return xvfb.stopAsync().return(null).catch(() => {// noop
|
||||
});
|
||||
},
|
||||
|
||||
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 = 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
|
||||
verify() {
|
||||
return xvfb.startAsync().return(true).catch(err => {
|
||||
debug('Could not verify xvfb: %s', err.message);
|
||||
return false;
|
||||
}).finally(xvfb.stopAsync);
|
||||
}
|
||||
|
||||
};
|
5
node_modules/cypress/lib/fs.js
generated
vendored
Normal file
5
node_modules/cypress/lib/fs.js
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
const Promise = require('bluebird');
|
||||
|
||||
module.exports = Promise.promisifyAll(require('fs-extra'));
|
59
node_modules/cypress/lib/logger.js
generated
vendored
Normal file
59
node_modules/cypress/lib/logger.js
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
"use strict";
|
||||
|
||||
const R = require('ramda');
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
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');
|
||||
R.forEach(log, lines);
|
||||
};
|
||||
|
||||
const print = () => {
|
||||
return logs.join('\n');
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
logs = [];
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
log,
|
||||
warn,
|
||||
error,
|
||||
always,
|
||||
logLines,
|
||||
print,
|
||||
reset,
|
||||
logLevel
|
||||
};
|
156
node_modules/cypress/lib/tasks/cache.js
generated
vendored
Normal file
156
node_modules/cypress/lib/tasks/cache.js
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
"use strict";
|
||||
|
||||
const state = require('./state');
|
||||
|
||||
const logger = require('../logger');
|
||||
|
||||
const fs = require('../fs');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const {
|
||||
join
|
||||
} = require('path');
|
||||
|
||||
const Table = require('cli-table3');
|
||||
|
||||
const dayjs = require('dayjs');
|
||||
|
||||
const relativeTime = require('dayjs/plugin/relativeTime');
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const getFolderSize = require('./get-folder-size');
|
||||
|
||||
const Bluebird = require('bluebird');
|
||||
|
||||
dayjs.extend(relativeTime); // output colors for the table
|
||||
|
||||
const colors = {
|
||||
titles: chalk.white,
|
||||
dates: chalk.cyan,
|
||||
values: chalk.green,
|
||||
size: chalk.gray
|
||||
};
|
||||
|
||||
const logCachePath = () => {
|
||||
logger.always(state.getCacheDir());
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
return fs.removeAsync(state.getCacheDir());
|
||||
};
|
||||
|
||||
const prune = () => {
|
||||
const cacheDir = state.getCacheDir();
|
||||
const currentVersion = util.pkgVersion();
|
||||
let deletedBinary = false;
|
||||
return fs.readdirAsync(cacheDir).then(versions => {
|
||||
return Bluebird.all(versions.map(version => {
|
||||
if (version !== currentVersion) {
|
||||
deletedBinary = true;
|
||||
const versionDir = join(cacheDir, version);
|
||||
return fs.removeAsync(versionDir);
|
||||
}
|
||||
}));
|
||||
}).then(() => {
|
||||
if (deletedBinary) {
|
||||
logger.always(`Deleted all binary caches except for the ${currentVersion} binary cache.`);
|
||||
} else {
|
||||
logger.always(`No binary caches found to prune.`);
|
||||
}
|
||||
}).catch({
|
||||
code: 'ENOENT'
|
||||
}, () => {
|
||||
logger.always(`No Cypress cache was found at ${cacheDir}. Nothing to prune.`);
|
||||
});
|
||||
};
|
||||
|
||||
const fileSizeInMB = size => {
|
||||
return `${(size / 1024 / 1024).toFixed(1)}MB`;
|
||||
};
|
||||
/**
|
||||
* Collects all cached versions, finds when each was used
|
||||
* and prints a table with results to the terminal
|
||||
*/
|
||||
|
||||
|
||||
const list = showSize => {
|
||||
return getCachedVersions(showSize).then(binaries => {
|
||||
const head = [colors.titles('version'), colors.titles('last used')];
|
||||
|
||||
if (showSize) {
|
||||
head.push(colors.titles('size'));
|
||||
}
|
||||
|
||||
const table = new Table({
|
||||
head
|
||||
});
|
||||
binaries.forEach(binary => {
|
||||
const versionString = colors.values(binary.version);
|
||||
const lastUsed = binary.accessed ? colors.dates(binary.accessed) : 'unknown';
|
||||
const row = [versionString, lastUsed];
|
||||
|
||||
if (showSize) {
|
||||
const size = colors.size(fileSizeInMB(binary.size));
|
||||
row.push(size);
|
||||
}
|
||||
|
||||
return table.push(row);
|
||||
});
|
||||
logger.always(table.toString());
|
||||
});
|
||||
};
|
||||
|
||||
const getCachedVersions = showSize => {
|
||||
const cacheDir = state.getCacheDir();
|
||||
return fs.readdirAsync(cacheDir).filter(util.isSemver).map(version => {
|
||||
return {
|
||||
version,
|
||||
folderPath: join(cacheDir, version)
|
||||
};
|
||||
}).mapSeries(binary => {
|
||||
// last access time on the folder is different from last access time
|
||||
// on the Cypress binary
|
||||
const binaryDir = state.getBinaryDir(binary.version);
|
||||
const executable = state.getPathToExecutable(binaryDir);
|
||||
return fs.statAsync(executable).then(stat => {
|
||||
const lastAccessedTime = _.get(stat, 'atime');
|
||||
|
||||
if (!lastAccessedTime) {
|
||||
// the test runner has never been opened
|
||||
// or could be a test simulating missing timestamp
|
||||
return binary;
|
||||
}
|
||||
|
||||
const accessed = dayjs(lastAccessedTime).fromNow();
|
||||
binary.accessed = accessed;
|
||||
return binary;
|
||||
}, e => {
|
||||
// could not find the binary or gets its stats
|
||||
return binary;
|
||||
});
|
||||
}).mapSeries(binary => {
|
||||
if (showSize) {
|
||||
const binaryDir = state.getBinaryDir(binary.version);
|
||||
return getFolderSize(binaryDir).then(size => {
|
||||
return { ...binary,
|
||||
size
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return binary;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
path: logCachePath,
|
||||
clear,
|
||||
prune,
|
||||
list,
|
||||
getCachedVersions
|
||||
};
|
343
node_modules/cypress/lib/tasks/download.js
generated
vendored
Normal file
343
node_modules/cypress/lib/tasks/download.js
generated
vendored
Normal file
@@ -0,0 +1,343 @@
|
||||
"use strict";
|
||||
|
||||
const arch = require('arch');
|
||||
|
||||
const la = require('lazy-ass');
|
||||
|
||||
const is = require('check-more-types');
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const url = require('url');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const debug = require('debug')('cypress:cli');
|
||||
|
||||
const request = require('@cypress/request');
|
||||
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const requestProgress = require('request-progress');
|
||||
|
||||
const {
|
||||
stripIndent
|
||||
} = require('common-tags');
|
||||
|
||||
const {
|
||||
throwFormErrorText,
|
||||
errors
|
||||
} = require('../errors');
|
||||
|
||||
const fs = require('../fs');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const defaultBaseUrl = 'https://download.cypress.io/';
|
||||
|
||||
const getProxyUrl = () => {
|
||||
return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.npm_config_https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || process.env.npm_config_proxy || null;
|
||||
};
|
||||
|
||||
const getRealOsArch = () => {
|
||||
// os.arch() returns the arch for which this node was compiled
|
||||
// we want the operating system's arch instead: x64 or x86
|
||||
const osArch = arch();
|
||||
|
||||
if (osArch === 'x86') {
|
||||
// match process.platform output
|
||||
return 'ia32';
|
||||
}
|
||||
|
||||
return osArch;
|
||||
};
|
||||
|
||||
const getBaseUrl = () => {
|
||||
if (util.getEnv('CYPRESS_DOWNLOAD_MIRROR')) {
|
||||
let baseUrl = util.getEnv('CYPRESS_DOWNLOAD_MIRROR');
|
||||
|
||||
if (!baseUrl.endsWith('/')) {
|
||||
baseUrl += '/';
|
||||
}
|
||||
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
return defaultBaseUrl;
|
||||
};
|
||||
|
||||
const getCA = () => {
|
||||
return new Promise(resolve => {
|
||||
if (!util.getEnv('CYPRESS_DOWNLOAD_USE_CA')) {
|
||||
resolve();
|
||||
}
|
||||
|
||||
if (process.env.npm_config_ca) {
|
||||
resolve(process.env.npm_config_ca);
|
||||
} else if (process.env.npm_config_cafile) {
|
||||
fs.readFile(process.env.npm_config_cafile, 'utf8').then(cafileContent => {
|
||||
resolve(cafileContent);
|
||||
}).catch(() => {
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const prepend = urlPath => {
|
||||
const endpoint = url.resolve(getBaseUrl(), urlPath);
|
||||
const platform = os.platform();
|
||||
const arch = getRealOsArch();
|
||||
return `${endpoint}?platform=${platform}&arch=${arch}`;
|
||||
};
|
||||
|
||||
const getUrl = version => {
|
||||
if (is.url(version)) {
|
||||
debug('version is already an url', version);
|
||||
return version;
|
||||
}
|
||||
|
||||
return version ? prepend(`desktop/${version}`) : prepend('desktop');
|
||||
};
|
||||
|
||||
const statusMessage = err => {
|
||||
return err.statusCode ? [err.statusCode, err.statusMessage].join(' - ') : err.toString();
|
||||
};
|
||||
|
||||
const prettyDownloadErr = (err, version) => {
|
||||
const msg = stripIndent`
|
||||
URL: ${getUrl(version)}
|
||||
${statusMessage(err)}
|
||||
`;
|
||||
debug(msg);
|
||||
return throwFormErrorText(errors.failedDownload)(msg);
|
||||
};
|
||||
/**
|
||||
* Checks checksum and file size for the given file. Allows both
|
||||
* values or just one of them to be checked.
|
||||
*/
|
||||
|
||||
|
||||
const verifyDownloadedFile = (filename, expectedSize, expectedChecksum) => {
|
||||
if (expectedSize && expectedChecksum) {
|
||||
debug('verifying checksum and file size');
|
||||
return Promise.join(util.getFileChecksum(filename), util.getFileSize(filename), (checksum, filesize) => {
|
||||
if (checksum === expectedChecksum && filesize === expectedSize) {
|
||||
debug('downloaded file has the expected checksum and size ✅');
|
||||
return;
|
||||
}
|
||||
|
||||
debug('raising error: checksum or file size mismatch');
|
||||
const text = stripIndent`
|
||||
Corrupted download
|
||||
|
||||
Expected downloaded file to have checksum: ${expectedChecksum}
|
||||
Computed checksum: ${checksum}
|
||||
|
||||
Expected downloaded file to have size: ${expectedSize}
|
||||
Computed size: ${filesize}
|
||||
`;
|
||||
debug(text);
|
||||
throw new Error(text);
|
||||
});
|
||||
}
|
||||
|
||||
if (expectedChecksum) {
|
||||
debug('only checking expected file checksum %d', expectedChecksum);
|
||||
return util.getFileChecksum(filename).then(checksum => {
|
||||
if (checksum === expectedChecksum) {
|
||||
debug('downloaded file has the expected checksum ✅');
|
||||
return;
|
||||
}
|
||||
|
||||
debug('raising error: file checksum mismatch');
|
||||
const text = stripIndent`
|
||||
Corrupted download
|
||||
|
||||
Expected downloaded file to have checksum: ${expectedChecksum}
|
||||
Computed checksum: ${checksum}
|
||||
`;
|
||||
throw new Error(text);
|
||||
});
|
||||
}
|
||||
|
||||
if (expectedSize) {
|
||||
// maybe we don't have a checksum, but at least CDN returns content length
|
||||
// which we can check against the file size
|
||||
debug('only checking expected file size %d', expectedSize);
|
||||
return util.getFileSize(filename).then(filesize => {
|
||||
if (filesize === expectedSize) {
|
||||
debug('downloaded file has the expected size ✅');
|
||||
return;
|
||||
}
|
||||
|
||||
debug('raising error: file size mismatch');
|
||||
const text = stripIndent`
|
||||
Corrupted download
|
||||
|
||||
Expected downloaded file to have size: ${expectedSize}
|
||||
Computed size: ${filesize}
|
||||
`;
|
||||
throw new Error(text);
|
||||
});
|
||||
}
|
||||
|
||||
debug('downloaded file lacks checksum or size to verify');
|
||||
return Promise.resolve();
|
||||
}; // downloads from given url
|
||||
// return an object with
|
||||
// {filename: ..., downloaded: true}
|
||||
|
||||
|
||||
const downloadFromUrl = ({
|
||||
url,
|
||||
downloadDestination,
|
||||
progress,
|
||||
ca
|
||||
}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const proxy = getProxyUrl();
|
||||
debug('Downloading package', {
|
||||
url,
|
||||
proxy,
|
||||
downloadDestination
|
||||
});
|
||||
let redirectVersion;
|
||||
const reqOptions = {
|
||||
url,
|
||||
proxy,
|
||||
|
||||
followRedirect(response) {
|
||||
const version = response.headers['x-version'];
|
||||
debug('redirect version:', version);
|
||||
|
||||
if (version) {
|
||||
// set the version in options if we have one.
|
||||
// this insulates us from potential redirect
|
||||
// problems where version would be set to undefined.
|
||||
redirectVersion = version;
|
||||
} // yes redirect
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
if (ca) {
|
||||
debug('using custom CA details from npm config');
|
||||
reqOptions.agentOptions = {
|
||||
ca
|
||||
};
|
||||
}
|
||||
|
||||
const req = request(reqOptions); // closure
|
||||
|
||||
let started = null;
|
||||
let expectedSize;
|
||||
let expectedChecksum;
|
||||
requestProgress(req, {
|
||||
throttle: progress.throttle
|
||||
}).on('response', response => {
|
||||
// we have computed checksum and filesize during test runner binary build
|
||||
// and have set it on the S3 object as user meta data, available via
|
||||
// these custom headers "x-amz-meta-..."
|
||||
// see https://github.com/cypress-io/cypress/pull/4092
|
||||
expectedSize = response.headers['x-amz-meta-size'] || response.headers['content-length'];
|
||||
expectedChecksum = response.headers['x-amz-meta-checksum'];
|
||||
|
||||
if (expectedChecksum) {
|
||||
debug('expected checksum %s', expectedChecksum);
|
||||
}
|
||||
|
||||
if (expectedSize) {
|
||||
// convert from string (all Amazon custom headers are strings)
|
||||
expectedSize = Number(expectedSize);
|
||||
debug('expected file size %d', expectedSize);
|
||||
} // start counting now once we've gotten
|
||||
// response headers
|
||||
|
||||
|
||||
started = new Date(); // if our status code does not start with 200
|
||||
|
||||
if (!/^2/.test(response.statusCode)) {
|
||||
debug('response code %d', response.statusCode);
|
||||
const err = new Error(stripIndent`
|
||||
Failed downloading the Cypress binary.
|
||||
Response code: ${response.statusCode}
|
||||
Response message: ${response.statusMessage}
|
||||
`);
|
||||
reject(err);
|
||||
}
|
||||
}).on('error', reject).on('progress', state => {
|
||||
// total time we've elapsed
|
||||
// starting on our first progress notification
|
||||
const elapsed = new Date() - started; // request-progress sends a value between 0 and 1
|
||||
|
||||
const percentage = util.convertPercentToPercentage(state.percent);
|
||||
const eta = util.calculateEta(percentage, elapsed); // send up our percent and seconds remaining
|
||||
|
||||
progress.onProgress(percentage, util.secsRemaining(eta));
|
||||
}) // save this download here
|
||||
.pipe(fs.createWriteStream(downloadDestination)).on('finish', () => {
|
||||
debug('downloading finished');
|
||||
verifyDownloadedFile(downloadDestination, expectedSize, expectedChecksum).then(() => {
|
||||
return resolve(redirectVersion);
|
||||
}, reject);
|
||||
});
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Download Cypress.zip from external url to local file.
|
||||
* @param [string] version Could be "3.3.0" or full URL
|
||||
* @param [string] downloadDestination Local filename to save as
|
||||
*/
|
||||
|
||||
|
||||
const start = opts => {
|
||||
let {
|
||||
version,
|
||||
downloadDestination,
|
||||
progress
|
||||
} = opts;
|
||||
|
||||
if (!downloadDestination) {
|
||||
la(is.unemptyString(downloadDestination), 'missing download dir', opts);
|
||||
}
|
||||
|
||||
if (!progress) {
|
||||
progress = {
|
||||
onProgress: () => {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const url = getUrl(version);
|
||||
progress.throttle = 100;
|
||||
debug('needed Cypress version: %s', version);
|
||||
debug('source url %s', url);
|
||||
debug(`downloading cypress.zip to "${downloadDestination}"`); // ensure download dir exists
|
||||
|
||||
return fs.ensureDirAsync(path.dirname(downloadDestination)).then(() => {
|
||||
return getCA();
|
||||
}).then(ca => {
|
||||
return downloadFromUrl({
|
||||
url,
|
||||
downloadDestination,
|
||||
progress,
|
||||
ca
|
||||
});
|
||||
}).catch(err => {
|
||||
return prettyDownloadErr(err, version);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
start,
|
||||
getUrl,
|
||||
getProxyUrl,
|
||||
getCA
|
||||
};
|
41
node_modules/cypress/lib/tasks/get-folder-size.js
generated
vendored
Normal file
41
node_modules/cypress/lib/tasks/get-folder-size.js
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require('../fs');
|
||||
|
||||
const {
|
||||
join
|
||||
} = require('path');
|
||||
|
||||
const Bluebird = require('bluebird');
|
||||
/**
|
||||
* Get the size of a folder or a file.
|
||||
*
|
||||
* This function returns the actual file size of the folder (size), not the allocated space on disk (size on disk).
|
||||
* For more details between the difference, check this link:
|
||||
* https://www.howtogeek.com/180369/why-is-there-a-big-difference-between-size-and-size-on-disk/
|
||||
*
|
||||
* @param {string} path path to the file or the folder.
|
||||
*/
|
||||
|
||||
|
||||
async function getSize(path) {
|
||||
const stat = await fs.lstat(path);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
const list = await fs.readdir(path);
|
||||
return Bluebird.resolve(list).reduce(async (prev, curr) => {
|
||||
const currPath = join(path, curr);
|
||||
const s = await fs.lstat(currPath);
|
||||
|
||||
if (s.isDirectory()) {
|
||||
return prev + (await getSize(currPath));
|
||||
}
|
||||
|
||||
return prev + s.size;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return stat.size;
|
||||
}
|
||||
|
||||
module.exports = getSize;
|
449
node_modules/cypress/lib/tasks/install.js
generated
vendored
Normal file
449
node_modules/cypress/lib/tasks/install.js
generated
vendored
Normal file
@@ -0,0 +1,449 @@
|
||||
"use strict";
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const url = require('url');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
const debug = require('debug')('cypress:cli');
|
||||
|
||||
const {
|
||||
Listr
|
||||
} = require('listr2');
|
||||
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const logSymbols = require('log-symbols');
|
||||
|
||||
const {
|
||||
stripIndent
|
||||
} = require('common-tags');
|
||||
|
||||
const fs = require('../fs');
|
||||
|
||||
const download = require('./download');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const state = require('./state');
|
||||
|
||||
const unzip = require('./unzip');
|
||||
|
||||
const logger = require('../logger');
|
||||
|
||||
const {
|
||||
throwFormErrorText,
|
||||
errors
|
||||
} = require('../errors');
|
||||
|
||||
const verbose = require('../VerboseRenderer');
|
||||
|
||||
const getNpmArgv = () => {
|
||||
const json = process.env.npm_config_argv;
|
||||
|
||||
if (!json) {
|
||||
return;
|
||||
}
|
||||
|
||||
debug('found npm argv json %o', json);
|
||||
|
||||
try {
|
||||
return JSON.parse(json).original || [];
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}; // attempt to discover the version specifier used to install Cypress
|
||||
// for example: "^5.0.0", "https://cdn.cypress.io/...", ...
|
||||
|
||||
|
||||
const getVersionSpecifier = (startDir = path.resolve(__dirname, '../..')) => {
|
||||
const argv = getNpmArgv();
|
||||
|
||||
if (argv) {
|
||||
const tgz = _.find(argv, t => t.endsWith('cypress.tgz'));
|
||||
|
||||
if (tgz) {
|
||||
return tgz;
|
||||
}
|
||||
}
|
||||
|
||||
const getVersionSpecifierFromPkg = dir => {
|
||||
debug('looking for versionSpecifier %o', {
|
||||
dir
|
||||
});
|
||||
|
||||
const tryParent = () => {
|
||||
const parentPath = path.resolve(dir, '..');
|
||||
|
||||
if (parentPath === dir) {
|
||||
debug('reached FS root with no versionSpecifier found');
|
||||
return;
|
||||
}
|
||||
|
||||
return getVersionSpecifierFromPkg(parentPath);
|
||||
};
|
||||
|
||||
return fs.readJSON(path.join(dir, 'package.json')).catch(() => ({})).then(pkg => {
|
||||
const specifier = _.chain(['dependencies', 'devDependencies', 'optionalDependencies']).map(prop => _.get(pkg, `${prop}.cypress`)).compact().first().value();
|
||||
|
||||
return specifier || tryParent();
|
||||
});
|
||||
}; // recurse through parent directories until package.json with `cypress` is found
|
||||
|
||||
|
||||
return getVersionSpecifierFromPkg(startDir).then(versionSpecifier => {
|
||||
debug('finished looking for versionSpecifier', {
|
||||
versionSpecifier
|
||||
});
|
||||
return versionSpecifier;
|
||||
});
|
||||
};
|
||||
|
||||
const betaNpmUrlRe = /^\/beta\/npm\/(?<version>[0-9.]+)\/(?<artifactSlug>[^/]+)\/cypress\.tgz$/; // convert a prerelease NPM package .tgz URL to the corresponding binary .zip URL
|
||||
|
||||
const getBinaryUrlFromPrereleaseNpmUrl = npmUrl => {
|
||||
let parsed;
|
||||
|
||||
try {
|
||||
parsed = url.parse(npmUrl);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
const matches = betaNpmUrlRe.exec(parsed.pathname);
|
||||
|
||||
if (parsed.hostname !== 'cdn.cypress.io' || !matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
version,
|
||||
artifactSlug
|
||||
} = matches.groups;
|
||||
parsed.pathname = `/beta/binary/${version}/${os.platform()}-${os.arch()}/${artifactSlug}/cypress.zip`;
|
||||
return parsed.format();
|
||||
};
|
||||
|
||||
const alreadyInstalledMsg = () => {
|
||||
if (!util.isPostInstall()) {
|
||||
logger.log(stripIndent`
|
||||
Skipping installation:
|
||||
|
||||
Pass the ${chalk.yellow('--force')} option if you'd like to reinstall anyway.
|
||||
`);
|
||||
}
|
||||
};
|
||||
|
||||
const displayCompletionMsg = () => {
|
||||
// check here to see if we are globally installed
|
||||
if (util.isInstalledGlobally()) {
|
||||
// if we are display a warning
|
||||
logger.log();
|
||||
logger.warn(stripIndent`
|
||||
${logSymbols.warning} Warning: It looks like you\'ve installed Cypress globally.
|
||||
|
||||
This will work, but it'\s not recommended.
|
||||
|
||||
The recommended way to install Cypress is as a devDependency per project.
|
||||
|
||||
You should probably run these commands:
|
||||
|
||||
- ${chalk.cyan('npm uninstall -g cypress')}
|
||||
- ${chalk.cyan('npm install --save-dev cypress')}
|
||||
`);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log();
|
||||
logger.log('You can now open Cypress by running:', chalk.cyan(path.join('node_modules', '.bin', 'cypress'), 'open'));
|
||||
logger.log();
|
||||
logger.log(chalk.grey('https://on.cypress.io/installing-cypress'));
|
||||
logger.log();
|
||||
};
|
||||
|
||||
const downloadAndUnzip = ({
|
||||
version,
|
||||
installDir,
|
||||
downloadDir
|
||||
}) => {
|
||||
const progress = {
|
||||
throttle: 100,
|
||||
onProgress: null
|
||||
};
|
||||
const downloadDestination = path.join(downloadDir, `cypress-${process.pid}.zip`);
|
||||
const rendererOptions = getRendererOptions(); // let the user know what version of cypress we're downloading!
|
||||
|
||||
logger.log(`Installing Cypress ${chalk.gray(`(version: ${version})`)}`);
|
||||
logger.log();
|
||||
const tasks = new Listr([{
|
||||
options: {
|
||||
title: util.titleize('Downloading Cypress')
|
||||
},
|
||||
task: (ctx, task) => {
|
||||
// as our download progresses indicate the status
|
||||
progress.onProgress = progessify(task, 'Downloading Cypress');
|
||||
return download.start({
|
||||
version,
|
||||
downloadDestination,
|
||||
progress
|
||||
}).then(redirectVersion => {
|
||||
if (redirectVersion) version = redirectVersion;
|
||||
debug(`finished downloading file: ${downloadDestination}`);
|
||||
}).then(() => {
|
||||
// save the download destination for unzipping
|
||||
util.setTaskTitle(task, util.titleize(chalk.green('Downloaded Cypress')), rendererOptions.renderer);
|
||||
});
|
||||
}
|
||||
}, unzipTask({
|
||||
progress,
|
||||
zipFilePath: downloadDestination,
|
||||
installDir,
|
||||
rendererOptions
|
||||
}), {
|
||||
options: {
|
||||
title: util.titleize('Finishing Installation')
|
||||
},
|
||||
task: (ctx, task) => {
|
||||
const cleanup = () => {
|
||||
debug('removing zip file %s', downloadDestination);
|
||||
return fs.removeAsync(downloadDestination);
|
||||
};
|
||||
|
||||
return cleanup().then(() => {
|
||||
debug('finished installation in', installDir);
|
||||
util.setTaskTitle(task, util.titleize(chalk.green('Finished Installation'), chalk.gray(installDir)), rendererOptions.renderer);
|
||||
});
|
||||
}
|
||||
}], {
|
||||
rendererOptions
|
||||
}); // start the tasks!
|
||||
|
||||
return Promise.resolve(tasks.run());
|
||||
};
|
||||
|
||||
const start = (options = {}) => {
|
||||
debug('installing with options %j', options);
|
||||
|
||||
_.defaults(options, {
|
||||
force: false
|
||||
});
|
||||
|
||||
const pkgVersion = util.pkgVersion();
|
||||
let needVersion = pkgVersion;
|
||||
let binaryUrlOverride;
|
||||
debug('version in package.json is', needVersion); // let this environment variable reset the binary version we need
|
||||
|
||||
if (util.getEnv('CYPRESS_INSTALL_BINARY')) {
|
||||
// because passed file paths are often double quoted
|
||||
// and might have extra whitespace around, be robust and trim the string
|
||||
const trimAndRemoveDoubleQuotes = true;
|
||||
const envVarVersion = util.getEnv('CYPRESS_INSTALL_BINARY', trimAndRemoveDoubleQuotes);
|
||||
debug('using environment variable CYPRESS_INSTALL_BINARY "%s"', envVarVersion);
|
||||
|
||||
if (envVarVersion === '0') {
|
||||
debug('environment variable CYPRESS_INSTALL_BINARY = 0, skipping install');
|
||||
logger.log(stripIndent`
|
||||
${chalk.yellow('Note:')} Skipping binary installation: Environment variable CYPRESS_INSTALL_BINARY = 0.`);
|
||||
logger.log();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
binaryUrlOverride = envVarVersion;
|
||||
}
|
||||
|
||||
if (util.getEnv('CYPRESS_CACHE_FOLDER')) {
|
||||
const envCache = util.getEnv('CYPRESS_CACHE_FOLDER');
|
||||
logger.log(stripIndent`
|
||||
${chalk.yellow('Note:')} Overriding Cypress cache directory to: ${chalk.cyan(envCache)}
|
||||
|
||||
Previous installs of Cypress may not be found.
|
||||
`);
|
||||
logger.log();
|
||||
}
|
||||
|
||||
const installDir = state.getVersionDir(pkgVersion);
|
||||
const cacheDir = state.getCacheDir();
|
||||
const binaryDir = state.getBinaryDir(pkgVersion);
|
||||
return fs.ensureDirAsync(cacheDir).catch({
|
||||
code: 'EACCES'
|
||||
}, err => {
|
||||
return throwFormErrorText(errors.invalidCacheDirectory)(stripIndent`
|
||||
Failed to access ${chalk.cyan(cacheDir)}:
|
||||
|
||||
${err.message}
|
||||
`);
|
||||
}).then(() => {
|
||||
return Promise.all([state.getBinaryPkgAsync(binaryDir).then(state.getBinaryPkgVersion), getVersionSpecifier()]);
|
||||
}).then(([binaryVersion, versionSpecifier]) => {
|
||||
if (!binaryUrlOverride && versionSpecifier) {
|
||||
const computedBinaryUrl = getBinaryUrlFromPrereleaseNpmUrl(versionSpecifier);
|
||||
|
||||
if (computedBinaryUrl) {
|
||||
debug('computed binary url from version specifier %o', {
|
||||
computedBinaryUrl,
|
||||
needVersion
|
||||
});
|
||||
binaryUrlOverride = computedBinaryUrl;
|
||||
}
|
||||
}
|
||||
|
||||
needVersion = binaryUrlOverride || needVersion;
|
||||
debug('installed version is', binaryVersion, 'version needed is', needVersion);
|
||||
|
||||
if (!binaryVersion) {
|
||||
debug('no binary installed under cli version');
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.log();
|
||||
logger.log(stripIndent`
|
||||
Cypress ${chalk.green(binaryVersion)} is installed in ${chalk.cyan(installDir)}
|
||||
`);
|
||||
logger.log();
|
||||
|
||||
if (options.force) {
|
||||
debug('performing force install over existing binary');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (binaryVersion === needVersion || !util.isSemver(needVersion)) {
|
||||
// our version matches, tell the user this is a noop
|
||||
alreadyInstalledMsg();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).then(shouldInstall => {
|
||||
// noop if we've been told not to download
|
||||
if (!shouldInstall) {
|
||||
debug('Not downloading or installing binary');
|
||||
return;
|
||||
}
|
||||
|
||||
if (needVersion !== pkgVersion) {
|
||||
logger.log(chalk.yellow(stripIndent`
|
||||
${logSymbols.warning} Warning: Forcing a binary version different than the default.
|
||||
|
||||
The CLI expected to install version: ${chalk.green(pkgVersion)}
|
||||
|
||||
Instead we will install version: ${chalk.green(needVersion)}
|
||||
|
||||
These versions may not work properly together.
|
||||
`));
|
||||
logger.log();
|
||||
} // see if version supplied is a path to a binary
|
||||
|
||||
|
||||
return fs.pathExistsAsync(needVersion).then(exists => {
|
||||
if (exists) {
|
||||
return path.extname(needVersion) === '.zip' ? needVersion : false;
|
||||
}
|
||||
|
||||
const possibleFile = util.formAbsolutePath(needVersion);
|
||||
debug('checking local file', possibleFile, 'cwd', process.cwd());
|
||||
return fs.pathExistsAsync(possibleFile).then(exists => {
|
||||
// if this exists return the path to it
|
||||
// else false
|
||||
if (exists && path.extname(possibleFile) === '.zip') {
|
||||
return possibleFile;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}).then(pathToLocalFile => {
|
||||
if (pathToLocalFile) {
|
||||
const absolutePath = path.resolve(needVersion);
|
||||
debug('found local file at', absolutePath);
|
||||
debug('skipping download');
|
||||
const rendererOptions = getRendererOptions();
|
||||
return new Listr([unzipTask({
|
||||
progress: {
|
||||
throttle: 100,
|
||||
onProgress: null
|
||||
},
|
||||
zipFilePath: absolutePath,
|
||||
installDir,
|
||||
rendererOptions
|
||||
})], {
|
||||
rendererOptions
|
||||
}).run();
|
||||
}
|
||||
|
||||
if (options.force) {
|
||||
debug('Cypress already installed at', installDir);
|
||||
debug('but the installation was forced');
|
||||
}
|
||||
|
||||
debug('preparing to download and unzip version ', needVersion, 'to path', installDir);
|
||||
const downloadDir = os.tmpdir();
|
||||
return downloadAndUnzip({
|
||||
version: needVersion,
|
||||
installDir,
|
||||
downloadDir
|
||||
});
|
||||
}) // delay 1 sec for UX, unless we are testing
|
||||
.then(() => {
|
||||
return Promise.delay(1000);
|
||||
}).then(displayCompletionMsg);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
start,
|
||||
_getVersionSpecifier: getVersionSpecifier,
|
||||
_getBinaryUrlFromPrereleaseNpmUrl: getBinaryUrlFromPrereleaseNpmUrl
|
||||
};
|
||||
|
||||
const unzipTask = ({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
progress,
|
||||
rendererOptions
|
||||
}) => {
|
||||
return {
|
||||
options: {
|
||||
title: util.titleize('Unzipping Cypress')
|
||||
},
|
||||
task: (ctx, task) => {
|
||||
// as our unzip progresses indicate the status
|
||||
progress.onProgress = progessify(task, 'Unzipping Cypress');
|
||||
return unzip.start({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
progress
|
||||
}).then(() => {
|
||||
util.setTaskTitle(task, util.titleize(chalk.green('Unzipped Cypress')), rendererOptions.renderer);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const progessify = (task, title) => {
|
||||
// return higher order function
|
||||
return (percentComplete, remaining) => {
|
||||
percentComplete = chalk.white(` ${percentComplete}%`); // pluralize seconds remaining
|
||||
|
||||
remaining = chalk.gray(`${remaining}s`);
|
||||
util.setTaskTitle(task, util.titleize(title, percentComplete, remaining), getRendererOptions().renderer);
|
||||
};
|
||||
}; // if we are running in CI then use
|
||||
// the verbose renderer else use
|
||||
// the default
|
||||
|
||||
|
||||
const getRendererOptions = () => {
|
||||
let renderer = util.isCi() ? verbose : 'default';
|
||||
|
||||
if (logger.logLevel() === 'silent') {
|
||||
renderer = 'silent';
|
||||
}
|
||||
|
||||
return {
|
||||
renderer
|
||||
};
|
||||
};
|
226
node_modules/cypress/lib/tasks/state.js
generated
vendored
Normal file
226
node_modules/cypress/lib/tasks/state.js
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
"use strict";
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const untildify = require('untildify');
|
||||
|
||||
const R = require('ramda');
|
||||
|
||||
const debug = require('debug')('cypress:cli');
|
||||
|
||||
const fs = require('../fs');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
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()) => {
|
||||
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.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('using environment variable CYPRESS_CACHE_FOLDER %s', envVarCacheDir);
|
||||
|
||||
if (!path.isAbsolute(envVarCacheDir) && isInstallingFromPostinstallHook()) {
|
||||
const packageRootFolder = path.join('..', '..', envVarCacheDir);
|
||||
cache_directory = path.resolve(packageRootFolder);
|
||||
debug('installing from postinstall hook, original root folder is %s', packageRootFolder);
|
||||
debug('and resolved cache directory is %s', cache_directory);
|
||||
} else {
|
||||
cache_directory = path.resolve(envVarCacheDir);
|
||||
}
|
||||
}
|
||||
|
||||
return cache_directory;
|
||||
};
|
||||
|
||||
const parseRealPlatformBinaryFolderAsync = binaryPath => {
|
||||
return fs.realpathAsync(binaryPath).then(realPath => {
|
||||
debug('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 => {
|
||||
const fullPath = getBinaryStatePath(binaryDir);
|
||||
return fs.readJsonAsync(fullPath).catch({
|
||||
code: 'ENOENT'
|
||||
}, SyntaxError, () => {
|
||||
debug('could not read binary_state.json file at "%s"', fullPath);
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
||||
const getBinaryVerifiedAsync = binaryDir => {
|
||||
return getBinaryStateContentsAsync(binaryDir).tap(debug).get('verified');
|
||||
};
|
||||
|
||||
const clearBinaryStateAsync = binaryDir => {
|
||||
return fs.removeAsync(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) => {
|
||||
return getBinaryStateContentsAsync(binaryDir).then(contents => {
|
||||
return fs.outputJsonAsync(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 => {
|
||||
const pathToPackageJson = getBinaryPkgPath(binaryDir);
|
||||
debug('Reading binary package.json from:', pathToPackageJson);
|
||||
return fs.pathExistsAsync(pathToPackageJson).then(exists => {
|
||||
if (!exists) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fs.readJsonAsync(pathToPackageJson);
|
||||
});
|
||||
};
|
||||
|
||||
const getBinaryPkgVersion = R.propOr(null, 'version');
|
||||
const getBinaryElectronVersion = R.propOr(null, 'electronVersion');
|
||||
const getBinaryElectronNodeVersion = R.propOr(null, 'electronNodeVersion');
|
||||
module.exports = {
|
||||
getPathToExecutable,
|
||||
getPlatformExecutable,
|
||||
// those names start to sound like Java
|
||||
getBinaryElectronNodeVersion,
|
||||
getBinaryElectronVersion,
|
||||
getBinaryPkgVersion,
|
||||
getBinaryVerifiedAsync,
|
||||
getBinaryPkgAsync,
|
||||
getBinaryPkgPath,
|
||||
getBinaryDir,
|
||||
getCacheDir,
|
||||
clearBinaryStateAsync,
|
||||
writeBinaryVerifiedAsync,
|
||||
parseRealPlatformBinaryFolderAsync,
|
||||
getDistDir,
|
||||
getVersionDir
|
||||
};
|
226
node_modules/cypress/lib/tasks/unzip.js
generated
vendored
Normal file
226
node_modules/cypress/lib/tasks/unzip.js
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
"use strict";
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const la = require('lazy-ass');
|
||||
|
||||
const is = require('check-more-types');
|
||||
|
||||
const cp = require('child_process');
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const yauzl = require('yauzl');
|
||||
|
||||
const debug = require('debug')('cypress:cli:unzip');
|
||||
|
||||
const extract = require('extract-zip');
|
||||
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const readline = require('readline');
|
||||
|
||||
const {
|
||||
throwFormErrorText,
|
||||
errors
|
||||
} = require('../errors');
|
||||
|
||||
const fs = require('../fs');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const unzipTools = {
|
||||
extract
|
||||
}; // expose this function for simple testing
|
||||
|
||||
const unzip = ({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
progress
|
||||
}) => {
|
||||
debug('unzipping from %s', zipFilePath);
|
||||
debug('into', installDir);
|
||||
|
||||
if (!zipFilePath) {
|
||||
throw new Error('Missing zip filename');
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
let yauzlDoneTime = 0;
|
||||
return fs.ensureDirAsync(installDir).then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return yauzl.open(zipFilePath, (err, zipFile) => {
|
||||
yauzlDoneTime = Date.now();
|
||||
|
||||
if (err) {
|
||||
debug('error using yauzl %s', err.message);
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
const total = zipFile.entryCount;
|
||||
debug('zipFile entries count', total);
|
||||
const started = new Date();
|
||||
let percent = 0;
|
||||
let count = 0;
|
||||
|
||||
const notify = percent => {
|
||||
const elapsed = +new Date() - +started;
|
||||
const eta = util.calculateEta(percent, elapsed);
|
||||
progress.onProgress(percent, util.secsRemaining(eta));
|
||||
};
|
||||
|
||||
const tick = () => {
|
||||
count += 1;
|
||||
percent = count / total * 100;
|
||||
const displayPercent = percent.toFixed(0);
|
||||
return notify(displayPercent);
|
||||
};
|
||||
|
||||
const unzipWithNode = () => {
|
||||
debug('unzipping with node.js (slow)');
|
||||
const opts = {
|
||||
dir: installDir,
|
||||
onEntry: tick
|
||||
};
|
||||
debug('calling Node extract tool %s %o', zipFilePath, opts);
|
||||
return unzipTools.extract(zipFilePath, opts).then(() => {
|
||||
debug('node unzip finished');
|
||||
return resolve();
|
||||
}).catch(err => {
|
||||
if (err) {
|
||||
debug('error %s', err.message);
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const unzipFallback = _.once(unzipWithNode);
|
||||
|
||||
const unzipWithUnzipTool = () => {
|
||||
debug('unzipping via `unzip`');
|
||||
const inflatingRe = /inflating:/;
|
||||
const sp = cp.spawn('unzip', ['-o', zipFilePath, '-d', installDir]);
|
||||
sp.on('error', err => {
|
||||
debug('unzip tool error: %s', err.message);
|
||||
unzipFallback();
|
||||
});
|
||||
sp.on('close', code => {
|
||||
debug('unzip tool close with code %d', code);
|
||||
|
||||
if (code === 0) {
|
||||
percent = 100;
|
||||
notify(percent);
|
||||
return resolve();
|
||||
}
|
||||
|
||||
debug('`unzip` failed %o', {
|
||||
code
|
||||
});
|
||||
return unzipFallback();
|
||||
});
|
||||
sp.stdout.on('data', data => {
|
||||
if (inflatingRe.test(data)) {
|
||||
return tick();
|
||||
}
|
||||
});
|
||||
sp.stderr.on('data', data => {
|
||||
debug('`unzip` stderr %s', data);
|
||||
});
|
||||
}; // we attempt to first unzip with the native osx
|
||||
// ditto because its less likely to have problems
|
||||
// with corruption, symlinks, or icons causing failures
|
||||
// and can handle resource forks
|
||||
// http://automatica.com.au/2011/02/unzip-mac-os-x-zip-in-terminal/
|
||||
|
||||
|
||||
const unzipWithOsx = () => {
|
||||
debug('unzipping via `ditto`');
|
||||
const copyingFileRe = /^copying file/;
|
||||
const sp = cp.spawn('ditto', ['-xkV', zipFilePath, installDir]); // f-it just unzip with node
|
||||
|
||||
sp.on('error', err => {
|
||||
debug(err.message);
|
||||
unzipFallback();
|
||||
});
|
||||
sp.on('close', code => {
|
||||
if (code === 0) {
|
||||
// make sure we get to 100% on the progress bar
|
||||
// because reading in lines is not really accurate
|
||||
percent = 100;
|
||||
notify(percent);
|
||||
return resolve();
|
||||
}
|
||||
|
||||
debug('`ditto` failed %o', {
|
||||
code
|
||||
});
|
||||
return unzipFallback();
|
||||
});
|
||||
return readline.createInterface({
|
||||
input: sp.stderr
|
||||
}).on('line', line => {
|
||||
if (copyingFileRe.test(line)) {
|
||||
return tick();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
switch (os.platform()) {
|
||||
case 'darwin':
|
||||
return unzipWithOsx();
|
||||
|
||||
case 'linux':
|
||||
return unzipWithUnzipTool();
|
||||
|
||||
case 'win32':
|
||||
return unzipWithNode();
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
});
|
||||
}).tap(() => {
|
||||
debug('unzip completed %o', {
|
||||
yauzlMs: yauzlDoneTime - startTime,
|
||||
unzipMs: Date.now() - yauzlDoneTime
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const start = ({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
progress
|
||||
}) => {
|
||||
la(is.unemptyString(installDir), 'missing installDir');
|
||||
|
||||
if (!progress) {
|
||||
progress = {
|
||||
onProgress: () => {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return fs.pathExists(installDir).then(exists => {
|
||||
if (exists) {
|
||||
debug('removing existing unzipped binary', installDir);
|
||||
return fs.removeAsync(installDir);
|
||||
}
|
||||
}).then(() => {
|
||||
return unzip({
|
||||
zipFilePath,
|
||||
installDir,
|
||||
progress
|
||||
});
|
||||
}).catch(throwFormErrorText(errors.failedUnzip));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
start,
|
||||
utils: {
|
||||
unzip,
|
||||
unzipTools
|
||||
}
|
||||
};
|
349
node_modules/cypress/lib/tasks/verify.js
generated
vendored
Normal file
349
node_modules/cypress/lib/tasks/verify.js
generated
vendored
Normal file
@@ -0,0 +1,349 @@
|
||||
"use strict";
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
const {
|
||||
Listr
|
||||
} = require('listr2');
|
||||
|
||||
const debug = require('debug')('cypress:cli');
|
||||
|
||||
const {
|
||||
stripIndent
|
||||
} = require('common-tags');
|
||||
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const logSymbols = require('log-symbols');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const verbose = require('../VerboseRenderer');
|
||||
|
||||
const {
|
||||
throwFormErrorText,
|
||||
errors
|
||||
} = require('../errors');
|
||||
|
||||
const util = require('../util');
|
||||
|
||||
const logger = require('../logger');
|
||||
|
||||
const xvfb = require('../exec/xvfb');
|
||||
|
||||
const state = require('./state');
|
||||
|
||||
const VERIFY_TEST_RUNNER_TIMEOUT_MS = 30000;
|
||||
|
||||
const checkExecutable = binaryDir => {
|
||||
const executable = state.getPathToExecutable(binaryDir);
|
||||
debug('checking if executable exists', executable);
|
||||
return util.isExecutableAsync(executable).then(isExecutable => {
|
||||
debug('Binary is executable? :', isExecutable);
|
||||
|
||||
if (!isExecutable) {
|
||||
return throwFormErrorText(errors.binaryNotExecutable(executable))();
|
||||
}
|
||||
}).catch({
|
||||
code: 'ENOENT'
|
||||
}, () => {
|
||||
if (util.isCi()) {
|
||||
return throwFormErrorText(errors.notInstalledCI(executable))();
|
||||
}
|
||||
|
||||
return throwFormErrorText(errors.missingApp(binaryDir))(stripIndent`
|
||||
Cypress executable not found at: ${chalk.cyan(executable)}
|
||||
`);
|
||||
});
|
||||
};
|
||||
|
||||
const runSmokeTest = (binaryDir, options) => {
|
||||
let executable = state.getPathToExecutable(binaryDir);
|
||||
|
||||
const onSmokeTestError = (smokeTestCommand, linuxWithDisplayEnv) => {
|
||||
return 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 throwFormErrorText(errors.smokeTestFailure(smokeTestCommand, true))(errMessage);
|
||||
}
|
||||
|
||||
if (linuxWithDisplayEnv && util.isBrokenGtkDisplay(errMessage)) {
|
||||
util.logBrokenGtkDisplayWarning();
|
||||
return throwFormErrorText(errors.invalidSmokeTestDisplayError)(errMessage);
|
||||
}
|
||||
|
||||
return throwFormErrorText(errors.missingDependency)(errMessage);
|
||||
};
|
||||
};
|
||||
|
||||
const needsXvfb = xvfb.isNeeded();
|
||||
debug('needs Xvfb?', needsXvfb);
|
||||
/**
|
||||
* Spawn Cypress running smoke test to check if all operating system
|
||||
* dependencies are good.
|
||||
*/
|
||||
|
||||
const spawn = linuxWithDisplayEnv => {
|
||||
const random = _.random(0, 1000);
|
||||
|
||||
const args = ['--smoke-test', `--ping=${random}`];
|
||||
|
||||
if (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.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 env = _.extend({}, process.env, {
|
||||
ELECTRON_ENABLE_LOGGING: true
|
||||
});
|
||||
|
||||
const stdioOptions = _.extend({}, {
|
||||
env,
|
||||
timeout: options.smokeTestTimeout
|
||||
});
|
||||
|
||||
return Promise.resolve(util.exec(executable, args, stdioOptions)).catch(onSmokeTestError(smokeTestCommand, linuxWithDisplayEnv)).then(result => {
|
||||
// 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('smoke test stdout "%s"', smokeTestStdout);
|
||||
|
||||
if (!util.stdoutLineMatches(String(random), smokeTestStdout)) {
|
||||
debug('Smoke test failed because could not find %d in:', random, result);
|
||||
|
||||
const smokeTestStderr = _.get(result, 'stderr', '');
|
||||
|
||||
const errorText = smokeTestStderr || smokeTestStdout;
|
||||
return throwFormErrorText(errors.smokeTestFailure(smokeTestCommand, false))(errorText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const spawnInXvfb = linuxWithDisplayEnv => {
|
||||
return xvfb.start().then(() => {
|
||||
return spawn(linuxWithDisplayEnv);
|
||||
}).finally(xvfb.stop);
|
||||
};
|
||||
|
||||
const userFriendlySpawn = linuxWithDisplayEnv => {
|
||||
debug('spawning, should retry on display problem?', Boolean(linuxWithDisplayEnv));
|
||||
return spawn(linuxWithDisplayEnv).catch({
|
||||
code: 'INVALID_SMOKE_TEST_DISPLAY_ERROR'
|
||||
}, () => {
|
||||
return spawnInXvfb(linuxWithDisplayEnv);
|
||||
});
|
||||
};
|
||||
|
||||
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.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.log(stripIndent`
|
||||
It looks like this is your first time using Cypress: ${chalk.cyan(version)}
|
||||
`);
|
||||
}
|
||||
|
||||
logger.log(); // if we are running in CI then use
|
||||
// the verbose renderer else use
|
||||
// the default
|
||||
|
||||
let renderer = util.isCi() ? verbose : 'default';
|
||||
if (logger.logLevel() === 'silent') renderer = 'silent';
|
||||
const rendererOptions = {
|
||||
renderer
|
||||
};
|
||||
const tasks = new Listr([{
|
||||
options: {
|
||||
title: util.titleize('Verifying Cypress can run', chalk.gray(binaryDir))
|
||||
},
|
||||
task: (ctx, task) => {
|
||||
debug('clearing out the verified version');
|
||||
return state.clearBinaryStateAsync(binaryDir).then(() => {
|
||||
return Promise.all([runSmokeTest(binaryDir, options), Promise.resolve().delay(1500) // good user experience
|
||||
]);
|
||||
}).then(() => {
|
||||
debug('write verified: true');
|
||||
return state.writeBinaryVerifiedAsync(true, binaryDir);
|
||||
}).then(() => {
|
||||
util.setTaskTitle(task, util.titleize(chalk.green('Verified Cypress!'), chalk.gray(binaryDir)), rendererOptions.renderer);
|
||||
});
|
||||
}
|
||||
}], {
|
||||
rendererOptions
|
||||
});
|
||||
return tasks.run();
|
||||
}
|
||||
|
||||
const maybeVerify = (installedVersion, binaryDir, options) => {
|
||||
return state.getBinaryVerifiedAsync(binaryDir).then(isVerified => {
|
||||
debug('is Verified ?', isVerified);
|
||||
let shouldVerify = !isVerified; // force verify if options.force
|
||||
|
||||
if (options.force) {
|
||||
debug('force verify');
|
||||
shouldVerify = true;
|
||||
}
|
||||
|
||||
if (shouldVerify) {
|
||||
return testBinary(installedVersion, binaryDir, options).then(() => {
|
||||
if (options.welcomeMessage) {
|
||||
logger.log();
|
||||
logger.log('Opening Cypress...');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const start = (options = {}) => {
|
||||
debug('verifying Cypress app');
|
||||
const packageVersion = util.pkgVersion();
|
||||
let binaryDir = state.getBinaryDir(packageVersion);
|
||||
|
||||
_.defaults(options, {
|
||||
dev: false,
|
||||
force: false,
|
||||
welcomeMessage: true,
|
||||
smokeTestTimeout: VERIFY_TEST_RUNNER_TIMEOUT_MS
|
||||
});
|
||||
|
||||
if (options.dev) {
|
||||
return runSmokeTest('', options);
|
||||
}
|
||||
|
||||
const parseBinaryEnvVar = () => {
|
||||
const envBinaryPath = util.getEnv('CYPRESS_RUN_BINARY');
|
||||
debug('CYPRESS_RUN_BINARY exists, =', envBinaryPath);
|
||||
logger.log(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.
|
||||
`);
|
||||
logger.log();
|
||||
return util.isExecutableAsync(envBinaryPath).then(isExecutable => {
|
||||
debug('CYPRESS_RUN_BINARY is executable? :', isExecutable);
|
||||
|
||||
if (!isExecutable) {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(stripIndent`
|
||||
The supplied binary path is not executable
|
||||
`);
|
||||
}
|
||||
}).then(() => {
|
||||
return state.parseRealPlatformBinaryFolderAsync(envBinaryPath);
|
||||
}).then(envBinaryDir => {
|
||||
if (!envBinaryDir) {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))();
|
||||
}
|
||||
|
||||
debug('CYPRESS_RUN_BINARY has binaryDir:', envBinaryDir);
|
||||
binaryDir = envBinaryDir;
|
||||
}).catch({
|
||||
code: 'ENOENT'
|
||||
}, err => {
|
||||
return throwFormErrorText(errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
return Promise.try(() => {
|
||||
debug('checking environment variables');
|
||||
|
||||
if (util.getEnv('CYPRESS_RUN_BINARY')) {
|
||||
return parseBinaryEnvVar();
|
||||
}
|
||||
}).then(() => {
|
||||
return checkExecutable(binaryDir);
|
||||
}).tap(() => {
|
||||
return debug('binaryDir is ', binaryDir);
|
||||
}).then(() => {
|
||||
return state.getBinaryPkgAsync(binaryDir);
|
||||
}).then(pkg => {
|
||||
return state.getBinaryPkgVersion(pkg);
|
||||
}).then(binaryVersion => {
|
||||
if (!binaryVersion) {
|
||||
debug('no Cypress binary found for cli version ', packageVersion);
|
||||
return throwFormErrorText(errors.missingApp(binaryDir))(`
|
||||
Cannot read binary version from: ${chalk.cyan(state.getBinaryPkgPath(binaryDir))}
|
||||
`);
|
||||
}
|
||||
|
||||
debug(`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
|
||||
logger.log(`Found binary version ${chalk.green(binaryVersion)} installed in: ${chalk.cyan(binaryDir)}`);
|
||||
logger.log();
|
||||
logger.warn(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.
|
||||
`);
|
||||
logger.log();
|
||||
}
|
||||
|
||||
return maybeVerify(binaryVersion, binaryDir, options);
|
||||
}).catch(err => {
|
||||
if (err.known) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return throwFormErrorText(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();
|
||||
|
||||
module.exports = {
|
||||
start,
|
||||
VERIFY_TEST_RUNNER_TIMEOUT_MS,
|
||||
needsSandbox
|
||||
};
|
480
node_modules/cypress/lib/util.js
generated
vendored
Normal file
480
node_modules/cypress/lib/util.js
generated
vendored
Normal file
@@ -0,0 +1,480 @@
|
||||
"use strict";
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const R = require('ramda');
|
||||
|
||||
const os = require('os');
|
||||
|
||||
const ospath = require('ospath');
|
||||
|
||||
const crypto = require('crypto');
|
||||
|
||||
const la = require('lazy-ass');
|
||||
|
||||
const is = require('check-more-types');
|
||||
|
||||
const tty = require('tty');
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const isCi = require('is-ci');
|
||||
|
||||
const execa = require('execa');
|
||||
|
||||
const getos = require('getos');
|
||||
|
||||
const chalk = require('chalk');
|
||||
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const cachedir = require('cachedir');
|
||||
|
||||
const logSymbols = require('log-symbols');
|
||||
|
||||
const executable = require('executable');
|
||||
|
||||
const {
|
||||
stripIndent
|
||||
} = require('common-tags');
|
||||
|
||||
const supportsColor = require('supports-color');
|
||||
|
||||
const isInstalledGlobally = require('is-installed-globally');
|
||||
|
||||
const pkg = require(path.join(__dirname, '..', 'package.json'));
|
||||
|
||||
const logger = require('./logger');
|
||||
|
||||
const debug = require('debug')('cypress:cli');
|
||||
|
||||
const fs = require('./fs');
|
||||
|
||||
const issuesUrl = 'https://github.com/cypress-io/cypress/issues';
|
||||
const getosAsync = Promise.promisify(getos);
|
||||
/**
|
||||
* Returns SHA512 of a file
|
||||
*
|
||||
* Implementation lifted from https://github.com/sindresorhus/hasha
|
||||
* but without bringing that dependency (since hasha is Node v8+)
|
||||
*/
|
||||
|
||||
const getFileChecksum = filename => {
|
||||
la(is.unemptyString(filename), 'expected filename', filename);
|
||||
|
||||
const hashStream = () => {
|
||||
const s = crypto.createHash('sha512');
|
||||
s.setEncoding('hex');
|
||||
return s;
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const stream = fs.createReadStream(filename);
|
||||
stream.on('error', reject).pipe(hashStream()).on('error', reject).on('finish', function () {
|
||||
resolve(this.read());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getFileSize = filename => {
|
||||
la(is.unemptyString(filename), 'expected filename', filename);
|
||||
return fs.statAsync(filename).get('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('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
|
||||
|
||||
logger.warn(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.
|
||||
`);
|
||||
logger.warn();
|
||||
};
|
||||
|
||||
function stdoutLineMatches(expectedLine, stdout) {
|
||||
const lines = stdout.split('\n').map(R.trim);
|
||||
const lineMatches = R.equals(expectedLine);
|
||||
return lines.some(lineMatches);
|
||||
}
|
||||
/**
|
||||
* 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.yml"
|
||||
|
||||
|
||||
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) {
|
||||
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 => {
|
||||
la(is.string(str), 'expected a string to remove double quotes', str);
|
||||
|
||||
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, 'browser', 'cachePath', 'cacheList', 'cacheClear', 'cachePrune', 'ciBuildId', 'config', 'configFile', 'cypressVersion', 'destination', 'detached', 'dev', 'exit', 'env', 'force', 'global', 'group', 'headed', 'headless', 'key', 'path', 'parallel', 'port', 'project', 'quiet', 'reporter', 'reporterOptions', 'record', '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 removeQuotes = {
|
||||
group: dequote,
|
||||
ciBuildId: dequote
|
||||
};
|
||||
const cleanOpts = R.evolve(removeQuotes, opts);
|
||||
debug('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
|
||||
|
||||
const folder = env.CYPRESS_KONFIG_ENV || env.CYPRESS_INTERNAL_ENV || 'development';
|
||||
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);
|
||||
const p = path.join(ELECTRON_APP_DATA_PATH, 'cy', folder, ...paths);
|
||||
return p;
|
||||
};
|
||||
|
||||
const util = {
|
||||
normalizeModuleOptions,
|
||||
parseOpts,
|
||||
isValidCypressInternalEnvValue,
|
||||
isNonProductionCypressInternalEnvValue,
|
||||
printNodeOptions,
|
||||
|
||||
isCi() {
|
||||
return isCi;
|
||||
},
|
||||
|
||||
getEnvOverrides(options = {}) {
|
||||
return _.chain({}).extend(util.getEnvColors()).extend(util.getForceTty()).omitBy(_.isUndefined) // remove undefined values
|
||||
.mapValues(value => {
|
||||
// stringify to 1 or 0
|
||||
return value ? '1' : '0';
|
||||
}).extend(util.getOriginalNodeOptions(options)).value();
|
||||
},
|
||||
|
||||
getOriginalNodeOptions(options) {
|
||||
if (process.env.NODE_OPTIONS) {
|
||||
return {
|
||||
ORIGINAL_NODE_OPTIONS: process.env.NODE_OPTIONS
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
|
||||
getForceTty() {
|
||||
return {
|
||||
FORCE_STDIN_TTY: util.isTty(process.stdin.fd),
|
||||
FORCE_STDOUT_TTY: util.isTty(process.stdout.fd),
|
||||
FORCE_STDERR_TTY: util.isTty(process.stderr.fd)
|
||||
};
|
||||
},
|
||||
|
||||
getEnvColors() {
|
||||
const sc = util.supportsColor();
|
||||
return {
|
||||
FORCE_COLOR: sc,
|
||||
DEBUG_COLORS: sc,
|
||||
MOCHA_COLORS: sc ? true : undefined
|
||||
};
|
||||
},
|
||||
|
||||
isTty(fd) {
|
||||
return tty.isatty(fd);
|
||||
},
|
||||
|
||||
supportsColor() {
|
||||
// if we've been explictly 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.cwd();
|
||||
},
|
||||
|
||||
pkgVersion() {
|
||||
return pkg.version;
|
||||
},
|
||||
|
||||
exit(code) {
|
||||
process.exit(code);
|
||||
},
|
||||
|
||||
logErrorExit1(err) {
|
||||
logger.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 Promise.try(() => {
|
||||
if (isLinux()) {
|
||||
return getosAsync().then(osInfo => {
|
||||
return [osInfo.dist, osInfo.release].join(' - ');
|
||||
}).catch(() => {
|
||||
return os.release();
|
||||
});
|
||||
}
|
||||
|
||||
return os.release();
|
||||
});
|
||||
},
|
||||
|
||||
getPlatformInfo() {
|
||||
return util.getOsVersionAsync().then(version => {
|
||||
return stripIndent`
|
||||
Platform: ${os.platform()} (${version})
|
||||
Cypress Version: ${util.pkgVersion()}
|
||||
`;
|
||||
});
|
||||
},
|
||||
|
||||
// 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.cwd(), '..', '..', filename);
|
||||
},
|
||||
|
||||
getEnv(varName, trim) {
|
||||
la(is.unemptyString(varName), 'expected environment variable name, not', varName);
|
||||
const configVarName = `npm_config_${varName}`;
|
||||
const packageConfigVarName = `npm_package_config_${varName}`;
|
||||
let result;
|
||||
|
||||
if (process.env.hasOwnProperty(varName)) {
|
||||
debug(`Using ${varName} from environment variable`);
|
||||
result = process.env[varName];
|
||||
} else if (process.env.hasOwnProperty(configVarName)) {
|
||||
debug(`Using ${varName} from npm config`);
|
||||
result = process.env[configVarName];
|
||||
} else if (process.env.hasOwnProperty(packageConfigVarName)) {
|
||||
debug(`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 ? 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) {
|
||||
la(is.positive(number), 'github issue should be a positive number', number);
|
||||
la(_.isInteger(number), 'github issue should be an integer', number);
|
||||
return `${issuesUrl}/${number}`;
|
||||
},
|
||||
|
||||
getFileChecksum,
|
||||
getFileSize,
|
||||
getApplicationDataFolder
|
||||
};
|
||||
module.exports = util;
|
Reference in New Issue
Block a user