cypress 15.1.0 → 15.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cypress +3 -1
- package/dist/VerboseRenderer.js +61 -0
- package/dist/cli.js +544 -0
- package/dist/cypress.js +104 -0
- package/dist/errors.js +391 -0
- package/dist/exec/info.js +103 -0
- package/dist/exec/open.js +103 -0
- package/dist/exec/run.js +177 -0
- package/dist/exec/shared.js +55 -0
- package/dist/exec/spawn.js +301 -0
- package/dist/exec/versions.js +67 -0
- package/dist/exec/xvfb.js +118 -0
- package/dist/index.js +52 -0
- package/dist/index.mjs +9 -0
- package/dist/logger.js +55 -0
- package/dist/tasks/cache.js +144 -0
- package/dist/tasks/download.js +304 -0
- package/dist/tasks/get-folder-size.js +44 -0
- package/dist/tasks/install.js +326 -0
- package/dist/tasks/state.js +184 -0
- package/dist/tasks/unzip.js +192 -0
- package/dist/tasks/verify.js +303 -0
- package/dist/util.js +452 -0
- package/package.json +10 -13
- package/types/cypress-automation.d.ts +2 -1
- package/types/cypress.d.ts +1 -0
- package/index.js +0 -27
- package/index.mjs +0 -17
- package/lib/VerboseRenderer.js +0 -58
- package/lib/cli.js +0 -411
- package/lib/cypress.js +0 -98
- package/lib/errors.js +0 -392
- package/lib/exec/info.js +0 -92
- package/lib/exec/open.js +0 -90
- package/lib/exec/run.js +0 -176
- package/lib/exec/shared.js +0 -62
- package/lib/exec/spawn.js +0 -247
- package/lib/exec/versions.js +0 -53
- package/lib/exec/xvfb.js +0 -93
- package/lib/fs.js +0 -4
- package/lib/logger.js +0 -50
- package/lib/tasks/cache.js +0 -132
- package/lib/tasks/download.js +0 -324
- package/lib/tasks/get-folder-size.js +0 -33
- package/lib/tasks/install.js +0 -368
- package/lib/tasks/state.js +0 -185
- package/lib/tasks/unzip.js +0 -200
- package/lib/tasks/verify.js +0 -300
- package/lib/util.js +0 -448
@@ -0,0 +1,184 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
13
|
+
};
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
15
|
+
const lodash_1 = __importDefault(require("lodash"));
|
16
|
+
const os_1 = __importDefault(require("os"));
|
17
|
+
const path_1 = __importDefault(require("path"));
|
18
|
+
const untildify_1 = __importDefault(require("untildify"));
|
19
|
+
const debug_1 = __importDefault(require("debug"));
|
20
|
+
const process_1 = require("process");
|
21
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
22
|
+
const util_1 = __importDefault(require("../util"));
|
23
|
+
const debug = (0, debug_1.default)('cypress:cli');
|
24
|
+
const getPlatformExecutable = () => {
|
25
|
+
const platform = os_1.default.platform();
|
26
|
+
switch (platform) {
|
27
|
+
case 'darwin': return 'Contents/MacOS/Cypress';
|
28
|
+
case 'linux': return 'Cypress';
|
29
|
+
case 'win32': return 'Cypress.exe';
|
30
|
+
// TODO handle this error using our standard
|
31
|
+
default: throw new Error(`Platform: "${platform}" is not supported.`);
|
32
|
+
}
|
33
|
+
};
|
34
|
+
const getPlatFormBinaryFolder = () => {
|
35
|
+
const platform = os_1.default.platform();
|
36
|
+
switch (platform) {
|
37
|
+
case 'darwin': return 'Cypress.app';
|
38
|
+
case 'linux': return 'Cypress';
|
39
|
+
case 'win32': return 'Cypress';
|
40
|
+
// TODO handle this error using our standard
|
41
|
+
default: throw new Error(`Platform: "${platform}" is not supported.`);
|
42
|
+
}
|
43
|
+
};
|
44
|
+
const getBinaryPkgPath = (binaryDir) => {
|
45
|
+
const platform = os_1.default.platform();
|
46
|
+
switch (platform) {
|
47
|
+
case 'darwin': return path_1.default.join(binaryDir, 'Contents', 'Resources', 'app', 'package.json');
|
48
|
+
case 'linux': return path_1.default.join(binaryDir, 'resources', 'app', 'package.json');
|
49
|
+
case 'win32': return path_1.default.join(binaryDir, 'resources', 'app', 'package.json');
|
50
|
+
// TODO handle this error using our standard
|
51
|
+
default: throw new Error(`Platform: "${platform}" is not supported.`);
|
52
|
+
}
|
53
|
+
};
|
54
|
+
/**
|
55
|
+
* Get path to binary directory
|
56
|
+
*/
|
57
|
+
const getBinaryDir = (version = util_1.default.pkgVersion()) => {
|
58
|
+
return path_1.default.join(getVersionDir(version), getPlatFormBinaryFolder());
|
59
|
+
};
|
60
|
+
const getVersionDir = (version = util_1.default.pkgVersion(), buildInfo = util_1.default.pkgBuildInfo()) => {
|
61
|
+
if (buildInfo && !buildInfo.stable) {
|
62
|
+
version = ['beta', version, buildInfo.commitBranch, buildInfo.commitSha.slice(0, 8)].join('-');
|
63
|
+
}
|
64
|
+
return path_1.default.join(getCacheDir(), version);
|
65
|
+
};
|
66
|
+
/**
|
67
|
+
* When executing "npm postinstall" hook, the working directory is set to
|
68
|
+
* "<current folder>/node_modules/cypress", which can be surprising when using relative paths.
|
69
|
+
*/
|
70
|
+
const isInstallingFromPostinstallHook = () => {
|
71
|
+
// individual folders
|
72
|
+
const cwdFolders = (0, process_1.cwd)().split(path_1.default.sep);
|
73
|
+
const length = cwdFolders.length;
|
74
|
+
return cwdFolders[length - 2] === 'node_modules' && cwdFolders[length - 1] === 'cypress';
|
75
|
+
};
|
76
|
+
const getCacheDir = () => {
|
77
|
+
let cache_directory = util_1.default.getCacheDir();
|
78
|
+
if (util_1.default.getEnv('CYPRESS_CACHE_FOLDER')) {
|
79
|
+
const envVarCacheDir = (0, untildify_1.default)(util_1.default.getEnv('CYPRESS_CACHE_FOLDER'));
|
80
|
+
debug('using environment variable CYPRESS_CACHE_FOLDER %s', envVarCacheDir);
|
81
|
+
if (!path_1.default.isAbsolute(envVarCacheDir) && isInstallingFromPostinstallHook()) {
|
82
|
+
const packageRootFolder = path_1.default.join('..', '..', envVarCacheDir);
|
83
|
+
cache_directory = path_1.default.resolve(packageRootFolder);
|
84
|
+
debug('installing from postinstall hook, original root folder is %s', packageRootFolder);
|
85
|
+
debug('and resolved cache directory is %s', cache_directory);
|
86
|
+
}
|
87
|
+
else {
|
88
|
+
cache_directory = path_1.default.resolve(envVarCacheDir);
|
89
|
+
}
|
90
|
+
}
|
91
|
+
return cache_directory;
|
92
|
+
};
|
93
|
+
const parseRealPlatformBinaryFolderAsync = (binaryPath) => __awaiter(void 0, void 0, void 0, function* () {
|
94
|
+
const realPath = yield fs_extra_1.default.realpath(binaryPath);
|
95
|
+
debug('CYPRESS_RUN_BINARY has realpath:', realPath);
|
96
|
+
if (!realPath.toString().endsWith(getPlatformExecutable())) {
|
97
|
+
return false;
|
98
|
+
}
|
99
|
+
if (os_1.default.platform() === 'darwin') {
|
100
|
+
return path_1.default.resolve(realPath, '..', '..', '..');
|
101
|
+
}
|
102
|
+
return path_1.default.resolve(realPath, '..');
|
103
|
+
});
|
104
|
+
const getDistDir = () => {
|
105
|
+
return path_1.default.join(__dirname, '..', '..', 'dist');
|
106
|
+
};
|
107
|
+
/**
|
108
|
+
* Returns full filename to the file that keeps the Test Runner verification state as JSON text.
|
109
|
+
* Note: the binary state file will be stored one level up from the given binary folder.
|
110
|
+
* @param {string} binaryDir - full path to the folder holding the binary.
|
111
|
+
*/
|
112
|
+
const getBinaryStatePath = (binaryDir) => {
|
113
|
+
return path_1.default.join(binaryDir, '..', 'binary_state.json');
|
114
|
+
};
|
115
|
+
const getBinaryStateContentsAsync = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
|
116
|
+
const fullPath = getBinaryStatePath(binaryDir);
|
117
|
+
try {
|
118
|
+
const contents = yield fs_extra_1.default.readJson(fullPath);
|
119
|
+
debug('binary_state.json contents:', contents);
|
120
|
+
return contents;
|
121
|
+
}
|
122
|
+
catch (error) {
|
123
|
+
if (error.code === 'ENOENT' || error instanceof SyntaxError) {
|
124
|
+
debug('could not read binary_state.json file at "%s"', fullPath);
|
125
|
+
return {};
|
126
|
+
}
|
127
|
+
throw error;
|
128
|
+
}
|
129
|
+
});
|
130
|
+
const getBinaryVerifiedAsync = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
|
131
|
+
const contents = yield getBinaryStateContentsAsync(binaryDir);
|
132
|
+
return contents.verified;
|
133
|
+
});
|
134
|
+
const clearBinaryStateAsync = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
|
135
|
+
yield fs_extra_1.default.remove(getBinaryStatePath(binaryDir));
|
136
|
+
});
|
137
|
+
/**
|
138
|
+
* Writes the new binary status.
|
139
|
+
* @param {boolean} verified The new test runner state after smoke test
|
140
|
+
* @param {string} binaryDir Folder holding the binary
|
141
|
+
* @returns {Promise<void>} returns a promise
|
142
|
+
*/
|
143
|
+
const writeBinaryVerifiedAsync = (verified, binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
|
144
|
+
const contents = yield getBinaryStateContentsAsync(binaryDir);
|
145
|
+
yield fs_extra_1.default.outputJson(getBinaryStatePath(binaryDir), lodash_1.default.extend(contents, { verified }), { spaces: 2 });
|
146
|
+
});
|
147
|
+
const getPathToExecutable = (binaryDir) => {
|
148
|
+
return path_1.default.join(binaryDir, getPlatformExecutable());
|
149
|
+
};
|
150
|
+
/**
|
151
|
+
* Resolves with an object read from the binary app package.json file.
|
152
|
+
* If the file does not exist resolves with null
|
153
|
+
*/
|
154
|
+
const getBinaryPkgAsync = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
|
155
|
+
const pathToPackageJson = getBinaryPkgPath(binaryDir);
|
156
|
+
debug('Reading binary package.json from:', pathToPackageJson);
|
157
|
+
const exists = yield fs_extra_1.default.pathExists(pathToPackageJson);
|
158
|
+
if (!exists) {
|
159
|
+
return null;
|
160
|
+
}
|
161
|
+
return fs_extra_1.default.readJson(pathToPackageJson);
|
162
|
+
});
|
163
|
+
const getBinaryPkgVersion = (o) => lodash_1.default.get(o, 'version', null);
|
164
|
+
const getBinaryElectronVersion = (o) => lodash_1.default.get(o, 'electronVersion', null);
|
165
|
+
const getBinaryElectronNodeVersion = (o) => lodash_1.default.get(o, 'electronNodeVersion', null);
|
166
|
+
const stateModule = {
|
167
|
+
getPathToExecutable,
|
168
|
+
getPlatformExecutable,
|
169
|
+
// those names start to sound like Java
|
170
|
+
getBinaryElectronNodeVersion,
|
171
|
+
getBinaryElectronVersion,
|
172
|
+
getBinaryPkgVersion,
|
173
|
+
getBinaryVerifiedAsync,
|
174
|
+
getBinaryPkgAsync,
|
175
|
+
getBinaryPkgPath,
|
176
|
+
getBinaryDir,
|
177
|
+
getCacheDir,
|
178
|
+
clearBinaryStateAsync,
|
179
|
+
writeBinaryVerifiedAsync,
|
180
|
+
parseRealPlatformBinaryFolderAsync,
|
181
|
+
getDistDir,
|
182
|
+
getVersionDir,
|
183
|
+
};
|
184
|
+
exports.default = stateModule;
|
@@ -0,0 +1,192 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
13
|
+
};
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
15
|
+
const lodash_1 = __importDefault(require("lodash"));
|
16
|
+
const child_process_1 = __importDefault(require("child_process"));
|
17
|
+
const os_1 = __importDefault(require("os"));
|
18
|
+
const yauzl_1 = __importDefault(require("yauzl"));
|
19
|
+
const debug_1 = __importDefault(require("debug"));
|
20
|
+
const extract_zip_1 = __importDefault(require("extract-zip"));
|
21
|
+
const readline_1 = __importDefault(require("readline"));
|
22
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
23
|
+
const errors_1 = require("../errors");
|
24
|
+
const util_1 = __importDefault(require("../util"));
|
25
|
+
const assert_1 = __importDefault(require("assert"));
|
26
|
+
const debug = (0, debug_1.default)('cypress:cli:unzip');
|
27
|
+
const unzipTools = {
|
28
|
+
extract: extract_zip_1.default,
|
29
|
+
};
|
30
|
+
// expose this function for simple testing
|
31
|
+
const unzip = (_a) => __awaiter(void 0, [_a], void 0, function* ({ zipFilePath, installDir, progress }) {
|
32
|
+
debug('unzipping from %s', zipFilePath);
|
33
|
+
debug('into', installDir);
|
34
|
+
if (!zipFilePath) {
|
35
|
+
throw new Error('Missing zip filename');
|
36
|
+
}
|
37
|
+
const startTime = Date.now();
|
38
|
+
let yauzlDoneTime = 0;
|
39
|
+
yield fs_extra_1.default.ensureDir(installDir);
|
40
|
+
yield new Promise((resolve, reject) => {
|
41
|
+
return yauzl_1.default.open(zipFilePath, (err, zipFile) => {
|
42
|
+
yauzlDoneTime = Date.now();
|
43
|
+
if (err) {
|
44
|
+
debug('error using yauzl %s', err.message);
|
45
|
+
return reject(err);
|
46
|
+
}
|
47
|
+
const total = zipFile.entryCount;
|
48
|
+
debug('zipFile entries count', total);
|
49
|
+
const started = new Date();
|
50
|
+
let percent = 0;
|
51
|
+
let count = 0;
|
52
|
+
const notify = (percent) => {
|
53
|
+
const elapsed = +new Date() - +started;
|
54
|
+
const eta = util_1.default.calculateEta(percent, elapsed);
|
55
|
+
progress.onProgress(percent, util_1.default.secsRemaining(eta));
|
56
|
+
};
|
57
|
+
const tick = () => {
|
58
|
+
count += 1;
|
59
|
+
percent = ((count / total) * 100);
|
60
|
+
const displayPercent = percent.toFixed(0);
|
61
|
+
return notify(Number(displayPercent));
|
62
|
+
};
|
63
|
+
const unzipWithNode = () => __awaiter(void 0, void 0, void 0, function* () {
|
64
|
+
debug('unzipping with node.js (slow)');
|
65
|
+
const opts = {
|
66
|
+
dir: installDir,
|
67
|
+
onEntry: tick,
|
68
|
+
};
|
69
|
+
debug('calling Node extract tool %s %o', zipFilePath, opts);
|
70
|
+
try {
|
71
|
+
yield unzipTools.extract(zipFilePath, opts);
|
72
|
+
debug('node unzip finished');
|
73
|
+
return resolve();
|
74
|
+
}
|
75
|
+
catch (err) {
|
76
|
+
const error = err || new Error('Unknown error with Node extract tool');
|
77
|
+
debug('error %s', error.message);
|
78
|
+
return reject(error);
|
79
|
+
}
|
80
|
+
});
|
81
|
+
const unzipFallback = lodash_1.default.once(unzipWithNode);
|
82
|
+
const unzipWithUnzipTool = () => {
|
83
|
+
debug('unzipping via `unzip`');
|
84
|
+
const inflatingRe = /inflating:/;
|
85
|
+
const sp = child_process_1.default.spawn('unzip', ['-o', zipFilePath, '-d', installDir]);
|
86
|
+
sp.on('error', (err) => {
|
87
|
+
debug('unzip tool error: %s', err.message);
|
88
|
+
unzipFallback();
|
89
|
+
});
|
90
|
+
sp.on('close', (code) => {
|
91
|
+
debug('unzip tool close with code %d', code);
|
92
|
+
if (code === 0) {
|
93
|
+
percent = 100;
|
94
|
+
notify(percent);
|
95
|
+
return resolve();
|
96
|
+
}
|
97
|
+
debug('`unzip` failed %o', { code });
|
98
|
+
return unzipFallback();
|
99
|
+
});
|
100
|
+
sp.stdout.on('data', (data) => {
|
101
|
+
if (inflatingRe.test(data)) {
|
102
|
+
return tick();
|
103
|
+
}
|
104
|
+
});
|
105
|
+
sp.stderr.on('data', (data) => {
|
106
|
+
debug('`unzip` stderr %s', data);
|
107
|
+
});
|
108
|
+
};
|
109
|
+
// we attempt to first unzip with the native osx
|
110
|
+
// ditto because its less likely to have problems
|
111
|
+
// with corruption, symlinks, or icons causing failures
|
112
|
+
// and can handle resource forks
|
113
|
+
// http://automatica.com.au/2011/02/unzip-mac-os-x-zip-in-terminal/
|
114
|
+
const unzipWithOsx = () => {
|
115
|
+
debug('unzipping via `ditto`');
|
116
|
+
const copyingFileRe = /^copying file/;
|
117
|
+
const sp = child_process_1.default.spawn('ditto', ['-xkV', zipFilePath, installDir]);
|
118
|
+
// f-it just unzip with node
|
119
|
+
sp.on('error', (err) => {
|
120
|
+
debug(err.message);
|
121
|
+
unzipFallback();
|
122
|
+
});
|
123
|
+
sp.on('close', (code) => {
|
124
|
+
if (code === 0) {
|
125
|
+
// make sure we get to 100% on the progress bar
|
126
|
+
// because reading in lines is not really accurate
|
127
|
+
percent = 100;
|
128
|
+
notify(percent);
|
129
|
+
return resolve();
|
130
|
+
}
|
131
|
+
debug('`ditto` failed %o', { code });
|
132
|
+
return unzipFallback();
|
133
|
+
});
|
134
|
+
return readline_1.default.createInterface({
|
135
|
+
input: sp.stderr,
|
136
|
+
})
|
137
|
+
.on('line', (line) => {
|
138
|
+
if (copyingFileRe.test(line)) {
|
139
|
+
return tick();
|
140
|
+
}
|
141
|
+
});
|
142
|
+
};
|
143
|
+
switch (os_1.default.platform()) {
|
144
|
+
case 'darwin':
|
145
|
+
return unzipWithOsx();
|
146
|
+
case 'linux':
|
147
|
+
return unzipWithUnzipTool();
|
148
|
+
case 'win32':
|
149
|
+
return unzipWithNode();
|
150
|
+
default:
|
151
|
+
return;
|
152
|
+
}
|
153
|
+
});
|
154
|
+
});
|
155
|
+
debug('unzip completed %o', {
|
156
|
+
yauzlMs: yauzlDoneTime - startTime,
|
157
|
+
unzipMs: Date.now() - yauzlDoneTime,
|
158
|
+
});
|
159
|
+
});
|
160
|
+
function isMaybeWindowsMaxPathLengthError(err) {
|
161
|
+
return os_1.default.platform() === 'win32' && err.code === 'ENOENT' && err.syscall === 'realpath';
|
162
|
+
}
|
163
|
+
const start = (_a) => __awaiter(void 0, [_a], void 0, function* ({ zipFilePath, installDir, progress }) {
|
164
|
+
assert_1.default.ok(lodash_1.default.isString(installDir) && !lodash_1.default.isEmpty(installDir), 'missing installDir');
|
165
|
+
if (!progress) {
|
166
|
+
progress = { onProgress: () => {
|
167
|
+
return {};
|
168
|
+
} };
|
169
|
+
}
|
170
|
+
try {
|
171
|
+
const installDirExists = yield fs_extra_1.default.pathExists(installDir);
|
172
|
+
if (installDirExists) {
|
173
|
+
debug('removing existing unzipped binary', installDir);
|
174
|
+
yield fs_extra_1.default.remove(installDir);
|
175
|
+
}
|
176
|
+
yield unzip({ zipFilePath, installDir, progress });
|
177
|
+
}
|
178
|
+
catch (err) {
|
179
|
+
const errorTemplate = isMaybeWindowsMaxPathLengthError(err) ?
|
180
|
+
errors_1.errors.failedUnzipWindowsMaxPathLength
|
181
|
+
: errors_1.errors.failedUnzip;
|
182
|
+
yield (0, errors_1.throwFormErrorText)(errorTemplate)(err);
|
183
|
+
}
|
184
|
+
});
|
185
|
+
const unzipModule = {
|
186
|
+
start,
|
187
|
+
utils: {
|
188
|
+
unzip,
|
189
|
+
unzipTools,
|
190
|
+
},
|
191
|
+
};
|
192
|
+
exports.default = unzipModule;
|
@@ -0,0 +1,303 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
9
|
+
});
|
10
|
+
};
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
13
|
+
};
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
15
|
+
exports.needsSandbox = exports.start = exports.verifyTestRunnerTimeoutMs = void 0;
|
16
|
+
const lodash_1 = __importDefault(require("lodash"));
|
17
|
+
const chalk_1 = __importDefault(require("chalk"));
|
18
|
+
const listr2_1 = require("listr2");
|
19
|
+
const debug_1 = __importDefault(require("debug"));
|
20
|
+
const common_tags_1 = require("common-tags");
|
21
|
+
const bluebird_1 = __importDefault(require("bluebird"));
|
22
|
+
const log_symbols_1 = __importDefault(require("log-symbols"));
|
23
|
+
const path_1 = __importDefault(require("path"));
|
24
|
+
const os_1 = __importDefault(require("os"));
|
25
|
+
const VerboseRenderer_1 = __importDefault(require("../VerboseRenderer"));
|
26
|
+
const errors_1 = require("../errors");
|
27
|
+
const util_1 = __importDefault(require("../util"));
|
28
|
+
const logger_1 = __importDefault(require("../logger"));
|
29
|
+
const xvfb_1 = __importDefault(require("../exec/xvfb"));
|
30
|
+
const state_1 = __importDefault(require("./state"));
|
31
|
+
const debug = (0, debug_1.default)('cypress:cli');
|
32
|
+
const verifyTestRunnerTimeoutMs = () => {
|
33
|
+
const verifyTimeout = +((util_1.default === null || util_1.default === void 0 ? void 0 : util_1.default.getEnv('CYPRESS_VERIFY_TIMEOUT')) || 'NaN');
|
34
|
+
if (lodash_1.default.isNumber(verifyTimeout) && !lodash_1.default.isNaN(verifyTimeout)) {
|
35
|
+
return verifyTimeout;
|
36
|
+
}
|
37
|
+
return 30000;
|
38
|
+
};
|
39
|
+
exports.verifyTestRunnerTimeoutMs = verifyTestRunnerTimeoutMs;
|
40
|
+
const checkExecutable = (binaryDir) => __awaiter(void 0, void 0, void 0, function* () {
|
41
|
+
const executable = state_1.default.getPathToExecutable(binaryDir);
|
42
|
+
debug('checking if executable exists', executable);
|
43
|
+
try {
|
44
|
+
const isExecutable = yield util_1.default.isExecutableAsync(executable);
|
45
|
+
debug('Binary is executable? :', isExecutable);
|
46
|
+
if (!isExecutable) {
|
47
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.binaryNotExecutable(executable))();
|
48
|
+
}
|
49
|
+
}
|
50
|
+
catch (err) {
|
51
|
+
if (err.code === 'ENOENT') {
|
52
|
+
if (util_1.default.isCi()) {
|
53
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.notInstalledCI(executable))();
|
54
|
+
}
|
55
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.missingApp(binaryDir))((0, common_tags_1.stripIndent) `
|
56
|
+
Cypress executable not found at: ${chalk_1.default.cyan(executable)}
|
57
|
+
`);
|
58
|
+
}
|
59
|
+
throw err;
|
60
|
+
}
|
61
|
+
});
|
62
|
+
const runSmokeTest = (binaryDir, options) => {
|
63
|
+
let executable = state_1.default.getPathToExecutable(binaryDir);
|
64
|
+
const needsXvfb = xvfb_1.default.isNeeded();
|
65
|
+
debug('needs Xvfb?', needsXvfb);
|
66
|
+
/**
|
67
|
+
* Spawn Cypress running smoke test to check if all operating system
|
68
|
+
* dependencies are good.
|
69
|
+
*/
|
70
|
+
const spawn = (linuxWithDisplayEnv) => __awaiter(void 0, void 0, void 0, function* () {
|
71
|
+
const random = lodash_1.default.random(0, 1000);
|
72
|
+
const args = ['--smoke-test', `--ping=${random}`];
|
73
|
+
if ((0, exports.needsSandbox)()) {
|
74
|
+
// electron requires --no-sandbox to run as root
|
75
|
+
debug('disabling Electron sandbox');
|
76
|
+
args.unshift('--no-sandbox');
|
77
|
+
}
|
78
|
+
if (options.dev) {
|
79
|
+
executable = 'node';
|
80
|
+
args.unshift(path_1.default.resolve(__dirname, '..', '..', '..', 'scripts', 'start.js'));
|
81
|
+
}
|
82
|
+
const smokeTestCommand = `${executable} ${args.join(' ')}`;
|
83
|
+
debug('running smoke test');
|
84
|
+
debug('using Cypress executable %s', executable);
|
85
|
+
debug('smoke test command:', smokeTestCommand);
|
86
|
+
debug('smoke test timeout %d ms', options.smokeTestTimeout);
|
87
|
+
const stdioOptions = lodash_1.default.extend({}, {
|
88
|
+
env: Object.assign(Object.assign({}, process.env), { FORCE_COLOR: '0' }),
|
89
|
+
timeout: options.smokeTestTimeout,
|
90
|
+
});
|
91
|
+
try {
|
92
|
+
const result = yield util_1.default.exec(executable, args, stdioOptions);
|
93
|
+
// TODO: when execa > 1.1 is released
|
94
|
+
// change this to `result.all` for both stderr and stdout
|
95
|
+
// use lodash to be robust during tests against null result or missing stdout
|
96
|
+
const smokeTestStdout = lodash_1.default.get(result, 'stdout', '');
|
97
|
+
debug('smoke test stdout "%s"', smokeTestStdout);
|
98
|
+
if (!util_1.default.stdoutLineMatches(String(random), smokeTestStdout)) {
|
99
|
+
debug('Smoke test failed because could not find %d in:', random, result);
|
100
|
+
const smokeTestStderr = lodash_1.default.get(result, 'stderr', '');
|
101
|
+
const errorText = smokeTestStderr || smokeTestStdout;
|
102
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.smokeTestFailure(smokeTestCommand, false))(errorText);
|
103
|
+
}
|
104
|
+
}
|
105
|
+
catch (err) {
|
106
|
+
debug('Smoke test failed:', err);
|
107
|
+
let errMessage = err.stderr || err.message;
|
108
|
+
debug('error message:', errMessage);
|
109
|
+
if (err.timedOut) {
|
110
|
+
debug('error timedOut is true');
|
111
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.smokeTestFailure(smokeTestCommand, true))(errMessage);
|
112
|
+
}
|
113
|
+
if (linuxWithDisplayEnv && util_1.default.isBrokenGtkDisplay(errMessage)) {
|
114
|
+
util_1.default.logBrokenGtkDisplayWarning();
|
115
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.invalidSmokeTestDisplayError)(errMessage);
|
116
|
+
}
|
117
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.missingDependency)(errMessage);
|
118
|
+
}
|
119
|
+
});
|
120
|
+
const spawnInXvfb = (linuxWithDisplayEnv) => __awaiter(void 0, void 0, void 0, function* () {
|
121
|
+
yield xvfb_1.default.start();
|
122
|
+
return spawn(linuxWithDisplayEnv || false).finally(() => __awaiter(void 0, void 0, void 0, function* () {
|
123
|
+
yield xvfb_1.default.stop();
|
124
|
+
}));
|
125
|
+
});
|
126
|
+
const userFriendlySpawn = (linuxWithDisplayEnv) => __awaiter(void 0, void 0, void 0, function* () {
|
127
|
+
debug('spawning, should retry on display problem?', Boolean(linuxWithDisplayEnv));
|
128
|
+
try {
|
129
|
+
yield spawn(linuxWithDisplayEnv);
|
130
|
+
}
|
131
|
+
catch (err) {
|
132
|
+
if (err.code === 'INVALID_SMOKE_TEST_DISPLAY_ERROR') {
|
133
|
+
return spawnInXvfb(linuxWithDisplayEnv);
|
134
|
+
}
|
135
|
+
throw err;
|
136
|
+
}
|
137
|
+
});
|
138
|
+
if (needsXvfb) {
|
139
|
+
return spawnInXvfb();
|
140
|
+
}
|
141
|
+
// if we are on linux and there's already a DISPLAY
|
142
|
+
// set, then we may need to rerun cypress after
|
143
|
+
// spawning our own Xvfb server
|
144
|
+
const linuxWithDisplayEnv = util_1.default.isPossibleLinuxWithIncorrectDisplay();
|
145
|
+
return userFriendlySpawn(linuxWithDisplayEnv);
|
146
|
+
};
|
147
|
+
function testBinary(version, binaryDir, options) {
|
148
|
+
debug('running binary verification check', version);
|
149
|
+
// if running from 'cypress verify', don't print this message
|
150
|
+
if (!options.force) {
|
151
|
+
logger_1.default.log((0, common_tags_1.stripIndent) `
|
152
|
+
It looks like this is your first time using Cypress: ${chalk_1.default.cyan(version)}
|
153
|
+
`);
|
154
|
+
}
|
155
|
+
logger_1.default.log();
|
156
|
+
// if we are running in CI then use
|
157
|
+
// the verbose renderer else use
|
158
|
+
// the default
|
159
|
+
let renderer = util_1.default.isCi() ? VerboseRenderer_1.default : 'default';
|
160
|
+
// NOTE: under test we set the listr renderer to 'silent' in order to get deterministic snapshots
|
161
|
+
if (logger_1.default.logLevel() === 'silent' || options.listrRenderer)
|
162
|
+
renderer = 'silent';
|
163
|
+
const rendererOptions = {
|
164
|
+
renderer,
|
165
|
+
};
|
166
|
+
const tasks = new listr2_1.Listr([
|
167
|
+
{
|
168
|
+
title: util_1.default.titleize('Verifying Cypress can run', chalk_1.default.gray(binaryDir)),
|
169
|
+
task: (ctx, task) => __awaiter(this, void 0, void 0, function* () {
|
170
|
+
debug('clearing out the verified version');
|
171
|
+
yield state_1.default.clearBinaryStateAsync(binaryDir);
|
172
|
+
yield Promise.all([
|
173
|
+
runSmokeTest(binaryDir, options),
|
174
|
+
bluebird_1.default.delay(1500), // good user experience
|
175
|
+
]);
|
176
|
+
debug('write verified: true');
|
177
|
+
yield state_1.default.writeBinaryVerifiedAsync(true, binaryDir);
|
178
|
+
util_1.default.setTaskTitle(task, util_1.default.titleize(chalk_1.default.green('Verified Cypress!'), chalk_1.default.gray(binaryDir)), rendererOptions.renderer);
|
179
|
+
}),
|
180
|
+
},
|
181
|
+
], rendererOptions);
|
182
|
+
return tasks.run();
|
183
|
+
}
|
184
|
+
const maybeVerify = (installedVersion, binaryDir, options) => __awaiter(void 0, void 0, void 0, function* () {
|
185
|
+
const isVerified = yield state_1.default.getBinaryVerifiedAsync(binaryDir);
|
186
|
+
debug('is Verified ?', isVerified);
|
187
|
+
let shouldVerify = !isVerified;
|
188
|
+
// force verify if options.force
|
189
|
+
if (options.force) {
|
190
|
+
debug('force verify');
|
191
|
+
shouldVerify = true;
|
192
|
+
}
|
193
|
+
if (shouldVerify) {
|
194
|
+
yield testBinary(installedVersion, binaryDir, options);
|
195
|
+
if (options.welcomeMessage) {
|
196
|
+
logger_1.default.log();
|
197
|
+
logger_1.default.log('Opening Cypress...');
|
198
|
+
}
|
199
|
+
}
|
200
|
+
});
|
201
|
+
const start = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (options = {}) {
|
202
|
+
debug('verifying Cypress app');
|
203
|
+
const packageVersion = util_1.default.pkgVersion();
|
204
|
+
let binaryDir = state_1.default.getBinaryDir(packageVersion);
|
205
|
+
lodash_1.default.defaults(options, {
|
206
|
+
dev: false,
|
207
|
+
force: false,
|
208
|
+
welcomeMessage: true,
|
209
|
+
smokeTestTimeout: (0, exports.verifyTestRunnerTimeoutMs)(),
|
210
|
+
skipVerify: util_1.default.getEnv('CYPRESS_SKIP_VERIFY') === 'true',
|
211
|
+
});
|
212
|
+
if (options.skipVerify) {
|
213
|
+
debug('skipping verification of the Cypress app');
|
214
|
+
return Promise.resolve();
|
215
|
+
}
|
216
|
+
if (options.dev) {
|
217
|
+
return runSmokeTest('', options);
|
218
|
+
}
|
219
|
+
const parseBinaryEnvVar = () => __awaiter(void 0, void 0, void 0, function* () {
|
220
|
+
const envBinaryPath = util_1.default.getEnv('CYPRESS_RUN_BINARY');
|
221
|
+
debug('CYPRESS_RUN_BINARY exists, =', envBinaryPath);
|
222
|
+
logger_1.default.log((0, common_tags_1.stripIndent) `
|
223
|
+
${chalk_1.default.yellow('Note:')} You have set the environment variable:
|
224
|
+
|
225
|
+
${chalk_1.default.white('CYPRESS_RUN_BINARY=')}${chalk_1.default.cyan(envBinaryPath)}
|
226
|
+
|
227
|
+
This overrides the default Cypress binary path used.
|
228
|
+
`);
|
229
|
+
logger_1.default.log();
|
230
|
+
try {
|
231
|
+
const isExecutable = yield util_1.default.isExecutableAsync(envBinaryPath);
|
232
|
+
debug('CYPRESS_RUN_BINARY is executable? :', isExecutable);
|
233
|
+
if (!isExecutable) {
|
234
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))((0, common_tags_1.stripIndent) `
|
235
|
+
The supplied binary path is not executable
|
236
|
+
`);
|
237
|
+
}
|
238
|
+
const envBinaryDir = yield state_1.default.parseRealPlatformBinaryFolderAsync(envBinaryPath);
|
239
|
+
if (!envBinaryDir) {
|
240
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))();
|
241
|
+
}
|
242
|
+
debug('CYPRESS_RUN_BINARY has binaryDir:', envBinaryDir);
|
243
|
+
binaryDir = envBinaryDir;
|
244
|
+
}
|
245
|
+
catch (err) {
|
246
|
+
if (err.code === 'ENOENT') {
|
247
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.CYPRESS_RUN_BINARY.notValid(envBinaryPath))(err.message);
|
248
|
+
}
|
249
|
+
throw err;
|
250
|
+
}
|
251
|
+
});
|
252
|
+
try {
|
253
|
+
debug('checking environment variables');
|
254
|
+
if (util_1.default.getEnv('CYPRESS_RUN_BINARY')) {
|
255
|
+
yield parseBinaryEnvVar();
|
256
|
+
}
|
257
|
+
yield checkExecutable(binaryDir);
|
258
|
+
debug('binaryDir is ', binaryDir);
|
259
|
+
const pkg = yield state_1.default.getBinaryPkgAsync(binaryDir);
|
260
|
+
const binaryVersion = state_1.default.getBinaryPkgVersion(pkg);
|
261
|
+
if (!binaryVersion) {
|
262
|
+
debug('no Cypress binary found for cli version ', packageVersion);
|
263
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.missingApp(binaryDir))(`
|
264
|
+
Cannot read binary version from: ${chalk_1.default.cyan(state_1.default.getBinaryPkgPath(binaryDir))}
|
265
|
+
`);
|
266
|
+
}
|
267
|
+
debug(`Found binary version ${chalk_1.default.green(binaryVersion)} installed in: ${chalk_1.default.cyan(binaryDir)}`);
|
268
|
+
if (binaryVersion !== packageVersion) {
|
269
|
+
// warn if we installed with CYPRESS_INSTALL_BINARY or changed version
|
270
|
+
// in the package.json
|
271
|
+
logger_1.default.log(`Found binary version ${chalk_1.default.green(binaryVersion)} installed in: ${chalk_1.default.cyan(binaryDir)}`);
|
272
|
+
logger_1.default.log();
|
273
|
+
logger_1.default.warn((0, common_tags_1.stripIndent) `
|
274
|
+
|
275
|
+
|
276
|
+
${log_symbols_1.default.warning} Warning: Binary version ${chalk_1.default.green(binaryVersion)} does not match the expected package version ${chalk_1.default.green(packageVersion)}
|
277
|
+
|
278
|
+
These versions may not work properly together.
|
279
|
+
`);
|
280
|
+
logger_1.default.log();
|
281
|
+
}
|
282
|
+
yield maybeVerify(binaryVersion, binaryDir, options);
|
283
|
+
}
|
284
|
+
catch (err) {
|
285
|
+
if (err.known) {
|
286
|
+
throw err;
|
287
|
+
}
|
288
|
+
return (0, errors_1.throwFormErrorText)(errors_1.errors.unexpected)(err.stack);
|
289
|
+
}
|
290
|
+
});
|
291
|
+
exports.start = start;
|
292
|
+
const isLinuxLike = () => os_1.default.platform() !== 'win32';
|
293
|
+
/**
|
294
|
+
* Returns true if running on a system where Electron needs "--no-sandbox" flag.
|
295
|
+
* @see https://crbug.com/638180
|
296
|
+
*
|
297
|
+
* On Debian we had problems running in sandbox even for non-root users.
|
298
|
+
* @see https://github.com/cypress-io/cypress/issues/5434
|
299
|
+
* Seems there is a lot of discussion around this issue among Electron users
|
300
|
+
* @see https://github.com/electron/electron/issues/17972
|
301
|
+
*/
|
302
|
+
const needsSandbox = () => isLinuxLike();
|
303
|
+
exports.needsSandbox = needsSandbox;
|