@wdio/cli 7.16.16 → 7.17.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/package.json +4 -4
- package/build/@types/yarn-install.d.ts +0 -16
- package/build/@types/yarn-install.d.ts.map +0 -1
- package/build/@types/yarn-install.js +0 -1
- package/build/commands/config.d.ts +0 -64
- package/build/commands/config.d.ts.map +0 -1
- package/build/commands/config.js +0 -236
- package/build/commands/install.d.ts +0 -18
- package/build/commands/install.d.ts.map +0 -1
- package/build/commands/install.js +0 -112
- package/build/commands/repl.d.ts +0 -36
- package/build/commands/repl.d.ts.map +0 -1
- package/build/commands/repl.js +0 -62
- package/build/commands/run.d.ts +0 -172
- package/build/commands/run.d.ts.map +0 -1
- package/build/commands/run.js +0 -172
- package/build/constants.d.ts +0 -485
- package/build/constants.d.ts.map +0 -1
- package/build/constants.js +0 -370
- package/build/index.d.ts +0 -5
- package/build/index.d.ts.map +0 -1
- package/build/index.js +0 -84
- package/build/interface.d.ts +0 -75
- package/build/interface.d.ts.map +0 -1
- package/build/interface.js +0 -245
- package/build/launcher.d.ts +0 -95
- package/build/launcher.d.ts.map +0 -1
- package/build/launcher.js +0 -418
- package/build/templates/afterTest.ejs +0 -20
- package/build/templates/exampleFiles/cucumber/features/login.feature +0 -12
- package/build/templates/exampleFiles/cucumber/step_definitions/steps.js.ejs +0 -52
- package/build/templates/exampleFiles/jasmine/example.e2e.js.ejs +0 -42
- package/build/templates/exampleFiles/mocha/example.e2e.js.ejs +0 -42
- package/build/templates/exampleFiles/pageobjects/login.page.js.ejs +0 -44
- package/build/templates/exampleFiles/pageobjects/page.js.ejs +0 -13
- package/build/templates/exampleFiles/pageobjects/secure.page.js.ejs +0 -19
- package/build/templates/reporters.ejs +0 -14
- package/build/templates/wdio.conf.tpl.ejs +0 -448
- package/build/types.d.ts +0 -110
- package/build/types.d.ts.map +0 -1
- package/build/types.js +0 -2
- package/build/utils.d.ts +0 -82
- package/build/utils.d.ts.map +0 -1
- package/build/utils.js +0 -345
- package/build/watcher.d.ts +0 -34
- package/build/watcher.d.ts.map +0 -1
- package/build/watcher.js +0 -150
package/build/utils.js
DELETED
|
@@ -1,345 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getDefaultFiles = exports.getPathForFileGeneration = exports.getAnswers = exports.generateTestFiles = exports.hasPackage = exports.hasFile = exports.getCapabilities = exports.validateServiceAnswers = exports.renderConfigurationFile = exports.convertPackageHashToObject = exports.addServiceDeps = exports.replaceConfig = exports.findInConfig = exports.getRunnerName = exports.runOnCompleteHook = exports.runLauncherHook = exports.runServiceHook = void 0;
|
|
7
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
-
const ejs_1 = __importDefault(require("ejs"));
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const inquirer_1 = __importDefault(require("inquirer"));
|
|
11
|
-
const logger_1 = __importDefault(require("@wdio/logger"));
|
|
12
|
-
const recursive_readdir_1 = __importDefault(require("recursive-readdir"));
|
|
13
|
-
const webdriverio_1 = require("webdriverio");
|
|
14
|
-
const child_process_1 = require("child_process");
|
|
15
|
-
const util_1 = require("util");
|
|
16
|
-
const constants_1 = require("./constants");
|
|
17
|
-
const log = (0, logger_1.default)('@wdio/cli:utils');
|
|
18
|
-
const TEMPLATE_ROOT_DIR = path_1.default.join(__dirname, 'templates', 'exampleFiles');
|
|
19
|
-
const renderFile = (0, util_1.promisify)(ejs_1.default.renderFile);
|
|
20
|
-
/**
|
|
21
|
-
* run service launch sequences
|
|
22
|
-
*/
|
|
23
|
-
async function runServiceHook(launcher, hookName, ...args) {
|
|
24
|
-
const start = Date.now();
|
|
25
|
-
return Promise.all(launcher.map(async (service) => {
|
|
26
|
-
try {
|
|
27
|
-
if (typeof service[hookName] === 'function') {
|
|
28
|
-
await service[hookName](...args);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
catch (err) {
|
|
32
|
-
const message = `A service failed in the '${hookName}' hook\n${err.stack}\n\n`;
|
|
33
|
-
if (err instanceof webdriverio_1.SevereServiceError) {
|
|
34
|
-
return { status: 'rejected', reason: message };
|
|
35
|
-
}
|
|
36
|
-
log.error(`${message}Continue...`);
|
|
37
|
-
}
|
|
38
|
-
})).then(results => {
|
|
39
|
-
if (launcher.length) {
|
|
40
|
-
log.debug(`Finished to run "${hookName}" hook in ${Date.now() - start}ms`);
|
|
41
|
-
}
|
|
42
|
-
const rejectedHooks = results.filter(p => p && p.status === 'rejected');
|
|
43
|
-
if (rejectedHooks.length) {
|
|
44
|
-
return Promise.reject(new Error(`\n${rejectedHooks.map(p => p && p.reason).join()}\n\nStopping runner...`));
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
exports.runServiceHook = runServiceHook;
|
|
49
|
-
/**
|
|
50
|
-
* Run hook in service launcher
|
|
51
|
-
* @param {Array|Function} hook - can be array of functions or single function
|
|
52
|
-
* @param {Object} config
|
|
53
|
-
* @param {Object} capabilities
|
|
54
|
-
*/
|
|
55
|
-
async function runLauncherHook(hook, ...args) {
|
|
56
|
-
const catchFn = (e) => log.error(`Error in hook: ${e.stack}`);
|
|
57
|
-
if (typeof hook === 'function') {
|
|
58
|
-
hook = [hook];
|
|
59
|
-
}
|
|
60
|
-
return Promise.all(hook.map((hook) => {
|
|
61
|
-
try {
|
|
62
|
-
return hook(...args);
|
|
63
|
-
}
|
|
64
|
-
catch (err) {
|
|
65
|
-
return catchFn(err);
|
|
66
|
-
}
|
|
67
|
-
})).catch(catchFn);
|
|
68
|
-
}
|
|
69
|
-
exports.runLauncherHook = runLauncherHook;
|
|
70
|
-
/**
|
|
71
|
-
* Run onCompleteHook in Launcher
|
|
72
|
-
* @param {Array|Function} onCompleteHook - can be array of functions or single function
|
|
73
|
-
* @param {*} config
|
|
74
|
-
* @param {*} capabilities
|
|
75
|
-
* @param {*} exitCode
|
|
76
|
-
* @param {*} results
|
|
77
|
-
*/
|
|
78
|
-
async function runOnCompleteHook(onCompleteHook, config, capabilities, exitCode, results) {
|
|
79
|
-
if (typeof onCompleteHook === 'function') {
|
|
80
|
-
onCompleteHook = [onCompleteHook];
|
|
81
|
-
}
|
|
82
|
-
return Promise.all(onCompleteHook.map(async (hook) => {
|
|
83
|
-
try {
|
|
84
|
-
await hook(exitCode, config, capabilities, results);
|
|
85
|
-
return 0;
|
|
86
|
-
}
|
|
87
|
-
catch (err) {
|
|
88
|
-
log.error(`Error in onCompleteHook: ${err.stack}`);
|
|
89
|
-
return 1;
|
|
90
|
-
}
|
|
91
|
-
}));
|
|
92
|
-
}
|
|
93
|
-
exports.runOnCompleteHook = runOnCompleteHook;
|
|
94
|
-
/**
|
|
95
|
-
* get runner identification by caps
|
|
96
|
-
*/
|
|
97
|
-
function getRunnerName(caps = {}) {
|
|
98
|
-
let runner = caps.browserName ||
|
|
99
|
-
caps.appPackage ||
|
|
100
|
-
caps.appWaitActivity ||
|
|
101
|
-
caps.app ||
|
|
102
|
-
caps.platformName ||
|
|
103
|
-
caps['appium:platformName'] ||
|
|
104
|
-
caps['appium:appPackage'] ||
|
|
105
|
-
caps['appium:appWaitActivity'] ||
|
|
106
|
-
caps['appium:app'];
|
|
107
|
-
// MultiRemote
|
|
108
|
-
if (!runner) {
|
|
109
|
-
runner = Object.values(caps).length === 0 || Object.values(caps).some(cap => !cap.capabilities) ? 'undefined' : 'MultiRemote';
|
|
110
|
-
}
|
|
111
|
-
return runner;
|
|
112
|
-
}
|
|
113
|
-
exports.getRunnerName = getRunnerName;
|
|
114
|
-
function buildNewConfigArray(str, type, change) {
|
|
115
|
-
var _a;
|
|
116
|
-
const newStr = str
|
|
117
|
-
.split(`${type}s: `)[1]
|
|
118
|
-
.replace(/'/g, '');
|
|
119
|
-
let newArray = ((_a = newStr.match(/(\w*)/gmi)) === null || _a === void 0 ? void 0 : _a.filter(e => !!e).concat([change])) || [];
|
|
120
|
-
return str
|
|
121
|
-
.replace('// ', '')
|
|
122
|
-
.replace(new RegExp(`(${type}s: )((.*\\s*)*)`), `$1[${newArray.map(e => `'${e}'`)}]`);
|
|
123
|
-
}
|
|
124
|
-
function buildNewConfigString(str, type, change) {
|
|
125
|
-
return str.replace(new RegExp(`(${type}: )('\\w*')`), `$1'${change}'`);
|
|
126
|
-
}
|
|
127
|
-
function findInConfig(config, type) {
|
|
128
|
-
let regexStr = `[\\/\\/]*[\\s]*${type}s: [\\s]*\\[([\\s]*['|"]\\w*['|"],*)*[\\s]*\\]`;
|
|
129
|
-
if (type === 'framework') {
|
|
130
|
-
regexStr = `[\\/\\/]*[\\s]*${type}: ([\\s]*['|"]\\w*['|"])`;
|
|
131
|
-
}
|
|
132
|
-
const regex = new RegExp(regexStr, 'gmi');
|
|
133
|
-
return config.match(regex);
|
|
134
|
-
}
|
|
135
|
-
exports.findInConfig = findInConfig;
|
|
136
|
-
function replaceConfig(config, type, name) {
|
|
137
|
-
if (type === 'framework') {
|
|
138
|
-
return buildNewConfigString(config, type, name);
|
|
139
|
-
}
|
|
140
|
-
const match = findInConfig(config, type);
|
|
141
|
-
if (!match || match.length === 0) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const text = match.pop() || '';
|
|
145
|
-
return config.replace(text, buildNewConfigArray(text, type, name));
|
|
146
|
-
}
|
|
147
|
-
exports.replaceConfig = replaceConfig;
|
|
148
|
-
function addServiceDeps(names, packages, update = false) {
|
|
149
|
-
/**
|
|
150
|
-
* automatically install latest Chromedriver if `wdio-chromedriver-service`
|
|
151
|
-
* was selected for install
|
|
152
|
-
*/
|
|
153
|
-
if (names.some(({ short }) => short === 'chromedriver')) {
|
|
154
|
-
packages.push('chromedriver');
|
|
155
|
-
if (update) {
|
|
156
|
-
// eslint-disable-next-line no-console
|
|
157
|
-
console.log('\n=======', '\nPlease change path to / in your wdio.conf.js:', "\npath: '/'", '\n=======\n');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* install Appium if it is not installed globally if `@wdio/appium-service`
|
|
162
|
-
* was selected for install
|
|
163
|
-
*/
|
|
164
|
-
if (names.some(({ short }) => short === 'appium')) {
|
|
165
|
-
const result = (0, child_process_1.execSync)('appium --version || echo APPIUM_MISSING').toString().trim();
|
|
166
|
-
if (result === 'APPIUM_MISSING') {
|
|
167
|
-
packages.push('appium');
|
|
168
|
-
}
|
|
169
|
-
else if (update) {
|
|
170
|
-
// eslint-disable-next-line no-console
|
|
171
|
-
console.log('\n=======', '\nUsing globally installed appium', result, '\nPlease add the following to your wdio.conf.js:', "\nappium: { command: 'appium' }", '\n=======\n');
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
exports.addServiceDeps = addServiceDeps;
|
|
176
|
-
/**
|
|
177
|
-
* @todo add JSComments
|
|
178
|
-
*/
|
|
179
|
-
function convertPackageHashToObject(pkg, hash = '$--$') {
|
|
180
|
-
const splitHash = pkg.split(hash);
|
|
181
|
-
return {
|
|
182
|
-
package: splitHash[0],
|
|
183
|
-
short: splitHash[1]
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
exports.convertPackageHashToObject = convertPackageHashToObject;
|
|
187
|
-
async function renderConfigurationFile(answers) {
|
|
188
|
-
const tplPath = path_1.default.join(__dirname, 'templates/wdio.conf.tpl.ejs');
|
|
189
|
-
const filename = `wdio.conf.${answers.isUsingTypeScript ? 'ts' : 'js'}`;
|
|
190
|
-
const renderedTpl = await renderFile(tplPath, { answers });
|
|
191
|
-
return fs_extra_1.default.promises.writeFile(path_1.default.join(process.cwd(), answers.isUsingTypeScript ? 'test' : '', filename), renderedTpl);
|
|
192
|
-
}
|
|
193
|
-
exports.renderConfigurationFile = renderConfigurationFile;
|
|
194
|
-
const validateServiceAnswers = (answers) => {
|
|
195
|
-
let result = true;
|
|
196
|
-
Object.entries(constants_1.EXCLUSIVE_SERVICES).forEach(([name, { services, message }]) => {
|
|
197
|
-
const exists = answers.some(answer => answer.includes(name));
|
|
198
|
-
const hasExclusive = services.some(service => answers.some(answer => answer.includes(service)));
|
|
199
|
-
if (exists && hasExclusive) {
|
|
200
|
-
result = `${name} cannot work together with ${services.join(', ')}\n${message}\nPlease uncheck one of them.`;
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
return result;
|
|
204
|
-
};
|
|
205
|
-
exports.validateServiceAnswers = validateServiceAnswers;
|
|
206
|
-
function getCapabilities(arg) {
|
|
207
|
-
const optionalCapabilites = {
|
|
208
|
-
platformVersion: arg.platformVersion,
|
|
209
|
-
udid: arg.udid,
|
|
210
|
-
...(arg.deviceName && { deviceName: arg.deviceName })
|
|
211
|
-
};
|
|
212
|
-
/**
|
|
213
|
-
* Parsing of option property and constructing desiredCapabilities
|
|
214
|
-
* for Appium session. Could be application(1) or browser(2-3) session.
|
|
215
|
-
*/
|
|
216
|
-
if (/.*\.(apk|app|ipa)$/.test(arg.option)) {
|
|
217
|
-
return {
|
|
218
|
-
capabilities: {
|
|
219
|
-
app: arg.option,
|
|
220
|
-
...(arg.option.endsWith('apk') ? constants_1.ANDROID_CONFIG : constants_1.IOS_CONFIG),
|
|
221
|
-
...optionalCapabilites,
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
else if (/android/.test(arg.option)) {
|
|
226
|
-
return { capabilities: { browserName: 'Chrome', ...constants_1.ANDROID_CONFIG, ...optionalCapabilites } };
|
|
227
|
-
}
|
|
228
|
-
else if (/ios/.test(arg.option)) {
|
|
229
|
-
return { capabilities: { browserName: 'Safari', ...constants_1.IOS_CONFIG, ...optionalCapabilites } };
|
|
230
|
-
}
|
|
231
|
-
return { capabilities: { browserName: arg.option } };
|
|
232
|
-
}
|
|
233
|
-
exports.getCapabilities = getCapabilities;
|
|
234
|
-
/**
|
|
235
|
-
* Check if file exists in current work directory
|
|
236
|
-
* @param {string} filename to check existance for
|
|
237
|
-
*/
|
|
238
|
-
function hasFile(filename) {
|
|
239
|
-
return fs_extra_1.default.existsSync(path_1.default.join(process.cwd(), filename));
|
|
240
|
-
}
|
|
241
|
-
exports.hasFile = hasFile;
|
|
242
|
-
/**
|
|
243
|
-
* Check if package is installed
|
|
244
|
-
* @param {string} package to check existance for
|
|
245
|
-
*/
|
|
246
|
-
function hasPackage(pkg) {
|
|
247
|
-
try {
|
|
248
|
-
/**
|
|
249
|
-
* this is only for testing purposes as we want to check whether
|
|
250
|
-
* we add `@babel/register` to the packages to install when resolving fails
|
|
251
|
-
*/
|
|
252
|
-
if (process.env.JEST_WORKER_ID && process.env.WDIO_TEST_THROW_RESOLVE) {
|
|
253
|
-
throw new Error('resolve error');
|
|
254
|
-
}
|
|
255
|
-
require.resolve(pkg);
|
|
256
|
-
return true;
|
|
257
|
-
}
|
|
258
|
-
catch (err) {
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
exports.hasPackage = hasPackage;
|
|
263
|
-
/**
|
|
264
|
-
* generate test files based on CLI answers
|
|
265
|
-
*/
|
|
266
|
-
async function generateTestFiles(answers) {
|
|
267
|
-
const testFiles = answers.framework === 'cucumber'
|
|
268
|
-
? [path_1.default.join(TEMPLATE_ROOT_DIR, 'cucumber')]
|
|
269
|
-
: (answers.framework === 'mocha'
|
|
270
|
-
? [path_1.default.join(TEMPLATE_ROOT_DIR, 'mocha')]
|
|
271
|
-
: [path_1.default.join(TEMPLATE_ROOT_DIR, 'jasmine')]);
|
|
272
|
-
if (answers.usePageObjects) {
|
|
273
|
-
testFiles.push(path_1.default.join(TEMPLATE_ROOT_DIR, 'pageobjects'));
|
|
274
|
-
}
|
|
275
|
-
const files = (await Promise.all(testFiles.map((dirPath) => (0, recursive_readdir_1.default)(dirPath, [(file, stats) => !stats.isDirectory() && !(file.endsWith('.ejs') || file.endsWith('.feature'))])))).reduce((cur, acc) => [...acc, ...(cur)], []);
|
|
276
|
-
for (const file of files) {
|
|
277
|
-
const renderedTpl = await renderFile(file, answers);
|
|
278
|
-
let destPath = (file.endsWith('page.js.ejs')
|
|
279
|
-
? `${answers.destPageObjectRootPath}/${path_1.default.basename(file)}`
|
|
280
|
-
: file.includes('step_definition')
|
|
281
|
-
? `${answers.stepDefinitions}`
|
|
282
|
-
: `${answers.destSpecRootPath}/${path_1.default.basename(file)}`).replace(/\.ejs$/, '').replace(/\.js$/, answers.isUsingTypeScript ? '.ts' : '.js');
|
|
283
|
-
fs_extra_1.default.ensureDirSync(path_1.default.dirname(destPath));
|
|
284
|
-
await fs_extra_1.default.promises.writeFile(destPath, renderedTpl);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
exports.generateTestFiles = generateTestFiles;
|
|
288
|
-
async function getAnswers(yes) {
|
|
289
|
-
return yes
|
|
290
|
-
? constants_1.QUESTIONNAIRE.reduce((answers, question) => Object.assign(answers, question.when && !question.when(answers)
|
|
291
|
-
/**
|
|
292
|
-
* set nothing if question doesn't apply
|
|
293
|
-
*/
|
|
294
|
-
? {}
|
|
295
|
-
: { [question.name]: typeof question.default !== 'undefined'
|
|
296
|
-
/**
|
|
297
|
-
* set default value if existing
|
|
298
|
-
*/
|
|
299
|
-
? typeof question.default === 'function'
|
|
300
|
-
? question.default(answers)
|
|
301
|
-
: question.default
|
|
302
|
-
: question.choices && question.choices.length
|
|
303
|
-
/**
|
|
304
|
-
* pick first choice, select value if it exists
|
|
305
|
-
*/
|
|
306
|
-
? question.choices[0].value
|
|
307
|
-
? question.choices[0].value
|
|
308
|
-
: question.choices[0]
|
|
309
|
-
: {}
|
|
310
|
-
}), {})
|
|
311
|
-
: await inquirer_1.default.prompt(constants_1.QUESTIONNAIRE);
|
|
312
|
-
}
|
|
313
|
-
exports.getAnswers = getAnswers;
|
|
314
|
-
function getPathForFileGeneration(answers) {
|
|
315
|
-
const destSpecRootPath = path_1.default.join(process.cwd(), path_1.default.dirname(answers.specs || '').replace(/\*\*$/, ''));
|
|
316
|
-
const destStepRootPath = path_1.default.join(process.cwd(), path_1.default.dirname(answers.stepDefinitions || ''));
|
|
317
|
-
const destPageObjectRootPath = answers.usePageObjects
|
|
318
|
-
? path_1.default.join(process.cwd(), path_1.default.dirname(answers.pages || '').replace(/\*\*$/, ''))
|
|
319
|
-
: '';
|
|
320
|
-
let relativePath = (answers.generateTestFiles && answers.usePageObjects)
|
|
321
|
-
? !(convertPackageHashToObject(answers.framework).short === 'cucumber')
|
|
322
|
-
? path_1.default.relative(destSpecRootPath, destPageObjectRootPath)
|
|
323
|
-
: path_1.default.relative(destStepRootPath, destPageObjectRootPath)
|
|
324
|
-
: '';
|
|
325
|
-
/**
|
|
326
|
-
* On Windows, path.relative can return backslashes that could be interpreted as espace sequences in strings
|
|
327
|
-
*/
|
|
328
|
-
if (process.platform === 'win32') {
|
|
329
|
-
relativePath = relativePath.replace(/\\/g, '/');
|
|
330
|
-
}
|
|
331
|
-
return {
|
|
332
|
-
destSpecRootPath: destSpecRootPath,
|
|
333
|
-
destStepRootPath: destStepRootPath,
|
|
334
|
-
destPageObjectRootPath: destPageObjectRootPath,
|
|
335
|
-
relativePath: relativePath
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
exports.getPathForFileGeneration = getPathForFileGeneration;
|
|
339
|
-
function getDefaultFiles(answers, filePath) {
|
|
340
|
-
var _a;
|
|
341
|
-
return ((_a = answers === null || answers === void 0 ? void 0 : answers.isUsingCompiler) === null || _a === void 0 ? void 0 : _a.toString().includes('TypeScript'))
|
|
342
|
-
? `${filePath}.ts`
|
|
343
|
-
: `${filePath}.js`;
|
|
344
|
-
}
|
|
345
|
-
exports.getDefaultFiles = getDefaultFiles;
|
package/build/watcher.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { Workers } from '@wdio/types';
|
|
2
|
-
import { RunCommandArguments, ValueKeyIteratee } from './types.js';
|
|
3
|
-
declare type Spec = string | string[];
|
|
4
|
-
export default class Watcher {
|
|
5
|
-
private _configFile;
|
|
6
|
-
private _args;
|
|
7
|
-
private _launcher;
|
|
8
|
-
private _specs;
|
|
9
|
-
constructor(_configFile: string, _args: Omit<RunCommandArguments, 'configPath'>);
|
|
10
|
-
watch(): Promise<void>;
|
|
11
|
-
/**
|
|
12
|
-
* return file listener callback that calls `run` method
|
|
13
|
-
* @param {Boolean} [passOnFile=true] if true pass on file change as parameter
|
|
14
|
-
* @return {Function} chokidar event callback
|
|
15
|
-
*/
|
|
16
|
-
getFileListener(passOnFile?: boolean): (spec: string) => void[];
|
|
17
|
-
/**
|
|
18
|
-
* helper method to get workers from worker pool of wdio runner
|
|
19
|
-
* @param predicate filter by property value (see lodash.pickBy)
|
|
20
|
-
* @param includeBusyWorker don't filter out busy worker (default: false)
|
|
21
|
-
* @return Object with workers, e.g. {'0-0': { ... }}
|
|
22
|
-
*/
|
|
23
|
-
getWorkers(predicate?: ValueKeyIteratee<Workers.Worker> | null | undefined, includeBusyWorker?: boolean): Workers.WorkerPool;
|
|
24
|
-
/**
|
|
25
|
-
* run workers with params
|
|
26
|
-
* @param params parameters to run the worker with
|
|
27
|
-
*/
|
|
28
|
-
run(params?: Omit<Partial<RunCommandArguments>, 'spec'> & {
|
|
29
|
-
spec?: Spec;
|
|
30
|
-
}): void;
|
|
31
|
-
cleanUp(): void;
|
|
32
|
-
}
|
|
33
|
-
export {};
|
|
34
|
-
//# sourceMappingURL=watcher.d.ts.map
|
package/build/watcher.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAgB,OAAO,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAIlE,aAAK,IAAI,GAAG,MAAM,GAAG,MAAM,EAAE,CAAA;AAC7B,MAAM,CAAC,OAAO,OAAO,OAAO;IAKpB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,KAAK;IALjB,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,MAAM,CAAQ;gBAGV,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,YAAY,CAAC;IAYpD,KAAK;IAwCX;;;;OAIG;IACH,eAAe,CAAE,UAAU,UAAO,UAChB,MAAM;IA6BxB;;;;;OAKG;IACH,UAAU,CAAE,SAAS,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,SAAS,EAAE,iBAAiB,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU;IAiB7H;;;OAGG;IACH,GAAG,CAAE,MAAM,GAAE,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,GAAG;QAAE,IAAI,CAAC,EAAE,IAAI,CAAA;KAAO;IAuC9E,OAAO;CAGV"}
|
package/build/watcher.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const chokidar_1 = __importDefault(require("chokidar"));
|
|
7
|
-
const logger_1 = __importDefault(require("@wdio/logger"));
|
|
8
|
-
const lodash_pickby_1 = __importDefault(require("lodash.pickby"));
|
|
9
|
-
const lodash_flattendeep_1 = __importDefault(require("lodash.flattendeep"));
|
|
10
|
-
const lodash_union_1 = __importDefault(require("lodash.union"));
|
|
11
|
-
const launcher_1 = __importDefault(require("./launcher"));
|
|
12
|
-
const log = (0, logger_1.default)('@wdio/cli:watch');
|
|
13
|
-
class Watcher {
|
|
14
|
-
constructor(_configFile, _args) {
|
|
15
|
-
this._configFile = _configFile;
|
|
16
|
-
this._args = _args;
|
|
17
|
-
log.info('Starting launcher in watch mode');
|
|
18
|
-
this._launcher = new launcher_1.default(this._configFile, this._args, true);
|
|
19
|
-
const specs = this._launcher.configParser.getSpecs();
|
|
20
|
-
const capSpecs = this._launcher.isMultiremote ? [] : (0, lodash_union_1.default)((0, lodash_flattendeep_1.default)(this._launcher.configParser.getCapabilities().map(cap => cap.specs || [])));
|
|
21
|
-
this._specs = [...specs, ...capSpecs];
|
|
22
|
-
}
|
|
23
|
-
async watch() {
|
|
24
|
-
/**
|
|
25
|
-
* listen on spec changes and rerun specific spec file
|
|
26
|
-
*/
|
|
27
|
-
let flattenedSpecs = (0, lodash_flattendeep_1.default)(this._specs);
|
|
28
|
-
chokidar_1.default.watch(flattenedSpecs, { ignoreInitial: true })
|
|
29
|
-
.on('add', this.getFileListener())
|
|
30
|
-
.on('change', this.getFileListener());
|
|
31
|
-
/**
|
|
32
|
-
* listen on filesToWatch changes an rerun complete suite
|
|
33
|
-
*/
|
|
34
|
-
const { filesToWatch } = this._launcher.configParser.getConfig();
|
|
35
|
-
if (filesToWatch.length) {
|
|
36
|
-
chokidar_1.default.watch(filesToWatch, { ignoreInitial: true })
|
|
37
|
-
.on('add', this.getFileListener(false))
|
|
38
|
-
.on('change', this.getFileListener(false));
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* run initial test suite
|
|
42
|
-
*/
|
|
43
|
-
await this._launcher.run();
|
|
44
|
-
/**
|
|
45
|
-
* clean interface once all worker finish
|
|
46
|
-
*/
|
|
47
|
-
const workers = this.getWorkers();
|
|
48
|
-
Object.values(workers).forEach((worker) => worker.on('exit', () => {
|
|
49
|
-
/**
|
|
50
|
-
* check if all workers have finished
|
|
51
|
-
*/
|
|
52
|
-
if (Object.values(workers).find((w) => w.isBusy)) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
this._launcher.interface.finalise();
|
|
56
|
-
}));
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* return file listener callback that calls `run` method
|
|
60
|
-
* @param {Boolean} [passOnFile=true] if true pass on file change as parameter
|
|
61
|
-
* @return {Function} chokidar event callback
|
|
62
|
-
*/
|
|
63
|
-
getFileListener(passOnFile = true) {
|
|
64
|
-
return (spec) => {
|
|
65
|
-
const runSpecs = [];
|
|
66
|
-
let singleSpecFound = false;
|
|
67
|
-
for (let index = 0, length = this._specs.length; index < length; index += 1) {
|
|
68
|
-
const value = this._specs[index];
|
|
69
|
-
if (Array.isArray(value) && value.indexOf(spec) > -1) {
|
|
70
|
-
runSpecs.push(value);
|
|
71
|
-
}
|
|
72
|
-
else if (!singleSpecFound && spec === value) {
|
|
73
|
-
// Only need to run a singleFile once - so avoid duplicates
|
|
74
|
-
singleSpecFound = true;
|
|
75
|
-
runSpecs.push(value);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
// If the runSpecs array is empty, then this must be a new file/array
|
|
79
|
-
// so add the spec directly to the runSpecs
|
|
80
|
-
if (runSpecs.length === 0) {
|
|
81
|
-
runSpecs.push(spec);
|
|
82
|
-
}
|
|
83
|
-
// Do not pass the `spec` command line option to `this.run()`
|
|
84
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
85
|
-
const { spec: _specArg, ...args } = this._args;
|
|
86
|
-
return runSpecs.map((spec) => {
|
|
87
|
-
return this.run({ ...args, ...(passOnFile ? { spec } : {}) });
|
|
88
|
-
});
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* helper method to get workers from worker pool of wdio runner
|
|
93
|
-
* @param predicate filter by property value (see lodash.pickBy)
|
|
94
|
-
* @param includeBusyWorker don't filter out busy worker (default: false)
|
|
95
|
-
* @return Object with workers, e.g. {'0-0': { ... }}
|
|
96
|
-
*/
|
|
97
|
-
getWorkers(predicate, includeBusyWorker) {
|
|
98
|
-
let workers = this._launcher.runner.workerPool;
|
|
99
|
-
if (typeof predicate === 'function') {
|
|
100
|
-
workers = (0, lodash_pickby_1.default)(workers, predicate);
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* filter out busy workers, only skip if explicitly desired
|
|
104
|
-
*/
|
|
105
|
-
if (!includeBusyWorker) {
|
|
106
|
-
workers = (0, lodash_pickby_1.default)(workers, (worker) => !worker.isBusy);
|
|
107
|
-
}
|
|
108
|
-
return workers;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* run workers with params
|
|
112
|
-
* @param params parameters to run the worker with
|
|
113
|
-
*/
|
|
114
|
-
run(params = {}) {
|
|
115
|
-
const workers = this.getWorkers((params.spec ? (worker) => {
|
|
116
|
-
if (Array.isArray(params.spec)) {
|
|
117
|
-
return params.spec === worker.specs;
|
|
118
|
-
}
|
|
119
|
-
return worker.specs.includes(params.spec);
|
|
120
|
-
} : undefined));
|
|
121
|
-
/**
|
|
122
|
-
* don't do anything if no worker was found
|
|
123
|
-
*/
|
|
124
|
-
if (Object.keys(workers).length === 0) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* update total worker count interface
|
|
129
|
-
* ToDo: this should have a cleaner solution
|
|
130
|
-
*/
|
|
131
|
-
this._launcher.interface.totalWorkerCnt = Object.entries(workers).length;
|
|
132
|
-
/**
|
|
133
|
-
* clean up interface
|
|
134
|
-
*/
|
|
135
|
-
this.cleanUp();
|
|
136
|
-
/**
|
|
137
|
-
* trigger new run for non busy worker
|
|
138
|
-
*/
|
|
139
|
-
for (const [, worker] of Object.entries(workers)) {
|
|
140
|
-
const { cid, caps, specs, sessionId } = worker;
|
|
141
|
-
const args = Object.assign({ sessionId }, params);
|
|
142
|
-
worker.postMessage('run', args);
|
|
143
|
-
this._launcher.interface.emit('job:start', { cid, caps, specs });
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
cleanUp() {
|
|
147
|
-
this._launcher.interface.setup();
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
exports.default = Watcher;
|