/
wdio.conf.mjs
473 lines (432 loc) · 15.1 KB
/
wdio.conf.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
import path from 'path';
import { launchProxy, app } from './server.mjs';
import { fileURLToPath } from 'url';
import { SevereServiceError } from 'webdriverio';
import { GitHubService, build, browserstackPublicURL, browserstackPrivateURL } from './ci.mjs';
/**
* The hooks available to a service added to the config.
* @typedef {import("@wdio/types").Services.ServiceClass} ServiceClass
* @typedef {import("@wdio/types").Services.ServiceInstance} ServiceInstance
*/
const buildName = await build();
const localIdentifier = buildName.replace(/[^a-z0-9_]+/gi, '_');
const port = 9998;
const user = process.env.BROWSERSTACK_USERNAME;
const key = process.env.BROWSERSTACK_ACCESS_KEY;
if (!user || !key) {
throw new Error('Envvars BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY must be set.')
}
const __dirname = path.dirname(fileURLToPath(import.meta.url));
function buildConfig() {
return {
runner: 'local',
// =================
// Service Providers
// =================
user: process.env.BROWSERSTACK_USERNAME,
key: process.env.BROWSERSTACK_ACCESS_KEY,
services: [
// Launch the test API server, an ngrok tunnel, set some defaults.
[RavelinJSServerLauncher, {
port: port,
build: buildName,
localIdentifier: localIdentifier,
}],
// Launch the browserstack local tunnel and create selenium sessions.
['browserstack', {
browserstackLocal: true,
setSessionName: false,
opts: {
kill: true,
force: true,
localProxyHost: 'localhost',
localProxyPort: port,
localIdentifier: localIdentifier,
'disable-dashboard': true,
},
}],
// Send updates to GitHub.
[GitHubService, {
context: 'browserstack',
sha: process.env.COMMIT_SHA,
repo: process.env.HEAD_REPO_URL,
token: process.env.GITHUB_TOKEN,
links: [
async () => browserstackPrivateURL(buildName),
async (caps, cfg) => browserstackPublicURL(cfg.user, cfg.key, buildName),
],
}],
],
// ==================
// Specify Test Files
// ==================
// Define which test specs should run. The pattern is relative to the
// directory from which `wdio` was called.
//
// The specs are defined as an array of spec files (optionally using wildcards
// that will be expanded). The test for each spec file will be run in a
// separate worker process. In order to have a group of spec files run in the
// same worker process enclose them in an array within the specs array.
//
// If you are calling `wdio` from an NPM script (see
// https://docs.npmjs.com/cli/run-script), then the current working directory
// is where your `package.json` resides, so `wdio` will be called from there.
specs: [
path.join(__dirname, '*/*.spec.mjs'),
],
// Patterns to exclude.
exclude: [],
// ===================
// Test Configurations
// ===================
// Define all options that are relevant for the WebdriverIO instance here.
// Level of logging verbosity: trace | debug | info | warn | error | silent
logLevel: process.env.LOG_LEVEL || 'info',
// Set specific log levels per logger
// use 'silent' level to disable logger
// logLevels: {
// webdriver: 'info',
// '@wdio/appium-service': 'info'
// },
// Set directory to store all logs into
// outputDir: __dirname,
// If you only want to run your tests until a specific amount of tests have
// failed use bail (default is 0 - don't bail, run all tests).
bail: parseInt(process.env.BAIL, 10) || 0,
// Set a base URL in order to shorten `url()` command calls. If your `url`
// parameter starts with `/`, the `baseUrl` is prepended, not including the
// path portion of `baseUrl`. If your `url` parameter starts without a scheme
// or `/` (like `some/path`), the `baseUrl` gets prepended directly.
baseUrl: 'http://bs-local.com',
// Default timeout for all waitForXXX commands.
waitforTimeout: 1000,
// Add files to watch (e.g. application code or page objects) when running
// `wdio` command with `--watch` flag. Globbing is supported.
filesToWatch: [
// e.g. rerun tests if I change my application code
// './app/**/*.js'
],
// Framework you want to run your specs with. The following are supported:
// 'mocha', 'jasmine', and 'cucumber' See also:
// https://webdriver.io/docs/frameworks.html
//
// Make sure you have the wdio adapter package for the specific framework
// installed before running any tests.
framework: 'mocha',
// The number of times to retry the entire specfile when it fails as a whole.
specFileRetries: 1,
// Delay in seconds between the spec file retry attempts.
specFileRetriesDelay: 0,
// Whether or not retried specfiles should be retried immediately or deferred
// to the end of the queue.
specFileRetriesDeferred: true,
// Test reporter for stdout. The only one supported by default is 'dot'.
// See also: https://webdriver.io/docs/dot-reporter.html , and click on
// 'Reporters' in left column.
reporters: ['dot'],
// Options to be passed to Mocha.
// See the full list at: http://mochajs.org
mochaOpts: {
ui: 'bdd',
timeout: 600000,
},
// For convenience, if ts-node or @babel/register modules are detected
// they are automatically loaded for config parsing so that TypeScript and
// future ES features can be used in wdio configs, and are also
// automatically loaded for test running so that tests can be written
// using TypeScript and future ES features.
// Because this may not be ideal in every situation, the following options
// may be used to customize the loading for test running, incase it has
// other requirements.
autoCompileOpts: {
// To disable auto-loading entirely set this to false.
autoCompile: true, // <boolean> Disable this to turn off autoloading. Note: When disabling, you will need to handle calling any such libraries yourself.
// If you have ts-node installed, you can customize how options are passed
// to it here: Any valid ts-node config option is allowed. Alternatively
// the ENV Vars could also be used instead of this. See also:
// https://github.com/TypeStrong/ts-node#cli-and-programmatic-options See
// also RegisterOptions in
// https://github.com/TypeStrong/ts-node/blob/master/src/index.ts
tsNodeOpts: {
transpileOnly: true,
project: 'tsconfig.json'
},
// If @babel/register is installed, you can customize how options are
// passed to it here: Any valid @babel/register config option is allowed.
// https://babeljs.io/docs/en/babel-register#specifying-options
babelOpts: {
ignore: []
},
},
// ============
// Capabilities
// ============
// Define your capabilities here. WebdriverIO can run multiple capabilities at
// the same time. Depending on the number of capabilities, WebdriverIO
// launches several test sessions. Within your `capabilities`, you can
// overwrite the `spec` and `exclude` options in order to group specific specs
// to a specific capability.
// First, you can define how many instances should be started at the same
// time. Let's say you have 3 different capabilities (Chrome, Firefox, and
// Safari) and you have set `maxInstances` to 1. wdio will spawn 3 processes.
//
// Therefore, if you have 10 spec files and you set `maxInstances` to 10, all
// spec files will be tested at the same time and 30 processes will be
// spawned.
//
// The property handles how many capabilities from the same test should run
// tests.
maxInstances: parseInt(process.env.PARALLEL, 10) || 1,
// Or set a limit to run tests with a specific capability.
maxInstancesPerCapability: 10,
// Inserts WebdriverIO's globals (e.g. `browser`, `$` and `$$`) into the
// global environment. If you set to `false`, you should import from
// `@wdio/globals`. Note: WebdriverIO doesn't handle injection of test
// framework specific globals.
injectGlobals: true,
// Additional list of node arguments to use when starting child processes.
execArgv: [],
// If you have trouble getting all important capabilities together, check out the
// BrowserStack platform configurator - a great tool to configure your capabilities:
// https://www.browserstack.com/automate/capabilities?tag=selenium-4.
capabilities: filterCaps([
// Internet Explorer.
{
'browserName': 'IE',
'browserVersion': '11.0',
'bstack:options': {
'os': 'Windows',
'osVersion': '10',
'sendKeys': true,
},
},
{
'browserName': 'IE',
'browserVersion': '10.0',
'bstack:options': {
'os': 'Windows',
'osVersion': '8',
'sendKeys': true,
'seleniumVersion': '3.5.2',
},
'rav:send:skipCrossDomainTest': true,
},
{
'browserName': 'IE',
'browserVersion': '9.0',
'bstack:options': {
'os': 'Windows',
'osVersion': '7',
'sendKeys': true,
'seleniumVersion': '3.5.2',
},
'rav:send:skipCrossDomainTest': true,
},
{
'browserName': 'IE',
'browserVersion': '8.0',
'bstack:options': {
'os': 'Windows',
'osVersion': '7',
'sendKeys': true,
'seleniumVersion': '3.5.2',
},
'rav:send:skipCrossDomainTest': true,
},
// Android
// Reminder set for 16-01-2023 to uncomment this next block and check if
// BROWSERS=s6 npm run test:integration passes. If so, open a PR :)
// {
// 'bstack:options': {
// 'osVersion': '5.0',
// 'deviceName': 'Samsung Galaxy S6',
// 'realMobile': 'true'
// },
// 'browserName': 'Android'
// },
{
'bstack:options': {
'osVersion': '7.0',
'deviceName': 'Samsung Galaxy S8',
'realMobile': 'true',
},
'browserName': 'Android'
},
// iOS
// iOS11 currently failing to start with 'COULD NOT START MOBILE BROWSER'.
// https://automate.browserstack.com/dashboard/v2/builds/2840b7364373dc5f6f27be58d7bb33374d492668?overallStatus=error
// {
// 'bstack:options': {
// 'osVersion': '11',
// 'deviceName': 'iPhone 8',
// 'realMobile': 'true',
// },
// 'browserName': 'iPhone',
// },
{
'browserName': 'iPhone',
'bstack:options': {
'osVersion': '12',
'deviceName': 'iPhone 8',
'realMobile': 'true',
},
},
{
'browserName': 'iPhone',
'bstack:options': {
'osVersion': '13',
'deviceName': 'iPhone 8',
'realMobile': 'true',
},
},
{
'browserName': 'iPhone',
'bstack:options': {
'osVersion': '14',
'deviceName': 'iPhone 11',
'realMobile': 'true',
},
},
// Chrome.
{
'browserVersion': 'beta',
'browserName': 'Chrome',
'bstack:options': {
'os': 'Windows',
'osVersion': '10',
},
},
{
'browserVersion': 'latest',
'browserName': 'Chrome',
'bstack:options': {
'os': 'OS X',
'osVersion': 'Mojave',
},
},
{
'browserVersion': '40.0',
'browserName': 'Chrome',
'bstack:options': {
'os': 'OS X',
'osVersion': 'Mojave',
'seleniumVersion': '3.14.0',
},
},
// Safari.
{
'browserName': 'Safari',
'browserVersion': '13.0',
'bstack:options': {
'os': 'OS X',
'osVersion': 'Catalina',
},
},
// Finding that High Sierra isn't starting properly:
// https://automate.browserstack.com/dashboard/v2/builds/ffecf909566a3dd424c71404338350fe6ccecabf/sessions/8fb5ed210e940700b992559292c8516ca3fc33e3
// {
// 'bstack:options': {
// 'os': 'OS X',
// 'osVersion': 'High Sierra',
// },
// 'browserName': 'Safari',
// 'browserVersion': '11.0',
// },
{
'browserName': 'Safari',
'browserVersion': '7.1',
'bstack:options': {
'os': 'OS X',
'osVersion': 'Mavericks',
'seleniumVersion': '3.14.0',
},
},
// Firefox.
{
'browserName': 'Firefox',
'browserVersion': 'latest',
'bstack:options': {
'os': 'Windows',
'osVersion': '11',
},
},
]),
};
}
/**
* @implements {ServiceClass}
*/
class RavelinJSServerLauncher {
constructor(opts) {
this.port = opts.port;
this.build = opts.build;
this.localIdentifier = opts.localIdentifier;
}
async onPrepare(config, caps) {
try {
// Launch our local server and ngrok proxy.
const api = await launchProxy(app(), this.port, process.env.NGROK_ENABLED !== '0');
process.env.TEST_INTERNAL = api.internal;
process.env.TEST_LOCAL = config.baseUrl;
process.env.TEST_REMOTE = api.remote;
// Set default properties on the capabilities.
caps.forEach(c => {
c['bstack:options'] ||= {};
const bs = c['bstack:options'];
bs.projectName = 'ravelinjs';
bs.buildName = this.build;
bs.localIdentifier = this.localIdentifier;
if (bs.realMobile !== 'true') {
bs.seleniumVersion ??= '4.7.2';
}
// bs.debug ??= true;
bs.networkLogs ??= true;
bs.consoleLogs ??= 'verbose';
});
} catch(err) {
console.error(err);
throw new SevereServiceError(err);
}
}
async onWorkerStart(cid, cap, specs, args, execArgv) {
try {
// Set a session title.
const o = cap['bstack:options'];
o.sessionName = [
specs && specs[0].split('/').pop(),
'-',
cap.browserName,
cap.browserVersion, o.browserVersion,
'-',
o.os, cap.os, !o.os && cap.browserName == 'iPhone' && 'iOS',
o.osVersion, cap.os_version,
'-',
o.deviceName,
].filter(Boolean).join(" ").replace(/^[ -]+|[ -]+$/g, '');
} catch(err) {
console.error(err);
throw new SevereServiceError(err);
}
}
}
function filterCaps(caps) {
if (process.env.BROWSERS) {
const toks = process.env.BROWSERS.toLowerCase().split(',');
caps = caps.filter(v => {
const j = JSON.stringify(v).toLowerCase();
for (const tok of toks) {
if (!j.includes(tok)) {
return false;
}
}
return true;
});
}
if (process.env.LIMIT) {
const lim = parseInt(process.env.LIMIT, 10);
if (!lim) throw new Error('LIMIT envvar cannot be parsed as integer');
caps = caps.slice(0, lim);
}
return caps;
}
export const config = buildConfig();