@wdio/browserstack-service 9.7.0 → 9.7.2

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.
@@ -0,0 +1,1621 @@
1
+ // src/util.ts
2
+ import { hostname, platform, type, version, arch } from "node:os";
3
+ import fs4 from "node:fs";
4
+ import zlib from "node:zlib";
5
+ import { format, promisify } from "node:util";
6
+ import path3 from "node:path";
7
+ import util from "node:util";
8
+ import gitRepoInfo from "git-repo-info";
9
+ import gitconfig from "gitconfiglocal";
10
+ import { FormData } from "formdata-node";
11
+
12
+ // src/logPatcher.ts
13
+ import Transport from "winston-transport";
14
+ var LOG_LEVELS = {
15
+ INFO: "INFO",
16
+ ERROR: "ERROR",
17
+ DEBUG: "DEBUG",
18
+ TRACE: "TRACE",
19
+ WARN: "WARN"
20
+ };
21
+ var logPatcher = class extends Transport {
22
+ logToTestOps = (level = LOG_LEVELS.INFO, message = [""]) => {
23
+ process.emit(`bs:addLog:${process.pid}`, {
24
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
25
+ level: level.toUpperCase(),
26
+ message: `"${message.join(", ")}"`,
27
+ kind: "TEST_LOG",
28
+ http_response: {}
29
+ });
30
+ };
31
+ /* Patching this would show user an extended trace on their cli */
32
+ trace = (...message) => {
33
+ this.logToTestOps(LOG_LEVELS.TRACE, message);
34
+ };
35
+ debug = (...message) => {
36
+ this.logToTestOps(LOG_LEVELS.DEBUG, message);
37
+ };
38
+ info = (...message) => {
39
+ this.logToTestOps(LOG_LEVELS.INFO, message);
40
+ };
41
+ warn = (...message) => {
42
+ this.logToTestOps(LOG_LEVELS.WARN, message);
43
+ };
44
+ error = (...message) => {
45
+ this.logToTestOps(LOG_LEVELS.ERROR, message);
46
+ };
47
+ log = (...message) => {
48
+ this.logToTestOps(LOG_LEVELS.INFO, message);
49
+ };
50
+ };
51
+ var logPatcher_default = logPatcher;
52
+
53
+ // src/performance-tester.ts
54
+ import { createObjectCsvWriter } from "csv-writer";
55
+ import fs2 from "node:fs";
56
+ import { performance, PerformanceObserver } from "node:perf_hooks";
57
+
58
+ // src/bstackLogger.ts
59
+ import path from "node:path";
60
+ import fs from "node:fs";
61
+ import chalk from "chalk";
62
+ import logger from "@wdio/logger";
63
+
64
+ // package.json
65
+ var package_default = {
66
+ name: "@wdio/browserstack-service",
67
+ version: "9.7.1",
68
+ description: "WebdriverIO service for better Browserstack integration",
69
+ author: "Adam Bjerstedt <abjerstedt@gmail.com>",
70
+ homepage: "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-browserstack-service",
71
+ license: "MIT",
72
+ engines: {
73
+ node: ">=18.20.0"
74
+ },
75
+ repository: {
76
+ type: "git",
77
+ url: "git+https://github.com/webdriverio/webdriverio.git",
78
+ directory: "packages/wdio-browserstack-service"
79
+ },
80
+ keywords: [
81
+ "webdriverio",
82
+ "wdio",
83
+ "browserstack",
84
+ "wdio-service"
85
+ ],
86
+ bugs: {
87
+ url: "https://github.com/webdriverio/webdriverio/issues"
88
+ },
89
+ type: "module",
90
+ types: "./build/index.d.ts",
91
+ exports: {
92
+ ".": {
93
+ types: "./build/index.d.ts",
94
+ import: "./build/index.js"
95
+ },
96
+ "./cleanup": {
97
+ import: "./build/cleanup.js",
98
+ source: "./src/cleanup.ts"
99
+ }
100
+ },
101
+ typeScriptVersion: "3.8.3",
102
+ dependencies: {
103
+ "@browserstack/ai-sdk-node": "1.5.17",
104
+ "@percy/appium-app": "^2.0.1",
105
+ "@percy/selenium-webdriver": "^2.0.3",
106
+ "@types/gitconfiglocal": "^2.0.1",
107
+ "@wdio/logger": "workspace:*",
108
+ "@wdio/reporter": "workspace:*",
109
+ "@wdio/types": "workspace:*",
110
+ "browserstack-local": "^1.5.1",
111
+ chalk: "^5.3.0",
112
+ "csv-writer": "^1.6.0",
113
+ "formdata-node": "5.0.1",
114
+ "git-repo-info": "^2.1.1",
115
+ gitconfiglocal: "^2.1.0",
116
+ uuid: "^10.0.0",
117
+ webdriverio: "workspace:*",
118
+ "winston-transport": "^4.5.0",
119
+ yauzl: "^3.0.0"
120
+ },
121
+ peerDependencies: {
122
+ "@wdio/cli": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
123
+ },
124
+ devDependencies: {
125
+ "@types/node": "^20.1.0",
126
+ "@types/yauzl": "^2.10.3",
127
+ "@wdio/globals": "workspace:*"
128
+ },
129
+ publishConfig: {
130
+ access: "public"
131
+ }
132
+ };
133
+
134
+ // src/constants.ts
135
+ var bstackServiceVersion = package_default.version;
136
+ var consoleHolder = Object.assign({}, console);
137
+ var DATA_ENDPOINT = "https://collector-observability.browserstack.com";
138
+ var BSTACK_SERVICE_VERSION = bstackServiceVersion;
139
+ var LOGS_FILE = "logs/bstack-wdio-service.log";
140
+ var FUNNEL_INSTRUMENTATION_URL = "https://api.browserstack.com/sdk/v1/event";
141
+ var BROWSERSTACK_TESTHUB_JWT = "BROWSERSTACK_TESTHUB_JWT";
142
+ var TESTOPS_SCREENSHOT_ENV = "BS_TESTOPS_ALLOW_SCREENSHOTS";
143
+ var BROWSERSTACK_TESTHUB_UUID = "BROWSERSTACK_TESTHUB_UUID";
144
+ var PERF_MEASUREMENT_ENV = "BROWSERSTACK_O11Y_PERF_MEASUREMENT";
145
+ var RERUN_ENV = "BROWSERSTACK_RERUN";
146
+ var TESTOPS_BUILD_COMPLETED_ENV = "BS_TESTOPS_BUILD_COMPLETED";
147
+ var BROWSERSTACK_ACCESSIBILITY = "BROWSERSTACK_ACCESSIBILITY";
148
+ var BROWSERSTACK_OBSERVABILITY = "BROWSERSTACK_OBSERVABILITY";
149
+ var MAX_GIT_META_DATA_SIZE_IN_BYTES = 64 * 1024;
150
+ var GIT_META_DATA_TRUNCATED = "...[TRUNCATED]";
151
+
152
+ // src/bstackLogger.ts
153
+ var log = logger("@wdio/browserstack-service");
154
+ var BStackLogger = class {
155
+ static logFilePath = path.join(process.cwd(), LOGS_FILE);
156
+ static logFolderPath = path.join(process.cwd(), "logs");
157
+ static logFileStream;
158
+ static logToFile(logMessage, logLevel) {
159
+ try {
160
+ if (!this.logFileStream) {
161
+ this.ensureLogsFolder();
162
+ this.logFileStream = fs.createWriteStream(this.logFilePath, { flags: "a" });
163
+ }
164
+ if (this.logFileStream && this.logFileStream.writable) {
165
+ this.logFileStream.write(this.formatLog(logMessage, logLevel));
166
+ }
167
+ } catch (error) {
168
+ log.debug(`Failed to log to file. Error ${error}`);
169
+ }
170
+ }
171
+ static formatLog(logMessage, level) {
172
+ return `${chalk.gray((/* @__PURE__ */ new Date()).toISOString())} ${chalk[COLORS[level]](level.toUpperCase())} ${chalk.whiteBright("@wdio/browserstack-service")} ${logMessage}
173
+ `;
174
+ }
175
+ static info(message) {
176
+ this.logToFile(message, "info");
177
+ log.info(message);
178
+ }
179
+ static error(message) {
180
+ this.logToFile(message, "error");
181
+ log.error(message);
182
+ }
183
+ static debug(message, param) {
184
+ this.logToFile(message, "debug");
185
+ if (param) {
186
+ log.debug(message, param);
187
+ } else {
188
+ log.debug(message);
189
+ }
190
+ }
191
+ static warn(message) {
192
+ this.logToFile(message, "warn");
193
+ log.warn(message);
194
+ }
195
+ static trace(message) {
196
+ this.logToFile(message, "trace");
197
+ log.trace(message);
198
+ }
199
+ static clearLogger() {
200
+ if (this.logFileStream) {
201
+ this.logFileStream.end();
202
+ }
203
+ this.logFileStream = null;
204
+ }
205
+ static clearLogFile() {
206
+ if (fs.existsSync(this.logFilePath)) {
207
+ fs.truncateSync(this.logFilePath);
208
+ }
209
+ }
210
+ static ensureLogsFolder() {
211
+ if (!fs.existsSync(this.logFolderPath)) {
212
+ fs.mkdirSync(this.logFolderPath);
213
+ }
214
+ }
215
+ };
216
+
217
+ // src/performance-tester.ts
218
+ var PerformanceTester = class {
219
+ static _observer;
220
+ static _csvWriter;
221
+ static _events = [];
222
+ static started = false;
223
+ static startMonitoring(csvName = "performance-report.csv") {
224
+ this._observer = new PerformanceObserver((list) => {
225
+ list.getEntries().forEach((entry) => {
226
+ this._events.push(entry);
227
+ });
228
+ });
229
+ this._observer.observe({ buffered: true, entryTypes: ["function"] });
230
+ this.started = true;
231
+ this._csvWriter = createObjectCsvWriter({
232
+ path: csvName,
233
+ header: [
234
+ { id: "name", title: "Function Name" },
235
+ { id: "time", title: "Execution Time (ms)" }
236
+ ]
237
+ });
238
+ }
239
+ static getPerformance() {
240
+ return performance;
241
+ }
242
+ static calculateTimes(methods) {
243
+ const times = {};
244
+ this._events.map((entry) => {
245
+ if (!times[entry.name]) {
246
+ times[entry.name] = 0;
247
+ }
248
+ times[entry.name] += entry.duration;
249
+ });
250
+ const timeTaken = methods.reduce((a, c) => {
251
+ return times[c] + (a || 0);
252
+ }, 0);
253
+ BStackLogger.info(`Time for ${methods} is ${timeTaken}`);
254
+ return timeTaken;
255
+ }
256
+ static async stopAndGenerate(filename = "performance-own.html") {
257
+ if (!this.started) {
258
+ return;
259
+ }
260
+ await sleep(2e3);
261
+ this._observer.disconnect();
262
+ this.started = false;
263
+ this.generateCSV(this._events);
264
+ const content = this.generateReport(this._events);
265
+ const path6 = process.cwd() + "/" + filename;
266
+ fs2.writeFile(path6, content, (err) => {
267
+ if (err) {
268
+ BStackLogger.error(`Error in writing html ${err}`);
269
+ return;
270
+ }
271
+ BStackLogger.info(`Performance report is at ${path6}`);
272
+ });
273
+ }
274
+ static generateReport(entries) {
275
+ let html = "<!DOCTYPE html><html><head><title>Performance Report</title></head><body>";
276
+ html += "<h1>Performance Report</h1>";
277
+ html += "<table><thead><tr><th>Function Name</th><th>Duration (ms)</th></tr></thead><tbody>";
278
+ entries.forEach((entry) => {
279
+ html += `<tr><td>${entry.name}</td><td>${entry.duration}</td></tr>`;
280
+ });
281
+ html += "</tbody></table></body></html>";
282
+ return html;
283
+ }
284
+ static generateCSV(entries) {
285
+ const times = {};
286
+ entries.map((entry) => {
287
+ if (!times[entry.name]) {
288
+ times[entry.name] = 0;
289
+ }
290
+ times[entry.name] += entry.duration;
291
+ return {
292
+ name: entry.name,
293
+ time: entry.duration
294
+ };
295
+ });
296
+ const dat = Object.entries(times).map(([key, value]) => {
297
+ return {
298
+ name: key,
299
+ time: value
300
+ };
301
+ });
302
+ this._csvWriter.writeRecords(dat).then(() => BStackLogger.info("Performance CSV report generated successfully")).catch((error) => console.error(error));
303
+ }
304
+ };
305
+
306
+ // src/testHub/utils.ts
307
+ var getProductMap = (config) => {
308
+ return {
309
+ observability: config.testObservability.enabled,
310
+ accessibility: config.accessibility,
311
+ percy: config.percy,
312
+ automate: config.automate,
313
+ app_automate: config.appAutomate
314
+ };
315
+ };
316
+ var handleErrorForObservability = (error) => {
317
+ process.env[BROWSERSTACK_OBSERVABILITY] = "false";
318
+ logBuildError(error, "observability");
319
+ };
320
+ var handleErrorForAccessibility = (error) => {
321
+ process.env[BROWSERSTACK_ACCESSIBILITY] = "false";
322
+ logBuildError(error, "accessibility");
323
+ };
324
+ var logBuildError = (error, product = "") => {
325
+ if (!error || !error.errors) {
326
+ BStackLogger.error(`${product.toUpperCase()} Build creation failed ${error}`);
327
+ return;
328
+ }
329
+ for (const errorJson of error.errors) {
330
+ const errorType = errorJson.key;
331
+ const errorMessage = errorJson.message;
332
+ if (errorMessage) {
333
+ switch (errorType) {
334
+ case "ERROR_INVALID_CREDENTIALS":
335
+ BStackLogger.error(errorMessage);
336
+ break;
337
+ case "ERROR_ACCESS_DENIED":
338
+ BStackLogger.info(errorMessage);
339
+ break;
340
+ case "ERROR_SDK_DEPRECATED":
341
+ BStackLogger.error(errorMessage);
342
+ break;
343
+ default:
344
+ BStackLogger.error(errorMessage);
345
+ }
346
+ }
347
+ }
348
+ };
349
+
350
+ // src/crash-reporter.ts
351
+ var CrashReporter = class {
352
+ /* User test config for build run minus PII */
353
+ static userConfigForReporting = {};
354
+ /* User credentials used for reporting crashes in browserstack service */
355
+ static credentialsForCrashReportUpload = {};
356
+ static setCredentialsForCrashReportUpload(options, config) {
357
+ this.credentialsForCrashReportUpload = {
358
+ username: getObservabilityUser(options, config),
359
+ password: getObservabilityKey(options, config)
360
+ };
361
+ process.env.CREDENTIALS_FOR_CRASH_REPORTING = JSON.stringify(this.credentialsForCrashReportUpload);
362
+ }
363
+ static setConfigDetails(userConfig, capabilities, options) {
364
+ const configWithoutPII = this.filterPII(userConfig);
365
+ const filteredCapabilities = this.filterCapabilities(capabilities);
366
+ this.userConfigForReporting = {
367
+ framework: userConfig.framework,
368
+ services: configWithoutPII.services,
369
+ capabilities: filteredCapabilities,
370
+ env: {
371
+ "BROWSERSTACK_BUILD": process.env.BROWSERSTACK_BUILD,
372
+ "BROWSERSTACK_BUILD_NAME": process.env.BROWSERSTACK_BUILD_NAME,
373
+ "BUILD_TAG": process.env.BUILD_TAG
374
+ }
375
+ };
376
+ process.env.USER_CONFIG_FOR_REPORTING = JSON.stringify(this.userConfigForReporting);
377
+ this.setCredentialsForCrashReportUpload(options, userConfig);
378
+ }
379
+ static async uploadCrashReport(exception, stackTrace) {
380
+ try {
381
+ if (!this.credentialsForCrashReportUpload.username || !this.credentialsForCrashReportUpload.password) {
382
+ this.credentialsForCrashReportUpload = process.env.CREDENTIALS_FOR_CRASH_REPORTING !== void 0 ? JSON.parse(process.env.CREDENTIALS_FOR_CRASH_REPORTING) : this.credentialsForCrashReportUpload;
383
+ }
384
+ } catch (error) {
385
+ return BStackLogger.error(`[Crash_Report_Upload] Failed to parse user credentials while reporting crash due to ${error}`);
386
+ }
387
+ if (!this.credentialsForCrashReportUpload.username || !this.credentialsForCrashReportUpload.password) {
388
+ return BStackLogger.error("[Crash_Report_Upload] Failed to parse user credentials while reporting crash");
389
+ }
390
+ try {
391
+ if (Object.keys(this.userConfigForReporting).length === 0) {
392
+ this.userConfigForReporting = process.env.USER_CONFIG_FOR_REPORTING !== void 0 ? JSON.parse(process.env.USER_CONFIG_FOR_REPORTING) : {};
393
+ }
394
+ } catch (error) {
395
+ BStackLogger.error(`[Crash_Report_Upload] Failed to parse user config while reporting crash due to ${error}`);
396
+ this.userConfigForReporting = {};
397
+ }
398
+ const data = {
399
+ hashed_id: process.env[BROWSERSTACK_TESTHUB_UUID],
400
+ observability_version: {
401
+ frameworkName: "WebdriverIO-" + (this.userConfigForReporting.framework || "null"),
402
+ sdkVersion: BSTACK_SERVICE_VERSION
403
+ },
404
+ exception: {
405
+ error: exception.toString(),
406
+ stackTrace
407
+ },
408
+ config: this.userConfigForReporting
409
+ };
410
+ const url = `${DATA_ENDPOINT}/api/v1/analytics`;
411
+ const encodedAuth = Buffer.from(`${this.credentialsForCrashReportUpload.username}:${this.credentialsForCrashReportUpload.password}`, "utf8").toString("base64");
412
+ const headers = {
413
+ ...DEFAULT_REQUEST_CONFIG.headers,
414
+ Authorization: `Basic ${encodedAuth}`
415
+ };
416
+ const response = await fetch(url, {
417
+ method: "POST",
418
+ body: JSON.stringify(data),
419
+ headers
420
+ });
421
+ if (response.ok) {
422
+ BStackLogger.debug(`[Crash_Report_Upload] Success response: ${JSON.stringify(await response.json())}`);
423
+ } else {
424
+ BStackLogger.error(`[Crash_Report_Upload] Failed due to ${response.body}`);
425
+ }
426
+ }
427
+ static recursivelyRedactKeysFromObject(obj, keys) {
428
+ if (!obj) {
429
+ return;
430
+ }
431
+ if (Array.isArray(obj)) {
432
+ obj.map((ele) => this.recursivelyRedactKeysFromObject(ele, keys));
433
+ } else {
434
+ for (const prop in obj) {
435
+ if (keys.includes(prop.toLowerCase())) {
436
+ obj[prop] = "[REDACTED]";
437
+ } else if (typeof obj[prop] === "object") {
438
+ this.recursivelyRedactKeysFromObject(obj[prop], keys);
439
+ }
440
+ }
441
+ }
442
+ }
443
+ static deletePIIKeysFromObject(obj) {
444
+ if (!obj) {
445
+ return;
446
+ }
447
+ ["user", "username", "key", "accessKey"].forEach((key) => delete obj[key]);
448
+ }
449
+ static filterCapabilities(capabilities) {
450
+ const capsCopy = JSON.parse(JSON.stringify(capabilities));
451
+ this.recursivelyRedactKeysFromObject(capsCopy, ["extensions"]);
452
+ return capsCopy;
453
+ }
454
+ static filterPII(userConfig) {
455
+ const configWithoutPII = JSON.parse(JSON.stringify(userConfig));
456
+ this.deletePIIKeysFromObject(configWithoutPII);
457
+ const finalServices = [];
458
+ const initialServices = configWithoutPII.services;
459
+ delete configWithoutPII.services;
460
+ try {
461
+ for (const serviceArray of initialServices) {
462
+ if (Array.isArray(serviceArray) && serviceArray.length >= 2 && serviceArray[0] === "browserstack") {
463
+ for (let idx = 1; idx < serviceArray.length; idx++) {
464
+ this.deletePIIKeysFromObject(serviceArray[idx]);
465
+ if (serviceArray[idx]) {
466
+ this.deletePIIKeysFromObject(serviceArray[idx].testObservabilityOptions);
467
+ }
468
+ }
469
+ finalServices.push(serviceArray);
470
+ break;
471
+ }
472
+ }
473
+ } catch (err) {
474
+ BStackLogger.error(`Error in parsing user config PII with error ${err ? err.stack || err : err}`);
475
+ return configWithoutPII;
476
+ }
477
+ configWithoutPII.services = finalServices;
478
+ return configWithoutPII;
479
+ }
480
+ };
481
+
482
+ // src/testOps/featureStats.ts
483
+ var FeatureStats = class _FeatureStats {
484
+ triggeredCount = 0;
485
+ sentCount = 0;
486
+ failedCount = 0;
487
+ groups = {};
488
+ mark(status, groupId) {
489
+ switch (status) {
490
+ case "triggered":
491
+ this.triggered(groupId);
492
+ break;
493
+ case "success":
494
+ case "sent":
495
+ this.sent(groupId);
496
+ break;
497
+ case "failed":
498
+ this.failed(groupId);
499
+ break;
500
+ default:
501
+ BStackLogger.debug("Request to mark usage for unknown status - " + status);
502
+ break;
503
+ }
504
+ }
505
+ triggered(groupId) {
506
+ this.triggeredCount += 1;
507
+ if (groupId) {
508
+ this.createGroup(groupId).triggered();
509
+ }
510
+ }
511
+ sent(groupId) {
512
+ this.sentCount += 1;
513
+ if (groupId) {
514
+ this.createGroup(groupId).sent();
515
+ }
516
+ }
517
+ failed(groupId) {
518
+ this.failedCount += 1;
519
+ if (groupId) {
520
+ this.createGroup(groupId).failed();
521
+ }
522
+ }
523
+ success(groupId) {
524
+ this.sent(groupId);
525
+ }
526
+ createGroup(groupId) {
527
+ if (!this.groups[groupId]) {
528
+ this.groups[groupId] = new _FeatureStats();
529
+ }
530
+ return this.groups[groupId];
531
+ }
532
+ getTriggeredCount() {
533
+ return this.triggeredCount;
534
+ }
535
+ getSentCount() {
536
+ return this.sentCount;
537
+ }
538
+ getFailedCount() {
539
+ return this.failedCount;
540
+ }
541
+ getUsageForGroup(groupId) {
542
+ return this.groups[groupId] || new _FeatureStats();
543
+ }
544
+ getOverview() {
545
+ return { triggeredCount: this.triggeredCount, sentCount: this.sentCount, failedCount: this.failedCount };
546
+ }
547
+ getGroups() {
548
+ return this.groups;
549
+ }
550
+ add(featureStats) {
551
+ this.triggeredCount += featureStats.getTriggeredCount();
552
+ this.sentCount += featureStats.getSentCount();
553
+ this.failedCount += featureStats.getFailedCount();
554
+ Object.entries(featureStats.getGroups()).forEach(([groupId, group]) => {
555
+ this.createGroup(groupId).add(group);
556
+ });
557
+ }
558
+ // omitGroups: true/false -> Include groups or not
559
+ // onlyGroups: true/false -> data includes only groups
560
+ // nestedGroups: true/false -> groups will be nested in groups if true
561
+ toJSON(config = {}) {
562
+ const overviewData = !config.onlyGroups ? {
563
+ triggeredCount: this.triggeredCount,
564
+ sentCount: this.sentCount,
565
+ failedCount: this.failedCount
566
+ } : {};
567
+ const groupsData = {};
568
+ if (!config.omitGroups) {
569
+ Object.entries(this.groups).forEach(([groupId, group2]) => {
570
+ groupsData[groupId] = group2.toJSON();
571
+ });
572
+ }
573
+ const group = config.nestedGroups ? { groups: groupsData } : groupsData;
574
+ return {
575
+ ...overviewData,
576
+ ...group
577
+ };
578
+ }
579
+ static fromJSON(json) {
580
+ const stats = new _FeatureStats();
581
+ if (!json || isObjectEmpty(json)) {
582
+ return stats;
583
+ }
584
+ stats.triggeredCount = json.triggeredCount;
585
+ stats.sentCount = json.sentCount;
586
+ stats.failedCount = json.failedCount;
587
+ if (!json.groups) {
588
+ return stats;
589
+ }
590
+ Object.entries(json.groups).forEach(([groupId, group]) => {
591
+ stats.groups[groupId] = _FeatureStats.fromJSON(group);
592
+ });
593
+ return stats;
594
+ }
595
+ };
596
+ var featureStats_default = FeatureStats;
597
+
598
+ // src/testOps/featureUsage.ts
599
+ var FeatureUsage = class {
600
+ isTriggered;
601
+ status;
602
+ error;
603
+ constructor(isTriggered) {
604
+ if (isTriggered !== void 0) {
605
+ this.isTriggered = isTriggered;
606
+ }
607
+ }
608
+ getTriggered() {
609
+ return this.isTriggered;
610
+ }
611
+ setTriggered(triggered) {
612
+ this.isTriggered = triggered;
613
+ }
614
+ setStatus(status) {
615
+ this.status = status;
616
+ }
617
+ setError(error) {
618
+ this.error = error;
619
+ }
620
+ triggered() {
621
+ this.isTriggered = true;
622
+ }
623
+ failed(e) {
624
+ this.status = "failed";
625
+ this.error = getErrorString(e);
626
+ }
627
+ success() {
628
+ this.status = "success";
629
+ }
630
+ getStatus() {
631
+ return this.status;
632
+ }
633
+ getError() {
634
+ return this.error;
635
+ }
636
+ toJSON() {
637
+ return {
638
+ isTriggered: this.isTriggered,
639
+ status: this.status,
640
+ error: this.error
641
+ };
642
+ }
643
+ };
644
+ var featureUsage_default = FeatureUsage;
645
+
646
+ // src/testOps/testOpsConfig.ts
647
+ var TestOpsConfig = class _TestOpsConfig {
648
+ constructor(enabled = true, manuallySet = false) {
649
+ this.enabled = enabled;
650
+ this.manuallySet = manuallySet;
651
+ _TestOpsConfig._instance = this;
652
+ }
653
+ static _instance;
654
+ buildStopped = false;
655
+ buildHashedId;
656
+ static getInstance(...args) {
657
+ if (!this._instance) {
658
+ this._instance = new _TestOpsConfig(...args);
659
+ }
660
+ return this._instance;
661
+ }
662
+ };
663
+ var testOpsConfig_default = TestOpsConfig;
664
+
665
+ // src/testOps/usageStats.ts
666
+ var UsageStats = class _UsageStats {
667
+ static instance;
668
+ testStartedStats;
669
+ testFinishedStats;
670
+ hookStartedStats;
671
+ hookFinishedStats;
672
+ cbtSessionStats;
673
+ logStats;
674
+ launchBuildUsage;
675
+ stopBuildUsage;
676
+ static getInstance() {
677
+ if (!_UsageStats.instance) {
678
+ _UsageStats.instance = new _UsageStats();
679
+ }
680
+ return _UsageStats.instance;
681
+ }
682
+ constructor() {
683
+ this.testStartedStats = new featureStats_default();
684
+ this.testFinishedStats = new featureStats_default();
685
+ this.hookStartedStats = new featureStats_default();
686
+ this.hookFinishedStats = new featureStats_default();
687
+ this.cbtSessionStats = new featureStats_default();
688
+ this.logStats = new featureStats_default();
689
+ this.launchBuildUsage = new featureUsage_default();
690
+ this.stopBuildUsage = new featureUsage_default();
691
+ }
692
+ add(usageStats) {
693
+ this.testStartedStats.add(usageStats.testStartedStats);
694
+ this.testFinishedStats.add(usageStats.testFinishedStats);
695
+ this.hookStartedStats.add(usageStats.hookStartedStats);
696
+ this.hookFinishedStats.add(usageStats.hookFinishedStats);
697
+ this.cbtSessionStats.add(usageStats.cbtSessionStats);
698
+ this.logStats.add(usageStats.logStats);
699
+ }
700
+ getFormattedData(workersData) {
701
+ this.addDataFromWorkers(workersData);
702
+ const testOpsConfig = testOpsConfig_default.getInstance();
703
+ const usage = {
704
+ enabled: testOpsConfig.enabled,
705
+ manuallySet: testOpsConfig.manuallySet,
706
+ buildHashedId: testOpsConfig.buildHashedId
707
+ };
708
+ if (!usage.enabled) {
709
+ return usage;
710
+ }
711
+ try {
712
+ usage.events = this.getEventsData();
713
+ } catch (e) {
714
+ BStackLogger.debug("exception in getFormattedData: " + e);
715
+ }
716
+ return usage;
717
+ }
718
+ addDataFromWorkers(workersData) {
719
+ workersData.map((workerData) => {
720
+ try {
721
+ const usageStatsForWorker = _UsageStats.fromJSON(workerData.usageStats);
722
+ this.add(usageStatsForWorker);
723
+ } catch (e) {
724
+ BStackLogger.debug("Exception in adding workerData: " + e);
725
+ }
726
+ });
727
+ }
728
+ getEventsData() {
729
+ return {
730
+ buildEvents: {
731
+ started: this.launchBuildUsage.toJSON(),
732
+ finished: this.stopBuildUsage.toJSON()
733
+ },
734
+ testEvents: {
735
+ started: this.testStartedStats.toJSON(),
736
+ finished: this.testFinishedStats.toJSON({ omitGroups: true }),
737
+ ...this.testFinishedStats.toJSON({ onlyGroups: true })
738
+ },
739
+ hookEvents: {
740
+ started: this.hookStartedStats.toJSON(),
741
+ finished: this.hookFinishedStats.toJSON({ omitGroups: true }),
742
+ ...this.hookFinishedStats.toJSON({ onlyGroups: true })
743
+ },
744
+ logEvents: this.logStats.toJSON(),
745
+ cbtSessionEvents: this.cbtSessionStats.toJSON()
746
+ };
747
+ }
748
+ getDataToSave() {
749
+ return {
750
+ testEvents: {
751
+ started: this.testStartedStats.toJSON(),
752
+ finished: this.testFinishedStats.toJSON({ nestedGroups: true })
753
+ },
754
+ hookEvents: {
755
+ started: this.hookStartedStats.toJSON(),
756
+ finished: this.hookFinishedStats.toJSON({ nestedGroups: true })
757
+ },
758
+ logEvents: this.logStats.toJSON({ nestedGroups: true }),
759
+ cbtSessionEvents: this.cbtSessionStats.toJSON()
760
+ };
761
+ }
762
+ static fromJSON(data) {
763
+ const usageStats = new _UsageStats();
764
+ usageStats.testStartedStats = featureStats_default.fromJSON(data.testEvents.started);
765
+ usageStats.testFinishedStats = featureStats_default.fromJSON(data.testEvents.finished);
766
+ usageStats.hookStartedStats = featureStats_default.fromJSON(data.hookEvents.started);
767
+ usageStats.hookFinishedStats = featureStats_default.fromJSON(data.hookEvents.finished);
768
+ usageStats.logStats = featureStats_default.fromJSON(data.logEvents);
769
+ usageStats.cbtSessionStats = featureStats_default.fromJSON(data.cbtSessionStats);
770
+ return usageStats;
771
+ }
772
+ };
773
+ var usageStats_default = UsageStats;
774
+
775
+ // src/scripts/accessibility-scripts.ts
776
+ import path2 from "node:path";
777
+ import fs3 from "node:fs";
778
+ import os from "node:os";
779
+ var AccessibilityScripts = class _AccessibilityScripts {
780
+ static instance = null;
781
+ performScan = null;
782
+ getResults = null;
783
+ getResultsSummary = null;
784
+ saveTestResults = null;
785
+ commandsToWrap = null;
786
+ browserstackFolderPath = "";
787
+ commandsPath = "";
788
+ // don't allow to create instances from it other than through `checkAndGetInstance`
789
+ constructor() {
790
+ this.browserstackFolderPath = this.getWritableDir();
791
+ this.commandsPath = path2.join(this.browserstackFolderPath, "commands.json");
792
+ }
793
+ static checkAndGetInstance() {
794
+ if (!_AccessibilityScripts.instance) {
795
+ _AccessibilityScripts.instance = new _AccessibilityScripts();
796
+ _AccessibilityScripts.instance.readFromExistingFile();
797
+ }
798
+ return _AccessibilityScripts.instance;
799
+ }
800
+ /* eslint-disable @typescript-eslint/no-unused-vars */
801
+ getWritableDir() {
802
+ const orderedPaths = [
803
+ path2.join(os.homedir(), ".browserstack"),
804
+ process.cwd(),
805
+ os.tmpdir()
806
+ ];
807
+ for (const orderedPath of orderedPaths) {
808
+ try {
809
+ if (fs3.existsSync(orderedPath)) {
810
+ fs3.accessSync(orderedPath);
811
+ return orderedPath;
812
+ }
813
+ fs3.mkdirSync(orderedPath, { recursive: true });
814
+ return orderedPath;
815
+ } catch (error) {
816
+ }
817
+ }
818
+ return "";
819
+ }
820
+ readFromExistingFile() {
821
+ try {
822
+ if (fs3.existsSync(this.commandsPath)) {
823
+ const data = fs3.readFileSync(this.commandsPath, "utf8");
824
+ if (data) {
825
+ this.update(JSON.parse(data));
826
+ }
827
+ }
828
+ } catch {
829
+ }
830
+ }
831
+ update(data) {
832
+ if (data.scripts) {
833
+ this.performScan = data.scripts.scan;
834
+ this.getResults = data.scripts.getResults;
835
+ this.getResultsSummary = data.scripts.getResultsSummary;
836
+ this.saveTestResults = data.scripts.saveResults;
837
+ }
838
+ if (data.commands && data.commands.length) {
839
+ this.commandsToWrap = data.commands;
840
+ }
841
+ }
842
+ store() {
843
+ if (!fs3.existsSync(this.browserstackFolderPath)) {
844
+ fs3.mkdirSync(this.browserstackFolderPath);
845
+ }
846
+ fs3.writeFileSync(this.commandsPath, JSON.stringify({
847
+ commands: this.commandsToWrap,
848
+ scripts: {
849
+ scan: this.performScan,
850
+ getResults: this.getResults,
851
+ getResultsSummary: this.getResultsSummary,
852
+ saveResults: this.saveTestResults
853
+ }
854
+ }));
855
+ }
856
+ };
857
+ var accessibility_scripts_default = AccessibilityScripts.checkAndGetInstance();
858
+
859
+ // src/util.ts
860
+ var pGitconfig = promisify(gitconfig);
861
+ var DEFAULT_REQUEST_CONFIG = {
862
+ headers: {
863
+ "Content-Type": "application/json",
864
+ "X-BSTACK-OBS": "true"
865
+ }
866
+ };
867
+ var COLORS = {
868
+ error: "red",
869
+ warn: "yellow",
870
+ info: "cyanBright",
871
+ debug: "green",
872
+ trace: "cyan",
873
+ progress: "magenta"
874
+ };
875
+ function processError(error, fn, args) {
876
+ BStackLogger.error(`Error in executing ${fn.name} with args ${args}: ${error}`);
877
+ let argsString;
878
+ try {
879
+ argsString = JSON.stringify(args);
880
+ } catch {
881
+ argsString = util.inspect(args, { depth: 2 });
882
+ }
883
+ CrashReporter.uploadCrashReport(`Error in executing ${fn.name} with args ${argsString} : ${error}`, error && error.stack || "unknown error");
884
+ }
885
+ function o11yErrorHandler(fn) {
886
+ return function(...args) {
887
+ try {
888
+ let functionToHandle = fn;
889
+ if (process.env[PERF_MEASUREMENT_ENV]) {
890
+ functionToHandle = PerformanceTester.getPerformance().timerify(functionToHandle);
891
+ }
892
+ const result = functionToHandle(...args);
893
+ if (result instanceof Promise) {
894
+ return result.catch((error) => processError(error, fn, args));
895
+ }
896
+ return result;
897
+ } catch (error) {
898
+ processError(error, fn, args);
899
+ }
900
+ };
901
+ }
902
+ var processTestObservabilityResponse = (response) => {
903
+ if (!response.observability) {
904
+ handleErrorForObservability(null);
905
+ return;
906
+ }
907
+ if (!response.observability.success) {
908
+ handleErrorForObservability(response.observability);
909
+ return;
910
+ }
911
+ process.env[BROWSERSTACK_OBSERVABILITY] = "true";
912
+ if (response.observability.options.allow_screenshots) {
913
+ process.env[TESTOPS_SCREENSHOT_ENV] = response.observability.options.allow_screenshots.toString();
914
+ }
915
+ };
916
+ var jsonifyAccessibilityArray = (dataArray, keyName, valueName) => {
917
+ const result = {};
918
+ dataArray.forEach((element) => {
919
+ result[element[keyName]] = element[valueName];
920
+ });
921
+ return result;
922
+ };
923
+ var processAccessibilityResponse = (response) => {
924
+ if (!response.accessibility) {
925
+ handleErrorForAccessibility(null);
926
+ return;
927
+ }
928
+ if (!response.accessibility.success) {
929
+ handleErrorForAccessibility(response.accessibility);
930
+ return;
931
+ }
932
+ if (response.accessibility.options) {
933
+ const { accessibilityToken, pollingTimeout, scannerVersion } = jsonifyAccessibilityArray(response.accessibility.options.capabilities, "name", "value");
934
+ const scriptsJson = {
935
+ "scripts": jsonifyAccessibilityArray(response.accessibility.options.scripts, "name", "command"),
936
+ "commands": response.accessibility.options.commandsToWrap.commands
937
+ };
938
+ if (scannerVersion) {
939
+ process.env.BSTACK_A11Y_SCANNER_VERSION = scannerVersion;
940
+ BStackLogger.debug(`Accessibility scannerVersion ${scannerVersion}`);
941
+ }
942
+ if (accessibilityToken) {
943
+ process.env.BSTACK_A11Y_JWT = accessibilityToken;
944
+ process.env[BROWSERSTACK_ACCESSIBILITY] = "true";
945
+ }
946
+ if (pollingTimeout) {
947
+ process.env.BSTACK_A11Y_POLLING_TIMEOUT = pollingTimeout;
948
+ }
949
+ if (scriptsJson) {
950
+ accessibility_scripts_default.update(scriptsJson);
951
+ accessibility_scripts_default.store();
952
+ }
953
+ }
954
+ };
955
+ var processLaunchBuildResponse = (response, options) => {
956
+ if (options.testObservability) {
957
+ processTestObservabilityResponse(response);
958
+ }
959
+ if (options.accessibility) {
960
+ processAccessibilityResponse(response);
961
+ }
962
+ };
963
+ var launchTestSession = o11yErrorHandler(async function launchTestSession2(options, config, bsConfig, bStackConfig) {
964
+ const launchBuildUsage = usageStats_default.getInstance().launchBuildUsage;
965
+ launchBuildUsage.triggered();
966
+ const data = {
967
+ format: "json",
968
+ project_name: getObservabilityProject(options, bsConfig.projectName),
969
+ name: getObservabilityBuild(options, bsConfig.buildName),
970
+ build_identifier: bsConfig.buildIdentifier,
971
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
972
+ tags: getObservabilityBuildTags(options, bsConfig.buildTag),
973
+ host_info: {
974
+ hostname: hostname(),
975
+ platform: platform(),
976
+ type: type(),
977
+ version: version(),
978
+ arch: arch()
979
+ },
980
+ ci_info: getCiInfo(),
981
+ build_run_identifier: process.env.BROWSERSTACK_BUILD_RUN_IDENTIFIER,
982
+ failed_tests_rerun: process.env[RERUN_ENV] || false,
983
+ version_control: await getGitMetaData(),
984
+ accessibility: {
985
+ settings: options.accessibilityOptions
986
+ },
987
+ browserstackAutomation: shouldAddServiceVersion(config, options.testObservability),
988
+ framework_details: {
989
+ frameworkName: "WebdriverIO-" + config.framework,
990
+ frameworkVersion: bsConfig.bstackServiceVersion,
991
+ sdkVersion: bsConfig.bstackServiceVersion,
992
+ language: "ECMAScript",
993
+ testFramework: {
994
+ name: "WebdriverIO",
995
+ version: bsConfig.bstackServiceVersion
996
+ }
997
+ },
998
+ product_map: getProductMap(bStackConfig),
999
+ config: {}
1000
+ };
1001
+ try {
1002
+ if (Object.keys(CrashReporter.userConfigForReporting).length === 0) {
1003
+ CrashReporter.userConfigForReporting = process.env.USER_CONFIG_FOR_REPORTING !== void 0 ? JSON.parse(process.env.USER_CONFIG_FOR_REPORTING) : {};
1004
+ }
1005
+ } catch (error) {
1006
+ return BStackLogger.error(`[Crash_Report_Upload] Failed to parse user config while sending build start event due to ${error}`);
1007
+ }
1008
+ data.config = CrashReporter.userConfigForReporting;
1009
+ try {
1010
+ const url = `${DATA_ENDPOINT}/api/v2/builds`;
1011
+ const encodedAuth = Buffer.from(`${getObservabilityUser(options, config)}:${getObservabilityKey(options, config)}`, "utf8").toString("base64");
1012
+ const headers = {
1013
+ ...DEFAULT_REQUEST_CONFIG.headers,
1014
+ Authorization: `Basic ${encodedAuth}`
1015
+ };
1016
+ const response = await fetch(url, {
1017
+ method: "POST",
1018
+ headers,
1019
+ body: JSON.stringify(data)
1020
+ });
1021
+ const jsonResponse = await response.json();
1022
+ BStackLogger.debug(`[Start_Build] Success response: ${JSON.stringify(jsonResponse)}`);
1023
+ process.env[TESTOPS_BUILD_COMPLETED_ENV] = "true";
1024
+ if (jsonResponse.jwt) {
1025
+ process.env[BROWSERSTACK_TESTHUB_JWT] = jsonResponse.jwt;
1026
+ }
1027
+ if (jsonResponse.build_hashed_id) {
1028
+ process.env[BROWSERSTACK_TESTHUB_UUID] = jsonResponse.build_hashed_id;
1029
+ testOpsConfig_default.getInstance().buildHashedId = jsonResponse.build_hashed_id;
1030
+ }
1031
+ processLaunchBuildResponse(jsonResponse, options);
1032
+ launchBuildUsage.success();
1033
+ } catch (error) {
1034
+ BStackLogger.debug(`TestHub build start failed: ${format(error)}`);
1035
+ if (!error.success) {
1036
+ launchBuildUsage.failed(error);
1037
+ logBuildError(error);
1038
+ return;
1039
+ }
1040
+ }
1041
+ });
1042
+ var stopBuildUpstream = o11yErrorHandler(async function stopBuildUpstream2() {
1043
+ const stopBuildUsage = usageStats_default.getInstance().stopBuildUsage;
1044
+ stopBuildUsage.triggered();
1045
+ if (!process.env[TESTOPS_BUILD_COMPLETED_ENV]) {
1046
+ stopBuildUsage.failed("Build is not completed yet");
1047
+ return {
1048
+ status: "error",
1049
+ message: "Build is not completed yet"
1050
+ };
1051
+ }
1052
+ if (!process.env[BROWSERSTACK_TESTHUB_JWT]) {
1053
+ stopBuildUsage.failed("Token/buildID is undefined, build creation might have failed");
1054
+ BStackLogger.debug("[STOP_BUILD] Missing Authentication Token/ Build ID");
1055
+ return {
1056
+ status: "error",
1057
+ message: "Token/buildID is undefined, build creation might have failed"
1058
+ };
1059
+ }
1060
+ const data = {
1061
+ "stop_time": (/* @__PURE__ */ new Date()).toISOString()
1062
+ };
1063
+ try {
1064
+ const url = `${DATA_ENDPOINT}/api/v1/builds/${process.env[BROWSERSTACK_TESTHUB_UUID]}/stop`;
1065
+ const response = await fetch(url, {
1066
+ method: "PUT",
1067
+ headers: {
1068
+ ...DEFAULT_REQUEST_CONFIG.headers,
1069
+ "Authorization": `Bearer ${process.env[BROWSERSTACK_TESTHUB_JWT]}`
1070
+ },
1071
+ body: JSON.stringify(data)
1072
+ });
1073
+ BStackLogger.debug(`[STOP_BUILD] Success response: ${await response.text()}`);
1074
+ stopBuildUsage.success();
1075
+ return {
1076
+ status: "success",
1077
+ message: ""
1078
+ };
1079
+ } catch (error) {
1080
+ stopBuildUsage.failed(error);
1081
+ BStackLogger.debug(`[STOP_BUILD] Failed. Error: ${error}`);
1082
+ return {
1083
+ status: "error",
1084
+ message: error.message
1085
+ };
1086
+ }
1087
+ });
1088
+ function getCiInfo() {
1089
+ const env = process.env;
1090
+ if (typeof env.JENKINS_URL === "string" && env.JENKINS_URL.length > 0 || typeof env.JENKINS_HOME === "string" && env.JENKINS_HOME.length > 0) {
1091
+ return {
1092
+ name: "Jenkins",
1093
+ build_url: env.BUILD_URL,
1094
+ job_name: env.JOB_NAME,
1095
+ build_number: env.BUILD_NUMBER
1096
+ };
1097
+ }
1098
+ if (isTrue(env.CI) && isTrue(env.CIRCLECI)) {
1099
+ return {
1100
+ name: "CircleCI",
1101
+ build_url: env.CIRCLE_BUILD_URL,
1102
+ job_name: env.CIRCLE_JOB,
1103
+ build_number: env.CIRCLE_BUILD_NUM
1104
+ };
1105
+ }
1106
+ if (isTrue(env.CI) && isTrue(env.TRAVIS)) {
1107
+ return {
1108
+ name: "Travis CI",
1109
+ build_url: env.TRAVIS_BUILD_WEB_URL,
1110
+ job_name: env.TRAVIS_JOB_NAME,
1111
+ build_number: env.TRAVIS_BUILD_NUMBER
1112
+ };
1113
+ }
1114
+ if (isTrue(env.CI) && env.CI_NAME === "codeship") {
1115
+ return {
1116
+ name: "Codeship",
1117
+ build_url: null,
1118
+ job_name: null,
1119
+ build_number: null
1120
+ };
1121
+ }
1122
+ if (env.BITBUCKET_BRANCH && env.BITBUCKET_COMMIT) {
1123
+ return {
1124
+ name: "Bitbucket",
1125
+ build_url: env.BITBUCKET_GIT_HTTP_ORIGIN,
1126
+ job_name: null,
1127
+ build_number: env.BITBUCKET_BUILD_NUMBER
1128
+ };
1129
+ }
1130
+ if (isTrue(env.CI) && isTrue(env.DRONE)) {
1131
+ return {
1132
+ name: "Drone",
1133
+ build_url: env.DRONE_BUILD_LINK,
1134
+ job_name: null,
1135
+ build_number: env.DRONE_BUILD_NUMBER
1136
+ };
1137
+ }
1138
+ if (isTrue(env.CI) && isTrue(env.SEMAPHORE)) {
1139
+ return {
1140
+ name: "Semaphore",
1141
+ build_url: env.SEMAPHORE_ORGANIZATION_URL,
1142
+ job_name: env.SEMAPHORE_JOB_NAME,
1143
+ build_number: env.SEMAPHORE_JOB_ID
1144
+ };
1145
+ }
1146
+ if (isTrue(env.CI) && isTrue(env.GITLAB_CI)) {
1147
+ return {
1148
+ name: "GitLab",
1149
+ build_url: env.CI_JOB_URL,
1150
+ job_name: env.CI_JOB_NAME,
1151
+ build_number: env.CI_JOB_ID
1152
+ };
1153
+ }
1154
+ if (isTrue(env.CI) && isTrue(env.BUILDKITE)) {
1155
+ return {
1156
+ name: "Buildkite",
1157
+ build_url: env.BUILDKITE_BUILD_URL,
1158
+ job_name: env.BUILDKITE_LABEL || env.BUILDKITE_PIPELINE_NAME,
1159
+ build_number: env.BUILDKITE_BUILD_NUMBER
1160
+ };
1161
+ }
1162
+ if (isTrue(env.TF_BUILD) && env.TF_BUILD_BUILDNUMBER) {
1163
+ return {
1164
+ name: "Visual Studio Team Services",
1165
+ build_url: `${env.SYSTEM_TEAMFOUNDATIONSERVERURI}${env.SYSTEM_TEAMPROJECTID}`,
1166
+ job_name: env.SYSTEM_DEFINITIONID,
1167
+ build_number: env.BUILD_BUILDID
1168
+ };
1169
+ }
1170
+ if (isTrue(env.APPVEYOR)) {
1171
+ return {
1172
+ name: "Appveyor",
1173
+ build_url: `${env.APPVEYOR_URL}/project/${env.APPVEYOR_ACCOUNT_NAME}/${env.APPVEYOR_PROJECT_SLUG}/builds/${env.APPVEYOR_BUILD_ID}`,
1174
+ job_name: env.APPVEYOR_JOB_NAME,
1175
+ build_number: env.APPVEYOR_BUILD_NUMBER
1176
+ };
1177
+ }
1178
+ if (env.AZURE_HTTP_USER_AGENT && env.TF_BUILD) {
1179
+ return {
1180
+ name: "Azure CI",
1181
+ build_url: `${env.SYSTEM_TEAMFOUNDATIONSERVERURI}${env.SYSTEM_TEAMPROJECT}/_build/results?buildId=${env.BUILD_BUILDID}`,
1182
+ job_name: env.BUILD_BUILDID,
1183
+ build_number: env.BUILD_BUILDID
1184
+ };
1185
+ }
1186
+ if (env.CODEBUILD_BUILD_ID || env.CODEBUILD_RESOLVED_SOURCE_VERSION || env.CODEBUILD_SOURCE_VERSION) {
1187
+ return {
1188
+ name: "AWS CodeBuild",
1189
+ build_url: env.CODEBUILD_PUBLIC_BUILD_URL,
1190
+ job_name: env.CODEBUILD_BUILD_ID,
1191
+ build_number: env.CODEBUILD_BUILD_ID
1192
+ };
1193
+ }
1194
+ if (env.bamboo_buildNumber) {
1195
+ return {
1196
+ name: "Bamboo",
1197
+ build_url: env.bamboo_buildResultsUrl,
1198
+ job_name: env.bamboo_shortJobName,
1199
+ build_number: env.bamboo_buildNumber
1200
+ };
1201
+ }
1202
+ if (env.WERCKER || env.WERCKER_MAIN_PIPELINE_STARTED) {
1203
+ return {
1204
+ name: "Wercker",
1205
+ build_url: env.WERCKER_BUILD_URL,
1206
+ job_name: env.WERCKER_MAIN_PIPELINE_STARTED ? "Main Pipeline" : null,
1207
+ build_number: env.WERCKER_GIT_COMMIT
1208
+ };
1209
+ }
1210
+ if (env.GCP_PROJECT || env.GCLOUD_PROJECT || env.GOOGLE_CLOUD_PROJECT) {
1211
+ return {
1212
+ name: "Google Cloud",
1213
+ build_url: null,
1214
+ job_name: env.PROJECT_ID,
1215
+ build_number: env.BUILD_ID
1216
+ };
1217
+ }
1218
+ if (env.SHIPPABLE) {
1219
+ return {
1220
+ name: "Shippable",
1221
+ build_url: env.SHIPPABLE_BUILD_URL,
1222
+ job_name: env.SHIPPABLE_JOB_ID ? `Job #${env.SHIPPABLE_JOB_ID}` : null,
1223
+ build_number: env.SHIPPABLE_BUILD_NUMBER
1224
+ };
1225
+ }
1226
+ if (isTrue(env.NETLIFY)) {
1227
+ return {
1228
+ name: "Netlify",
1229
+ build_url: env.DEPLOY_URL,
1230
+ job_name: env.SITE_NAME,
1231
+ build_number: env.BUILD_ID
1232
+ };
1233
+ }
1234
+ if (isTrue(env.GITHUB_ACTIONS)) {
1235
+ return {
1236
+ name: "GitHub Actions",
1237
+ build_url: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`,
1238
+ job_name: env.GITHUB_WORKFLOW,
1239
+ build_number: env.GITHUB_RUN_ID
1240
+ };
1241
+ }
1242
+ if (isTrue(env.CI) && env.VERCEL === "1") {
1243
+ return {
1244
+ name: "Vercel",
1245
+ build_url: `http://${env.VERCEL_URL}`,
1246
+ job_name: null,
1247
+ build_number: null
1248
+ };
1249
+ }
1250
+ if (env.TEAMCITY_VERSION) {
1251
+ return {
1252
+ name: "Teamcity",
1253
+ build_url: null,
1254
+ job_name: null,
1255
+ build_number: env.BUILD_NUMBER
1256
+ };
1257
+ }
1258
+ if (env.CONCOURSE || env.CONCOURSE_URL || env.CONCOURSE_USERNAME || env.CONCOURSE_TEAM) {
1259
+ return {
1260
+ name: "Concourse",
1261
+ build_url: null,
1262
+ job_name: env.BUILD_JOB_NAME || null,
1263
+ build_number: env.BUILD_ID || null
1264
+ };
1265
+ }
1266
+ if (env.GO_JOB_NAME) {
1267
+ return {
1268
+ name: "GoCD",
1269
+ build_url: null,
1270
+ job_name: env.GO_JOB_NAME,
1271
+ build_number: env.GO_PIPELINE_COUNTER
1272
+ };
1273
+ }
1274
+ if (env.CF_BUILD_ID) {
1275
+ return {
1276
+ name: "CodeFresh",
1277
+ build_url: env.CF_BUILD_URL,
1278
+ job_name: env.CF_PIPELINE_NAME,
1279
+ build_number: env.CF_BUILD_ID
1280
+ };
1281
+ }
1282
+ return null;
1283
+ }
1284
+ async function getGitMetaData() {
1285
+ const info = gitRepoInfo();
1286
+ if (!info.commonGitDir) {
1287
+ return;
1288
+ }
1289
+ const { remote } = await pGitconfig(info.commonGitDir);
1290
+ const remotes = remote ? Object.keys(remote).map((remoteName) => ({ name: remoteName, url: remote[remoteName].url })) : [];
1291
+ let gitMetaData = {
1292
+ name: "git",
1293
+ sha: info.sha,
1294
+ short_sha: info.abbreviatedSha,
1295
+ branch: info.branch,
1296
+ tag: info.tag,
1297
+ committer: info.committer,
1298
+ committer_date: info.committerDate,
1299
+ author: info.author,
1300
+ author_date: info.authorDate,
1301
+ commit_message: info.commitMessage,
1302
+ root: info.root,
1303
+ common_git_dir: info.commonGitDir,
1304
+ worktree_git_dir: info.worktreeGitDir,
1305
+ last_tag: info.lastTag,
1306
+ commits_since_last_tag: info.commitsSinceLastTag,
1307
+ remotes
1308
+ };
1309
+ gitMetaData = checkAndTruncateVCSInfo(gitMetaData);
1310
+ return gitMetaData;
1311
+ }
1312
+ function isBStackSession(config) {
1313
+ if (typeof config.user === "string" && typeof config.key === "string" && config.key.length === 20) {
1314
+ return true;
1315
+ }
1316
+ return false;
1317
+ }
1318
+ function isBrowserstackInfra(config, caps) {
1319
+ const isBrowserstack = (str) => {
1320
+ return str.includes("browserstack.com");
1321
+ };
1322
+ if (config.hostname && !isBrowserstack(config.hostname)) {
1323
+ return false;
1324
+ }
1325
+ if (caps && typeof caps === "object") {
1326
+ if (Array.isArray(caps)) {
1327
+ for (const capability of caps) {
1328
+ if (capability.hostname && !isBrowserstack(capability.hostname)) {
1329
+ return false;
1330
+ }
1331
+ }
1332
+ } else {
1333
+ for (const key in caps) {
1334
+ const capability = caps[key];
1335
+ if (capability.hostname && !isBrowserstack(capability.hostname)) {
1336
+ return false;
1337
+ }
1338
+ }
1339
+ }
1340
+ }
1341
+ if (!isBStackSession(config)) {
1342
+ return false;
1343
+ }
1344
+ return true;
1345
+ }
1346
+ function shouldAddServiceVersion(config, testObservability, caps) {
1347
+ if (config.services && config.services.toString().includes("chromedriver") && testObservability !== false || !isBrowserstackInfra(config, caps)) {
1348
+ return false;
1349
+ }
1350
+ return true;
1351
+ }
1352
+ function getObservabilityUser(options, config) {
1353
+ if (process.env.BROWSERSTACK_USERNAME) {
1354
+ return process.env.BROWSERSTACK_USERNAME;
1355
+ }
1356
+ if (options.testObservabilityOptions && options.testObservabilityOptions.user) {
1357
+ return options.testObservabilityOptions.user;
1358
+ }
1359
+ return config.user;
1360
+ }
1361
+ function getObservabilityKey(options, config) {
1362
+ if (process.env.BROWSERSTACK_ACCESS_KEY) {
1363
+ return process.env.BROWSERSTACK_ACCESS_KEY;
1364
+ }
1365
+ if (options.testObservabilityOptions && options.testObservabilityOptions.key) {
1366
+ return options.testObservabilityOptions.key;
1367
+ }
1368
+ return config.key;
1369
+ }
1370
+ function getObservabilityProject(options, bstackProjectName) {
1371
+ if (process.env.TEST_OBSERVABILITY_PROJECT_NAME) {
1372
+ return process.env.TEST_OBSERVABILITY_PROJECT_NAME;
1373
+ }
1374
+ if (options.testObservabilityOptions && options.testObservabilityOptions.projectName) {
1375
+ return options.testObservabilityOptions.projectName;
1376
+ }
1377
+ return bstackProjectName;
1378
+ }
1379
+ function getObservabilityBuild(options, bstackBuildName) {
1380
+ if (process.env.TEST_OBSERVABILITY_BUILD_NAME) {
1381
+ return process.env.TEST_OBSERVABILITY_BUILD_NAME;
1382
+ }
1383
+ if (options.testObservabilityOptions && options.testObservabilityOptions.buildName) {
1384
+ return options.testObservabilityOptions.buildName;
1385
+ }
1386
+ return bstackBuildName || path3.basename(path3.resolve(process.cwd()));
1387
+ }
1388
+ function getObservabilityBuildTags(options, bstackBuildTag) {
1389
+ if (process.env.TEST_OBSERVABILITY_BUILD_TAG) {
1390
+ return process.env.TEST_OBSERVABILITY_BUILD_TAG.split(",");
1391
+ }
1392
+ if (options.testObservabilityOptions && options.testObservabilityOptions.buildTag) {
1393
+ return options.testObservabilityOptions.buildTag;
1394
+ }
1395
+ if (bstackBuildTag) {
1396
+ return [bstackBuildTag];
1397
+ }
1398
+ return [];
1399
+ }
1400
+ function isTrue(value) {
1401
+ return (value + "").toLowerCase() === "true";
1402
+ }
1403
+ var patchConsoleLogs = o11yErrorHandler(() => {
1404
+ const BSTestOpsPatcher = new logPatcher_default({});
1405
+ Object.keys(consoleHolder).forEach((method) => {
1406
+ const origMethod = console[method].bind(console);
1407
+ if (typeof console[method] === "function" && method !== "Console") {
1408
+ console[method] = (...args) => {
1409
+ origMethod(...args);
1410
+ BSTestOpsPatcher[method](...args);
1411
+ };
1412
+ }
1413
+ });
1414
+ });
1415
+ var sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms));
1416
+ var getPlatformVersion = o11yErrorHandler(function getPlatformVersion2(caps) {
1417
+ if (!caps) {
1418
+ return void 0;
1419
+ }
1420
+ const bstackOptions = caps?.["bstack:options"];
1421
+ const keys = ["platformVersion", "platform_version", "osVersion", "os_version"];
1422
+ for (const key of keys) {
1423
+ if (bstackOptions && bstackOptions?.[key]) {
1424
+ return String(bstackOptions?.[key]);
1425
+ } else if (caps[key]) {
1426
+ return String(caps[key]);
1427
+ }
1428
+ }
1429
+ return void 0;
1430
+ });
1431
+ var isObjectEmpty = (objectName) => {
1432
+ return objectName && Object.keys(objectName).length === 0 && objectName.constructor === Object;
1433
+ };
1434
+ var getErrorString = (err) => {
1435
+ if (!err) {
1436
+ return void 0;
1437
+ }
1438
+ if (typeof err === "string") {
1439
+ return err;
1440
+ } else if (err instanceof Error) {
1441
+ return err.message;
1442
+ }
1443
+ };
1444
+ function truncateString(field, truncateSizeInBytes) {
1445
+ try {
1446
+ const bufferSizeInBytes = Buffer.from(GIT_META_DATA_TRUNCATED).length;
1447
+ const fieldBufferObj = Buffer.from(field);
1448
+ const lenOfFieldBufferObj = fieldBufferObj.length;
1449
+ const finalLen = Math.ceil(lenOfFieldBufferObj - truncateSizeInBytes - bufferSizeInBytes);
1450
+ if (finalLen > 0) {
1451
+ const truncatedString = fieldBufferObj.subarray(0, finalLen).toString() + GIT_META_DATA_TRUNCATED;
1452
+ return truncatedString;
1453
+ }
1454
+ } catch (error) {
1455
+ BStackLogger.debug(`Error while truncating field, nothing was truncated here: ${error}`);
1456
+ }
1457
+ return field;
1458
+ }
1459
+ function getSizeOfJsonObjectInBytes(jsonData) {
1460
+ try {
1461
+ const buffer = Buffer.from(JSON.stringify(jsonData));
1462
+ return buffer.length;
1463
+ } catch (error) {
1464
+ BStackLogger.debug(`Something went wrong while calculating size of JSON object: ${error}`);
1465
+ }
1466
+ return -1;
1467
+ }
1468
+ function checkAndTruncateVCSInfo(gitMetaData) {
1469
+ const gitMetaDataSizeInBytes = getSizeOfJsonObjectInBytes(gitMetaData);
1470
+ if (gitMetaDataSizeInBytes && gitMetaDataSizeInBytes > MAX_GIT_META_DATA_SIZE_IN_BYTES) {
1471
+ const truncateSize = gitMetaDataSizeInBytes - MAX_GIT_META_DATA_SIZE_IN_BYTES;
1472
+ const truncatedCommitMessage = truncateString(gitMetaData.commit_message, truncateSize);
1473
+ gitMetaData.commit_message = truncatedCommitMessage;
1474
+ BStackLogger.info(`The commit has been truncated. Size of commit after truncation is ${getSizeOfJsonObjectInBytes(gitMetaData) / 1024} KB`);
1475
+ }
1476
+ return gitMetaData;
1477
+ }
1478
+
1479
+ // src/cleanup.ts
1480
+ import fs7 from "node:fs";
1481
+
1482
+ // src/instrumentation/funnelInstrumentation.ts
1483
+ import os2 from "node:os";
1484
+ import util2, { format as format2 } from "node:util";
1485
+ import path5 from "node:path";
1486
+ import fs6 from "node:fs";
1487
+
1488
+ // src/data-store.ts
1489
+ import path4 from "node:path";
1490
+ import fs5 from "node:fs";
1491
+ var workersDataDirPath = path4.join(process.cwd(), "logs", "worker_data");
1492
+
1493
+ // src/fetchWrapper.ts
1494
+ var ResponseError = class extends Error {
1495
+ response;
1496
+ constructor(message, res) {
1497
+ super(message);
1498
+ this.response = res;
1499
+ }
1500
+ };
1501
+ async function fetchWrap(input, init) {
1502
+ const res = await fetch(input, init);
1503
+ if (!res.ok) {
1504
+ throw new ResponseError(`Error response from server ${res.status}: ${await res.text()}`, res);
1505
+ }
1506
+ return res;
1507
+ }
1508
+
1509
+ // src/instrumentation/funnelInstrumentation.ts
1510
+ function redactCredentialsFromFunnelData(data) {
1511
+ if (data) {
1512
+ if (data.userName) {
1513
+ data.userName = "[REDACTED]";
1514
+ }
1515
+ if (data.accessKey) {
1516
+ data.accessKey = "[REDACTED]";
1517
+ }
1518
+ }
1519
+ return data;
1520
+ }
1521
+ async function fireFunnelRequest(data) {
1522
+ const { userName, accessKey } = data;
1523
+ redactCredentialsFromFunnelData(data);
1524
+ BStackLogger.debug("Sending SDK event with data " + util2.inspect(data, { depth: 6 }));
1525
+ const encodedAuth = Buffer.from(`${userName}:${accessKey}`, "utf8").toString("base64");
1526
+ const response = await fetchWrap(FUNNEL_INSTRUMENTATION_URL, {
1527
+ method: "POST",
1528
+ headers: {
1529
+ "content-type": "application/json",
1530
+ Authorization: `Basic ${encodedAuth}`
1531
+ },
1532
+ body: JSON.stringify(data)
1533
+ });
1534
+ BStackLogger.debug("Funnel Event Response: " + JSON.stringify(await response.text()));
1535
+ }
1536
+
1537
+ // src/cleanup.ts
1538
+ var BStackCleanup = class _BStackCleanup {
1539
+ static async startCleanup() {
1540
+ try {
1541
+ const funnelDataCleanup = process.argv.includes("--funnelData");
1542
+ let funnelData = null;
1543
+ if (funnelDataCleanup) {
1544
+ const index = process.argv.indexOf("--funnelData");
1545
+ const filePath = process.argv[index + 1];
1546
+ funnelData = _BStackCleanup.getFunnelDataFromFile(filePath);
1547
+ }
1548
+ if (process.argv.includes("--observability") && funnelData) {
1549
+ await this.executeObservabilityCleanup(funnelData);
1550
+ }
1551
+ if (funnelDataCleanup && funnelData) {
1552
+ await this.sendFunnelData(funnelData);
1553
+ }
1554
+ } catch (err) {
1555
+ const error = err;
1556
+ BStackLogger.error(error);
1557
+ }
1558
+ }
1559
+ static async executeObservabilityCleanup(funnelData) {
1560
+ if (!process.env[BROWSERSTACK_TESTHUB_JWT]) {
1561
+ return;
1562
+ }
1563
+ BStackLogger.debug("Executing observability cleanup");
1564
+ try {
1565
+ const result = await stopBuildUpstream();
1566
+ if (process.env[BROWSERSTACK_OBSERVABILITY] && process.env[BROWSERSTACK_TESTHUB_UUID]) {
1567
+ BStackLogger.info(`
1568
+ Visit https://observability.browserstack.com/builds/${process.env[BROWSERSTACK_TESTHUB_UUID]} to view build report, insights, and many more debugging information all at one place!
1569
+ `);
1570
+ }
1571
+ const status = result && result.status || "failed";
1572
+ const message = result && result.message;
1573
+ this.updateO11yStopData(funnelData, status, status === "failed" ? message : void 0);
1574
+ } catch (e) {
1575
+ BStackLogger.error("Error in stopping Observability build: " + e);
1576
+ this.updateO11yStopData(funnelData, "failed", e);
1577
+ }
1578
+ }
1579
+ static updateO11yStopData(funnelData, status, error = void 0) {
1580
+ const toData = funnelData?.event_properties?.productUsage?.testObservability;
1581
+ if (!toData) {
1582
+ return;
1583
+ }
1584
+ let existingStopData = toData.events.buildEvents.finished;
1585
+ existingStopData = existingStopData || {};
1586
+ existingStopData = {
1587
+ ...existingStopData,
1588
+ status,
1589
+ error: getErrorString(error),
1590
+ stoppedFrom: "exitHook"
1591
+ };
1592
+ toData.events.buildEvents.finished = existingStopData;
1593
+ }
1594
+ static async sendFunnelData(funnelData) {
1595
+ try {
1596
+ await fireFunnelRequest(funnelData);
1597
+ BStackLogger.debug("Funnel data sent successfully from cleanup");
1598
+ } catch (e) {
1599
+ BStackLogger.error("Error in sending funnel data: " + e);
1600
+ }
1601
+ }
1602
+ static getFunnelDataFromFile(filePath) {
1603
+ if (!filePath) {
1604
+ return null;
1605
+ }
1606
+ const content = fs7.readFileSync(filePath, "utf8");
1607
+ const data = JSON.parse(content);
1608
+ this.removeFunnelDataFile(filePath);
1609
+ return data;
1610
+ }
1611
+ static removeFunnelDataFile(filePath) {
1612
+ if (!filePath) {
1613
+ return;
1614
+ }
1615
+ fs7.rmSync(filePath, { force: true });
1616
+ }
1617
+ };
1618
+ await BStackCleanup.startCleanup();
1619
+ export {
1620
+ BStackCleanup as default
1621
+ };