@wdio/cli 9.0.0-alpha.9 → 9.0.1
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/build/commands/config.d.ts.map +1 -1
- package/build/commands/repl.d.ts.map +1 -1
- package/build/commands/run.d.ts +14 -13
- package/build/commands/run.d.ts.map +1 -1
- package/build/constants.d.ts +83 -10
- package/build/constants.d.ts.map +1 -1
- package/build/index.cjs +46 -0
- package/build/index.d.cts +2 -0
- package/build/index.d.cts.map +1 -0
- package/build/index.js +3233 -4
- package/build/interface.d.ts +0 -1
- package/build/interface.d.ts.map +1 -1
- package/build/launcher.d.ts.map +1 -1
- package/build/run.d.ts.map +1 -1
- package/build/types.d.ts +3 -4
- package/build/types.d.ts.map +1 -1
- package/build/utils.d.ts +7 -11
- package/build/utils.d.ts.map +1 -1
- package/build/watcher.d.ts.map +1 -1
- package/package.json +20 -25
- package/build/cjs/index.d.ts +0 -2
- package/build/cjs/index.d.ts.map +0 -1
- package/build/cjs/index.js +0 -26
- package/build/cjs/package.json +0 -5
- package/build/commands/config.js +0 -197
- package/build/commands/index.js +0 -5
- package/build/commands/install.js +0 -109
- package/build/commands/repl.js +0 -50
- package/build/commands/run.js +0 -262
- package/build/constants.js +0 -909
- package/build/install.js +0 -38
- package/build/interface.js +0 -285
- package/build/launcher.js +0 -513
- package/build/run.js +0 -75
- package/build/templates/EjsHelpers.js +0 -59
- package/build/templates/EjsHelpers.ts +0 -84
- package/build/templates/exampleFiles/browser/Component.css.ejs +0 -121
- package/build/templates/exampleFiles/browser/Component.lit.ejs +0 -154
- package/build/templates/exampleFiles/browser/Component.lit.test.ejs +0 -24
- package/build/templates/exampleFiles/browser/Component.preact.ejs +0 -28
- package/build/templates/exampleFiles/browser/Component.preact.test.ejs +0 -59
- package/build/templates/exampleFiles/browser/Component.react.ejs +0 -29
- package/build/templates/exampleFiles/browser/Component.react.test.ejs +0 -58
- package/build/templates/exampleFiles/browser/Component.solid.ejs +0 -28
- package/build/templates/exampleFiles/browser/Component.solid.test.ejs +0 -58
- package/build/templates/exampleFiles/browser/Component.stencil.ejs +0 -43
- package/build/templates/exampleFiles/browser/Component.stencil.test.ejs +0 -45
- package/build/templates/exampleFiles/browser/Component.svelte.ejs +0 -47
- package/build/templates/exampleFiles/browser/Component.svelte.test.ejs +0 -58
- package/build/templates/exampleFiles/browser/Component.vue.ejs +0 -34
- package/build/templates/exampleFiles/browser/Component.vue.test.ejs +0 -62
- package/build/templates/exampleFiles/browser/standalone.test.ejs +0 -13
- package/build/templates/exampleFiles/cucumber/features/login.feature +0 -12
- package/build/templates/exampleFiles/cucumber/step_definitions/steps.js.ejs +0 -55
- package/build/templates/exampleFiles/mochaJasmine/test.e2e.js.ejs +0 -11
- package/build/templates/exampleFiles/pageobjects/login.page.js.ejs +0 -45
- package/build/templates/exampleFiles/pageobjects/page.js.ejs +0 -17
- package/build/templates/exampleFiles/pageobjects/secure.page.js.ejs +0 -20
- package/build/templates/exampleFiles/serenity-js/common/config/serenity.properties.ejs +0 -1
- package/build/templates/exampleFiles/serenity-js/common/serenity/github-api/GitHubStatus.ts.ejs +0 -41
- package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoList.ts.ejs +0 -100
- package/build/templates/exampleFiles/serenity-js/common/serenity/todo-list-app/TodoListItem.ts.ejs +0 -36
- package/build/templates/exampleFiles/serenity-js/cucumber/step-definitions/steps.ts.ejs +0 -37
- package/build/templates/exampleFiles/serenity-js/cucumber/support/parameter.config.ts.ejs +0 -18
- package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/completing_items.feature.ejs +0 -23
- package/build/templates/exampleFiles/serenity-js/cucumber/todo-list/narrative.md.ejs +0 -17
- package/build/templates/exampleFiles/serenity-js/jasmine/example.spec.ts.ejs +0 -86
- package/build/templates/exampleFiles/serenity-js/mocha/example.spec.ts.ejs +0 -88
- package/build/templates/snippets/afterTest.ejs +0 -20
- package/build/templates/snippets/capabilities.ejs +0 -57
- package/build/templates/snippets/cucumber.ejs +0 -50
- package/build/templates/snippets/electronTest.js.ejs +0 -7
- package/build/templates/snippets/jasmine.ejs +0 -20
- package/build/templates/snippets/macosTest.js.ejs +0 -11
- package/build/templates/snippets/mocha.ejs +0 -14
- package/build/templates/snippets/reporters.ejs +0 -14
- package/build/templates/snippets/serenity.ejs +0 -18
- package/build/templates/snippets/services.ejs +0 -18
- package/build/templates/snippets/testWithPO.js.ejs +0 -22
- package/build/templates/snippets/testWithoutPO.js.ejs +0 -19
- package/build/templates/snippets/vscodeTest.js.ejs +0 -9
- package/build/templates/wdio.conf.tpl.ejs +0 -422
- package/build/types.js +0 -1
- package/build/utils.js +0 -930
- package/build/watcher.js +0 -156
- /package/{LICENSE-MIT → LICENSE} +0 -0
package/build/launcher.js
DELETED
|
@@ -1,513 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import exitHook from 'async-exit-hook';
|
|
4
|
-
import logger from '@wdio/logger';
|
|
5
|
-
import { validateConfig } from '@wdio/config';
|
|
6
|
-
import { ConfigParser } from '@wdio/config/node';
|
|
7
|
-
import { initializePlugin, initializeLauncherService, sleep } from '@wdio/utils';
|
|
8
|
-
import { setupDriver, setupBrowser } from '@wdio/utils/node';
|
|
9
|
-
import CLInterface from './interface.js';
|
|
10
|
-
import { runLauncherHook, runOnCompleteHook, runServiceHook } from './utils.js';
|
|
11
|
-
import { TESTRUNNER_DEFAULTS, WORKER_GROUPLOGS_MESSAGES } from './constants.js';
|
|
12
|
-
const log = logger('@wdio/cli:launcher');
|
|
13
|
-
class Launcher {
|
|
14
|
-
_configFilePath;
|
|
15
|
-
_args;
|
|
16
|
-
_isWatchMode;
|
|
17
|
-
configParser;
|
|
18
|
-
isMultiremote = false;
|
|
19
|
-
isParallelMultiremote = false;
|
|
20
|
-
runner;
|
|
21
|
-
interface;
|
|
22
|
-
_exitCode = 0;
|
|
23
|
-
_hasTriggeredExitRoutine = false;
|
|
24
|
-
_schedule = [];
|
|
25
|
-
_rid = [];
|
|
26
|
-
_runnerStarted = 0;
|
|
27
|
-
_runnerFailed = 0;
|
|
28
|
-
_launcher;
|
|
29
|
-
_resolve;
|
|
30
|
-
constructor(_configFilePath, _args = {}, _isWatchMode = false) {
|
|
31
|
-
this._configFilePath = _configFilePath;
|
|
32
|
-
this._args = _args;
|
|
33
|
-
this._isWatchMode = _isWatchMode;
|
|
34
|
-
this.configParser = new ConfigParser(this._configFilePath, this._args);
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* run sequence
|
|
38
|
-
* @return {Promise} that only gets resolved with either an exitCode or an error
|
|
39
|
-
*/
|
|
40
|
-
async run() {
|
|
41
|
-
await this.configParser.initialize(this._args);
|
|
42
|
-
const config = this.configParser.getConfig();
|
|
43
|
-
/**
|
|
44
|
-
* assign parsed autocompile options into args so it can be used within the worker
|
|
45
|
-
* without having to read the config again
|
|
46
|
-
*/
|
|
47
|
-
this._args.autoCompileOpts = config.autoCompileOpts;
|
|
48
|
-
const capabilities = this.configParser.getCapabilities();
|
|
49
|
-
this.isParallelMultiremote = Array.isArray(capabilities) &&
|
|
50
|
-
capabilities.every(cap => Object.values(cap).length > 0 && Object.values(cap).every(c => typeof c === 'object' && c.capabilities));
|
|
51
|
-
this.isMultiremote = this.isParallelMultiremote || !Array.isArray(capabilities);
|
|
52
|
-
validateConfig(TESTRUNNER_DEFAULTS, { ...config, capabilities });
|
|
53
|
-
if (config.outputDir) {
|
|
54
|
-
await fs.mkdir(path.join(config.outputDir), { recursive: true });
|
|
55
|
-
process.env.WDIO_LOG_PATH = path.join(config.outputDir, 'wdio.log');
|
|
56
|
-
}
|
|
57
|
-
logger.setLogLevelsConfig(config.logLevels, config.logLevel);
|
|
58
|
-
/**
|
|
59
|
-
* For Parallel-Multiremote, only get the specs and excludes from the first object
|
|
60
|
-
*/
|
|
61
|
-
const totalWorkerCnt = Array.isArray(capabilities)
|
|
62
|
-
? capabilities
|
|
63
|
-
.map((c) => {
|
|
64
|
-
if (this.isParallelMultiremote) {
|
|
65
|
-
const keys = Object.keys(c);
|
|
66
|
-
return this.configParser.getSpecs(c[keys[0]].capabilities.specs, c[keys[0]].capabilities.exclude).length;
|
|
67
|
-
}
|
|
68
|
-
return this.configParser.getSpecs(c.specs, c.exclude).length;
|
|
69
|
-
})
|
|
70
|
-
.reduce((a, b) => a + b, 0)
|
|
71
|
-
: 1;
|
|
72
|
-
this.interface = new CLInterface(config, totalWorkerCnt, this._isWatchMode);
|
|
73
|
-
config.runnerEnv.FORCE_COLOR = Number(this.interface.hasAnsiSupport);
|
|
74
|
-
const [runnerName, runnerOptions] = Array.isArray(config.runner) ? config.runner : [config.runner, {}];
|
|
75
|
-
const Runner = (await initializePlugin(runnerName, 'runner')).default;
|
|
76
|
-
this.runner = new Runner(runnerOptions, config);
|
|
77
|
-
/**
|
|
78
|
-
* catches ctrl+c event
|
|
79
|
-
*/
|
|
80
|
-
exitHook(this._exitHandler.bind(this));
|
|
81
|
-
let exitCode = 0;
|
|
82
|
-
let error = undefined;
|
|
83
|
-
try {
|
|
84
|
-
const caps = this.configParser.getCapabilities();
|
|
85
|
-
const { ignoredWorkerServices, launcherServices } = await initializeLauncherService(config, caps);
|
|
86
|
-
this._launcher = launcherServices;
|
|
87
|
-
this._args.ignoredWorkerServices = ignoredWorkerServices;
|
|
88
|
-
/**
|
|
89
|
-
* run pre test tasks for runner plugins
|
|
90
|
-
* (e.g. deploy Lambda function to AWS)
|
|
91
|
-
*/
|
|
92
|
-
await this.runner.initialize();
|
|
93
|
-
/**
|
|
94
|
-
* run onPrepare hook
|
|
95
|
-
*/
|
|
96
|
-
log.info('Run onPrepare hook');
|
|
97
|
-
await runLauncherHook(config.onPrepare, config, caps);
|
|
98
|
-
await runServiceHook(this._launcher, 'onPrepare', config, caps);
|
|
99
|
-
/**
|
|
100
|
-
* pre-configure necessary driver for worker threads
|
|
101
|
-
*/
|
|
102
|
-
await Promise.all([
|
|
103
|
-
setupDriver(config, caps),
|
|
104
|
-
setupBrowser(config, caps)
|
|
105
|
-
]);
|
|
106
|
-
exitCode = await this._runMode(config, caps);
|
|
107
|
-
/**
|
|
108
|
-
* run onComplete hook
|
|
109
|
-
* Even if it fails we still want to see result and end logger stream.
|
|
110
|
-
* Also ensure that user hooks are run before service hooks so that e.g.
|
|
111
|
-
* a user can use plugin service, e.g. shared store service is still
|
|
112
|
-
* available running hooks in this order
|
|
113
|
-
*/
|
|
114
|
-
log.info('Run onComplete hook');
|
|
115
|
-
const onCompleteResults = await runOnCompleteHook(config.onComplete, config, caps, exitCode, this.interface.result);
|
|
116
|
-
await runServiceHook(this._launcher, 'onComplete', exitCode, config, caps);
|
|
117
|
-
// if any of the onComplete hooks failed, update the exit code
|
|
118
|
-
exitCode = onCompleteResults.includes(1) ? 1 : exitCode;
|
|
119
|
-
await logger.waitForBuffer();
|
|
120
|
-
this.interface.finalise();
|
|
121
|
-
}
|
|
122
|
-
catch (err) {
|
|
123
|
-
error = err;
|
|
124
|
-
}
|
|
125
|
-
finally {
|
|
126
|
-
if (!this._hasTriggeredExitRoutine) {
|
|
127
|
-
this._hasTriggeredExitRoutine = true;
|
|
128
|
-
const passesCodeCoverage = await this.runner.shutdown();
|
|
129
|
-
if (!passesCodeCoverage) {
|
|
130
|
-
exitCode = exitCode || 1;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
if (error) {
|
|
135
|
-
this.interface.logHookError(error);
|
|
136
|
-
throw error;
|
|
137
|
-
}
|
|
138
|
-
return exitCode;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* run without triggering onPrepare/onComplete hooks
|
|
142
|
-
*/
|
|
143
|
-
_runMode(config, caps) {
|
|
144
|
-
/**
|
|
145
|
-
* fail if no caps were found
|
|
146
|
-
*/
|
|
147
|
-
if (!caps) {
|
|
148
|
-
return new Promise((resolve) => {
|
|
149
|
-
log.error('Missing capabilities, exiting with failure');
|
|
150
|
-
return resolve(1);
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* avoid retries in watch mode
|
|
155
|
-
*/
|
|
156
|
-
const specFileRetries = this._isWatchMode ? 0 : config.specFileRetries;
|
|
157
|
-
/**
|
|
158
|
-
* schedule test runs
|
|
159
|
-
*/
|
|
160
|
-
let cid = 0;
|
|
161
|
-
if (this.isMultiremote && !this.isParallelMultiremote) {
|
|
162
|
-
/**
|
|
163
|
-
* Multiremote mode
|
|
164
|
-
*/
|
|
165
|
-
this._schedule.push({
|
|
166
|
-
cid: cid++,
|
|
167
|
-
caps: caps,
|
|
168
|
-
specs: this._formatSpecs(caps, specFileRetries),
|
|
169
|
-
availableInstances: config.maxInstances || 1,
|
|
170
|
-
runningInstances: 0
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
/**
|
|
175
|
-
* Regular mode & Parallel Multiremote
|
|
176
|
-
*/
|
|
177
|
-
for (const capabilities of caps) {
|
|
178
|
-
/**
|
|
179
|
-
* when using browser runner we only allow one session per browser
|
|
180
|
-
*/
|
|
181
|
-
const availableInstances = this.isParallelMultiremote ? config.maxInstances || 1 : config.runner === 'browser'
|
|
182
|
-
? 1
|
|
183
|
-
: capabilities.maxInstances || capabilities['wdio:maxInstances'] || config.maxInstancesPerCapability;
|
|
184
|
-
this._schedule.push({
|
|
185
|
-
cid: cid++,
|
|
186
|
-
caps: capabilities,
|
|
187
|
-
specs: this._formatSpecs(capabilities, specFileRetries),
|
|
188
|
-
availableInstances,
|
|
189
|
-
runningInstances: 0
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return new Promise((resolve) => {
|
|
194
|
-
this._resolve = resolve;
|
|
195
|
-
/**
|
|
196
|
-
* fail if no specs were found or specified
|
|
197
|
-
*/
|
|
198
|
-
if (Object.values(this._schedule).reduce((specCnt, schedule) => specCnt + schedule.specs.length, 0) === 0) {
|
|
199
|
-
const { total, current } = config.shard;
|
|
200
|
-
if (total > 1) {
|
|
201
|
-
log.info(`No specs to execute in shard ${current}/${total}, exiting!`);
|
|
202
|
-
return resolve(0);
|
|
203
|
-
}
|
|
204
|
-
log.error('No specs found to run, exiting with failure');
|
|
205
|
-
return resolve(1);
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* return immediately if no spec was run
|
|
209
|
-
*/
|
|
210
|
-
if (this._runSpecs()) {
|
|
211
|
-
resolve(0);
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Format the specs into an array of objects with files and retries
|
|
217
|
-
*/
|
|
218
|
-
_formatSpecs(capabilities, specFileRetries) {
|
|
219
|
-
let caps;
|
|
220
|
-
if ('alwaysMatch' in capabilities) {
|
|
221
|
-
caps = capabilities.alwaysMatch;
|
|
222
|
-
}
|
|
223
|
-
else if (typeof Object.keys(capabilities)[0] === 'object' && 'capabilities' in capabilities[Object.keys(capabilities)[0]]) {
|
|
224
|
-
caps = {};
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
caps = capabilities;
|
|
228
|
-
}
|
|
229
|
-
const specs = caps.specs || caps['wdio:specs'];
|
|
230
|
-
const excludes = caps.exclude || caps['wdio:exclude'];
|
|
231
|
-
const files = this.configParser.getSpecs(specs, excludes);
|
|
232
|
-
return files.map((file) => {
|
|
233
|
-
if (typeof file === 'string') {
|
|
234
|
-
return { files: [file], retries: specFileRetries };
|
|
235
|
-
}
|
|
236
|
-
else if (Array.isArray(file)) {
|
|
237
|
-
return { files: file, retries: specFileRetries };
|
|
238
|
-
}
|
|
239
|
-
log.warn('Unexpected entry in specs that is neither string nor array: ', file);
|
|
240
|
-
// Returning an empty structure to avoid undefined
|
|
241
|
-
return { files: [], retries: specFileRetries };
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* run multiple single remote tests
|
|
246
|
-
* @return {Boolean} true if all specs have been run and all instances have finished
|
|
247
|
-
*/
|
|
248
|
-
_runSpecs() {
|
|
249
|
-
/**
|
|
250
|
-
* stop spawning new processes when CTRL+C was triggered
|
|
251
|
-
*/
|
|
252
|
-
if (this._hasTriggeredExitRoutine) {
|
|
253
|
-
return true;
|
|
254
|
-
}
|
|
255
|
-
const config = this.configParser.getConfig();
|
|
256
|
-
while (this._getNumberOfRunningInstances() < config.maxInstances) {
|
|
257
|
-
const schedulableCaps = this._schedule
|
|
258
|
-
/**
|
|
259
|
-
* bail if number of errors exceeds allowed
|
|
260
|
-
*/
|
|
261
|
-
.filter(() => {
|
|
262
|
-
const filter = typeof config.bail !== 'number' || config.bail < 1 ||
|
|
263
|
-
config.bail > this._runnerFailed;
|
|
264
|
-
/**
|
|
265
|
-
* clear number of specs when filter is false
|
|
266
|
-
*/
|
|
267
|
-
if (!filter) {
|
|
268
|
-
this._schedule.forEach((t) => { t.specs = []; });
|
|
269
|
-
}
|
|
270
|
-
return filter;
|
|
271
|
-
})
|
|
272
|
-
/**
|
|
273
|
-
* make sure complete number of running instances is not higher than general maxInstances number
|
|
274
|
-
*/
|
|
275
|
-
.filter(() => this._getNumberOfRunningInstances() < config.maxInstances)
|
|
276
|
-
/**
|
|
277
|
-
* make sure the capability has available capacities
|
|
278
|
-
*/
|
|
279
|
-
.filter((a) => a.availableInstances > 0)
|
|
280
|
-
/**
|
|
281
|
-
* make sure capability has still caps to run
|
|
282
|
-
*/
|
|
283
|
-
.filter((a) => a.specs.length > 0)
|
|
284
|
-
/**
|
|
285
|
-
* make sure we are running caps with less running instances first
|
|
286
|
-
*/
|
|
287
|
-
.sort((a, b) => a.runningInstances - b.runningInstances);
|
|
288
|
-
/**
|
|
289
|
-
* continue if no capability were schedulable
|
|
290
|
-
*/
|
|
291
|
-
if (schedulableCaps.length === 0) {
|
|
292
|
-
break;
|
|
293
|
-
}
|
|
294
|
-
const specs = schedulableCaps[0].specs.shift();
|
|
295
|
-
this._startInstance(specs.files, schedulableCaps[0].caps, schedulableCaps[0].cid, specs.rid, specs.retries);
|
|
296
|
-
schedulableCaps[0].availableInstances--;
|
|
297
|
-
schedulableCaps[0].runningInstances++;
|
|
298
|
-
}
|
|
299
|
-
return this._getNumberOfRunningInstances() === 0 && this._getNumberOfSpecsLeft() === 0;
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* gets number of all running instances
|
|
303
|
-
* @return {number} number of running instances
|
|
304
|
-
*/
|
|
305
|
-
_getNumberOfRunningInstances() {
|
|
306
|
-
return this._schedule.map((a) => a.runningInstances).reduce((a, b) => a + b);
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* get number of total specs left to complete whole suites
|
|
310
|
-
* @return {number} specs left to complete suite
|
|
311
|
-
*/
|
|
312
|
-
_getNumberOfSpecsLeft() {
|
|
313
|
-
return this._schedule.map((a) => a.specs.length).reduce((a, b) => a + b);
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Start instance in a child process.
|
|
317
|
-
* @param {Array} specs Specs to run
|
|
318
|
-
* @param {number} cid Capabilities ID
|
|
319
|
-
* @param {string} rid Runner ID override
|
|
320
|
-
* @param {number} retries Number of retries remaining
|
|
321
|
-
*/
|
|
322
|
-
async _startInstance(specs, caps, cid, rid, retries) {
|
|
323
|
-
if (!this.runner || !this.interface) {
|
|
324
|
-
throw new Error('Internal Error: no runner initialized, call run() first');
|
|
325
|
-
}
|
|
326
|
-
const config = this.configParser.getConfig();
|
|
327
|
-
// wait before retrying the spec file
|
|
328
|
-
if (typeof config.specFileRetriesDelay === 'number' && config.specFileRetries > 0 && config.specFileRetries !== retries) {
|
|
329
|
-
await sleep(config.specFileRetriesDelay * 1000);
|
|
330
|
-
}
|
|
331
|
-
// Retried tests receive the cid of the failing test as rid
|
|
332
|
-
// so they can run with the same cid of the failing test.
|
|
333
|
-
const runnerId = rid || this._getRunnerId(cid);
|
|
334
|
-
const processNumber = this._runnerStarted + 1;
|
|
335
|
-
// process.debugPort defaults to 5858 and is set even when process
|
|
336
|
-
// is not being debugged.
|
|
337
|
-
const debugArgs = [];
|
|
338
|
-
let debugType;
|
|
339
|
-
let debugHost = '';
|
|
340
|
-
const debugPort = process.debugPort;
|
|
341
|
-
for (const i in process.execArgv) {
|
|
342
|
-
const debugArgs = process.execArgv[i].match('--(debug|inspect)(?:-brk)?(?:=(.*):)?');
|
|
343
|
-
if (debugArgs) {
|
|
344
|
-
const [, type, host] = debugArgs;
|
|
345
|
-
if (type) {
|
|
346
|
-
debugType = type;
|
|
347
|
-
}
|
|
348
|
-
if (host) {
|
|
349
|
-
debugHost = `${host}:`;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (debugType) {
|
|
354
|
-
debugArgs.push(`--${debugType}=${debugHost}${(debugPort + processNumber)}`);
|
|
355
|
-
}
|
|
356
|
-
// if you would like to add --debug-brk, use a different port, etc...
|
|
357
|
-
const capExecArgs = [...(config.execArgv || [])];
|
|
358
|
-
// The default value for child.fork execArgs is process.execArgs,
|
|
359
|
-
// so continue to use this unless another value is specified in config.
|
|
360
|
-
const defaultArgs = (capExecArgs.length) ? process.execArgv : [];
|
|
361
|
-
// If an arg appears multiple times the last occurrence is used
|
|
362
|
-
const execArgv = [...defaultArgs, ...debugArgs, ...capExecArgs];
|
|
363
|
-
// bump up worker count
|
|
364
|
-
this._runnerStarted++;
|
|
365
|
-
// run worker hook to allow modify runtime and capabilities of a specific worker
|
|
366
|
-
log.info('Run onWorkerStart hook');
|
|
367
|
-
await runLauncherHook(config.onWorkerStart, runnerId, caps, specs, this._args, execArgv)
|
|
368
|
-
.catch((error) => this._workerHookError(error));
|
|
369
|
-
await runServiceHook(this._launcher, 'onWorkerStart', runnerId, caps, specs, this._args, execArgv)
|
|
370
|
-
.catch((error) => this._workerHookError(error));
|
|
371
|
-
// prefer launcher settings in capabilities over general launcher
|
|
372
|
-
const worker = await this.runner.run({
|
|
373
|
-
cid: runnerId,
|
|
374
|
-
command: 'run',
|
|
375
|
-
configFile: this._configFilePath,
|
|
376
|
-
args: {
|
|
377
|
-
...this._args,
|
|
378
|
-
...(config?.autoCompileOpts
|
|
379
|
-
? { autoCompileOpts: config.autoCompileOpts }
|
|
380
|
-
: {}),
|
|
381
|
-
/**
|
|
382
|
-
* Pass on user and key values to ensure they are available in the worker process when using
|
|
383
|
-
* environment variables that were locally exported but not part of the environment.
|
|
384
|
-
*/
|
|
385
|
-
user: config.user,
|
|
386
|
-
key: config.key
|
|
387
|
-
},
|
|
388
|
-
caps,
|
|
389
|
-
specs,
|
|
390
|
-
execArgv,
|
|
391
|
-
retries
|
|
392
|
-
});
|
|
393
|
-
worker.on('message', this.interface.onMessage.bind(this.interface));
|
|
394
|
-
worker.on('error', this.interface.onMessage.bind(this.interface));
|
|
395
|
-
worker.on('exit', (code) => {
|
|
396
|
-
if (!this.configParser.getConfig().groupLogsByTestSpec) {
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
if (code.exitCode === 0) {
|
|
400
|
-
console.log(WORKER_GROUPLOGS_MESSAGES.normalExit(code.cid));
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
console.log(WORKER_GROUPLOGS_MESSAGES.exitWithError(code.cid));
|
|
404
|
-
}
|
|
405
|
-
worker.logsAggregator.forEach((logLine) => {
|
|
406
|
-
console.log(logLine.replace(new RegExp('\\n$'), ''));
|
|
407
|
-
});
|
|
408
|
-
});
|
|
409
|
-
worker.on('exit', this._endHandler.bind(this));
|
|
410
|
-
}
|
|
411
|
-
_workerHookError(error) {
|
|
412
|
-
if (!this.interface) {
|
|
413
|
-
throw new Error('Internal Error: no interface initialized, call run() first');
|
|
414
|
-
}
|
|
415
|
-
this.interface.logHookError(error);
|
|
416
|
-
if (this._resolve) {
|
|
417
|
-
this._resolve(1);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* generates a runner id
|
|
422
|
-
* @param {number} cid capability id (unique identifier for a capability)
|
|
423
|
-
* @return {String} runner id (combination of cid and test id e.g. 0a, 0b, 1a, 1b ...)
|
|
424
|
-
*/
|
|
425
|
-
_getRunnerId(cid) {
|
|
426
|
-
if (!this._rid[cid]) {
|
|
427
|
-
this._rid[cid] = 0;
|
|
428
|
-
}
|
|
429
|
-
return `${cid}-${this._rid[cid]++}`;
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Close test runner process once all child processes have exited
|
|
433
|
-
* @param {number} cid Capabilities ID
|
|
434
|
-
* @param {number} exitCode exit code of child process
|
|
435
|
-
* @param {Array} specs Specs that were run
|
|
436
|
-
* @param {number} retries Number or retries remaining
|
|
437
|
-
*/
|
|
438
|
-
async _endHandler({ cid: rid, exitCode, specs, retries }) {
|
|
439
|
-
const passed = this._isWatchModeHalted() || exitCode === 0;
|
|
440
|
-
if (!passed && retries > 0) {
|
|
441
|
-
// Default is true, so test for false explicitly
|
|
442
|
-
const requeue = this.configParser.getConfig().specFileRetriesDeferred !== false ? 'push' : 'unshift';
|
|
443
|
-
this._schedule[parseInt(rid, 10)].specs[requeue]({ files: specs, retries: retries - 1, rid });
|
|
444
|
-
}
|
|
445
|
-
else {
|
|
446
|
-
this._exitCode = this._isWatchModeHalted() ? 0 : this._exitCode || exitCode;
|
|
447
|
-
this._runnerFailed += !passed ? 1 : 0;
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* avoid emitting job:end if watch mode has been stopped by user
|
|
451
|
-
*/
|
|
452
|
-
if (!this._isWatchModeHalted() && this.interface) {
|
|
453
|
-
this.interface.emit('job:end', { cid: rid, passed, retries });
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* Update schedule now this process has ended
|
|
457
|
-
* get cid (capability id) from rid (runner id)
|
|
458
|
-
*/
|
|
459
|
-
const cid = parseInt(rid, 10);
|
|
460
|
-
this._schedule[cid].availableInstances++;
|
|
461
|
-
this._schedule[cid].runningInstances--;
|
|
462
|
-
log.info('Run onWorkerEnd hook');
|
|
463
|
-
const config = this.configParser.getConfig();
|
|
464
|
-
await runLauncherHook(config.onWorkerEnd, rid, exitCode, specs, retries)
|
|
465
|
-
.catch((error) => this._workerHookError(error));
|
|
466
|
-
await runServiceHook(this._launcher, 'onWorkerEnd', rid, exitCode, specs, retries)
|
|
467
|
-
.catch((error) => this._workerHookError(error));
|
|
468
|
-
/**
|
|
469
|
-
* do nothing if
|
|
470
|
-
* - there are specs to be executed
|
|
471
|
-
* - we are running watch mode
|
|
472
|
-
*/
|
|
473
|
-
const shouldRunSpecs = this._runSpecs();
|
|
474
|
-
const inWatchMode = this._isWatchMode && !this._hasTriggeredExitRoutine;
|
|
475
|
-
if (!shouldRunSpecs || inWatchMode) {
|
|
476
|
-
/**
|
|
477
|
-
* print reporter results when in watch mode
|
|
478
|
-
*/
|
|
479
|
-
if (inWatchMode) {
|
|
480
|
-
this.interface?.finalise();
|
|
481
|
-
}
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
if (this._resolve) {
|
|
485
|
-
this._resolve(passed ? this._exitCode : 1);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* We need exitHandler to catch SIGINT / SIGTERM events.
|
|
490
|
-
* Make sure all started selenium sessions get closed properly and prevent
|
|
491
|
-
* having dead driver processes. To do so let the runner end its Selenium
|
|
492
|
-
* session first before killing
|
|
493
|
-
*/
|
|
494
|
-
_exitHandler(callback) {
|
|
495
|
-
if (!callback || !this.runner || !this.interface) {
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
if (this._hasTriggeredExitRoutine) {
|
|
499
|
-
return callback(true);
|
|
500
|
-
}
|
|
501
|
-
this._hasTriggeredExitRoutine = true;
|
|
502
|
-
this.interface.sigintTrigger();
|
|
503
|
-
return this.runner.shutdown().then(callback);
|
|
504
|
-
}
|
|
505
|
-
/**
|
|
506
|
-
* returns true if user stopped watch mode, ex with ctrl+c
|
|
507
|
-
* @returns {boolean}
|
|
508
|
-
*/
|
|
509
|
-
_isWatchModeHalted() {
|
|
510
|
-
return this._isWatchMode && this._hasTriggeredExitRoutine;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
export default Launcher;
|
package/build/run.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import url from 'node:url';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import yargs from 'yargs';
|
|
5
|
-
import { hideBin } from 'yargs/helpers';
|
|
6
|
-
import { commands } from './commands/index.js';
|
|
7
|
-
import { handler, cmdArgs } from './commands/run.js';
|
|
8
|
-
import { CLI_EPILOGUE, pkg } from './constants.js';
|
|
9
|
-
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
10
|
-
const DEFAULT_CONFIG_FILENAME = 'wdio.conf.js';
|
|
11
|
-
const DESCRIPTION = [
|
|
12
|
-
'The `wdio` command allows you run and manage your WebdriverIO test suite.',
|
|
13
|
-
'If no command is provided it calls the `run` command by default, so:',
|
|
14
|
-
'',
|
|
15
|
-
'$ wdio wdio.conf.js',
|
|
16
|
-
'',
|
|
17
|
-
'is the same as:',
|
|
18
|
-
'$ wdio run wdio.conf.js',
|
|
19
|
-
'',
|
|
20
|
-
'For more information, visit: https://webdriver.io/docs/clioptions'
|
|
21
|
-
];
|
|
22
|
-
export default async function run() {
|
|
23
|
-
const commandDir = path.join(__dirname, 'commands');
|
|
24
|
-
const argv = yargs(hideBin(process.argv))
|
|
25
|
-
.command(commands)
|
|
26
|
-
.example('wdio run wdio.conf.js --suite foobar', 'Run suite on testsuite "foobar"')
|
|
27
|
-
.example('wdio run wdio.conf.js --spec ./tests/e2e/a.js --spec ./tests/e2e/b.js', 'Run suite on specific specs')
|
|
28
|
-
.example('wdio run wdio.conf.js --spec ./tests/e2e/a.feature:5', 'Run scenario by line number')
|
|
29
|
-
.example('wdio run wdio.conf.js --spec ./tests/e2e/a.feature:5:10', 'Run scenarios by line number')
|
|
30
|
-
.example('wdio run wdio.conf.js --spec ./tests/e2e/a.feature:5:10 --spec ./test/e2e/b.feature', 'Run scenarios by line number in single feature and another complete feature')
|
|
31
|
-
.example('wdio install reporter spec', 'Install @wdio/spec-reporter')
|
|
32
|
-
.example('wdio repl chrome -u <SAUCE_USERNAME> -k <SAUCE_ACCESS_KEY>', 'Run repl in Sauce Labs cloud')
|
|
33
|
-
.updateStrings({ 'Commands:': `${DESCRIPTION.join('\n')}\n\nCommands:` })
|
|
34
|
-
.version(pkg.version)
|
|
35
|
-
.epilogue(CLI_EPILOGUE);
|
|
36
|
-
/**
|
|
37
|
-
* parse CLI arguments according to what run expects, without this adding
|
|
38
|
-
* `--spec ./test.js` results in propagating the spec parameter as a
|
|
39
|
-
* string while in reality is should be parsed into a array of strings
|
|
40
|
-
*/
|
|
41
|
-
if (!process.argv.find((arg) => arg === '--help')) {
|
|
42
|
-
argv.options(cmdArgs);
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* The only way we reach this point is if the user runs the binary without a command (i.e. wdio wdio.conf.js)
|
|
46
|
-
* We can safely assume that if this is the case, the user is trying to execute the `run` command as it
|
|
47
|
-
* was previous to https://github.com/webdriverio/webdriverio/pull/4402
|
|
48
|
-
*
|
|
49
|
-
* Since the `run` command verifies if the configuration file exists before executing
|
|
50
|
-
* we don't have to check that again here.
|
|
51
|
-
*/
|
|
52
|
-
const params = await argv.parse();
|
|
53
|
-
const supportedCommands = fs
|
|
54
|
-
.readdirSync(commandDir)
|
|
55
|
-
.map((file) => file.slice(0, -3));
|
|
56
|
-
if (!params._ || params._.find((param) => supportedCommands.includes(param))) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
const args = {
|
|
60
|
-
...params,
|
|
61
|
-
configPath: path.resolve(process.cwd(), params._[0] && params._[0].toString() || DEFAULT_CONFIG_FILENAME)
|
|
62
|
-
};
|
|
63
|
-
try {
|
|
64
|
-
const cp = await handler(args);
|
|
65
|
-
return cp;
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
const output = await new Promise((resolve) => (yargs(hideBin(process.argv)).parse('--help', (err, argv, output) => resolve(output))));
|
|
69
|
-
console.error(`${output}\n\n${err.stack}`);
|
|
70
|
-
/* istanbul ignore if */
|
|
71
|
-
if (!process.env.VITEST_WORKER_ID) {
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
export class EjsHelpers {
|
|
2
|
-
useTypeScript;
|
|
3
|
-
useEsm;
|
|
4
|
-
constructor(config) {
|
|
5
|
-
this.useTypeScript = config.useTypeScript ?? false;
|
|
6
|
-
this.useEsm = config.useEsm ?? false;
|
|
7
|
-
}
|
|
8
|
-
if(condition, trueValue, falseValue = '') {
|
|
9
|
-
return condition
|
|
10
|
-
? trueValue
|
|
11
|
-
: falseValue;
|
|
12
|
-
}
|
|
13
|
-
ifTs = (trueValue, falseValue = '') => this.if(this.useTypeScript, trueValue, falseValue);
|
|
14
|
-
ifEsm = (trueValue, falseValue = '') => this.if(this.useEsm, trueValue, falseValue);
|
|
15
|
-
param(name, type) {
|
|
16
|
-
return this.useTypeScript
|
|
17
|
-
? `${name}: ${type}`
|
|
18
|
-
: name;
|
|
19
|
-
}
|
|
20
|
-
returns(type) {
|
|
21
|
-
return this.useTypeScript
|
|
22
|
-
? `: ${type}`
|
|
23
|
-
: '';
|
|
24
|
-
}
|
|
25
|
-
import(exports, moduleId) {
|
|
26
|
-
const individualExports = exports.split(',').map(id => id.trim());
|
|
27
|
-
const imports = this.useTypeScript
|
|
28
|
-
? individualExports
|
|
29
|
-
: individualExports.filter(id => !id.startsWith('type '));
|
|
30
|
-
if (!imports.length) {
|
|
31
|
-
return '';
|
|
32
|
-
}
|
|
33
|
-
const modulePath = this.modulePathFrom(moduleId);
|
|
34
|
-
return this.useEsm || this.useTypeScript
|
|
35
|
-
? `import { ${imports.join(', ')} } from '${modulePath}'`
|
|
36
|
-
: `const { ${imports.join(', ')} } = require('${modulePath}')`;
|
|
37
|
-
}
|
|
38
|
-
modulePathFrom(moduleId) {
|
|
39
|
-
if (!(moduleId.startsWith('.') && this.useEsm)) {
|
|
40
|
-
return moduleId;
|
|
41
|
-
}
|
|
42
|
-
if (moduleId.endsWith('/') && this.useEsm) {
|
|
43
|
-
return moduleId + 'index.js';
|
|
44
|
-
}
|
|
45
|
-
return moduleId + '.js';
|
|
46
|
-
}
|
|
47
|
-
export(keyword, name) {
|
|
48
|
-
if (this.useTypeScript) {
|
|
49
|
-
return `export ${keyword} ${name}`;
|
|
50
|
-
}
|
|
51
|
-
if (this.useEsm) {
|
|
52
|
-
return `export ${keyword} ${name}`;
|
|
53
|
-
}
|
|
54
|
-
if (['class', 'function'].includes(keyword)) {
|
|
55
|
-
return `module.exports.${name} = ${keyword} ${name}`;
|
|
56
|
-
}
|
|
57
|
-
return `module.exports.${name}`;
|
|
58
|
-
}
|
|
59
|
-
}
|