@wdio/browserstack-service 9.0.0-alpha.78 → 9.0.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.
Files changed (72) hide show
  1. package/README.md +1 -1
  2. package/build/Percy/Percy-Handler.d.ts +1 -1
  3. package/build/Percy/Percy-Handler.d.ts.map +1 -1
  4. package/build/Percy/PercyHelper.d.ts +1 -1
  5. package/build/Percy/PercyHelper.d.ts.map +1 -1
  6. package/build/accessibility-handler.d.ts +2 -2
  7. package/build/accessibility-handler.d.ts.map +1 -1
  8. package/build/ai-handler.d.ts +23 -0
  9. package/build/ai-handler.d.ts.map +1 -0
  10. package/build/constants.d.ts +10 -1
  11. package/build/constants.d.ts.map +1 -1
  12. package/build/crash-reporter.d.ts +2 -2
  13. package/build/crash-reporter.d.ts.map +1 -1
  14. package/build/fileStream.d.ts +0 -2
  15. package/build/fileStream.d.ts.map +1 -1
  16. package/build/index.js +5822 -12
  17. package/build/insights-handler.d.ts +5 -1
  18. package/build/insights-handler.d.ts.map +1 -1
  19. package/build/instrumentation/funnelInstrumentation.d.ts +2 -0
  20. package/build/instrumentation/funnelInstrumentation.d.ts.map +1 -1
  21. package/build/launcher.d.ts +6 -6
  22. package/build/launcher.d.ts.map +1 -1
  23. package/build/performance-tester.d.ts +0 -1
  24. package/build/performance-tester.d.ts.map +1 -1
  25. package/build/reporter.d.ts.map +1 -1
  26. package/build/service.d.ts +4 -4
  27. package/build/service.d.ts.map +1 -1
  28. package/build/testOps/listener.d.ts +1 -0
  29. package/build/testOps/listener.d.ts.map +1 -1
  30. package/build/types.d.ts +9 -3
  31. package/build/types.d.ts.map +1 -1
  32. package/build/util.d.ts +43 -30
  33. package/build/util.d.ts.map +1 -1
  34. package/package.json +13 -10
  35. package/build/Percy/Percy-Handler.js +0 -156
  36. package/build/Percy/Percy.js +0 -123
  37. package/build/Percy/PercyBinary.js +0 -142
  38. package/build/Percy/PercyCaptureMap.js +0 -35
  39. package/build/Percy/PercyHelper.js +0 -67
  40. package/build/Percy/PercyLogger.js +0 -67
  41. package/build/Percy/PercySDK.js +0 -39
  42. package/build/accessibility-handler.js +0 -287
  43. package/build/bstackLogger.js +0 -70
  44. package/build/cleanup.js +0 -89
  45. package/build/config.js +0 -39
  46. package/build/constants.js +0 -73
  47. package/build/crash-reporter.js +0 -138
  48. package/build/cucumber-types.js +0 -1
  49. package/build/data-store.js +0 -41
  50. package/build/exitHandler.js +0 -29
  51. package/build/fetchWrapper.js +0 -14
  52. package/build/fileStream.js +0 -12
  53. package/build/insights-handler.js +0 -719
  54. package/build/instrumentation/funnelInstrumentation.js +0 -120
  55. package/build/launcher.js +0 -741
  56. package/build/log4jsAppender.js +0 -19
  57. package/build/logPatcher.js +0 -38
  58. package/build/logReportingAPI.js +0 -56
  59. package/build/performance-tester.js +0 -94
  60. package/build/reporter.js +0 -251
  61. package/build/request-handler.js +0 -74
  62. package/build/scripts/accessibility-scripts.js +0 -61
  63. package/build/service.js +0 -428
  64. package/build/testOps/featureStats.js +0 -116
  65. package/build/testOps/featureUsage.js +0 -47
  66. package/build/testOps/listener.js +0 -222
  67. package/build/testOps/requestUtils.js +0 -39
  68. package/build/testOps/testOpsConfig.js +0 -19
  69. package/build/testOps/usageStats.js +0 -114
  70. package/build/types.js +0 -1
  71. package/build/util.js +0 -1132
  72. /package/{LICENSE-MIT → LICENSE} +0 -0
package/build/launcher.js DELETED
@@ -1,741 +0,0 @@
1
- import { FormData } from 'formdata-node';
2
- import { v4 as uuidv4 } from 'uuid';
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- import { promisify, format } from 'node:util';
6
- import { performance, PerformanceObserver } from 'node:perf_hooks';
7
- import os from 'node:os';
8
- import { SevereServiceError } from 'webdriverio';
9
- import * as BrowserstackLocalLauncher from 'browserstack-local';
10
- import PerformanceTester from './performance-tester.js';
11
- import { startPercy, stopPercy, getBestPlatformForPercySnapshot } from './Percy/PercyHelper.js';
12
- import { BSTACK_SERVICE_VERSION, NOT_ALLOWED_KEYS_IN_CAPS, PERF_MEASUREMENT_ENV, RERUN_ENV, RERUN_TESTS_ENV, TESTOPS_BUILD_ID_ENV, VALID_APP_EXTENSION } from './constants.js';
13
- import { launchTestSession, createAccessibilityTestRun, shouldAddServiceVersion, stopBuildUpstream, getCiInfo, isBStackSession, isUndefined, isAccessibilityAutomationSession, stopAccessibilityTestRun, isTrue, getBrowserStackUser, getBrowserStackKey, uploadLogs, ObjectsAreEqual, getBasicAuthHeader, } from './util.js';
14
- import CrashReporter from './crash-reporter.js';
15
- import { BStackLogger } from './bstackLogger.js';
16
- import { PercyLogger } from './Percy/PercyLogger.js';
17
- import { FileStream } from './fileStream.js';
18
- import BrowserStackConfig from './config.js';
19
- import { setupExitHandlers } from './exitHandler.js';
20
- import { sendFinish, sendStart } from './instrumentation/funnelInstrumentation.js';
21
- export default class BrowserstackLauncherService {
22
- _options;
23
- _config;
24
- browserstackLocal;
25
- _buildName;
26
- _projectName;
27
- _buildTag;
28
- _buildIdentifier;
29
- _accessibilityAutomation;
30
- _percy;
31
- _percyBestPlatformCaps;
32
- browserStackConfig;
33
- constructor(_options, capabilities, _config) {
34
- this._options = _options;
35
- this._config = _config;
36
- BStackLogger.clearLogFile();
37
- PercyLogger.clearLogFile();
38
- setupExitHandlers();
39
- // added to maintain backward compatibility with webdriverIO v5
40
- this._config || (this._config = _options);
41
- this.browserStackConfig = BrowserStackConfig.getInstance(_options, _config);
42
- if (Array.isArray(capabilities)) {
43
- capabilities
44
- .flatMap((c) => {
45
- if (Object.values(c).length > 0 && Object.values(c).every(c => typeof c === 'object' && c.capabilities)) {
46
- return Object.values(c).map((o) => o.capabilities);
47
- }
48
- return c;
49
- })
50
- .forEach((capability) => {
51
- if (!capability['bstack:options']) {
52
- // Skipping adding of service version if session is not of browserstack
53
- if (isBStackSession(this._config)) {
54
- const extensionCaps = Object.keys(capability).filter((cap) => cap.includes(':'));
55
- if (extensionCaps.length) {
56
- capability['bstack:options'] = { wdioService: BSTACK_SERVICE_VERSION };
57
- if (!isUndefined(capability['browserstack.accessibility'])) {
58
- this._accessibilityAutomation ||= isTrue(capability['browserstack.accessibility']);
59
- }
60
- else if (isTrue(this._options.accessibility)) {
61
- capability['bstack:options'].accessibility = true;
62
- }
63
- }
64
- else if (shouldAddServiceVersion(this._config, this._options.testObservability)) {
65
- capability['browserstack.wdioService'] = BSTACK_SERVICE_VERSION;
66
- }
67
- }
68
- // Need this details for sending data to Observability
69
- this._buildIdentifier = capability['browserstack.buildIdentifier']?.toString();
70
- this._buildName = capability.build?.toString();
71
- }
72
- else {
73
- capability['bstack:options'].wdioService = BSTACK_SERVICE_VERSION;
74
- this._buildName = capability['bstack:options'].buildName;
75
- this._projectName = capability['bstack:options'].projectName;
76
- this._buildTag = capability['bstack:options'].buildTag;
77
- this._buildIdentifier = capability['bstack:options'].buildIdentifier;
78
- if (!isUndefined(capability['bstack:options'].accessibility)) {
79
- this._accessibilityAutomation ||= isTrue(capability['bstack:options'].accessibility);
80
- }
81
- else if (isTrue(this._options.accessibility)) {
82
- capability['bstack:options'].accessibility = (isTrue(this._options.accessibility));
83
- }
84
- }
85
- });
86
- }
87
- else if (typeof capabilities === 'object') {
88
- Object.entries(capabilities).forEach(([, caps]) => {
89
- if (!caps.capabilities['bstack:options']) {
90
- if (isBStackSession(this._config)) {
91
- const extensionCaps = Object.keys(caps.capabilities).filter((cap) => cap.includes(':'));
92
- if (extensionCaps.length) {
93
- caps.capabilities['bstack:options'] = { wdioService: BSTACK_SERVICE_VERSION };
94
- if (!isUndefined(caps.capabilities['browserstack.accessibility'])) {
95
- this._accessibilityAutomation ||= isTrue(caps.capabilities['browserstack.accessibility']);
96
- }
97
- else if (isTrue(this._options.accessibility)) {
98
- caps.capabilities['bstack:options'] = { wdioService: BSTACK_SERVICE_VERSION, accessibility: (isTrue(this._options.accessibility)) };
99
- }
100
- }
101
- else if (shouldAddServiceVersion(this._config, this._options.testObservability)) {
102
- caps.capabilities['browserstack.wdioService'] = BSTACK_SERVICE_VERSION;
103
- }
104
- }
105
- this._buildIdentifier = caps.capabilities['browserstack.buildIdentifier'];
106
- }
107
- else {
108
- const bstackOptions = caps.capabilities['bstack:options'];
109
- bstackOptions.wdioService = BSTACK_SERVICE_VERSION;
110
- this._buildName = bstackOptions.buildName;
111
- this._projectName = bstackOptions.projectName;
112
- this._buildTag = bstackOptions.buildTag;
113
- this._buildIdentifier = bstackOptions.buildIdentifier;
114
- if (!isUndefined(bstackOptions.accessibility)) {
115
- this._accessibilityAutomation ||= isTrue(bstackOptions.accessibility);
116
- }
117
- else if (isTrue(this._options.accessibility)) {
118
- bstackOptions.accessibility = isTrue(this._options.accessibility);
119
- }
120
- }
121
- });
122
- }
123
- this.browserStackConfig.buildIdentifier = this._buildIdentifier;
124
- this.browserStackConfig.buildName = this._buildName;
125
- if (process.env[PERF_MEASUREMENT_ENV]) {
126
- PerformanceTester.startMonitoring('performance-report-launcher.csv');
127
- }
128
- this._accessibilityAutomation ||= isTrue(this._options.accessibility);
129
- this._options.accessibility = this._accessibilityAutomation;
130
- // by default observability will be true unless specified as false
131
- this._options.testObservability = this._options.testObservability !== false;
132
- if (this._options.testObservability
133
- &&
134
- // update files to run if it's a rerun
135
- process.env[RERUN_ENV] && process.env[RERUN_TESTS_ENV]) {
136
- this._config.specs = process.env[RERUN_TESTS_ENV].split(',');
137
- }
138
- try {
139
- CrashReporter.setConfigDetails(this._config, capabilities, this._options);
140
- }
141
- catch (error) {
142
- BStackLogger.error(`[Crash_Report_Upload] Config processing failed due to ${error}`);
143
- }
144
- }
145
- async onWorkerStart(cid, caps) {
146
- try {
147
- if (this._options.percy && this._percyBestPlatformCaps) {
148
- const isThisBestPercyPlatform = ObjectsAreEqual(caps, this._percyBestPlatformCaps);
149
- if (isThisBestPercyPlatform) {
150
- process.env.BEST_PLATFORM_CID = cid;
151
- }
152
- }
153
- }
154
- catch (err) {
155
- PercyLogger.error(`Error while setting best platform for Percy snapshot at worker start ${err}`);
156
- }
157
- }
158
- async onPrepare(config, capabilities) {
159
- // Send Funnel start request
160
- await sendStart(this.browserStackConfig);
161
- /**
162
- * Upload app to BrowserStack if valid file path to app is given.
163
- * Update app value of capability directly if app_url, custom_id, shareable_id is given
164
- */
165
- if (!this._options.app) {
166
- BStackLogger.info('app is not defined in browserstack-service config, skipping ...');
167
- }
168
- else {
169
- let app = {};
170
- const appConfig = this._options.app;
171
- try {
172
- app = await this._validateApp(appConfig);
173
- }
174
- catch (error) {
175
- throw new SevereServiceError(error);
176
- }
177
- if (VALID_APP_EXTENSION.includes(path.extname(app.app))) {
178
- if (fs.existsSync(app.app)) {
179
- const data = await this._uploadApp(app);
180
- BStackLogger.info(`app upload completed: ${JSON.stringify(data)}`);
181
- app.app = data.app_url;
182
- }
183
- else if (app.customId) {
184
- app.app = app.customId;
185
- }
186
- else {
187
- throw new SevereServiceError(`[Invalid app path] app path ${app.app} is not correct, Provide correct path to app under test`);
188
- }
189
- }
190
- BStackLogger.info(`Using app: ${app.app}`);
191
- this._updateCaps(capabilities, 'app', app.app);
192
- }
193
- /**
194
- * buildIdentifier in service options will take precedence over specified in capabilities
195
- */
196
- if (this._options.buildIdentifier) {
197
- this._buildIdentifier = this._options.buildIdentifier;
198
- this._updateCaps(capabilities, 'buildIdentifier', this._buildIdentifier);
199
- }
200
- /**
201
- * evaluate buildIdentifier in case unique execution identifiers are present
202
- * e.g., ${BUILD_NUMBER} and ${DATE_TIME}
203
- */
204
- this._handleBuildIdentifier(capabilities);
205
- // remove accessibilityOptions from the capabilities if present
206
- this._updateObjectTypeCaps(capabilities, 'accessibilityOptions');
207
- if (this._accessibilityAutomation) {
208
- const scannerVersion = await createAccessibilityTestRun(this._options, this._config, {
209
- projectName: this._projectName,
210
- buildName: this._buildName,
211
- buildTag: this._buildTag,
212
- bstackServiceVersion: BSTACK_SERVICE_VERSION,
213
- buildIdentifier: this._buildIdentifier,
214
- accessibilityOptions: this._options.accessibilityOptions
215
- });
216
- if (scannerVersion) {
217
- process.env.BSTACK_A11Y_SCANNER_VERSION = scannerVersion;
218
- }
219
- BStackLogger.debug(`Accessibility scannerVersion ${scannerVersion}`);
220
- }
221
- if (this._options.accessibilityOptions) {
222
- const filteredOpts = Object.keys(this._options.accessibilityOptions)
223
- .filter(key => !NOT_ALLOWED_KEYS_IN_CAPS.includes(key))
224
- .reduce((opts, key) => {
225
- return {
226
- ...opts,
227
- [key]: this._options.accessibilityOptions?.[key]
228
- };
229
- }, {});
230
- this._updateObjectTypeCaps(capabilities, 'accessibilityOptions', filteredOpts);
231
- }
232
- else if (isAccessibilityAutomationSession(this._accessibilityAutomation)) {
233
- this._updateObjectTypeCaps(capabilities, 'accessibilityOptions', {});
234
- }
235
- if (this._options.testObservability) {
236
- BStackLogger.debug('Sending launch start event');
237
- await launchTestSession(this._options, this._config, {
238
- projectName: this._projectName,
239
- buildName: this._buildName,
240
- buildTag: this._buildTag,
241
- bstackServiceVersion: BSTACK_SERVICE_VERSION,
242
- buildIdentifier: this._buildIdentifier
243
- });
244
- }
245
- if (this._options.percy) {
246
- try {
247
- const bestPlatformPercyCaps = getBestPlatformForPercySnapshot(capabilities);
248
- this._percyBestPlatformCaps = bestPlatformPercyCaps;
249
- await this.setupPercy(this._options, this._config, {
250
- projectName: this._projectName
251
- });
252
- }
253
- catch (err) {
254
- PercyLogger.error(`Error while setting up Percy ${err}`);
255
- }
256
- }
257
- if (!this._options.browserstackLocal) {
258
- return BStackLogger.info('browserstackLocal is not enabled - skipping...');
259
- }
260
- const opts = {
261
- key: this._config.key,
262
- ...this._options.opts
263
- };
264
- this.browserstackLocal = new BrowserstackLocalLauncher.Local();
265
- this._updateCaps(capabilities, 'local');
266
- if (opts.localIdentifier) {
267
- this._updateCaps(capabilities, 'localIdentifier', opts.localIdentifier);
268
- }
269
- /**
270
- * measure BrowserStack tunnel boot time
271
- */
272
- const obs = new PerformanceObserver((list) => {
273
- const entry = list.getEntries()[0];
274
- BStackLogger.info(`Browserstack Local successfully started after ${entry.duration}ms`);
275
- });
276
- obs.observe({ entryTypes: ['measure'] });
277
- let timer;
278
- performance.mark('tbTunnelStart');
279
- return Promise.race([
280
- promisify(this.browserstackLocal.start.bind(this.browserstackLocal))(opts),
281
- new Promise((resolve, reject) => {
282
- /* istanbul ignore next */
283
- timer = setTimeout(function () {
284
- reject('Browserstack Local failed to start within 60 seconds!');
285
- }, 60000);
286
- })
287
- ]).then(function (result) {
288
- clearTimeout(timer);
289
- performance.mark('tbTunnelEnd');
290
- performance.measure('bootTime', 'tbTunnelStart', 'tbTunnelEnd');
291
- return Promise.resolve(result);
292
- }, function (err) {
293
- clearTimeout(timer);
294
- return Promise.reject(err);
295
- });
296
- }
297
- async onComplete() {
298
- BStackLogger.debug('Inside OnComplete hook..');
299
- if (isAccessibilityAutomationSession(this._accessibilityAutomation)) {
300
- await stopAccessibilityTestRun().catch((error) => {
301
- BStackLogger.error(`Exception in stop accessibility test run: ${error}`);
302
- });
303
- }
304
- if (this._options.testObservability) {
305
- BStackLogger.debug('Sending stop launch event');
306
- await stopBuildUpstream();
307
- if (process.env[TESTOPS_BUILD_ID_ENV]) {
308
- console.log(`\nVisit https://observability.browserstack.com/builds/${process.env[TESTOPS_BUILD_ID_ENV]} to view build report, insights, and many more debugging information all at one place!\n`);
309
- }
310
- this.browserStackConfig.testObservability.buildStopped = true;
311
- if (process.env[PERF_MEASUREMENT_ENV]) {
312
- await PerformanceTester.stopAndGenerate('performance-launcher.html');
313
- PerformanceTester.calculateTimes(['launchTestSession', 'stopBuildUpstream']);
314
- if (!process.env.START_TIME) {
315
- return;
316
- }
317
- const duration = (new Date()).getTime() - (new Date(process.env.START_TIME)).getTime();
318
- BStackLogger.info(`Total duration is ${duration / 1000} s`);
319
- }
320
- }
321
- try {
322
- await this._uploadServiceLogs();
323
- }
324
- catch (error) {
325
- BStackLogger.debug(`Failed to upload BrowserStack WDIO Service logs ${error}`);
326
- }
327
- BStackLogger.clearLogger();
328
- if (this._options.percy) {
329
- await this.stopPercy();
330
- }
331
- PercyLogger.clearLogger();
332
- await sendFinish(this.browserStackConfig);
333
- if (!this.browserstackLocal || !this.browserstackLocal.isRunning()) {
334
- return;
335
- }
336
- if (this._options.forcedStop) {
337
- return process.kill(this.browserstackLocal.pid);
338
- }
339
- let timer;
340
- return Promise.race([
341
- new Promise((resolve, reject) => {
342
- this.browserstackLocal?.stop((err) => {
343
- if (err) {
344
- return reject(err);
345
- }
346
- resolve();
347
- });
348
- }),
349
- new Promise((resolve, reject) => {
350
- /* istanbul ignore next */
351
- timer = setTimeout(() => reject(new Error('Browserstack Local failed to stop within 60 seconds!')), 60000);
352
- })
353
- ]).then(function (result) {
354
- clearTimeout(timer);
355
- return Promise.resolve(result);
356
- }, function (err) {
357
- clearTimeout(timer);
358
- return Promise.reject(err);
359
- });
360
- }
361
- async setupPercy(options, config, bsConfig) {
362
- if (this._percy?.isRunning()) {
363
- return;
364
- }
365
- try {
366
- this._percy = await startPercy(options, config, bsConfig);
367
- if (!this._percy) {
368
- throw new Error('Could not start percy, check percy logs for info.');
369
- }
370
- PercyLogger.info('Percy started successfully');
371
- let signal = 0;
372
- const handler = async () => {
373
- signal++;
374
- signal === 1 && await this.stopPercy();
375
- };
376
- process.on('beforeExit', handler);
377
- process.on('SIGINT', handler);
378
- process.on('SIGTERM', handler);
379
- }
380
- catch (err) {
381
- PercyLogger.debug(`Error in percy setup ${err}`);
382
- }
383
- }
384
- async stopPercy() {
385
- if (!this._percy || !this._percy.isRunning()) {
386
- return;
387
- }
388
- try {
389
- await stopPercy(this._percy);
390
- PercyLogger.info('Percy stopped');
391
- }
392
- catch (err) {
393
- PercyLogger.error('Error occured while stopping percy : ' + err);
394
- }
395
- }
396
- async _uploadApp(app) {
397
- BStackLogger.info(`uploading app ${app.app} ${app.customId ? `and custom_id: ${app.customId}` : ''} to browserstack`);
398
- const form = new FormData();
399
- if (app.app) {
400
- const fileName = path.basename(app.app);
401
- form.append('file', new FileStream(fs.createReadStream(app.app)), fileName);
402
- }
403
- if (app.customId) {
404
- form.append('custom_id', app.customId);
405
- }
406
- const headers = {
407
- 'Content-Type': 'multipart/form-data',
408
- Authorization: getBasicAuthHeader(this._config.user, this._config.key),
409
- };
410
- const res = await fetch('https://api-cloud.browserstack.com/app-automate/upload', {
411
- method: 'POST',
412
- body: form,
413
- headers
414
- });
415
- if (!res.ok) {
416
- throw new SevereServiceError(`app upload failed ${res.body}`);
417
- }
418
- return await res.json();
419
- }
420
- /**
421
- * @param {String | AppConfig} appConfig <string>: should be "app file path" or "app_url" or "custom_id" or "shareable_id".
422
- * <object>: only "path" and "custom_id" should coexist as multiple properties.
423
- */
424
- async _validateApp(appConfig) {
425
- const app = {};
426
- if (typeof appConfig === 'string') {
427
- app.app = appConfig;
428
- }
429
- else if (typeof appConfig === 'object' && Object.keys(appConfig).length) {
430
- if (Object.keys(appConfig).length > 2 || (Object.keys(appConfig).length === 2 && (!appConfig.path || !appConfig.custom_id))) {
431
- throw new SevereServiceError(`keys ${Object.keys(appConfig)} can't co-exist as app values, use any one property from
432
- {id<string>, path<string>, custom_id<string>, shareable_id<string>}, only "path" and "custom_id" can co-exist.`);
433
- }
434
- app.app = appConfig.id || appConfig.path || appConfig.custom_id || appConfig.shareable_id;
435
- app.customId = appConfig.custom_id;
436
- }
437
- else {
438
- throw new SevereServiceError('[Invalid format] app should be string or an object');
439
- }
440
- if (!app.app) {
441
- throw new SevereServiceError(`[Invalid app property] supported properties are {id<string>, path<string>, custom_id<string>, shareable_id<string>}.
442
- For more details please visit https://www.browserstack.com/docs/app-automate/appium/set-up-tests/specify-app ')`);
443
- }
444
- return app;
445
- }
446
- async _uploadServiceLogs() {
447
- const clientBuildUuid = this._getClientBuildUuid();
448
- const response = await uploadLogs(getBrowserStackUser(this._config), getBrowserStackKey(this._config), clientBuildUuid);
449
- BStackLogger.logToFile(`Response - ${format(response)}`, 'debug');
450
- }
451
- _updateObjectTypeCaps(capabilities, capType, value) {
452
- try {
453
- if (Array.isArray(capabilities)) {
454
- capabilities
455
- .flatMap((c) => {
456
- if (Object.values(c).length > 0 && Object.values(c).every(c => typeof c === 'object' && c.capabilities)) {
457
- return Object.values(c).map((o) => o.capabilities);
458
- }
459
- return c;
460
- })
461
- .forEach((capability) => {
462
- if (!capability['bstack:options']) {
463
- const extensionCaps = Object.keys(capability).filter((cap) => cap.includes(':'));
464
- if (extensionCaps.length) {
465
- if (capType === 'accessibilityOptions' && value) {
466
- capability['bstack:options'] = { accessibilityOptions: value };
467
- }
468
- }
469
- else if (capType === 'accessibilityOptions') {
470
- if (value) {
471
- const accessibilityOpts = { ...value };
472
- if (capability?.accessibility) {
473
- accessibilityOpts.authToken = process.env.BSTACK_A11Y_JWT;
474
- accessibilityOpts.scannerVersion = process.env.BSTACK_A11Y_SCANNER_VERSION;
475
- }
476
- capability['browserstack.accessibilityOptions'] = accessibilityOpts;
477
- }
478
- else {
479
- delete capability['browserstack.accessibilityOptions'];
480
- }
481
- }
482
- }
483
- else if (capType === 'accessibilityOptions') {
484
- if (value) {
485
- const accessibilityOpts = { ...value };
486
- if (capability['bstack:options'].accessibility) {
487
- accessibilityOpts.authToken = process.env.BSTACK_A11Y_JWT;
488
- accessibilityOpts.scannerVersion = process.env.BSTACK_A11Y_SCANNER_VERSION;
489
- }
490
- capability['bstack:options'].accessibilityOptions = accessibilityOpts;
491
- }
492
- else {
493
- delete capability['bstack:options'].accessibilityOptions;
494
- }
495
- }
496
- });
497
- }
498
- else if (typeof capabilities === 'object') {
499
- Object.entries(capabilities).forEach(([, caps]) => {
500
- if (!caps.capabilities['bstack:options']) {
501
- const extensionCaps = Object.keys(caps.capabilities).filter((cap) => cap.includes(':'));
502
- if (extensionCaps.length) {
503
- if (capType === 'accessibilityOptions' && value) {
504
- caps.capabilities['bstack:options'] = { accessibilityOptions: value };
505
- }
506
- }
507
- else if (capType === 'accessibilityOptions') {
508
- if (value) {
509
- const accessibilityOpts = { ...value };
510
- if (caps.capabilities['browserstack.accessibility']) {
511
- accessibilityOpts.authToken = process.env.BSTACK_A11Y_JWT;
512
- accessibilityOpts.scannerVersion = process.env.BSTACK_A11Y_SCANNER_VERSION;
513
- }
514
- caps.capabilities['browserstack.accessibilityOptions'] = accessibilityOpts;
515
- }
516
- else {
517
- delete caps.capabilities['browserstack.accessibilityOptions'];
518
- }
519
- }
520
- }
521
- else if (capType === 'accessibilityOptions') {
522
- if (value) {
523
- const accessibilityOpts = { ...value };
524
- if (caps.capabilities['bstack:options'].accessibility) {
525
- accessibilityOpts.authToken = process.env.BSTACK_A11Y_JWT;
526
- accessibilityOpts.scannerVersion = process.env.BSTACK_A11Y_SCANNER_VERSION;
527
- }
528
- caps.capabilities['bstack:options'].accessibilityOptions = accessibilityOpts;
529
- }
530
- else {
531
- delete caps.capabilities['bstack:options'].accessibilityOptions;
532
- }
533
- }
534
- });
535
- }
536
- }
537
- catch (error) {
538
- BStackLogger.debug(`Exception while retrieving capability value. Error - ${error}`);
539
- }
540
- }
541
- _updateCaps(capabilities, capType, value) {
542
- if (Array.isArray(capabilities)) {
543
- capabilities
544
- .flatMap((c) => {
545
- if (Object.values(c).length > 0 && Object.values(c).every(c => typeof c === 'object' && c.capabilities)) {
546
- return Object.values(c).map((o) => o.capabilities);
547
- }
548
- return c;
549
- })
550
- .forEach((capability) => {
551
- if (!capability['bstack:options']) {
552
- const extensionCaps = Object.keys(capability).filter((cap) => cap.includes(':'));
553
- if (extensionCaps.length) {
554
- if (capType === 'local') {
555
- capability['bstack:options'] = { local: true };
556
- }
557
- else if (capType === 'app') {
558
- capability['appium:app'] = value;
559
- }
560
- else if (capType === 'buildIdentifier' && value) {
561
- capability['bstack:options'] = { buildIdentifier: value };
562
- }
563
- }
564
- else if (capType === 'local') {
565
- capability['browserstack.local'] = true;
566
- }
567
- else if (capType === 'app') {
568
- capability.app = value;
569
- }
570
- else if (capType === 'buildIdentifier') {
571
- if (value) {
572
- capability['browserstack.buildIdentifier'] = value;
573
- }
574
- else {
575
- delete capability['browserstack.buildIdentifier'];
576
- }
577
- }
578
- else if (capType === 'localIdentifier') {
579
- capability['browserstack.localIdentifier'] = value;
580
- }
581
- }
582
- else if (capType === 'local') {
583
- capability['bstack:options'].local = true;
584
- }
585
- else if (capType === 'app') {
586
- capability['appium:app'] = value;
587
- }
588
- else if (capType === 'buildIdentifier') {
589
- if (value) {
590
- capability['bstack:options'].buildIdentifier = value;
591
- }
592
- else {
593
- delete capability['bstack:options'].buildIdentifier;
594
- }
595
- }
596
- else if (capType === 'localIdentifier') {
597
- capability['bstack:options'].localIdentifier = value;
598
- }
599
- });
600
- }
601
- else if (typeof capabilities === 'object') {
602
- Object.entries(capabilities).forEach(([, caps]) => {
603
- if (!caps.capabilities['bstack:options']) {
604
- const extensionCaps = Object.keys(caps.capabilities).filter((cap) => cap.includes(':'));
605
- if (extensionCaps.length) {
606
- if (capType === 'local') {
607
- caps.capabilities['bstack:options'] = { local: true };
608
- }
609
- else if (capType === 'app') {
610
- caps.capabilities['appium:app'] = value;
611
- }
612
- else if (capType === 'buildIdentifier' && value) {
613
- caps.capabilities['bstack:options'] = { buildIdentifier: value };
614
- }
615
- }
616
- else if (capType === 'local') {
617
- caps.capabilities['browserstack.local'] = true;
618
- }
619
- else if (capType === 'app') {
620
- caps.capabilities['appium:app'] = value;
621
- }
622
- else if (capType === 'buildIdentifier') {
623
- if (value) {
624
- caps.capabilities['browserstack.buildIdentifier'] = value;
625
- }
626
- else {
627
- delete caps.capabilities['browserstack.buildIdentifier'];
628
- }
629
- }
630
- else if (capType === 'localIdentifier') {
631
- caps.capabilities['browserstack.localIdentifier'] = value;
632
- }
633
- }
634
- else if (capType === 'local') {
635
- caps.capabilities['bstack:options'].local = true;
636
- }
637
- else if (capType === 'app') {
638
- caps.capabilities['appium:app'] = value;
639
- }
640
- else if (capType === 'buildIdentifier') {
641
- if (value) {
642
- caps.capabilities['bstack:options'].buildIdentifier = value;
643
- }
644
- else {
645
- delete caps.capabilities['bstack:options'].buildIdentifier;
646
- }
647
- }
648
- else if (capType === 'localIdentifier') {
649
- caps.capabilities['bstack:options'].localIdentifier = value;
650
- }
651
- });
652
- }
653
- else {
654
- throw new SevereServiceError('Capabilities should be an object or Array!');
655
- }
656
- }
657
- _handleBuildIdentifier(capabilities) {
658
- if (!this._buildIdentifier) {
659
- return;
660
- }
661
- if ((!this._buildName || process.env.BROWSERSTACK_BUILD_NAME) && this._buildIdentifier) {
662
- this._updateCaps(capabilities, 'buildIdentifier');
663
- BStackLogger.warn('Skipping buildIdentifier as buildName is not passed.');
664
- return;
665
- }
666
- if (this._buildIdentifier && this._buildIdentifier.includes('${DATE_TIME}')) {
667
- const formattedDate = new Intl.DateTimeFormat('en-GB', {
668
- month: 'short',
669
- day: '2-digit',
670
- hour: '2-digit',
671
- minute: '2-digit',
672
- hour12: false
673
- })
674
- .format(new Date())
675
- .replace(/ |, /g, '-');
676
- this._buildIdentifier = this._buildIdentifier.replace('${DATE_TIME}', formattedDate);
677
- this._updateCaps(capabilities, 'buildIdentifier', this._buildIdentifier);
678
- }
679
- if (!this._buildIdentifier.includes('${BUILD_NUMBER}')) {
680
- return;
681
- }
682
- const ciInfo = getCiInfo();
683
- if (ciInfo !== null && ciInfo.build_number) {
684
- this._buildIdentifier = this._buildIdentifier.replace('${BUILD_NUMBER}', 'CI ' + ciInfo.build_number);
685
- this._updateCaps(capabilities, 'buildIdentifier', this._buildIdentifier);
686
- }
687
- else {
688
- const localBuildNumber = this._getLocalBuildNumber();
689
- if (localBuildNumber) {
690
- this._buildIdentifier = this._buildIdentifier.replace('${BUILD_NUMBER}', localBuildNumber);
691
- this._updateCaps(capabilities, 'buildIdentifier', this._buildIdentifier);
692
- }
693
- }
694
- }
695
- /**
696
- * @return {string} if buildName doesn't exist in json file, it will return 1
697
- * else returns corresponding value in json file (e.g. { "wdio-build": { "identifier" : 2 } } => 2 in this case)
698
- */
699
- _getLocalBuildNumber() {
700
- const browserstackFolderPath = path.join(os.homedir(), '.browserstack');
701
- try {
702
- if (!fs.existsSync(browserstackFolderPath)) {
703
- fs.mkdirSync(browserstackFolderPath);
704
- }
705
- const filePath = path.join(browserstackFolderPath, '.build-name-cache.json');
706
- if (!fs.existsSync(filePath)) {
707
- fs.appendFileSync(filePath, JSON.stringify({}));
708
- }
709
- const buildCacheFileData = fs.readFileSync(filePath);
710
- const parsedBuildCacheFileData = JSON.parse(buildCacheFileData.toString());
711
- if (this._buildName && this._buildName in parsedBuildCacheFileData) {
712
- const prevIdentifier = parseInt((parsedBuildCacheFileData[this._buildName].identifier));
713
- const newIdentifier = prevIdentifier + 1;
714
- this._updateLocalBuildCache(filePath, this._buildName, newIdentifier);
715
- return newIdentifier.toString();
716
- }
717
- const newIdentifier = 1;
718
- this._updateLocalBuildCache(filePath, this._buildName, 1);
719
- return newIdentifier.toString();
720
- }
721
- catch (error) {
722
- return null;
723
- }
724
- }
725
- _updateLocalBuildCache(filePath, buildName, buildIdentifier) {
726
- if (!buildName || !filePath) {
727
- return;
728
- }
729
- const jsonContent = JSON.parse(fs.readFileSync(filePath).toString());
730
- jsonContent[buildName] = { 'identifier': buildIdentifier };
731
- fs.writeFileSync(filePath, JSON.stringify(jsonContent));
732
- }
733
- _getClientBuildUuid() {
734
- if (process.env[TESTOPS_BUILD_ID_ENV]) {
735
- return process.env[TESTOPS_BUILD_ID_ENV];
736
- }
737
- const uuid = uuidv4();
738
- BStackLogger.logToFile(`If facing any issues, please contact BrowserStack support with the Build Run Id - ${uuid}`, 'info');
739
- return uuid;
740
- }
741
- }