@wdio/browserstack-service 9.8.0 → 9.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cleanup.js CHANGED
@@ -1,13 +1,14 @@
1
1
  // src/util.ts
2
- import { hostname, platform, type, version, arch } from "node:os";
2
+ import { hostname as hostname2, platform as platform2, type as type2, version as version2, arch as arch2 } from "node:os";
3
3
  import fs4 from "node:fs";
4
4
  import zlib from "node:zlib";
5
5
  import { format, promisify } from "node:util";
6
- import path3 from "node:path";
7
- import util from "node:util";
6
+ import path4 from "node:path";
7
+ import util2 from "node:util";
8
8
  import gitRepoInfo from "git-repo-info";
9
9
  import gitconfig from "gitconfiglocal";
10
10
  import { FormData } from "formdata-node";
11
+ import { performance as performance2 } from "node:perf_hooks";
11
12
 
12
13
  // src/logPatcher.ts
13
14
  import Transport from "winston-transport";
@@ -50,10 +51,15 @@ var logPatcher = class extends Transport {
50
51
  };
51
52
  var logPatcher_default = logPatcher;
52
53
 
53
- // src/performance-tester.ts
54
+ // src/instrumentation/performance/performance-tester.ts
54
55
  import { createObjectCsvWriter } from "csv-writer";
55
56
  import fs2 from "node:fs";
57
+ import fsPromise from "node:fs/promises";
56
58
  import { performance, PerformanceObserver } from "node:perf_hooks";
59
+ import util from "node:util";
60
+ import worker from "node:worker_threads";
61
+ import path2 from "node:path";
62
+ import { arch, hostname, platform, type, version } from "node:os";
57
63
 
58
64
  // src/bstackLogger.ts
59
65
  import path from "node:path";
@@ -64,7 +70,7 @@ import logger from "@wdio/logger";
64
70
  // package.json
65
71
  var package_default = {
66
72
  name: "@wdio/browserstack-service",
67
- version: "9.7.3",
73
+ version: "9.9.0",
68
74
  description: "WebdriverIO service for better Browserstack integration",
69
75
  author: "Adam Bjerstedt <abjerstedt@gmail.com>",
70
76
  homepage: "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-browserstack-service",
@@ -135,9 +141,13 @@ var package_default = {
135
141
  var bstackServiceVersion = package_default.version;
136
142
  var consoleHolder = Object.assign({}, console);
137
143
  var DATA_ENDPOINT = "https://collector-observability.browserstack.com";
144
+ var APP_ALLY_ENDPOINT = "https://app-accessibility.browserstack.com/automate";
145
+ var APP_ALLY_ISSUES_ENDPOINT = "api/v1/issues";
146
+ var APP_ALLY_ISSUES_SUMMARY_ENDPOINT = "api/v1/issues-summary";
138
147
  var BSTACK_SERVICE_VERSION = bstackServiceVersion;
139
148
  var LOGS_FILE = "logs/bstack-wdio-service.log";
140
149
  var FUNNEL_INSTRUMENTATION_URL = "https://api.browserstack.com/sdk/v1/event";
150
+ var EDS_URL = "https://eds.browserstack.com";
141
151
  var BROWSERSTACK_TESTHUB_JWT = "BROWSERSTACK_TESTHUB_JWT";
142
152
  var TESTOPS_SCREENSHOT_ENV = "BS_TESTOPS_ALLOW_SCREENSHOTS";
143
153
  var BROWSERSTACK_TESTHUB_UUID = "BROWSERSTACK_TESTHUB_UUID";
@@ -214,30 +224,71 @@ var BStackLogger = class {
214
224
  }
215
225
  };
216
226
 
217
- // src/performance-tester.ts
218
- var PerformanceTester = class {
227
+ // src/fetchWrapper.ts
228
+ var ResponseError = class extends Error {
229
+ response;
230
+ constructor(message, res) {
231
+ super(message);
232
+ this.response = res;
233
+ }
234
+ };
235
+ async function fetchWrap(input, init) {
236
+ const res = await fetch(input, init);
237
+ if (!res.ok) {
238
+ throw new ResponseError(`Error response from server ${res.status}: ${await res.text()}`, res);
239
+ }
240
+ return res;
241
+ }
242
+
243
+ // src/instrumentation/performance/performance-tester.ts
244
+ var PerformanceTester = class _PerformanceTester {
219
245
  static _observer;
220
246
  static _csvWriter;
221
247
  static _events = [];
248
+ static _measuredEvents = [];
222
249
  static started = false;
250
+ static details = {};
251
+ static eventsMap = {};
252
+ static browser;
253
+ static scenarioThatRan;
254
+ static jsonReportDirName = "performance-report";
255
+ static jsonReportDirPath = path2.join(process.cwd(), "logs", this.jsonReportDirName);
256
+ static jsonReportFileName = `${this.jsonReportDirPath}/performance-report-${_PerformanceTester.getProcessId()}.json`;
223
257
  static startMonitoring(csvName = "performance-report.csv") {
258
+ if (!fs2.existsSync(this.jsonReportDirPath)) {
259
+ fs2.mkdirSync(this.jsonReportDirPath, { recursive: true });
260
+ }
224
261
  this._observer = new PerformanceObserver((list) => {
225
- list.getEntries().forEach((entry) => {
226
- this._events.push(entry);
227
- });
262
+ list.getEntries().filter((entry) => entry.entryType === "measure").forEach(
263
+ (entry) => {
264
+ let finalEntry = entry;
265
+ finalEntry = entry.toJSON();
266
+ if (this.details[entry.name]) {
267
+ finalEntry = Object.assign(finalEntry, this.details[entry.name]);
268
+ }
269
+ delete this.details[entry.name];
270
+ this._measuredEvents.push(finalEntry);
271
+ }
272
+ );
273
+ if (process.env[PERF_MEASUREMENT_ENV]) {
274
+ list.getEntries().forEach((entry) => this._events.push(entry));
275
+ }
228
276
  });
229
- this._observer.observe({ buffered: true, entryTypes: ["function"] });
277
+ const entryTypes = ["measure"];
278
+ if (process.env[PERF_MEASUREMENT_ENV]) {
279
+ entryTypes.push("function");
280
+ }
281
+ this._observer.observe({ buffered: true, entryTypes });
230
282
  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;
283
+ if (process.env[PERF_MEASUREMENT_ENV]) {
284
+ this._csvWriter = createObjectCsvWriter({
285
+ path: csvName,
286
+ header: [
287
+ { id: "name", title: "Function Name" },
288
+ { id: "time", title: "Execution Time (ms)" }
289
+ ]
290
+ });
291
+ }
241
292
  }
242
293
  static calculateTimes(methods) {
243
294
  const times = {};
@@ -250,26 +301,35 @@ var PerformanceTester = class {
250
301
  const timeTaken = methods.reduce((a, c) => {
251
302
  return times[c] + (a || 0);
252
303
  }, 0);
253
- BStackLogger.info(`Time for ${methods} is ${timeTaken}`);
304
+ BStackLogger.debug(`Time for ${methods} is ${timeTaken}`);
254
305
  return timeTaken;
255
306
  }
256
307
  static async stopAndGenerate(filename = "performance-own.html") {
257
308
  if (!this.started) {
258
309
  return;
259
310
  }
260
- await sleep(2e3);
311
+ try {
312
+ const eventsJson = JSON.stringify(this._measuredEvents);
313
+ const finalJSONStr = eventsJson.slice(1, -1) + ",";
314
+ await fsPromise.appendFile(this.jsonReportFileName, finalJSONStr);
315
+ } catch (er) {
316
+ BStackLogger.debug(`Failed to write events of the worker to ${this.jsonReportFileName}: ${util.format(er)}`);
317
+ }
261
318
  this._observer.disconnect();
319
+ if (!process.env[PERF_MEASUREMENT_ENV]) {
320
+ return;
321
+ }
322
+ await _PerformanceTester.sleep(2e3);
262
323
  this.started = false;
263
324
  this.generateCSV(this._events);
264
325
  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
- });
326
+ const dir = path2.join(process.cwd(), filename);
327
+ try {
328
+ await fsPromise.writeFile(dir, content);
329
+ BStackLogger.info(`Performance report is at ${path2}`);
330
+ } catch (err) {
331
+ BStackLogger.error(`Error in writing html ${util.format(err)}`);
332
+ }
273
333
  }
274
334
  static generateReport(entries) {
275
335
  let html = "<!DOCTYPE html><html><head><title>Performance Report</title></head><body>";
@@ -301,6 +361,221 @@ var PerformanceTester = class {
301
361
  });
302
362
  this._csvWriter.writeRecords(dat).then(() => BStackLogger.info("Performance CSV report generated successfully")).catch((error) => console.error(error));
303
363
  }
364
+ static Measure(label, details = {}) {
365
+ const self = this;
366
+ return (target, key, descriptor) => {
367
+ const originalMethod = descriptor.value;
368
+ if (descriptor.value) {
369
+ descriptor.value = function(...args) {
370
+ return _PerformanceTester.measure.apply(self, [label, originalMethod, { methodName: key.toString(), ...details }, args, this]);
371
+ };
372
+ }
373
+ };
374
+ }
375
+ static measureWrapper(name, fn, details = {}) {
376
+ const self = this;
377
+ details.worker = _PerformanceTester.getProcessId();
378
+ details.testName = _PerformanceTester.scenarioThatRan && _PerformanceTester.scenarioThatRan[_PerformanceTester.scenarioThatRan.length - 1];
379
+ details.platform = _PerformanceTester.browser?.sessionId;
380
+ return function(...args) {
381
+ return self.measure(name, fn, details, args);
382
+ };
383
+ }
384
+ static isEnabled() {
385
+ return !(process.env.BROWSERSTACK_SDK_INSTRUMENTATION === "false");
386
+ }
387
+ static measure(label, fn, details = {}, args, thisArg = null) {
388
+ if (!this.started || !this.isEnabled()) {
389
+ return fn.apply(thisArg, args);
390
+ }
391
+ _PerformanceTester.start(label);
392
+ if (this.details) {
393
+ this.details[label] = details;
394
+ }
395
+ try {
396
+ const returnVal = fn.apply(thisArg, args);
397
+ if (returnVal instanceof Promise) {
398
+ return new Promise((resolve, reject) => {
399
+ returnVal.then((v) => {
400
+ _PerformanceTester.end(label);
401
+ resolve(v);
402
+ }).catch((e) => {
403
+ _PerformanceTester.end(label, false, util.format(e));
404
+ reject(e);
405
+ });
406
+ });
407
+ }
408
+ _PerformanceTester.end(label);
409
+ return returnVal;
410
+ } catch (er) {
411
+ _PerformanceTester.end(label, false, util.format(er));
412
+ throw er;
413
+ }
414
+ }
415
+ static start(event) {
416
+ const finalEvent = event + "-start";
417
+ if (this.eventsMap[finalEvent]) {
418
+ return;
419
+ }
420
+ performance.mark(finalEvent);
421
+ this.eventsMap[finalEvent] = 1;
422
+ }
423
+ static end(event, success = true, failure, details = {}) {
424
+ performance.mark(event + "-end");
425
+ performance.measure(event, event + "-start", event + "-end");
426
+ this.details[event] = Object.assign({ success, failure: util.format(failure) }, Object.assign(Object.assign({
427
+ worker: _PerformanceTester.getProcessId(),
428
+ platform: _PerformanceTester.browser?.sessionId,
429
+ testName: _PerformanceTester.scenarioThatRan && _PerformanceTester.scenarioThatRan[_PerformanceTester.scenarioThatRan.length - 1]
430
+ }, details), this.details[event] || {}));
431
+ }
432
+ static getProcessId() {
433
+ return `${process.pid}-${worker.threadId}`;
434
+ }
435
+ static sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms));
436
+ static async uploadEventsData() {
437
+ try {
438
+ let measures = [];
439
+ if (await fsPromise.access(this.jsonReportDirPath).then(() => true).catch(() => false)) {
440
+ const files = (await fsPromise.readdir(this.jsonReportDirPath)).map((file) => path2.resolve(this.jsonReportDirPath, file));
441
+ measures = (await Promise.all(files.map((file) => fsPromise.readFile(file, "utf-8")))).map((el) => `[${el.slice(0, -1)}]`).map((el) => JSON.parse(el)).flat();
442
+ }
443
+ if (this._measuredEvents.length > 0) {
444
+ measures = measures.concat(this._measuredEvents);
445
+ }
446
+ const date = /* @__PURE__ */ new Date();
447
+ const options = {
448
+ timeZone: "UTC",
449
+ year: "numeric",
450
+ month: "2-digit",
451
+ day: "2-digit",
452
+ hour: "2-digit",
453
+ minute: "2-digit",
454
+ second: "2-digit",
455
+ fractionalSecondDigits: 3,
456
+ // To include microseconds
457
+ hour12: false
458
+ };
459
+ const formattedDate = new Intl.DateTimeFormat("en-GB", options).formatToParts(date).map(({ type: type3, value }) => type3 === "timeZoneName" ? "Z" : value).join("").replace(",", "T");
460
+ const payload = {
461
+ event_type: "sdk_events",
462
+ data: {
463
+ testhub_uuid: process.env.PERF_TESTHUB_UUID || process.env.SDK_RUN_ID,
464
+ created_day: formattedDate,
465
+ event_name: "SDKFeaturePerformance",
466
+ user_data: process.env.PERF_USER_NAME,
467
+ host_info: JSON.stringify({
468
+ hostname: hostname(),
469
+ platform: platform(),
470
+ type: type(),
471
+ version: version(),
472
+ arch: arch()
473
+ }),
474
+ event_json: { measures, sdkRunId: process.env.SDK_RUN_ID }
475
+ }
476
+ };
477
+ const result = await fetchWrap(`${EDS_URL}/send_sdk_events`, {
478
+ method: "POST",
479
+ headers: {
480
+ "content-type": "application/json"
481
+ },
482
+ body: JSON.stringify(payload)
483
+ });
484
+ BStackLogger.debug(`Successfully uploaded performance events ${util.format(await result.text())}`);
485
+ } catch (er) {
486
+ BStackLogger.debug(`Failed to upload performance events ${util.format(er)}`);
487
+ }
488
+ try {
489
+ if (await fsPromise.access(this.jsonReportDirPath).then(() => true, () => false)) {
490
+ const files = await fsPromise.readdir(this.jsonReportDirPath);
491
+ for (const file of files) {
492
+ await fsPromise.unlink(path2.join(this.jsonReportDirPath, file));
493
+ }
494
+ }
495
+ } catch (er) {
496
+ BStackLogger.debug(`Failed to delete performance related files ${util.format(er)}`);
497
+ }
498
+ }
499
+ };
500
+
501
+ // src/instrumentation/performance/constants.ts
502
+ var EVENTS = {
503
+ SDK_SETUP: "sdk:setup",
504
+ SDK_CLEANUP: "sdk:cleanup",
505
+ SDK_PRE_TEST: "sdk:pre-test",
506
+ SDK_TEST: "sdk:test",
507
+ SDK_POST_TEST: "sdk:post-test",
508
+ SDK_HOOK: "sdk:hook",
509
+ SDK_DRIVER: "sdk:driver",
510
+ SDK_A11Y: "sdk:a11y",
511
+ SDK_O11Y: "sdk:o11y",
512
+ SDK_AUTO_CAPTURE: "sdk:auto-capture",
513
+ SDK_PROXY_SETUP: "sdk:proxy-setup",
514
+ SDK_TESTHUB: "sdk:testhub",
515
+ SDK_AUTOMATE: "sdk:automate",
516
+ SDK_APP_AUTOMATE: "sdk:app-automate",
517
+ SDK_TURBOSCALE: "sdk:turboscale",
518
+ SDK_PERCY: "sdk:percy",
519
+ SDK_PRE_INITIALIZE: "sdk:driver:pre-initialization",
520
+ SDK_POST_INITIALIZE: "sdk:driver:post-initialization"
521
+ };
522
+ var TESTHUB_EVENTS = {
523
+ START: `${EVENTS.SDK_TESTHUB}:start`,
524
+ STOP: `${EVENTS.SDK_TESTHUB}:stop`
525
+ };
526
+ var AUTOMATE_EVENTS = {
527
+ KEEP_ALIVE: `${EVENTS.SDK_AUTOMATE}:keep-alive`,
528
+ HUB_MANAGEMENT: `${EVENTS.SDK_AUTOMATE}:hub-management`,
529
+ LOCAL_START: `${EVENTS.SDK_AUTOMATE}:local-start`,
530
+ LOCAL_STOP: `${EVENTS.SDK_AUTOMATE}:local-stop`,
531
+ DRIVER_MANAGE: `${EVENTS.SDK_AUTOMATE}:driver-manage`,
532
+ SESSION_NAME: `${EVENTS.SDK_AUTOMATE}:session-name`,
533
+ SESSION_STATUS: `${EVENTS.SDK_AUTOMATE}:session-status`,
534
+ SESSION_ANNOTATION: `${EVENTS.SDK_AUTOMATE}:session-annotation`,
535
+ IDLE_TIMEOUT: `${EVENTS.SDK_AUTOMATE}:idle-timeout`,
536
+ GENERATE_CI_ARTIFACT: `${EVENTS.SDK_AUTOMATE}:ci-artifacts`,
537
+ PRINT_BUILDLINK: `${EVENTS.SDK_AUTOMATE}:print-buildlink`
538
+ };
539
+ var A11Y_EVENTS = {
540
+ PERFORM_SCAN: `${EVENTS.SDK_A11Y}:driver-performscan`,
541
+ SAVE_RESULTS: `${EVENTS.SDK_A11Y}:save-results`,
542
+ GET_RESULTS: `${EVENTS.SDK_A11Y}:get-accessibility-results`,
543
+ GET_RESULTS_SUMMARY: `${EVENTS.SDK_A11Y}:get-accessibility-results-summary`
544
+ };
545
+ var PERCY_EVENTS = {
546
+ DOWNLOAD: `${EVENTS.SDK_PERCY}:download`,
547
+ SCREENSHOT: `${EVENTS.SDK_PERCY}:screenshot`,
548
+ START: `${EVENTS.SDK_PERCY}:start`,
549
+ STOP: `${EVENTS.SDK_PERCY}:stop`,
550
+ AUTO_CAPTURE: `${EVENTS.SDK_PERCY}:auto-capture`,
551
+ SNAPSHOT: `${EVENTS.SDK_PERCY}:snapshot`,
552
+ SCREENSHOT_APP: `${EVENTS.SDK_PERCY}:screenshot-app`
553
+ };
554
+ var O11Y_EVENTS = {
555
+ SYNC: `${EVENTS.SDK_O11Y}:sync`,
556
+ TAKE_SCREENSHOT: `${EVENTS.SDK_O11Y}:driver-takeScreenShot`,
557
+ PRINT_BUILDLINK: `${EVENTS.SDK_O11Y}:print-buildlink`
558
+ };
559
+ var HOOK_EVENTS = {
560
+ BEFORE_EACH: `${EVENTS.SDK_HOOK}:before-each`,
561
+ AFTER_EACH: `${EVENTS.SDK_HOOK}:after-each`,
562
+ AFTER_ALL: `${EVENTS.SDK_HOOK}:after-all`,
563
+ BEFORE_ALL: `${EVENTS.SDK_HOOK}:before-all`,
564
+ BEFORE: `${EVENTS.SDK_HOOK}:before`,
565
+ AFTER: `${EVENTS.SDK_HOOK}:after`
566
+ };
567
+ var TURBOSCALE_EVENTS = {
568
+ HUB_MANAGEMENT: `${EVENTS.SDK_TURBOSCALE}:hub-management`,
569
+ PRINT_BUILDLINK: `${EVENTS.SDK_TURBOSCALE}:print-buildlink`
570
+ };
571
+ var APP_AUTOMATE_EVENTS = {
572
+ APP_UPLOAD: `${EVENTS.SDK_APP_AUTOMATE}:app-upload`
573
+ };
574
+ var DRIVER_EVENT = {
575
+ QUIT: `${EVENTS.SDK_DRIVER}:quit`,
576
+ GET: `${EVENTS.SDK_DRIVER}:get`,
577
+ PRE_EXECUTE: `${EVENTS.SDK_DRIVER}:pre-execute`,
578
+ POST_EXECUTE: `${EVENTS.SDK_DRIVER}:post-execute`
304
579
  };
305
580
 
306
581
  // src/testHub/utils.ts
@@ -773,7 +1048,7 @@ var UsageStats = class _UsageStats {
773
1048
  var usageStats_default = UsageStats;
774
1049
 
775
1050
  // src/scripts/accessibility-scripts.ts
776
- import path2 from "node:path";
1051
+ import path3 from "node:path";
777
1052
  import fs3 from "node:fs";
778
1053
  import os from "node:os";
779
1054
  var AccessibilityScripts = class _AccessibilityScripts {
@@ -788,7 +1063,7 @@ var AccessibilityScripts = class _AccessibilityScripts {
788
1063
  // don't allow to create instances from it other than through `checkAndGetInstance`
789
1064
  constructor() {
790
1065
  this.browserstackFolderPath = this.getWritableDir();
791
- this.commandsPath = path2.join(this.browserstackFolderPath, "commands.json");
1066
+ this.commandsPath = path3.join(this.browserstackFolderPath, "commands.json");
792
1067
  }
793
1068
  static checkAndGetInstance() {
794
1069
  if (!_AccessibilityScripts.instance) {
@@ -800,7 +1075,7 @@ var AccessibilityScripts = class _AccessibilityScripts {
800
1075
  /* eslint-disable @typescript-eslint/no-unused-vars */
801
1076
  getWritableDir() {
802
1077
  const orderedPaths = [
803
- path2.join(os.homedir(), ".browserstack"),
1078
+ path3.join(os.homedir(), ".browserstack"),
804
1079
  process.cwd(),
805
1080
  os.tmpdir()
806
1081
  ];
@@ -878,7 +1153,7 @@ function processError(error, fn, args) {
878
1153
  try {
879
1154
  argsString = JSON.stringify(args);
880
1155
  } catch {
881
- argsString = util.inspect(args, { depth: 2 });
1156
+ argsString = util2.inspect(args, { depth: 2 });
882
1157
  }
883
1158
  CrashReporter.uploadCrashReport(`Error in executing ${fn.name} with args ${argsString} : ${error}`, error && error.stack || "unknown error");
884
1159
  }
@@ -887,7 +1162,7 @@ function o11yErrorHandler(fn) {
887
1162
  try {
888
1163
  let functionToHandle = fn;
889
1164
  if (process.env[PERF_MEASUREMENT_ENV]) {
890
- functionToHandle = PerformanceTester.getPerformance().timerify(functionToHandle);
1165
+ functionToHandle = performance2.timerify(functionToHandle);
891
1166
  }
892
1167
  const result = functionToHandle(...args);
893
1168
  if (result instanceof Promise) {
@@ -960,7 +1235,7 @@ var processLaunchBuildResponse = (response, options) => {
960
1235
  processAccessibilityResponse(response);
961
1236
  }
962
1237
  };
963
- var launchTestSession = o11yErrorHandler(async function launchTestSession2(options, config, bsConfig, bStackConfig) {
1238
+ var launchTestSession = PerformanceTester.measureWrapper(TESTHUB_EVENTS.START, o11yErrorHandler(async function launchTestSession2(options, config, bsConfig, bStackConfig) {
964
1239
  const launchBuildUsage = usageStats_default.getInstance().launchBuildUsage;
965
1240
  launchBuildUsage.triggered();
966
1241
  const data = {
@@ -971,11 +1246,11 @@ var launchTestSession = o11yErrorHandler(async function launchTestSession2(optio
971
1246
  started_at: (/* @__PURE__ */ new Date()).toISOString(),
972
1247
  tags: getObservabilityBuildTags(options, bsConfig.buildTag),
973
1248
  host_info: {
974
- hostname: hostname(),
975
- platform: platform(),
976
- type: type(),
977
- version: version(),
978
- arch: arch()
1249
+ hostname: hostname2(),
1250
+ platform: platform2(),
1251
+ type: type2(),
1252
+ version: version2(),
1253
+ arch: arch2()
979
1254
  },
980
1255
  ci_info: getCiInfo(),
981
1256
  build_run_identifier: process.env.BROWSERSTACK_BUILD_RUN_IDENTIFIER,
@@ -1027,6 +1302,7 @@ var launchTestSession = o11yErrorHandler(async function launchTestSession2(optio
1027
1302
  if (jsonResponse.build_hashed_id) {
1028
1303
  process.env[BROWSERSTACK_TESTHUB_UUID] = jsonResponse.build_hashed_id;
1029
1304
  testOpsConfig_default.getInstance().buildHashedId = jsonResponse.build_hashed_id;
1305
+ BStackLogger.info(`Testhub started with id: ${testOpsConfig_default.getInstance()?.buildHashedId}`);
1030
1306
  }
1031
1307
  processLaunchBuildResponse(jsonResponse, options);
1032
1308
  launchBuildUsage.success();
@@ -1038,8 +1314,162 @@ var launchTestSession = o11yErrorHandler(async function launchTestSession2(optio
1038
1314
  return;
1039
1315
  }
1040
1316
  }
1317
+ }));
1318
+ var isAccessibilityAutomationSession = (accessibilityFlag) => {
1319
+ try {
1320
+ const hasA11yJwtToken = typeof process.env.BSTACK_A11Y_JWT === "string" && process.env.BSTACK_A11Y_JWT.length > 0 && process.env.BSTACK_A11Y_JWT !== "null" && process.env.BSTACK_A11Y_JWT !== "undefined";
1321
+ return accessibilityFlag && hasA11yJwtToken;
1322
+ } catch (error) {
1323
+ BStackLogger.debug(`Exception in verifying the Accessibility session with error : ${error}`);
1324
+ }
1325
+ return false;
1326
+ };
1327
+ var isAppAccessibilityAutomationSession = (accessibilityFlag, isAppAutomate) => {
1328
+ const accessibilityAutomation = isAccessibilityAutomationSession(accessibilityFlag);
1329
+ return accessibilityAutomation && isAppAutomate;
1330
+ };
1331
+ var formatString = (template, ...values) => {
1332
+ let i = 0;
1333
+ if (template === null) {
1334
+ return "";
1335
+ }
1336
+ return template.replace(/%s/g, () => {
1337
+ const value = values[i++];
1338
+ return value !== null && value !== void 0 ? value : "";
1339
+ });
1340
+ };
1341
+ var _getParamsForAppAccessibility = (commandName) => {
1342
+ return {
1343
+ "thTestRunUuid": process.env.TEST_ANALYTICS_ID,
1344
+ "thBuildUuid": process.env.BROWSERSTACK_TESTHUB_UUID,
1345
+ "thJwtToken": process.env.BROWSERSTACK_TESTHUB_JWT,
1346
+ "authHeader": process.env.BSTACK_A11Y_JWT,
1347
+ "scanTimestamp": Date.now(),
1348
+ "method": commandName
1349
+ };
1350
+ };
1351
+ var performA11yScan = async (isAppAutomate, browser, isBrowserStackSession, isAccessibility, commandName) => {
1352
+ if (!isBrowserStackSession) {
1353
+ BStackLogger.warn("Not a BrowserStack Automate session, cannot perform Accessibility scan.");
1354
+ return;
1355
+ }
1356
+ if (!isAccessibilityAutomationSession(isAccessibility)) {
1357
+ BStackLogger.warn("Not an Accessibility Automation session, cannot perform Accessibility scan.");
1358
+ return;
1359
+ }
1360
+ try {
1361
+ if (isAppAccessibilityAutomationSession(isAccessibility, isAppAutomate)) {
1362
+ const results = await browser.execute(formatString(accessibility_scripts_default.performScan, JSON.stringify(_getParamsForAppAccessibility(commandName))), {});
1363
+ BStackLogger.debug(util2.format(results));
1364
+ return results;
1365
+ }
1366
+ if (accessibility_scripts_default.performScan) {
1367
+ const results = await executeAccessibilityScript(browser, accessibility_scripts_default.performScan, { method: commandName || "" });
1368
+ return results;
1369
+ }
1370
+ BStackLogger.error("AccessibilityScripts.performScan is null");
1371
+ return;
1372
+ } catch (err) {
1373
+ BStackLogger.error("Accessibility Scan could not be performed : " + err);
1374
+ return;
1375
+ }
1376
+ };
1377
+ var getA11yResults = PerformanceTester.measureWrapper(A11Y_EVENTS.GET_RESULTS, async (isAppAutomate, browser, isBrowserStackSession, isAccessibility) => {
1378
+ if (!isBrowserStackSession) {
1379
+ BStackLogger.warn("Not a BrowserStack Automate session, cannot retrieve Accessibility results.");
1380
+ return [];
1381
+ }
1382
+ if (!isAccessibilityAutomationSession(isAccessibility)) {
1383
+ BStackLogger.warn("Not an Accessibility Automation session, cannot retrieve Accessibility results.");
1384
+ return [];
1385
+ }
1386
+ try {
1387
+ BStackLogger.debug("Performing scan before getting results");
1388
+ await performA11yScan(isAppAutomate, browser, isBrowserStackSession, isAccessibility);
1389
+ if (accessibility_scripts_default.getResults) {
1390
+ const results = await executeAccessibilityScript(browser, accessibility_scripts_default.getResults);
1391
+ return results;
1392
+ }
1393
+ BStackLogger.error("AccessibilityScripts.getResults is null");
1394
+ return [];
1395
+ } catch (error) {
1396
+ BStackLogger.error("No accessibility results were found.");
1397
+ BStackLogger.debug(`getA11yResults Failed. Error: ${error}`);
1398
+ return [];
1399
+ }
1041
1400
  });
1042
- var stopBuildUpstream = o11yErrorHandler(async function stopBuildUpstream2() {
1401
+ var getAppA11yResults = PerformanceTester.measureWrapper(A11Y_EVENTS.GET_RESULTS, async (isAppAutomate, browser, isBrowserStackSession, isAccessibility, sessionId) => {
1402
+ if (!isBrowserStackSession) {
1403
+ return [];
1404
+ }
1405
+ if (!isAppAccessibilityAutomationSession(isAccessibility, isAppAutomate)) {
1406
+ BStackLogger.warn("Not an Accessibility Automation session, cannot retrieve Accessibility results summary.");
1407
+ return [];
1408
+ }
1409
+ try {
1410
+ const apiUrl = `${APP_ALLY_ENDPOINT}/${APP_ALLY_ISSUES_ENDPOINT}`;
1411
+ const apiRespone = await getAppA11yResultResponse(apiUrl, isAppAutomate, browser, isBrowserStackSession, isAccessibility, sessionId);
1412
+ const result = apiRespone?.data?.data?.issues;
1413
+ BStackLogger.debug(`Polling Result: ${JSON.stringify(result)}`);
1414
+ return result;
1415
+ } catch (error) {
1416
+ BStackLogger.error("No accessibility summary was found.");
1417
+ BStackLogger.debug(`getAppA11yResults Failed. Error: ${error}`);
1418
+ return [];
1419
+ }
1420
+ });
1421
+ var getAppA11yResultsSummary = PerformanceTester.measureWrapper(A11Y_EVENTS.GET_RESULTS_SUMMARY, async (isAppAutomate, browser, isBrowserStackSession, isAccessibility, sessionId) => {
1422
+ if (!isBrowserStackSession) {
1423
+ return {};
1424
+ }
1425
+ if (!isAppAccessibilityAutomationSession(isAccessibility, isAppAutomate)) {
1426
+ BStackLogger.warn("Not an Accessibility Automation session, cannot retrieve Accessibility results summary.");
1427
+ return {};
1428
+ }
1429
+ try {
1430
+ const apiUrl = `${APP_ALLY_ENDPOINT}/${APP_ALLY_ISSUES_SUMMARY_ENDPOINT}`;
1431
+ const apiRespone = await getAppA11yResultResponse(apiUrl, isAppAutomate, browser, isBrowserStackSession, isAccessibility, sessionId);
1432
+ const result = apiRespone?.data?.data?.summary;
1433
+ BStackLogger.debug(`Polling Result: ${JSON.stringify(result)}`);
1434
+ return result;
1435
+ } catch {
1436
+ BStackLogger.error("No accessibility summary was found.");
1437
+ return {};
1438
+ }
1439
+ });
1440
+ var getAppA11yResultResponse = async (apiUrl, isAppAutomate, browser, isBrowserStackSession, isAccessibility, sessionId) => {
1441
+ BStackLogger.debug("Performing scan before getting results summary");
1442
+ await performA11yScan(isAppAutomate, browser, isBrowserStackSession, isAccessibility);
1443
+ const upperTimeLimit = process.env.BSTACK_A11Y_POLLING_TIMEOUT ? Date.now() + parseInt(process.env.BSTACK_A11Y_POLLING_TIMEOUT) * 1e3 : Date.now() + 3e4;
1444
+ const params = { test_run_uuid: process.env.TEST_ANALYTICS_ID, session_id: sessionId, timestamp: Date.now() };
1445
+ const header = { Authorization: `Bearer ${process.env.BSTACK_A11Y_JWT}` };
1446
+ const apiRespone = await pollApi(apiUrl, params, header, upperTimeLimit);
1447
+ BStackLogger.debug(`Polling Result: ${JSON.stringify(apiRespone)}`);
1448
+ return apiRespone;
1449
+ };
1450
+ var getA11yResultsSummary = PerformanceTester.measureWrapper(A11Y_EVENTS.GET_RESULTS_SUMMARY, async (isAppAutomate, browser, isBrowserStackSession, isAccessibility) => {
1451
+ if (!isBrowserStackSession) {
1452
+ return {};
1453
+ }
1454
+ if (!isAccessibilityAutomationSession(isAccessibility)) {
1455
+ BStackLogger.warn("Not an Accessibility Automation session, cannot retrieve Accessibility results summary.");
1456
+ return {};
1457
+ }
1458
+ try {
1459
+ BStackLogger.debug("Performing scan before getting results summary");
1460
+ await performA11yScan(isAppAutomate, browser, isBrowserStackSession, isAccessibility);
1461
+ if (accessibility_scripts_default.getResultsSummary) {
1462
+ const summaryResults = await executeAccessibilityScript(browser, accessibility_scripts_default.getResultsSummary);
1463
+ return summaryResults;
1464
+ }
1465
+ BStackLogger.error("AccessibilityScripts.getResultsSummary is null");
1466
+ return {};
1467
+ } catch {
1468
+ BStackLogger.error("No accessibility summary was found.");
1469
+ return {};
1470
+ }
1471
+ });
1472
+ var stopBuildUpstream = PerformanceTester.measureWrapper(TESTHUB_EVENTS.STOP, o11yErrorHandler(async function stopBuildUpstream2() {
1043
1473
  const stopBuildUsage = usageStats_default.getInstance().stopBuildUsage;
1044
1474
  stopBuildUsage.triggered();
1045
1475
  if (!process.env[TESTOPS_BUILD_COMPLETED_ENV]) {
@@ -1084,7 +1514,7 @@ var stopBuildUpstream = o11yErrorHandler(async function stopBuildUpstream2() {
1084
1514
  message: error.message
1085
1515
  };
1086
1516
  }
1087
- });
1517
+ }));
1088
1518
  function getCiInfo() {
1089
1519
  const env = process.env;
1090
1520
  if (typeof env.JENKINS_URL === "string" && env.JENKINS_URL.length > 0 || typeof env.JENKINS_HOME === "string" && env.JENKINS_HOME.length > 0) {
@@ -1383,7 +1813,7 @@ function getObservabilityBuild(options, bstackBuildName) {
1383
1813
  if (options.testObservabilityOptions && options.testObservabilityOptions.buildName) {
1384
1814
  return options.testObservabilityOptions.buildName;
1385
1815
  }
1386
- return bstackBuildName || path3.basename(path3.resolve(process.cwd()));
1816
+ return bstackBuildName || path4.basename(path4.resolve(process.cwd()));
1387
1817
  }
1388
1818
  function getObservabilityBuildTags(options, bstackBuildTag) {
1389
1819
  if (process.env.TEST_OBSERVABILITY_BUILD_TAG) {
@@ -1412,7 +1842,6 @@ var patchConsoleLogs = o11yErrorHandler(() => {
1412
1842
  }
1413
1843
  });
1414
1844
  });
1415
- var sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms));
1416
1845
  var getPlatformVersion = o11yErrorHandler(function getPlatformVersion2(caps) {
1417
1846
  if (!caps) {
1418
1847
  return void 0;
@@ -1475,36 +1904,104 @@ function checkAndTruncateVCSInfo(gitMetaData) {
1475
1904
  }
1476
1905
  return gitMetaData;
1477
1906
  }
1907
+ async function pollApi(url, params, headers, upperLimit, startTime = Date.now()) {
1908
+ params.timestamp = Math.round(Date.now() / 1e3);
1909
+ BStackLogger.debug(`current timestamp ${params.timestamp}`);
1910
+ try {
1911
+ const response = await makeGetRequest(url, params, headers);
1912
+ const responseData = await response.json();
1913
+ return {
1914
+ data: responseData,
1915
+ headers: response.headers,
1916
+ message: "Polling succeeded."
1917
+ };
1918
+ } catch (error) {
1919
+ if (error.response && error.response.status === 404) {
1920
+ const nextPollTime = parseInt(error.response.headers.get("next_poll_time"), 10) * 1e3;
1921
+ BStackLogger.debug(`timeInMillis ${nextPollTime}`);
1922
+ if (isNaN(nextPollTime)) {
1923
+ BStackLogger.warn("Invalid or missing `nextPollTime` header. Stopping polling.");
1924
+ return {
1925
+ data: {},
1926
+ headers: error.response.headers,
1927
+ message: "Invalid nextPollTime header value. Polling stopped."
1928
+ };
1929
+ }
1930
+ const elapsedTime = nextPollTime - Date.now();
1931
+ BStackLogger.debug(
1932
+ `elapsedTime ${elapsedTime} timeInMillis ${nextPollTime} upperLimit ${upperLimit}`
1933
+ );
1934
+ if (nextPollTime > upperLimit) {
1935
+ BStackLogger.warn("Polling stopped due to upper time limit.");
1936
+ return {
1937
+ data: {},
1938
+ headers: error.response.headers,
1939
+ message: "Polling stopped due to upper time limit."
1940
+ };
1941
+ }
1942
+ BStackLogger.debug(`Polling again in ${elapsedTime}ms with params:`, params);
1943
+ await new Promise((resolve) => setTimeout(resolve, elapsedTime));
1944
+ return pollApi(url, params, headers, upperLimit, startTime);
1945
+ } else if (error.response) {
1946
+ let errorMessage = error.response.statusText;
1947
+ try {
1948
+ const parsedError = JSON.parse(error.response.json());
1949
+ errorMessage = parsedError.message;
1950
+ } catch {
1951
+ BStackLogger.debug(`Error parsing pollApi request body ${error.response.body}`);
1952
+ errorMessage = "Unknown error";
1953
+ }
1954
+ throw {
1955
+ data: {},
1956
+ headers: {},
1957
+ message: errorMessage
1958
+ };
1959
+ } else {
1960
+ BStackLogger.error(`Unexpected error occurred: ${error}`);
1961
+ return { data: {}, headers: {}, message: "Unexpected error occurred." };
1962
+ }
1963
+ }
1964
+ }
1965
+ async function makeGetRequest(url, params, headers) {
1966
+ const urlObj = new URL(url);
1967
+ Object.keys(params).forEach((key) => urlObj.searchParams.append(key, params[key]));
1968
+ const response = await fetch(urlObj.toString(), {
1969
+ method: "GET",
1970
+ headers
1971
+ });
1972
+ if (!response.ok) {
1973
+ const error = new Error("Request failed");
1974
+ error.response = response;
1975
+ throw error;
1976
+ }
1977
+ return response;
1978
+ }
1979
+ async function executeAccessibilityScript(browser, fnBody, arg) {
1980
+ return browser.execute(
1981
+ `return (function (...bstackSdkArgs) {
1982
+ return new Promise((resolve, reject) => {
1983
+ const data = bstackSdkArgs[0];
1984
+ bstackSdkArgs.push(resolve);
1985
+ ${fnBody.replace(/arguments/g, "bstackSdkArgs")}
1986
+ });
1987
+ })(${arg ? JSON.stringify(arg) : ""})`
1988
+ );
1989
+ }
1478
1990
 
1479
1991
  // src/cleanup.ts
1480
1992
  import fs7 from "node:fs";
1993
+ import util4 from "node:util";
1481
1994
 
1482
1995
  // src/instrumentation/funnelInstrumentation.ts
1483
1996
  import os2 from "node:os";
1484
- import util2, { format as format2 } from "node:util";
1485
- import path5 from "node:path";
1997
+ import util3, { format as format2 } from "node:util";
1998
+ import path6 from "node:path";
1486
1999
  import fs6 from "node:fs";
1487
2000
 
1488
2001
  // src/data-store.ts
1489
- import path4 from "node:path";
2002
+ import path5 from "node:path";
1490
2003
  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
- }
2004
+ var workersDataDirPath = path5.join(process.cwd(), "logs", "worker_data");
1508
2005
 
1509
2006
  // src/instrumentation/funnelInstrumentation.ts
1510
2007
  function redactCredentialsFromFunnelData(data) {
@@ -1521,7 +2018,7 @@ function redactCredentialsFromFunnelData(data) {
1521
2018
  async function fireFunnelRequest(data) {
1522
2019
  const { userName, accessKey } = data;
1523
2020
  redactCredentialsFromFunnelData(data);
1524
- BStackLogger.debug("Sending SDK event with data " + util2.inspect(data, { depth: 6 }));
2021
+ BStackLogger.debug("Sending SDK event with data " + util3.inspect(data, { depth: 6 }));
1525
2022
  const encodedAuth = Buffer.from(`${userName}:${accessKey}`, "utf8").toString("base64");
1526
2023
  const response = await fetchWrap(FUNNEL_INSTRUMENTATION_URL, {
1527
2024
  method: "POST",
@@ -1555,6 +2052,13 @@ var BStackCleanup = class _BStackCleanup {
1555
2052
  const error = err;
1556
2053
  BStackLogger.error(error);
1557
2054
  }
2055
+ try {
2056
+ if (process.argv.includes("--performanceData")) {
2057
+ await PerformanceTester.uploadEventsData();
2058
+ }
2059
+ } catch (er) {
2060
+ BStackLogger.debug(`Error in sending events data ${util4.format(er)}`);
2061
+ }
1558
2062
  }
1559
2063
  static async executeObservabilityCleanup(funnelData) {
1560
2064
  if (!process.env[BROWSERSTACK_TESTHUB_JWT]) {