@wdio/browserstack-service 9.23.2 → 9.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cleanup.js CHANGED
@@ -1,11 +1,11 @@
1
1
  // src/util.ts
2
- import { hostname as hostname2, platform as platform2, type as type2, version as version2, arch as arch2, tmpdir } from "node:os";
2
+ import { hostname as hostname2, platform as platform3, type as type2, version as version2, arch as arch3, tmpdir } from "node:os";
3
3
  import crypto from "node:crypto";
4
- import fs5 from "node:fs";
4
+ import fs7 from "node:fs";
5
5
  import zlib from "node:zlib";
6
- import { format, promisify } from "node:util";
7
- import path4 from "node:path";
8
- import util2 from "node:util";
6
+ import { format, promisify as promisify2 } from "node:util";
7
+ import path6 from "node:path";
8
+ import util3 from "node:util";
9
9
  import gitRepoInfo from "git-repo-info";
10
10
  import gitconfig from "gitconfiglocal";
11
11
  import { FormData } from "formdata-node";
@@ -54,13 +54,13 @@ var logPatcher_default = logPatcher;
54
54
 
55
55
  // src/instrumentation/performance/performance-tester.ts
56
56
  import { createObjectCsvWriter } from "csv-writer";
57
- import fs2 from "node:fs";
57
+ import fs4 from "node:fs";
58
58
  import fsPromise from "node:fs/promises";
59
59
  import { performance, PerformanceObserver } from "node:perf_hooks";
60
- import util from "node:util";
60
+ import util2 from "node:util";
61
61
  import worker from "node:worker_threads";
62
- import path2 from "node:path";
63
- import { arch, hostname, platform, type, version } from "node:os";
62
+ import path4 from "node:path";
63
+ import { arch as arch2, hostname, platform as platform2, type, version } from "node:os";
64
64
 
65
65
  // src/bstackLogger.ts
66
66
  import path from "node:path";
@@ -71,7 +71,7 @@ import logger from "@wdio/logger";
71
71
  // package.json
72
72
  var package_default = {
73
73
  name: "@wdio/browserstack-service",
74
- version: "9.23.1",
74
+ version: "9.23.3",
75
75
  description: "WebdriverIO service for better Browserstack integration",
76
76
  author: "Adam Bjerstedt <abjerstedt@gmail.com>",
77
77
  homepage: "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-browserstack-service",
@@ -122,7 +122,7 @@ var package_default = {
122
122
  "git-repo-info": "^2.1.1",
123
123
  gitconfiglocal: "^2.1.0",
124
124
  glob: "^11.0.0",
125
- tar: "^6.1.15",
125
+ tar: "^7.5.7",
126
126
  undici: "^6.21.3",
127
127
  uuid: "^11.1.0",
128
128
  webdriverio: "workspace:*",
@@ -163,7 +163,7 @@ var BROWSERSTACK_OBSERVABILITY = "BROWSERSTACK_OBSERVABILITY";
163
163
  var MAX_GIT_META_DATA_SIZE_IN_BYTES = 64 * 1024;
164
164
  var GIT_META_DATA_TRUNCATED = "...[TRUNCATED]";
165
165
  var WDIO_NAMING_PREFIX = "WebdriverIO-";
166
- var PERF_METRICS_WAIT_TIME = 2e3;
166
+ var UPDATED_CLI_ENDPOINT = "sdk/v1/update_cli";
167
167
 
168
168
  // src/bstackLogger.ts
169
169
  var log = logger("@wdio/browserstack-service");
@@ -256,6 +256,17 @@ var APIUtils = class {
256
256
  }
257
257
  };
258
258
 
259
+ // src/cli/cliUtils.ts
260
+ import fs3 from "node:fs";
261
+ import fsp from "node:fs/promises";
262
+ import { platform, arch, homedir } from "node:os";
263
+ import path3 from "node:path";
264
+ import util, { promisify } from "node:util";
265
+ import { exec } from "node:child_process";
266
+ import { Readable } from "node:stream";
267
+ import yauzl from "yauzl";
268
+ import { threadId } from "node:worker_threads";
269
+
259
270
  // src/fetchWrapper.ts
260
271
  import { fetch as undiciFetch, ProxyAgent } from "undici";
261
272
  var ResponseError = class extends Error {
@@ -288,29 +299,963 @@ function _fetch(input, init) {
288
299
  return fetch(input, init);
289
300
  }
290
301
 
302
+ // src/instrumentation/performance/constants.ts
303
+ var EVENTS = {
304
+ SDK_SETUP: "sdk:setup",
305
+ SDK_CLEANUP: "sdk:cleanup",
306
+ SDK_PRE_TEST: "sdk:pre-test",
307
+ SDK_TEST: "sdk:test",
308
+ SDK_POST_TEST: "sdk:post-test",
309
+ SDK_HOOK: "sdk:hook",
310
+ SDK_DRIVER: "sdk:driver",
311
+ SDK_A11Y: "sdk:a11y",
312
+ SDK_O11Y: "sdk:o11y",
313
+ SDK_AUTO_CAPTURE: "sdk:auto-capture",
314
+ SDK_PROXY_SETUP: "sdk:proxy-setup",
315
+ SDK_TESTHUB: "sdk:testhub",
316
+ SDK_AUTOMATE: "sdk:automate",
317
+ SDK_APP_AUTOMATE: "sdk:app-automate",
318
+ SDK_TURBOSCALE: "sdk:turboscale",
319
+ SDK_PERCY: "sdk:percy",
320
+ SDK_PRE_INITIALIZE: "sdk:driver:pre-initialization",
321
+ SDK_POST_INITIALIZE: "sdk:driver:post-initialization",
322
+ SDK_CLI_CHECK_UPDATE: "sdk:cli:check-update",
323
+ SDK_CLI_DOWNLOAD: "sdk:cli:download",
324
+ SDK_CLI_ON_BOOTSTRAP: "sdk:cli:on-bootstrap",
325
+ SDK_CLI_ON_CONNECT: "sdk:cli:on-connect",
326
+ SDK_CLI_START: "sdk:cli:start",
327
+ SDK_CLI_ON_STOP: "sdk:cli:on-stop",
328
+ SDK_CONNECT_BIN_SESSION: "sdk:connectBinSession",
329
+ SDK_START_BIN_SESSION: "sdk:startBinSession",
330
+ // New events from Python SDK
331
+ SDK_DRIVER_INIT: "sdk:driverInit",
332
+ SDK_FIND_NEAREST_HUB: "sdk:findNearestHub",
333
+ SDK_AUTOMATION_FRAMEWORK_INIT: "sdk:automationFrameworkInit",
334
+ SDK_AUTOMATION_FRAMEWORK_START: "sdk:automationFrameworkStart",
335
+ SDK_AUTOMATION_FRAMEWORK_STOP: "sdk:automationFrameworkStop",
336
+ SDK_ACCESSIBILITY_CONFIG: "sdk:accessibilityConfig",
337
+ SDK_OBSERVABILITY_CONFIG: "sdk:observabilityConfig",
338
+ SDK_AI_SELF_HEAL_STEP: "sdk:aiSelfHealStep",
339
+ SDK_AI_SELF_HEAL_GET_RESULT: "sdk:aiSelfHealGetResult",
340
+ SDK_TEST_FRAMEWORK_EVENT: "sdk:testFrameworkEvent",
341
+ SDK_TEST_SESSION_EVENT: "sdk:testSessionEvent",
342
+ SDK_CLI_LOG_CREATED_EVENT: "sdk:cli:logCreatedEvent",
343
+ SDK_CLI_ENQUEUE_TEST_EVENT: "sdk:cli:enqueueTestEvent",
344
+ SDK_ON_STOP: "sdk:onStop",
345
+ SDK_SEND_LOGS: "sdk:sendlogs",
346
+ // Funnel Events
347
+ SDK_FUNNEL_TEST_ATTEMPTED: "sdk:funnel:test-attempted",
348
+ SDK_FUNNEL_TEST_SUCCESSFUL: "sdk:funnel:test-successful",
349
+ // Log Upload Events
350
+ SDK_UPLOAD_LOGS: "sdk:upload-logs",
351
+ // Key Metrics Events
352
+ SDK_SEND_KEY_METRICS: "sdk:send-key-metrics",
353
+ SDK_KEY_METRICS_PREPARATION: "sdk:key-metrics:preparation",
354
+ SDK_KEY_METRICS_UPLOAD: "sdk:key-metrics:upload",
355
+ // CLI Binary Events
356
+ SDK_CLI_DOWNLOAD_BINARY: "sdk:cli:download-binary",
357
+ SDK_CLI_BINARY_VERIFICATION: "sdk:cli:binary-verification",
358
+ // Cleanup & Shutdown Events (tracking gap between driver:quit and funnel:test-successful)
359
+ SDK_LISTENER_WORKER_END: "sdk:listener:worker-end",
360
+ SDK_PERCY_TEARDOWN: "sdk:percy:teardown",
361
+ SDK_WORKER_SAVE_DATA: "sdk:worker:save-data",
362
+ SDK_PERFORMANCE_REPORT_GEN: "sdk:performance:report-generation",
363
+ SDK_PERFORMANCE_JSON_WRITE: "sdk:performance:json-write",
364
+ SDK_PERFORMANCE_HTML_GEN: "sdk:performance:html-generation",
365
+ // Device Allocation Event (tracking gap between beforeSession and before hooks)
366
+ SDK_DEVICE_ALLOCATION: "sdk:device-allocation"
367
+ };
368
+ var TESTHUB_EVENTS = {
369
+ START: `${EVENTS.SDK_TESTHUB}:start`,
370
+ STOP: `${EVENTS.SDK_TESTHUB}:stop`
371
+ };
372
+ var AUTOMATE_EVENTS = {
373
+ KEEP_ALIVE: `${EVENTS.SDK_AUTOMATE}:keep-alive`,
374
+ HUB_MANAGEMENT: `${EVENTS.SDK_AUTOMATE}:hub-management`,
375
+ LOCAL_START: `${EVENTS.SDK_AUTOMATE}:local-start`,
376
+ LOCAL_STOP: `${EVENTS.SDK_AUTOMATE}:local-stop`,
377
+ DRIVER_MANAGE: `${EVENTS.SDK_AUTOMATE}:driver-manage`,
378
+ SESSION_NAME: `${EVENTS.SDK_AUTOMATE}:session-name`,
379
+ SESSION_STATUS: `${EVENTS.SDK_AUTOMATE}:session-status`,
380
+ SESSION_ANNOTATION: `${EVENTS.SDK_AUTOMATE}:session-annotation`,
381
+ IDLE_TIMEOUT: `${EVENTS.SDK_AUTOMATE}:idle-timeout`,
382
+ GENERATE_CI_ARTIFACT: `${EVENTS.SDK_AUTOMATE}:ci-artifacts`,
383
+ PRINT_BUILDLINK: `${EVENTS.SDK_AUTOMATE}:print-buildlink`
384
+ };
385
+ var A11Y_EVENTS = {
386
+ PERFORM_SCAN: `${EVENTS.SDK_A11Y}:driver-performscan`,
387
+ SAVE_RESULTS: `${EVENTS.SDK_A11Y}:save-results`,
388
+ GET_RESULTS: `${EVENTS.SDK_A11Y}:get-accessibility-results`,
389
+ GET_RESULTS_SUMMARY: `${EVENTS.SDK_A11Y}:get-accessibility-results-summary`
390
+ };
391
+ var PERCY_EVENTS = {
392
+ DOWNLOAD: `${EVENTS.SDK_PERCY}:download`,
393
+ SCREENSHOT: `${EVENTS.SDK_PERCY}:screenshot`,
394
+ START: `${EVENTS.SDK_PERCY}:start`,
395
+ STOP: `${EVENTS.SDK_PERCY}:stop`,
396
+ AUTO_CAPTURE: `${EVENTS.SDK_PERCY}:auto-capture`,
397
+ SNAPSHOT: `${EVENTS.SDK_PERCY}:snapshot`,
398
+ SCREENSHOT_APP: `${EVENTS.SDK_PERCY}:screenshot-app`
399
+ };
400
+ var O11Y_EVENTS = {
401
+ SYNC: `${EVENTS.SDK_O11Y}:sync`,
402
+ TAKE_SCREENSHOT: `${EVENTS.SDK_O11Y}:driver-takeScreenShot`,
403
+ PRINT_BUILDLINK: `${EVENTS.SDK_O11Y}:print-buildlink`
404
+ };
405
+ var HOOK_EVENTS = {
406
+ BEFORE_EACH: `${EVENTS.SDK_HOOK}:before-each`,
407
+ AFTER_EACH: `${EVENTS.SDK_HOOK}:after-each`,
408
+ AFTER_ALL: `${EVENTS.SDK_HOOK}:after-all`,
409
+ BEFORE_ALL: `${EVENTS.SDK_HOOK}:before-all`,
410
+ BEFORE: `${EVENTS.SDK_HOOK}:before`,
411
+ AFTER: `${EVENTS.SDK_HOOK}:after`
412
+ };
413
+ var TURBOSCALE_EVENTS = {
414
+ HUB_MANAGEMENT: `${EVENTS.SDK_TURBOSCALE}:hub-management`,
415
+ PRINT_BUILDLINK: `${EVENTS.SDK_TURBOSCALE}:print-buildlink`
416
+ };
417
+ var APP_AUTOMATE_EVENTS = {
418
+ APP_UPLOAD: `${EVENTS.SDK_APP_AUTOMATE}:app-upload`
419
+ };
420
+ var DRIVER_EVENT = {
421
+ QUIT: `${EVENTS.SDK_DRIVER}:quit`,
422
+ GET: `${EVENTS.SDK_DRIVER}:get`,
423
+ PRE_EXECUTE: `${EVENTS.SDK_DRIVER}:pre-execute`,
424
+ POST_EXECUTE: `${EVENTS.SDK_DRIVER}:post-execute`,
425
+ INIT: EVENTS.SDK_DRIVER_INIT,
426
+ PRE_INITIALIZE: EVENTS.SDK_PRE_INITIALIZE,
427
+ POST_INITIALIZE: EVENTS.SDK_POST_INITIALIZE
428
+ };
429
+ var FRAMEWORK_EVENTS = {
430
+ INIT: EVENTS.SDK_AUTOMATION_FRAMEWORK_INIT,
431
+ START: EVENTS.SDK_AUTOMATION_FRAMEWORK_START,
432
+ STOP: EVENTS.SDK_AUTOMATION_FRAMEWORK_STOP
433
+ };
434
+ var CONFIG_EVENTS = {
435
+ ACCESSIBILITY: EVENTS.SDK_ACCESSIBILITY_CONFIG,
436
+ OBSERVABILITY: EVENTS.SDK_OBSERVABILITY_CONFIG
437
+ };
438
+ var AI_EVENTS = {
439
+ SELF_HEAL_STEP: EVENTS.SDK_AI_SELF_HEAL_STEP,
440
+ SELF_HEAL_GET_RESULT: EVENTS.SDK_AI_SELF_HEAL_GET_RESULT
441
+ };
442
+ var DISPATCHER_EVENTS = {
443
+ TEST_FRAMEWORK: EVENTS.SDK_TEST_FRAMEWORK_EVENT,
444
+ TEST_SESSION: EVENTS.SDK_TEST_SESSION_EVENT,
445
+ LOG_CREATED: EVENTS.SDK_CLI_LOG_CREATED_EVENT,
446
+ ENQUEUE_TEST: EVENTS.SDK_CLI_ENQUEUE_TEST_EVENT
447
+ };
448
+
449
+ // src/cli/cliLogger.ts
450
+ import path2 from "node:path";
451
+ import fs2 from "node:fs";
452
+ import chalk2 from "chalk";
453
+ import logger2 from "@wdio/logger";
454
+ var log2 = logger2("@wdio/browserstack-service/cli");
455
+ var BStackLogger2 = class {
456
+ static logFilePath = path2.join(process.cwd(), LOGS_FILE);
457
+ static logFolderPath = path2.join(process.cwd(), "logs");
458
+ static logFileStream;
459
+ static logToFile(logMessage, logLevel) {
460
+ try {
461
+ if (!this.logFileStream) {
462
+ this.ensureLogsFolder();
463
+ this.logFileStream = fs2.createWriteStream(this.logFilePath, { flags: "a" });
464
+ }
465
+ if (this.logFileStream && this.logFileStream.writable) {
466
+ this.logFileStream.write(this.formatLog(logMessage, logLevel));
467
+ }
468
+ } catch (error) {
469
+ log2.debug(`Failed to log to file. Error ${error}`);
470
+ }
471
+ }
472
+ static formatLog(logMessage, level) {
473
+ return `${chalk2.gray((/* @__PURE__ */ new Date()).toISOString())} ${chalk2[COLORS[level]](level.toUpperCase())} ${chalk2.whiteBright("@wdio/browserstack-service")} ${logMessage}
474
+ `;
475
+ }
476
+ static info(message) {
477
+ this.logToFile(message, "info");
478
+ log2.info(message);
479
+ }
480
+ static error(message) {
481
+ this.logToFile(message, "error");
482
+ log2.error(message);
483
+ }
484
+ static debug(message, param) {
485
+ this.logToFile(message, "debug");
486
+ if (param) {
487
+ log2.debug(message, param);
488
+ } else {
489
+ log2.debug(message);
490
+ }
491
+ }
492
+ static warn(message) {
493
+ this.logToFile(message, "warn");
494
+ log2.warn(message);
495
+ }
496
+ static trace(message) {
497
+ this.logToFile(message, "trace");
498
+ log2.trace(message);
499
+ }
500
+ static clearLogger() {
501
+ if (this.logFileStream) {
502
+ this.logFileStream.end();
503
+ }
504
+ this.logFileStream = null;
505
+ }
506
+ static clearLogFile() {
507
+ if (fs2.existsSync(this.logFilePath)) {
508
+ fs2.truncateSync(this.logFilePath);
509
+ }
510
+ }
511
+ static ensureLogsFolder() {
512
+ if (!fs2.existsSync(this.logFolderPath)) {
513
+ fs2.mkdirSync(this.logFolderPath);
514
+ }
515
+ }
516
+ };
517
+
518
+ // src/cli/frameworks/constants/testFrameworkConstants.ts
519
+ var TestFrameworkConstants = {
520
+ KEY_TEST_UUID: "test_uuid",
521
+ KEY_TEST_ID: "test_id",
522
+ KEY_TEST_NAME: "test_name",
523
+ KEY_TEST_FILE_PATH: "test_file_path",
524
+ KEY_TEST_TAGS: "test_tags",
525
+ KEY_TEST_RESULT: "test_result",
526
+ KEY_TEST_RESULT_AT: "test_result_at",
527
+ KEY_TEST_STARTED_AT: "test_started_at",
528
+ KEY_TEST_ENDED_AT: "test_ended_at",
529
+ KEY_TEST_LOCATION: "test_location",
530
+ KEY_TEST_SCOPE: "test_scope",
531
+ KEY_TEST_SCOPES: "test_scopes",
532
+ KEY_TEST_FRAMEWORK_NAME: "test_framework_name",
533
+ KEY_TEST_FRAMEWORK_VERSION: "test_framework_version",
534
+ KEY_TEST_CODE: "test_code",
535
+ KEY_TEST_RERUN_NAME: "test_rerun_name",
536
+ KEY_PLATFORM_INDEX: "platform_index",
537
+ KEY_TEST_FAILURE: "test_failure",
538
+ KEY_TEST_FAILURE_TYPE: "test_failure_type",
539
+ KEY_TEST_FAILURE_REASON: "test_failure_reason",
540
+ KEY_TEST_LOGS: "test_logs",
541
+ KEY_TEST_META: "test_meta",
542
+ KEY_TEST_DEFERRED: "test_deferred",
543
+ KEY_SESSION_NAME: "test_session_name",
544
+ KEY_AUTOMATE_SESSION_NAME: "automate_session_name",
545
+ KEY_AUTOMATE_SESSION_STATUS: "automate_session_status",
546
+ KEY_AUTOMATE_SESSION_REASON: "automate_session_reason",
547
+ KEY_EVENT_STARTED_AT: "event_started_at",
548
+ KEY_EVENT_ENDED_AT: "event_ended_at",
549
+ KEY_HOOK_ID: "hook_id",
550
+ KEY_HOOK_RESULT: "hook_result",
551
+ KEY_HOOK_LOGS: "hook_logs",
552
+ KEY_HOOK_NAME: "hook_name",
553
+ KEY_HOOKS_STARTED: "test_hooks_started",
554
+ KEY_HOOKS_FINISHED: "test_hooks_finished",
555
+ DEFAULT_TEST_RESULT: "pending",
556
+ DEFAULT_HOOK_RESULT: "pending",
557
+ KIND_SCREENSHOT: "TEST_SCREENSHOT",
558
+ KIND_LOG: "TEST_LOG",
559
+ HOOK_REGEX: "^(BEFORE_|AFTER_)"
560
+ };
561
+
562
+ // src/cli/cliUtils.ts
563
+ var CLI_LOCK_TIMEOUT_MS = 5 * 60 * 1e3;
564
+ var CLI_LOCK_POLL_MS = 1e3;
565
+ var CLI_DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
566
+ var CLI_DOWNLOAD_TMP_PREFIX = "downloaded_file_";
567
+ var CLI_DOWNLOAD_TMP_SUFFIX = ".zip";
568
+ var CLIUtils = class _CLIUtils {
569
+ static automationFrameworkDetail = {};
570
+ static testFrameworkDetail = {};
571
+ static CLISupportedFrameworks = ["mocha"];
572
+ static isDevelopmentEnv() {
573
+ return process.env.BROWSERSTACK_CLI_ENV === "development";
574
+ }
575
+ static getCLIParamsForDevEnv() {
576
+ return {
577
+ id: process.env.BROWSERSTACK_CLI_ENV || "",
578
+ listen: `unix:/tmp/sdk-platform-${process.env.BROWSERSTACK_CLI_ENV}.sock`
579
+ };
580
+ }
581
+ /**
582
+ * Build config object for binary session request
583
+ * @returns {string}
584
+ * @throws {Error}
585
+ */
586
+ static getBinConfig(config, capabilities, options, buildTag) {
587
+ const modifiedOpts = { ...options };
588
+ if (modifiedOpts.opts) {
589
+ modifiedOpts.browserStackLocalOptions = modifiedOpts.opts;
590
+ delete modifiedOpts.opts;
591
+ }
592
+ modifiedOpts.testContextOptions = {
593
+ skipSessionName: isFalse(modifiedOpts.setSessionName),
594
+ skipSessionStatus: isFalse(modifiedOpts.setSessionStatus),
595
+ sessionNameOmitTestTitle: modifiedOpts.sessionNameOmitTestTitle || false,
596
+ sessionNamePrependTopLevelSuiteTitle: modifiedOpts.sessionNamePrependTopLevelSuiteTitle || false,
597
+ sessionNameFormat: modifiedOpts.sessionNameFormat || ""
598
+ };
599
+ const commonBstackOptions = (() => {
600
+ if (capabilities && !Array.isArray(capabilities) && typeof capabilities === "object" && "bstack:options" in capabilities) {
601
+ return capabilities["bstack:options"] || {};
602
+ }
603
+ return {};
604
+ })();
605
+ const isNonBstackA11y = isTurboScale(options) || !shouldAddServiceVersion(
606
+ config,
607
+ options.testObservability
608
+ );
609
+ const observabilityOptions = options.testObservabilityOptions || {};
610
+ const binconfig = {
611
+ userName: observabilityOptions.user || config.user,
612
+ accessKey: observabilityOptions.key || config.key,
613
+ platforms: [],
614
+ isNonBstackA11yWDIO: isNonBstackA11y,
615
+ ...modifiedOpts,
616
+ ...commonBstackOptions
617
+ };
618
+ binconfig.buildName = observabilityOptions.buildName || binconfig.buildName;
619
+ binconfig.projectName = observabilityOptions.projectName || binconfig.projectName;
620
+ binconfig.buildTag = this.getObservabilityBuildTags(observabilityOptions, buildTag) || [];
621
+ let caps = capabilities;
622
+ if (capabilities && !Array.isArray(capabilities)) {
623
+ caps = [capabilities];
624
+ }
625
+ if (Array.isArray(caps)) {
626
+ for (const cap of caps) {
627
+ const platform4 = {};
628
+ const capability = cap;
629
+ Object.keys(capability).filter((key) => key !== "bstack:options").forEach((key) => {
630
+ platform4[key] = capability[key];
631
+ });
632
+ if (capability["bstack:options"]) {
633
+ Object.keys(
634
+ capability["bstack:options"]
635
+ ).forEach((key) => {
636
+ platform4[key] = capability["bstack:options"][key];
637
+ });
638
+ }
639
+ binconfig.platforms.push(platform4);
640
+ }
641
+ }
642
+ return JSON.stringify(binconfig);
643
+ }
644
+ static getSdkVersion() {
645
+ return BSTACK_SERVICE_VERSION;
646
+ }
647
+ static getSdkLanguage() {
648
+ return "ECMAScript";
649
+ }
650
+ static async setupCliPath(config) {
651
+ BStackLogger2.debug("Configuring Cli path.");
652
+ const developmentBinaryPath = process.env.SDK_CLI_BIN_PATH || null;
653
+ if (!isNullOrEmpty(developmentBinaryPath)) {
654
+ BStackLogger2.debug(`Development Cli Path: ${developmentBinaryPath}`);
655
+ return developmentBinaryPath;
656
+ }
657
+ try {
658
+ const cliDir = this.getCliDir();
659
+ if (isNullOrEmpty(cliDir)) {
660
+ throw new Error("No writable directory available for the CLI");
661
+ }
662
+ const existingCliPath = this.getExistingCliPath(cliDir);
663
+ const finalBinaryPath = await this.checkAndUpdateCli(
664
+ existingCliPath,
665
+ cliDir,
666
+ config
667
+ );
668
+ BStackLogger2.debug(`Resolved binary path: ${finalBinaryPath}`);
669
+ return finalBinaryPath;
670
+ } catch (err) {
671
+ BStackLogger2.debug(
672
+ `Error in setting up cli path directory, Exception: ${util.format(err)}`
673
+ );
674
+ }
675
+ return null;
676
+ }
677
+ static async checkAndUpdateCli(existingCliPath, cliDir, config) {
678
+ if (process.env.BROWSERSTACK_TESTHUB_JWT) {
679
+ BStackLogger2.debug(
680
+ `Worker process detected, skipping CLI update. Using existing: ${existingCliPath}`
681
+ );
682
+ if (existingCliPath && fs3.existsSync(existingCliPath)) {
683
+ return existingCliPath;
684
+ }
685
+ BStackLogger2.warn(
686
+ "Worker process has no existing CLI binary, attempting download as fallback."
687
+ );
688
+ }
689
+ PerformanceTester.start(EVENTS.SDK_CLI_CHECK_UPDATE);
690
+ BStackLogger2.info(`Current CLI Path Found: ${existingCliPath}`);
691
+ const queryParams = {
692
+ sdk_version: _CLIUtils.getSdkVersion(),
693
+ os: platform(),
694
+ os_arch: arch(),
695
+ cli_version: "0",
696
+ sdk_language: this.getSdkLanguage()
697
+ };
698
+ if (!isNullOrEmpty(existingCliPath)) {
699
+ queryParams.cli_version = await this.runShellCommand(
700
+ `${existingCliPath} version`
701
+ );
702
+ }
703
+ const response = await this.requestToUpdateCLI(queryParams, config);
704
+ if (nestedKeyValue(response, ["updated_cli_version"])) {
705
+ BStackLogger2.debug(
706
+ `Need to update binary, current binary version: ${queryParams.cli_version}`
707
+ );
708
+ const browserStackBinaryUrl = process.env.BROWSERSTACK_BINARY_URL || null;
709
+ if (!isNullOrEmpty(browserStackBinaryUrl)) {
710
+ BStackLogger2.debug(
711
+ `Using BROWSERSTACK_BINARY_URL: ${browserStackBinaryUrl}`
712
+ );
713
+ response.url = browserStackBinaryUrl;
714
+ }
715
+ const finalBinaryPath = await this.downloadLatestBinary(
716
+ nestedKeyValue(response, ["url"]),
717
+ cliDir
718
+ );
719
+ PerformanceTester.end(EVENTS.SDK_CLI_CHECK_UPDATE);
720
+ return finalBinaryPath;
721
+ }
722
+ PerformanceTester.end(EVENTS.SDK_CLI_CHECK_UPDATE);
723
+ return existingCliPath;
724
+ }
725
+ static getCliDir() {
726
+ const writableDir = this.getWritableDir();
727
+ try {
728
+ if (isNullOrEmpty(writableDir)) {
729
+ throw new Error("No writable directory available for the CLI");
730
+ }
731
+ const cliDirPath = path3.join(writableDir, "cli");
732
+ if (!fs3.existsSync(cliDirPath)) {
733
+ createDir(cliDirPath);
734
+ }
735
+ return cliDirPath;
736
+ } catch (err) {
737
+ BStackLogger2.error(
738
+ `Error in getting writable directory, writableDir=${util.format(err)}`
739
+ );
740
+ return "";
741
+ }
742
+ }
743
+ static getWritableDir() {
744
+ const writableDirOptions = [
745
+ process.env.BROWSERSTACK_FILES_DIR,
746
+ path3.join(homedir(), ".browserstack"),
747
+ path3.join("tmp", ".browserstack")
748
+ ];
749
+ for (const path9 of writableDirOptions) {
750
+ if (isNullOrEmpty(path9)) {
751
+ continue;
752
+ }
753
+ try {
754
+ if (fs3.existsSync(path9)) {
755
+ BStackLogger2.debug(`File ${path9} already exist`);
756
+ if (!isWritable(path9)) {
757
+ BStackLogger2.debug(`Giving write permission to ${path9}`);
758
+ const success = setReadWriteAccess(path9);
759
+ if (!isTrue(success)) {
760
+ BStackLogger2.warn(
761
+ `Unable to provide write permission to ${path9}`
762
+ );
763
+ }
764
+ }
765
+ } else {
766
+ BStackLogger2.debug(`File does not exist: ${path9}`);
767
+ createDir(path9);
768
+ BStackLogger2.debug(`Giving write permission to ${path9}`);
769
+ const success = setReadWriteAccess(path9);
770
+ if (!isTrue(success)) {
771
+ BStackLogger2.warn(
772
+ `Unable to provide write permission to ${path9}`
773
+ );
774
+ }
775
+ }
776
+ return path9;
777
+ } catch (err) {
778
+ BStackLogger2.error(
779
+ `Unable to get writable directory, exception ${util.format(err)}`
780
+ );
781
+ }
782
+ }
783
+ return null;
784
+ }
785
+ static getExistingCliPath(cliDir) {
786
+ try {
787
+ if (!fs3.existsSync(cliDir) || !fs3.statSync(cliDir).isDirectory()) {
788
+ return "";
789
+ }
790
+ const allBinaries = fs3.readdirSync(cliDir).map((file) => path3.join(cliDir, file)).filter(
791
+ (filePath) => fs3.statSync(filePath).isFile() && path3.basename(filePath).startsWith("binary-")
792
+ );
793
+ if (allBinaries.length > 0) {
794
+ const latestBinary = allBinaries.map((filePath) => ({
795
+ filePath,
796
+ mtime: fs3.statSync(filePath).mtime
797
+ })).reduce(
798
+ (latest, current) => {
799
+ if (!latest || !latest.mtime) {
800
+ return current;
801
+ }
802
+ if (current.mtime > latest.mtime) {
803
+ return current;
804
+ }
805
+ return latest;
806
+ },
807
+ null
808
+ );
809
+ return latestBinary ? latestBinary.filePath : "";
810
+ }
811
+ return "";
812
+ } catch (err) {
813
+ BStackLogger2.error(`Error while reading CLI path: ${util.format(err)}`);
814
+ return "";
815
+ }
816
+ }
817
+ static requestToUpdateCLI = async (queryParams, config) => {
818
+ const params = new URLSearchParams(queryParams);
819
+ const requestInit = {
820
+ method: "GET",
821
+ headers: {
822
+ Authorization: `Basic ${Buffer.from(`${getBrowserStackUser(config)}:${getBrowserStackKey(config)}`).toString("base64")}`
823
+ }
824
+ };
825
+ const response = await _fetch(
826
+ `${APIUtils.BROWSERSTACK_AUTOMATE_API_URL}/${UPDATED_CLI_ENDPOINT}?${params.toString()}`,
827
+ requestInit
828
+ );
829
+ const jsonResponse = await response.json();
830
+ BStackLogger2.debug(`response ${JSON.stringify(jsonResponse)}`);
831
+ return jsonResponse;
832
+ };
833
+ static runShellCommand(cmdCommand, workingDir = "") {
834
+ return new Promise((resolve) => {
835
+ const process2 = exec(
836
+ cmdCommand,
837
+ { cwd: workingDir, timeout: 5e3 },
838
+ (error, stdout, stderr) => {
839
+ if (error) {
840
+ resolve(stderr.trim() || "SHELL_EXECUTE_ERROR");
841
+ } else {
842
+ resolve(stdout.trim());
843
+ }
844
+ }
845
+ );
846
+ process2.on("error", () => {
847
+ resolve("SHELL_EXECUTE_ERROR");
848
+ });
849
+ });
850
+ }
851
+ static downloadLatestBinary = async (binDownloadUrl, cliDir) => {
852
+ const lockPath = path3.join(cliDir, "download.lock");
853
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
854
+ const parseLockFile = () => {
855
+ try {
856
+ const content = fs3.readFileSync(lockPath, "utf8").trim();
857
+ const [pidLine, timestampLine] = content.split("\n");
858
+ const pid = Number.parseInt(pidLine, 10);
859
+ const timestamp = Number.parseInt(timestampLine, 10);
860
+ if (!Number.isFinite(pid) || !Number.isFinite(timestamp)) {
861
+ return null;
862
+ }
863
+ return { pid, timestamp };
864
+ } catch {
865
+ return null;
866
+ }
867
+ };
868
+ const isProcessRunning = (pid) => {
869
+ try {
870
+ process.kill(pid, 0);
871
+ return true;
872
+ } catch {
873
+ return false;
874
+ }
875
+ };
876
+ const acquireLock = async (timeoutMs = CLI_LOCK_TIMEOUT_MS, pollMs = CLI_LOCK_POLL_MS) => {
877
+ const start = Date.now();
878
+ while (true) {
879
+ try {
880
+ const fd = fs3.openSync(lockPath, "wx");
881
+ try {
882
+ fs3.writeFileSync(fd, `${process.pid}
883
+ ${Date.now()}
884
+ `);
885
+ } catch {
886
+ }
887
+ return () => {
888
+ try {
889
+ fs3.closeSync(fd);
890
+ } catch {
891
+ }
892
+ try {
893
+ fs3.unlinkSync(lockPath);
894
+ } catch {
895
+ }
896
+ };
897
+ } catch (e) {
898
+ const error = e;
899
+ if (error.code === "EEXIST") {
900
+ const lockMeta = parseLockFile();
901
+ if (lockMeta) {
902
+ const lockAge = Date.now() - lockMeta.timestamp;
903
+ const running = isProcessRunning(lockMeta.pid);
904
+ if (!running || lockAge > timeoutMs) {
905
+ BStackLogger2.warn(
906
+ `Stale CLI download lock detected (pid=${lockMeta.pid}, age=${lockAge}ms). Removing lock.`
907
+ );
908
+ try {
909
+ fs3.unlinkSync(lockPath);
910
+ } catch {
911
+ }
912
+ continue;
913
+ }
914
+ }
915
+ const existingBinary = _CLIUtils.getExistingCliPath(cliDir);
916
+ if (existingBinary && fs3.existsSync(existingBinary) && fs3.statSync(existingBinary).size > 0) {
917
+ BStackLogger2.debug(
918
+ `Binary appeared while waiting for lock: ${existingBinary}`
919
+ );
920
+ return { alreadyExists: existingBinary };
921
+ }
922
+ if (Date.now() - start > timeoutMs) {
923
+ throw new Error(
924
+ `Timeout waiting for lock: ${lockPath}`
925
+ );
926
+ }
927
+ await sleep(pollMs);
928
+ continue;
929
+ }
930
+ throw e;
931
+ }
932
+ }
933
+ };
934
+ const cleanupTemporaryDownloads = (maxAgeMs = CLI_LOCK_TIMEOUT_MS) => {
935
+ try {
936
+ const now = Date.now();
937
+ for (const entry of fs3.readdirSync(cliDir)) {
938
+ if (!entry.startsWith(CLI_DOWNLOAD_TMP_PREFIX) || !entry.endsWith(CLI_DOWNLOAD_TMP_SUFFIX)) {
939
+ continue;
940
+ }
941
+ const filePath = path3.join(cliDir, entry);
942
+ let stats;
943
+ try {
944
+ stats = fs3.statSync(filePath);
945
+ } catch {
946
+ continue;
947
+ }
948
+ if (now - stats.mtimeMs < maxAgeMs) {
949
+ continue;
950
+ }
951
+ try {
952
+ fs3.unlinkSync(filePath);
953
+ } catch (err) {
954
+ BStackLogger2.debug(
955
+ `Failed to delete temp CLI zip file ${filePath}: ${util.format(err)}`
956
+ );
957
+ }
958
+ }
959
+ } catch (err) {
960
+ BStackLogger2.debug(
961
+ `Failed to scan temp CLI downloads in ${cliDir}: ${util.format(err)}`
962
+ );
963
+ }
964
+ };
965
+ PerformanceTester.start(EVENTS.SDK_CLI_DOWNLOAD);
966
+ BStackLogger2.debug(`Downloading SDK binary from: ${binDownloadUrl}`);
967
+ let downloadEnded = false;
968
+ const endDownload = (success = true, errMsg) => {
969
+ if (downloadEnded) {
970
+ return;
971
+ }
972
+ downloadEnded = true;
973
+ if (success) {
974
+ PerformanceTester.end(EVENTS.SDK_CLI_DOWNLOAD);
975
+ return;
976
+ }
977
+ PerformanceTester.end(
978
+ EVENTS.SDK_CLI_DOWNLOAD,
979
+ false,
980
+ errMsg
981
+ );
982
+ };
983
+ let releaseLock;
984
+ try {
985
+ const lockResult = await acquireLock();
986
+ if (typeof lockResult !== "function") {
987
+ endDownload();
988
+ return lockResult.alreadyExists;
989
+ }
990
+ releaseLock = lockResult;
991
+ const existingBinary = _CLIUtils.getExistingCliPath(cliDir);
992
+ if (existingBinary && fs3.existsSync(existingBinary) && fs3.statSync(existingBinary).size > 0) {
993
+ BStackLogger2.debug(
994
+ `Binary already exists after acquiring lock: ${existingBinary}`
995
+ );
996
+ endDownload();
997
+ releaseLock();
998
+ return existingBinary;
999
+ }
1000
+ cleanupTemporaryDownloads();
1001
+ const zipFilePath = path3.join(
1002
+ cliDir,
1003
+ `${CLI_DOWNLOAD_TMP_PREFIX}${process.pid}_${Date.now()}${CLI_DOWNLOAD_TMP_SUFFIX}`
1004
+ );
1005
+ const downloadedFileStream = fs3.createWriteStream(zipFilePath);
1006
+ return new Promise((resolve, reject) => {
1007
+ const binaryName = null;
1008
+ const processDownload = async () => {
1009
+ const abortController = new AbortController();
1010
+ const timeout = setTimeout(
1011
+ () => abortController.abort(),
1012
+ CLI_DOWNLOAD_TIMEOUT_MS
1013
+ );
1014
+ let response;
1015
+ try {
1016
+ response = await _fetch(binDownloadUrl, {
1017
+ signal: abortController.signal
1018
+ });
1019
+ } finally {
1020
+ clearTimeout(timeout);
1021
+ }
1022
+ if (!response.body) {
1023
+ throw new Error("No response body received");
1024
+ }
1025
+ downloadedFileStream.on("error", function(err) {
1026
+ BStackLogger2.error(
1027
+ `Got Error while downloading cli binary file: ${err}`
1028
+ );
1029
+ endDownload(false, util.format(err));
1030
+ releaseLock?.();
1031
+ reject(err);
1032
+ });
1033
+ try {
1034
+ const arrayBuffer = await response.arrayBuffer();
1035
+ const nodeStream = Readable.from([
1036
+ new Uint8Array(arrayBuffer)
1037
+ ]);
1038
+ nodeStream.pipe(downloadedFileStream);
1039
+ _CLIUtils.downloadFileStream(
1040
+ downloadedFileStream,
1041
+ binaryName,
1042
+ zipFilePath,
1043
+ cliDir,
1044
+ (result) => {
1045
+ endDownload();
1046
+ releaseLock?.();
1047
+ resolve(result);
1048
+ },
1049
+ (err) => {
1050
+ endDownload(false, util.format(err));
1051
+ releaseLock?.();
1052
+ reject(err);
1053
+ }
1054
+ );
1055
+ } catch (err) {
1056
+ BStackLogger2.error(
1057
+ `Got Error in cli binary downloading request ${util.format(err)}`
1058
+ );
1059
+ endDownload(false, util.format(err));
1060
+ releaseLock?.();
1061
+ reject(err);
1062
+ }
1063
+ };
1064
+ processDownload();
1065
+ });
1066
+ } catch (err) {
1067
+ releaseLock?.();
1068
+ endDownload(false, util.format(err));
1069
+ BStackLogger2.debug(
1070
+ `Failed to download binary, Exception: ${util.format(err)}`
1071
+ );
1072
+ return null;
1073
+ }
1074
+ };
1075
+ static downloadFileStream(downloadedFileStream, binaryName, zipFilePath, cliDir, resolve, reject) {
1076
+ downloadedFileStream.on("close", async function() {
1077
+ const yauzlOpenPromise = promisify(yauzl.open);
1078
+ try {
1079
+ const zipfile = await yauzlOpenPromise(zipFilePath, {
1080
+ lazyEntries: true
1081
+ });
1082
+ zipfile.readEntry();
1083
+ zipfile.on("entry", async (entry) => {
1084
+ if (!binaryName) {
1085
+ binaryName = entry.fileName;
1086
+ }
1087
+ if (/\/$/.test(entry.fileName)) {
1088
+ zipfile.readEntry();
1089
+ } else {
1090
+ const writeStream = fs3.createWriteStream(
1091
+ path3.join(cliDir, entry.fileName)
1092
+ );
1093
+ const openReadStreamPromise = promisify(
1094
+ zipfile.openReadStream
1095
+ ).bind(zipfile);
1096
+ try {
1097
+ const readStream = await openReadStreamPromise(entry);
1098
+ readStream.on("end", function() {
1099
+ writeStream.close();
1100
+ zipfile.readEntry();
1101
+ });
1102
+ readStream.pipe(writeStream);
1103
+ } catch (zipErr) {
1104
+ reject(zipErr);
1105
+ }
1106
+ if (entry.fileName === binaryName) {
1107
+ zipfile.close();
1108
+ }
1109
+ }
1110
+ });
1111
+ zipfile.on("error", (zipErr) => {
1112
+ reject(zipErr);
1113
+ });
1114
+ zipfile.once("end", () => {
1115
+ fsp.unlink(zipFilePath).catch(() => {
1116
+ BStackLogger2.warn(
1117
+ `Failed to delete zip file: ${zipFilePath}`
1118
+ );
1119
+ });
1120
+ fsp.chmod(`${cliDir}/${binaryName}`, "0755").then(() => {
1121
+ resolve(`${cliDir}/${binaryName}`);
1122
+ }).catch((err) => {
1123
+ reject(err);
1124
+ });
1125
+ zipfile.close();
1126
+ });
1127
+ } catch (err) {
1128
+ reject(err);
1129
+ }
1130
+ });
1131
+ }
1132
+ static getTestFrameworkDetail() {
1133
+ if (process.env.BROWSERSTACK_TEST_FRAMEWORK_DETAIL) {
1134
+ return JSON.parse(process.env.BROWSERSTACK_TEST_FRAMEWORK_DETAIL);
1135
+ }
1136
+ return this.testFrameworkDetail;
1137
+ }
1138
+ static getAutomationFrameworkDetail() {
1139
+ if (process.env.BROWSERSTACK_AUTOMATION_FRAMEWORK_DETAIL) {
1140
+ return JSON.parse(
1141
+ process.env.BROWSERSTACK_AUTOMATION_FRAMEWORK_DETAIL
1142
+ );
1143
+ }
1144
+ return this.automationFrameworkDetail;
1145
+ }
1146
+ static setFrameworkDetail(testFramework, automationFramework) {
1147
+ if (!testFramework || !automationFramework) {
1148
+ BStackLogger2.debug(
1149
+ `Test or Automation framework not provided testFramework=${testFramework}, automationFramework=${automationFramework}`
1150
+ );
1151
+ }
1152
+ this.testFrameworkDetail = {
1153
+ name: testFramework,
1154
+ version: { [testFramework]: _CLIUtils.getSdkVersion() }
1155
+ };
1156
+ this.automationFrameworkDetail = {
1157
+ name: automationFramework,
1158
+ version: { [automationFramework]: _CLIUtils.getSdkVersion() }
1159
+ };
1160
+ process.env.BROWSERSTACK_AUTOMATION_FRAMEWORK_DETAIL = JSON.stringify(
1161
+ this.automationFrameworkDetail
1162
+ );
1163
+ process.env.BROWSERSTACK_TEST_FRAMEWORK_DETAIL = JSON.stringify(
1164
+ this.testFrameworkDetail
1165
+ );
1166
+ }
1167
+ /**
1168
+ * Get the current instance name using thread id and processId
1169
+ * @returns {string}
1170
+ */
1171
+ static getCurrentInstanceName() {
1172
+ return `${process.pid}:${threadId}`;
1173
+ }
1174
+ /**
1175
+ * Generate a unique client worker identifier combining thread ID and process ID.
1176
+ * This identifier is used to track worker-specific events and performance metrics
1177
+ * across distributed test execution. Format matches the Python SDK implementation
1178
+ * for consistency across SDKs.
1179
+ *
1180
+ * Format: "threadId-processId"
1181
+ *
1182
+ * @param context - Optional execution context with threadId and processId
1183
+ * @returns Worker ID string in format "threadId-processId"
1184
+ * @example
1185
+ * const workerId = CLIUtils.getClientWorkerId() // Returns "1-12345"
1186
+ * const workerId = CLIUtils.getClientWorkerId({ threadId: 123, processId: 456 }) // Returns "123-456"
1187
+ */
1188
+ static getClientWorkerId(context) {
1189
+ const workerThreadId = context?.threadId?.toString() || threadId.toString();
1190
+ const workerProcessId = context?.processId?.toString() || process.pid.toString();
1191
+ return `${workerThreadId}-${workerProcessId}`;
1192
+ }
1193
+ /**
1194
+ *
1195
+ * @param {TestFrameworkState | AutomationFrameworkState} frameworkState
1196
+ * @param {HookState} hookState
1197
+ * @returns {string}
1198
+ */
1199
+ static getHookRegistryKey(frameworkState, hookState) {
1200
+ return `${frameworkState}:${hookState}`;
1201
+ }
1202
+ static matchHookRegex(hookState) {
1203
+ const pattern = new RegExp(TestFrameworkConstants.HOOK_REGEX);
1204
+ return pattern.test(hookState);
1205
+ }
1206
+ static getObservabilityBuildTags(observabilityOptions, bstackBuildTag) {
1207
+ if (process.env.TEST_OBSERVABILITY_BUILD_TAG) {
1208
+ return process.env.TEST_OBSERVABILITY_BUILD_TAG.split(",");
1209
+ }
1210
+ if (observabilityOptions.buildTag) {
1211
+ return observabilityOptions.buildTag;
1212
+ }
1213
+ if (bstackBuildTag) {
1214
+ return [bstackBuildTag];
1215
+ }
1216
+ return [];
1217
+ }
1218
+ static checkCLISupportedFrameworks(framework) {
1219
+ if (framework === void 0) {
1220
+ return false;
1221
+ }
1222
+ return this.CLISupportedFrameworks.includes(framework);
1223
+ }
1224
+ };
1225
+
291
1226
  // src/instrumentation/performance/performance-tester.ts
292
1227
  var PerformanceTester = class _PerformanceTester {
293
1228
  static _observer;
294
1229
  static _csvWriter;
295
1230
  static _events = [];
296
1231
  static _measuredEvents = [];
1232
+ static _hasStoppedGeneration = false;
1233
+ static _stopGenerateCallCount = 0;
297
1234
  static started = false;
298
1235
  static details = {};
299
1236
  static eventsMap = {};
300
1237
  static browser;
301
1238
  static scenarioThatRan;
302
1239
  static jsonReportDirName = "performance-report";
303
- static jsonReportDirPath = path2.join(process.cwd(), "logs", this.jsonReportDirName);
1240
+ static jsonReportDirPath = path4.join(process.cwd(), "logs", this.jsonReportDirName);
304
1241
  static jsonReportFileName = `${this.jsonReportDirPath}/performance-report-${_PerformanceTester.getProcessId()}.json`;
305
1242
  static startMonitoring(csvName = "performance-report.csv") {
306
- if (!fs2.existsSync(this.jsonReportDirPath)) {
307
- fs2.mkdirSync(this.jsonReportDirPath, { recursive: true });
1243
+ if (!fs4.existsSync(this.jsonReportDirPath)) {
1244
+ fs4.mkdirSync(this.jsonReportDirPath, { recursive: true });
308
1245
  }
309
1246
  this._observer = new PerformanceObserver((list) => {
310
1247
  list.getEntries().filter((entry) => entry.entryType === "measure").forEach(
311
1248
  (entry) => {
312
- let finalEntry = entry;
313
- finalEntry = entry.toJSON();
1249
+ let finalEntry = entry.toJSON();
1250
+ try {
1251
+ if (typeof finalEntry.startTime === "number" && typeof performance.timeOrigin === "number") {
1252
+ const originalStartTime = finalEntry.startTime;
1253
+ finalEntry.startTime = performance.timeOrigin + finalEntry.startTime;
1254
+ BStackLogger.debug(`Timestamp conversion for ${entry.name}: ${originalStartTime} -> ${finalEntry.startTime} (timeOrigin: ${performance.timeOrigin})`);
1255
+ }
1256
+ } catch (e) {
1257
+ BStackLogger.debug(`Error converting startTime to epoch: ${util2.format(e)}`);
1258
+ }
314
1259
  if (this.details[entry.name]) {
315
1260
  finalEntry = Object.assign(finalEntry, this.details[entry.name]);
316
1261
  }
@@ -356,28 +1301,26 @@ var PerformanceTester = class _PerformanceTester {
356
1301
  if (!this.started) {
357
1302
  return;
358
1303
  }
359
- await _PerformanceTester.sleep(PERF_METRICS_WAIT_TIME);
360
1304
  try {
361
1305
  const eventsJson = JSON.stringify(this._measuredEvents);
362
1306
  const finalJSONStr = eventsJson.slice(1, -1) + ",";
363
1307
  await fsPromise.appendFile(this.jsonReportFileName, finalJSONStr);
364
1308
  } catch (er) {
365
- BStackLogger.debug(`Failed to write events of the worker to ${this.jsonReportFileName}: ${util.format(er)}`);
1309
+ BStackLogger.debug(`Failed to write events of the worker to ${this.jsonReportFileName}: ${util2.format(er)}`);
366
1310
  }
367
1311
  this._observer.disconnect();
368
1312
  if (!process.env[PERF_MEASUREMENT_ENV]) {
369
1313
  return;
370
1314
  }
371
- await _PerformanceTester.sleep(PERF_METRICS_WAIT_TIME);
372
1315
  this.started = false;
373
1316
  this.generateCSV(this._events);
374
1317
  const content = this.generateReport(this._events);
375
- const dir = path2.join(process.cwd(), filename);
1318
+ const dir = path4.join(process.cwd(), filename);
376
1319
  try {
377
1320
  await fsPromise.writeFile(dir, content);
378
- BStackLogger.info(`Performance report is at ${path2}`);
1321
+ BStackLogger.info(`Performance report is at ${path4}`);
379
1322
  } catch (err) {
380
- BStackLogger.error(`Error in writing html ${util.format(err)}`);
1323
+ BStackLogger.error(`Error in writing html ${util2.format(err)}`);
381
1324
  }
382
1325
  }
383
1326
  static generateReport(entries) {
@@ -437,27 +1380,71 @@ var PerformanceTester = class _PerformanceTester {
437
1380
  if (!this.started || !this.isEnabled()) {
438
1381
  return fn.apply(thisArg, args);
439
1382
  }
440
- _PerformanceTester.start(label);
441
- if (this.details) {
442
- this.details[label] = details;
443
- }
1383
+ const uniqueId = `${Date.now()}-${Math.random().toString(36).substring(7)}`;
1384
+ const startMark = `${label}-start-${uniqueId}`;
1385
+ const endMark = `${label}-end-${uniqueId}`;
1386
+ performance.mark(startMark);
1387
+ const detailsWithContext = {
1388
+ ...details,
1389
+ measurementId: uniqueId
1390
+ };
444
1391
  try {
445
1392
  const returnVal = fn.apply(thisArg, args);
446
1393
  if (returnVal instanceof Promise) {
447
1394
  return new Promise((resolve, reject) => {
448
1395
  returnVal.then((v) => {
449
- _PerformanceTester.end(label);
1396
+ performance.mark(endMark);
1397
+ performance.measure(label, startMark, endMark);
1398
+ this.details[label] = Object.assign({
1399
+ success: true,
1400
+ failure: void 0
1401
+ }, Object.assign(Object.assign({
1402
+ clientWorkerId: _PerformanceTester.getClientWorkerId(),
1403
+ worker: _PerformanceTester.getProcessId(),
1404
+ platform: _PerformanceTester.browser?.sessionId,
1405
+ testName: _PerformanceTester.scenarioThatRan?.pop()
1406
+ }, detailsWithContext), this.details[label] || {}));
450
1407
  resolve(v);
451
1408
  }).catch((e) => {
452
- _PerformanceTester.end(label, false, util.format(e));
1409
+ performance.mark(endMark);
1410
+ performance.measure(label, startMark, endMark);
1411
+ this.details[label] = Object.assign({
1412
+ success: false,
1413
+ failure: util2.format(e)
1414
+ }, Object.assign(Object.assign({
1415
+ clientWorkerId: _PerformanceTester.getClientWorkerId(),
1416
+ worker: _PerformanceTester.getProcessId(),
1417
+ platform: _PerformanceTester.browser?.sessionId,
1418
+ testName: _PerformanceTester.scenarioThatRan?.pop()
1419
+ }, detailsWithContext), this.details[label] || {}));
453
1420
  reject(e);
454
1421
  });
455
1422
  });
456
1423
  }
457
- _PerformanceTester.end(label);
1424
+ performance.mark(endMark);
1425
+ performance.measure(label, startMark, endMark);
1426
+ this.details[label] = Object.assign({
1427
+ success: true,
1428
+ failure: void 0
1429
+ }, Object.assign(Object.assign({
1430
+ clientWorkerId: _PerformanceTester.getClientWorkerId(),
1431
+ worker: _PerformanceTester.getProcessId(),
1432
+ platform: _PerformanceTester.browser?.sessionId,
1433
+ testName: _PerformanceTester.scenarioThatRan?.pop()
1434
+ }, detailsWithContext), this.details[label] || {}));
458
1435
  return returnVal;
459
1436
  } catch (er) {
460
- _PerformanceTester.end(label, false, util.format(er));
1437
+ performance.mark(endMark);
1438
+ performance.measure(label, startMark, endMark);
1439
+ this.details[label] = Object.assign({
1440
+ success: false,
1441
+ failure: util2.format(er)
1442
+ }, Object.assign(Object.assign({
1443
+ clientWorkerId: _PerformanceTester.getClientWorkerId(),
1444
+ worker: _PerformanceTester.getProcessId(),
1445
+ platform: _PerformanceTester.browser?.sessionId,
1446
+ testName: _PerformanceTester.scenarioThatRan?.pop()
1447
+ }, detailsWithContext), this.details[label] || {}));
461
1448
  throw er;
462
1449
  }
463
1450
  }
@@ -472,26 +1459,62 @@ var PerformanceTester = class _PerformanceTester {
472
1459
  static end(event, success = true, failure, details = {}) {
473
1460
  performance.mark(event + "-end");
474
1461
  performance.measure(event, event + "-start", event + "-end");
475
- this.details[event] = Object.assign({ success, failure: util.format(failure) }, Object.assign(Object.assign({
1462
+ this.details[event] = Object.assign({ success, failure: util2.format(failure) }, Object.assign(Object.assign({
1463
+ clientWorkerId: _PerformanceTester.getClientWorkerId(),
476
1464
  worker: _PerformanceTester.getProcessId(),
477
1465
  platform: _PerformanceTester.browser?.sessionId,
478
1466
  testName: _PerformanceTester.scenarioThatRan && _PerformanceTester.scenarioThatRan[_PerformanceTester.scenarioThatRan.length - 1]
479
1467
  }, details), this.details[event] || {}));
480
1468
  }
1469
+ /**
1470
+ * Get client worker ID in format "threadId-processId".
1471
+ * This method provides a consistent identifier across the SDK for tracking
1472
+ * worker-specific events and performance metrics.
1473
+ *
1474
+ * @returns Worker ID string in format "threadId-processId"
1475
+ */
1476
+ static getClientWorkerId() {
1477
+ return CLIUtils.getClientWorkerId();
1478
+ }
481
1479
  static getProcessId() {
482
1480
  return `${process.pid}-${worker.threadId}`;
483
1481
  }
484
1482
  static sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms));
485
1483
  static async uploadEventsData() {
1484
+ this.start(EVENTS.SDK_SEND_KEY_METRICS);
486
1485
  try {
1486
+ const workerId = `${process.pid}`;
1487
+ BStackLogger.debug(`[Performance Upload] Starting upload for worker ${workerId}`);
1488
+ this.start(EVENTS.SDK_KEY_METRICS_PREPARATION);
487
1489
  let measures = [];
488
1490
  if (await fsPromise.access(this.jsonReportDirPath).then(() => true).catch(() => false)) {
489
- const files = (await fsPromise.readdir(this.jsonReportDirPath)).map((file) => path2.resolve(this.jsonReportDirPath, file));
1491
+ const files = (await fsPromise.readdir(this.jsonReportDirPath)).map((file) => path4.resolve(this.jsonReportDirPath, file));
490
1492
  measures = (await Promise.all(files.map((file) => fsPromise.readFile(file, "utf-8")))).map((el) => `[${el.slice(0, -1)}]`).map((el) => JSON.parse(el)).flat();
491
1493
  }
1494
+ BStackLogger.debug(`[Performance Upload] Total events from files: ${measures.length}`);
492
1495
  if (this._measuredEvents.length > 0) {
493
- measures = measures.concat(this._measuredEvents);
1496
+ BStackLogger.debug(`[Performance Upload] Adding ${this._measuredEvents.length} in-memory events`);
1497
+ measures = measures.concat(
1498
+ this._measuredEvents.map((e) => {
1499
+ if (typeof e.toJSON === "function") {
1500
+ return e.toJSON();
1501
+ }
1502
+ return e;
1503
+ })
1504
+ );
494
1505
  }
1506
+ const ensureEpochTimes = (arr) => {
1507
+ const now = Date.now();
1508
+ const cutoff = 1e12;
1509
+ const timeOrigin = typeof performance !== "undefined" && typeof performance.timeOrigin === "number" ? performance.timeOrigin : now - process.uptime() * 1e3;
1510
+ return arr.map((entry) => {
1511
+ if (typeof entry.startTime === "number" && entry.startTime < cutoff) {
1512
+ entry.startTime = timeOrigin + entry.startTime;
1513
+ }
1514
+ return entry;
1515
+ });
1516
+ };
1517
+ measures = ensureEpochTimes(measures);
495
1518
  const date = /* @__PURE__ */ new Date();
496
1519
  const options = {
497
1520
  timeZone: "UTC",
@@ -506,6 +1529,7 @@ var PerformanceTester = class _PerformanceTester {
506
1529
  hour12: false
507
1530
  };
508
1531
  const formattedDate = new Intl.DateTimeFormat("en-GB", options).formatToParts(date).map(({ type: type3, value }) => type3 === "timeZoneName" ? "Z" : value).join("").replace(",", "T");
1532
+ this.end(EVENTS.SDK_KEY_METRICS_PREPARATION, true);
509
1533
  const payload = {
510
1534
  event_type: "sdk_events",
511
1535
  data: {
@@ -515,10 +1539,10 @@ var PerformanceTester = class _PerformanceTester {
515
1539
  user_data: process.env.PERF_USER_NAME,
516
1540
  host_info: JSON.stringify({
517
1541
  hostname: hostname(),
518
- platform: platform(),
1542
+ platform: platform2(),
519
1543
  type: type(),
520
1544
  version: version(),
521
- arch: arch()
1545
+ arch: arch2()
522
1546
  }),
523
1547
  event_json: { measures, sdkRunId: process.env.SDK_RUN_ID }
524
1548
  }
@@ -530,111 +1554,27 @@ var PerformanceTester = class _PerformanceTester {
530
1554
  },
531
1555
  body: JSON.stringify(payload)
532
1556
  });
533
- BStackLogger.debug(`Successfully uploaded performance events ${util.format(await result.text())}`);
1557
+ BStackLogger.debug(`[Performance Upload] Successfully uploaded to EDS: ${util2.format(await result.text())}`);
1558
+ this.end(EVENTS.SDK_SEND_KEY_METRICS, true);
534
1559
  } catch (er) {
535
- BStackLogger.debug(`Failed to upload performance events ${util.format(er)}`);
1560
+ BStackLogger.debug(`[Performance Upload] Failed to upload events: ${util2.format(er)}`);
1561
+ this.end(EVENTS.SDK_KEY_METRICS_PREPARATION, false, er);
1562
+ this.end(EVENTS.SDK_SEND_KEY_METRICS, false, er);
536
1563
  }
537
1564
  try {
538
1565
  if (await fsPromise.access(this.jsonReportDirPath).then(() => true, () => false)) {
539
1566
  const files = await fsPromise.readdir(this.jsonReportDirPath);
540
1567
  for (const file of files) {
541
- await fsPromise.unlink(path2.join(this.jsonReportDirPath, file));
1568
+ await fsPromise.unlink(path4.join(this.jsonReportDirPath, file));
542
1569
  }
1570
+ BStackLogger.debug(`[Performance Upload] Cleaned up ${files.length} temporary report files`);
543
1571
  }
544
1572
  } catch (er) {
545
- BStackLogger.debug(`Failed to delete performance related files ${util.format(er)}`);
1573
+ BStackLogger.debug(`[Performance Upload] Failed to delete temporary files: ${util2.format(er)}`);
546
1574
  }
547
1575
  }
548
1576
  };
549
1577
 
550
- // src/instrumentation/performance/constants.ts
551
- var EVENTS = {
552
- SDK_SETUP: "sdk:setup",
553
- SDK_CLEANUP: "sdk:cleanup",
554
- SDK_PRE_TEST: "sdk:pre-test",
555
- SDK_TEST: "sdk:test",
556
- SDK_POST_TEST: "sdk:post-test",
557
- SDK_HOOK: "sdk:hook",
558
- SDK_DRIVER: "sdk:driver",
559
- SDK_A11Y: "sdk:a11y",
560
- SDK_O11Y: "sdk:o11y",
561
- SDK_AUTO_CAPTURE: "sdk:auto-capture",
562
- SDK_PROXY_SETUP: "sdk:proxy-setup",
563
- SDK_TESTHUB: "sdk:testhub",
564
- SDK_AUTOMATE: "sdk:automate",
565
- SDK_APP_AUTOMATE: "sdk:app-automate",
566
- SDK_TURBOSCALE: "sdk:turboscale",
567
- SDK_PERCY: "sdk:percy",
568
- SDK_PRE_INITIALIZE: "sdk:driver:pre-initialization",
569
- SDK_POST_INITIALIZE: "sdk:driver:post-initialization",
570
- SDK_CLI_CHECK_UPDATE: "sdk:cli:check-update",
571
- SDK_CLI_DOWNLOAD: "sdk:cli:download",
572
- SDK_CLI_ON_BOOTSTRAP: "sdk:cli:on-bootstrap",
573
- SDK_CLI_ON_CONNECT: "sdk:cli:on-connect",
574
- SDK_CLI_START: "sdk:cli:start",
575
- SDK_CLI_ON_STOP: "sdk:cli:on-stop",
576
- SDK_CONNECT_BIN_SESSION: "sdk:connectBinSession",
577
- SDK_START_BIN_SESSION: "sdk:startBinSession"
578
- };
579
- var TESTHUB_EVENTS = {
580
- START: `${EVENTS.SDK_TESTHUB}:start`,
581
- STOP: `${EVENTS.SDK_TESTHUB}:stop`
582
- };
583
- var AUTOMATE_EVENTS = {
584
- KEEP_ALIVE: `${EVENTS.SDK_AUTOMATE}:keep-alive`,
585
- HUB_MANAGEMENT: `${EVENTS.SDK_AUTOMATE}:hub-management`,
586
- LOCAL_START: `${EVENTS.SDK_AUTOMATE}:local-start`,
587
- LOCAL_STOP: `${EVENTS.SDK_AUTOMATE}:local-stop`,
588
- DRIVER_MANAGE: `${EVENTS.SDK_AUTOMATE}:driver-manage`,
589
- SESSION_NAME: `${EVENTS.SDK_AUTOMATE}:session-name`,
590
- SESSION_STATUS: `${EVENTS.SDK_AUTOMATE}:session-status`,
591
- SESSION_ANNOTATION: `${EVENTS.SDK_AUTOMATE}:session-annotation`,
592
- IDLE_TIMEOUT: `${EVENTS.SDK_AUTOMATE}:idle-timeout`,
593
- GENERATE_CI_ARTIFACT: `${EVENTS.SDK_AUTOMATE}:ci-artifacts`,
594
- PRINT_BUILDLINK: `${EVENTS.SDK_AUTOMATE}:print-buildlink`
595
- };
596
- var A11Y_EVENTS = {
597
- PERFORM_SCAN: `${EVENTS.SDK_A11Y}:driver-performscan`,
598
- SAVE_RESULTS: `${EVENTS.SDK_A11Y}:save-results`,
599
- GET_RESULTS: `${EVENTS.SDK_A11Y}:get-accessibility-results`,
600
- GET_RESULTS_SUMMARY: `${EVENTS.SDK_A11Y}:get-accessibility-results-summary`
601
- };
602
- var PERCY_EVENTS = {
603
- DOWNLOAD: `${EVENTS.SDK_PERCY}:download`,
604
- SCREENSHOT: `${EVENTS.SDK_PERCY}:screenshot`,
605
- START: `${EVENTS.SDK_PERCY}:start`,
606
- STOP: `${EVENTS.SDK_PERCY}:stop`,
607
- AUTO_CAPTURE: `${EVENTS.SDK_PERCY}:auto-capture`,
608
- SNAPSHOT: `${EVENTS.SDK_PERCY}:snapshot`,
609
- SCREENSHOT_APP: `${EVENTS.SDK_PERCY}:screenshot-app`
610
- };
611
- var O11Y_EVENTS = {
612
- SYNC: `${EVENTS.SDK_O11Y}:sync`,
613
- TAKE_SCREENSHOT: `${EVENTS.SDK_O11Y}:driver-takeScreenShot`,
614
- PRINT_BUILDLINK: `${EVENTS.SDK_O11Y}:print-buildlink`
615
- };
616
- var HOOK_EVENTS = {
617
- BEFORE_EACH: `${EVENTS.SDK_HOOK}:before-each`,
618
- AFTER_EACH: `${EVENTS.SDK_HOOK}:after-each`,
619
- AFTER_ALL: `${EVENTS.SDK_HOOK}:after-all`,
620
- BEFORE_ALL: `${EVENTS.SDK_HOOK}:before-all`,
621
- BEFORE: `${EVENTS.SDK_HOOK}:before`,
622
- AFTER: `${EVENTS.SDK_HOOK}:after`
623
- };
624
- var TURBOSCALE_EVENTS = {
625
- HUB_MANAGEMENT: `${EVENTS.SDK_TURBOSCALE}:hub-management`,
626
- PRINT_BUILDLINK: `${EVENTS.SDK_TURBOSCALE}:print-buildlink`
627
- };
628
- var APP_AUTOMATE_EVENTS = {
629
- APP_UPLOAD: `${EVENTS.SDK_APP_AUTOMATE}:app-upload`
630
- };
631
- var DRIVER_EVENT = {
632
- QUIT: `${EVENTS.SDK_DRIVER}:quit`,
633
- GET: `${EVENTS.SDK_DRIVER}:get`,
634
- PRE_EXECUTE: `${EVENTS.SDK_DRIVER}:pre-execute`,
635
- POST_EXECUTE: `${EVENTS.SDK_DRIVER}:post-execute`
636
- };
637
-
638
1578
  // src/testHub/utils.ts
639
1579
  var handleErrorForObservability = (error) => {
640
1580
  process.env[BROWSERSTACK_OBSERVABILITY] = "false";
@@ -680,7 +1620,7 @@ var getProductMapForBuildStartCall = (config, accessibilityAutomation) => {
680
1620
  };
681
1621
 
682
1622
  // src/testorchestration/testorcherstrationutils.ts
683
- import fs3 from "node:fs";
1623
+ import fs5 from "node:fs";
684
1624
  var RUN_SMART_SELECTION = "runSmartSelection";
685
1625
  var ALLOWED_ORCHESTRATION_KEYS = [
686
1626
  RUN_SMART_SELECTION
@@ -910,13 +1850,13 @@ var OrchestrationUtils = class _OrchestrationUtils {
910
1850
  * @returns Formatted list of repository configurations
911
1851
  */
912
1852
  _loadSourceFromFile(filePath) {
913
- if (!fs3.existsSync(filePath)) {
1853
+ if (!fs5.existsSync(filePath)) {
914
1854
  BStackLogger.error(`Source file '${filePath}' does not exist.`);
915
1855
  return [];
916
1856
  }
917
1857
  let data = null;
918
1858
  try {
919
- const fileContent = fs3.readFileSync(filePath, "utf8");
1859
+ const fileContent = fs5.readFileSync(filePath, "utf8");
920
1860
  data = JSON.parse(fileContent);
921
1861
  } catch (error) {
922
1862
  const message = error instanceof Error ? error.message : String(error);
@@ -1489,12 +2429,12 @@ var UsageStats = class _UsageStats {
1489
2429
  var usageStats_default = UsageStats;
1490
2430
 
1491
2431
  // src/util.ts
1492
- import tar from "tar";
2432
+ import { create } from "tar";
1493
2433
  import { fileFromPath } from "formdata-node/file-from-path";
1494
2434
 
1495
2435
  // src/scripts/accessibility-scripts.ts
1496
- import path3 from "node:path";
1497
- import fs4 from "node:fs";
2436
+ import path5 from "node:path";
2437
+ import fs6 from "node:fs";
1498
2438
  import os from "node:os";
1499
2439
  var AccessibilityScripts = class _AccessibilityScripts {
1500
2440
  static instance = null;
@@ -1509,7 +2449,7 @@ var AccessibilityScripts = class _AccessibilityScripts {
1509
2449
  // don't allow to create instances from it other than through `checkAndGetInstance`
1510
2450
  constructor() {
1511
2451
  this.browserstackFolderPath = this.getWritableDir();
1512
- this.commandsPath = path3.join(this.browserstackFolderPath, "commands.json");
2452
+ this.commandsPath = path5.join(this.browserstackFolderPath, "commands.json");
1513
2453
  }
1514
2454
  static checkAndGetInstance() {
1515
2455
  if (!_AccessibilityScripts.instance) {
@@ -1521,17 +2461,17 @@ var AccessibilityScripts = class _AccessibilityScripts {
1521
2461
  /* eslint-disable @typescript-eslint/no-unused-vars */
1522
2462
  getWritableDir() {
1523
2463
  const orderedPaths = [
1524
- path3.join(os.homedir(), ".browserstack"),
2464
+ path5.join(os.homedir(), ".browserstack"),
1525
2465
  process.cwd(),
1526
2466
  os.tmpdir()
1527
2467
  ];
1528
2468
  for (const orderedPath of orderedPaths) {
1529
2469
  try {
1530
- if (fs4.existsSync(orderedPath)) {
1531
- fs4.accessSync(orderedPath);
2470
+ if (fs6.existsSync(orderedPath)) {
2471
+ fs6.accessSync(orderedPath);
1532
2472
  return orderedPath;
1533
2473
  }
1534
- fs4.mkdirSync(orderedPath, { recursive: true });
2474
+ fs6.mkdirSync(orderedPath, { recursive: true });
1535
2475
  return orderedPath;
1536
2476
  } catch (error) {
1537
2477
  }
@@ -1540,8 +2480,8 @@ var AccessibilityScripts = class _AccessibilityScripts {
1540
2480
  }
1541
2481
  readFromExistingFile() {
1542
2482
  try {
1543
- if (fs4.existsSync(this.commandsPath)) {
1544
- const data = fs4.readFileSync(this.commandsPath, "utf8");
2483
+ if (fs6.existsSync(this.commandsPath)) {
2484
+ const data = fs6.readFileSync(this.commandsPath, "utf8");
1545
2485
  if (data) {
1546
2486
  this.update(JSON.parse(data));
1547
2487
  }
@@ -1564,10 +2504,10 @@ var AccessibilityScripts = class _AccessibilityScripts {
1564
2504
  }
1565
2505
  }
1566
2506
  store() {
1567
- if (!fs4.existsSync(this.browserstackFolderPath)) {
1568
- fs4.mkdirSync(this.browserstackFolderPath);
2507
+ if (!fs6.existsSync(this.browserstackFolderPath)) {
2508
+ fs6.mkdirSync(this.browserstackFolderPath);
1569
2509
  }
1570
- fs4.writeFileSync(this.commandsPath, JSON.stringify({
2510
+ fs6.writeFileSync(this.commandsPath, JSON.stringify({
1571
2511
  commands: this.commandsToWrap,
1572
2512
  scripts: {
1573
2513
  scan: this.performScan,
@@ -1582,7 +2522,7 @@ var AccessibilityScripts = class _AccessibilityScripts {
1582
2522
  var accessibility_scripts_default = AccessibilityScripts.checkAndGetInstance();
1583
2523
 
1584
2524
  // src/util.ts
1585
- var pGitconfig = promisify(gitconfig);
2525
+ var pGitconfig = promisify2(gitconfig);
1586
2526
  var DEFAULT_REQUEST_CONFIG = {
1587
2527
  headers: {
1588
2528
  "Content-Type": "application/json",
@@ -1603,7 +2543,7 @@ function processError(error, fn, args) {
1603
2543
  try {
1604
2544
  argsString = JSON.stringify(args);
1605
2545
  } catch {
1606
- argsString = util2.inspect(args, { depth: 2 });
2546
+ argsString = util3.inspect(args, { depth: 2 });
1607
2547
  }
1608
2548
  CrashReporter.uploadCrashReport(`Error in executing ${fn.name} with args ${argsString} : ${error}`, error && error.stack || "unknown error");
1609
2549
  }
@@ -1699,10 +2639,10 @@ var launchTestSession = PerformanceTester.measureWrapper(TESTHUB_EVENTS.START, o
1699
2639
  tags: getObservabilityBuildTags(options, bsConfig.buildTag),
1700
2640
  host_info: {
1701
2641
  hostname: hostname2(),
1702
- platform: platform2(),
2642
+ platform: platform3(),
1703
2643
  type: type2(),
1704
2644
  version: version2(),
1705
- arch: arch2()
2645
+ arch: arch3()
1706
2646
  },
1707
2647
  ci_info: getCiInfo(),
1708
2648
  build_run_identifier: process.env.BROWSERSTACK_BUILD_RUN_IDENTIFIER,
@@ -1815,7 +2755,7 @@ var performA11yScan = async (isAppAutomate, browser, isBrowserStackSession, isAc
1815
2755
  try {
1816
2756
  if (isAppAccessibilityAutomationSession(isAccessibility, isAppAutomate)) {
1817
2757
  const results = await browser.execute(formatString(accessibility_scripts_default.performScan, JSON.stringify(_getParamsForAppAccessibility(commandName))), {});
1818
- BStackLogger.debug(util2.format(results));
2758
+ BStackLogger.debug(util3.format(results));
1819
2759
  return results;
1820
2760
  }
1821
2761
  if (accessibility_scripts_default.performScan) {
@@ -2261,7 +3201,7 @@ function getObservabilityBuild(options, bstackBuildName) {
2261
3201
  if (options.testObservabilityOptions && options.testObservabilityOptions.buildName) {
2262
3202
  return options.testObservabilityOptions.buildName;
2263
3203
  }
2264
- return bstackBuildName || path4.basename(path4.resolve(process.cwd()));
3204
+ return bstackBuildName || path6.basename(path6.resolve(process.cwd()));
2265
3205
  }
2266
3206
  function getObservabilityBuildTags(options, bstackBuildTag) {
2267
3207
  if (process.env.TEST_OBSERVABILITY_BUILD_TAG) {
@@ -2275,9 +3215,24 @@ function getObservabilityBuildTags(options, bstackBuildTag) {
2275
3215
  }
2276
3216
  return [];
2277
3217
  }
3218
+ function getBrowserStackUser(config) {
3219
+ if (process.env.BROWSERSTACK_USERNAME) {
3220
+ return process.env.BROWSERSTACK_USERNAME;
3221
+ }
3222
+ return config.user;
3223
+ }
3224
+ function getBrowserStackKey(config) {
3225
+ if (process.env.BROWSERSTACK_ACCESS_KEY) {
3226
+ return process.env.BROWSERSTACK_ACCESS_KEY;
3227
+ }
3228
+ return config.key;
3229
+ }
2278
3230
  function isTrue(value) {
2279
3231
  return (value + "").toLowerCase() === "true";
2280
3232
  }
3233
+ function isFalse(value) {
3234
+ return (value + "").toLowerCase() === "false";
3235
+ }
2281
3236
  var patchConsoleLogs = o11yErrorHandler(() => {
2282
3237
  const BSTestOpsPatcher = new logPatcher_default({});
2283
3238
  Object.keys(consoleHolder).forEach((method) => {
@@ -2472,21 +3427,66 @@ function isValidEnabledValue(value) {
2472
3427
  }
2473
3428
  return false;
2474
3429
  }
3430
+ function isNullOrEmpty(string) {
3431
+ return !string || string.trim() === "";
3432
+ }
3433
+ function isHash(entity) {
3434
+ return Boolean(entity && typeof entity === "object" && !Array.isArray(entity));
3435
+ }
3436
+ function nestedKeyValue(hash, keys) {
3437
+ return keys.reduce((hash2, key) => isHash(hash2) ? hash2[key] : void 0, hash);
3438
+ }
3439
+ function removeDir(dir) {
3440
+ const list = fs7.readdirSync(dir);
3441
+ for (let i = 0; i < list.length; i++) {
3442
+ const filename = path6.join(dir, list[i]);
3443
+ const stat = fs7.statSync(filename);
3444
+ if (filename === "." || filename === "..") {
3445
+ } else if (stat.isDirectory()) {
3446
+ removeDir(filename);
3447
+ } else {
3448
+ fs7.unlinkSync(filename);
3449
+ }
3450
+ }
3451
+ fs7.rmdirSync(dir);
3452
+ }
3453
+ function createDir(dir) {
3454
+ if (fs7.existsSync(dir)) {
3455
+ removeDir(dir);
3456
+ }
3457
+ fs7.mkdirSync(dir, { recursive: true });
3458
+ }
3459
+ function isWritable(dirPath) {
3460
+ try {
3461
+ fs7.accessSync(dirPath, fs7.constants.W_OK);
3462
+ return true;
3463
+ } catch {
3464
+ return false;
3465
+ }
3466
+ }
3467
+ function setReadWriteAccess(dirPath) {
3468
+ try {
3469
+ fs7.chmodSync(dirPath, 438);
3470
+ BStackLogger.debug(`Directory ${dirPath} is now read/write accessible.`);
3471
+ } catch (err) {
3472
+ BStackLogger.error(`Failed to set directory access: ${err.stack}`);
3473
+ }
3474
+ }
2475
3475
 
2476
3476
  // src/cleanup.ts
2477
- import fs8 from "node:fs";
2478
- import util4 from "node:util";
3477
+ import fs10 from "node:fs";
3478
+ import util5 from "node:util";
2479
3479
 
2480
3480
  // src/instrumentation/funnelInstrumentation.ts
2481
3481
  import os2 from "node:os";
2482
- import util3, { format as format2 } from "node:util";
2483
- import path6 from "node:path";
2484
- import fs7 from "node:fs";
3482
+ import util4, { format as format2 } from "node:util";
3483
+ import path8 from "node:path";
3484
+ import fs9 from "node:fs";
2485
3485
 
2486
3486
  // src/data-store.ts
2487
- import path5 from "node:path";
2488
- import fs6 from "node:fs";
2489
- var workersDataDirPath = path5.join(process.cwd(), "logs", "worker_data");
3487
+ import path7 from "node:path";
3488
+ import fs8 from "node:fs";
3489
+ var workersDataDirPath = path7.join(process.cwd(), "logs", "worker_data");
2490
3490
 
2491
3491
  // src/instrumentation/funnelInstrumentation.ts
2492
3492
  function redactCredentialsFromFunnelData(data) {
@@ -2503,7 +3503,7 @@ function redactCredentialsFromFunnelData(data) {
2503
3503
  async function fireFunnelRequest(data) {
2504
3504
  const { userName, accessKey } = data;
2505
3505
  redactCredentialsFromFunnelData(data);
2506
- BStackLogger.debug("Sending SDK event with data " + util3.inspect(data, { depth: 6 }));
3506
+ BStackLogger.debug("Sending SDK event with data " + util4.inspect(data, { depth: 6 }));
2507
3507
  const encodedAuth = Buffer.from(`${userName}:${accessKey}`, "utf8").toString("base64");
2508
3508
  const response = await fetchWrap(APIUtils.FUNNEL_INSTRUMENTATION_URL, {
2509
3509
  method: "POST",
@@ -2542,7 +3542,7 @@ var BStackCleanup = class _BStackCleanup {
2542
3542
  await PerformanceTester.uploadEventsData();
2543
3543
  }
2544
3544
  } catch (er) {
2545
- BStackLogger.debug(`Error in sending events data ${util4.format(er)}`);
3545
+ BStackLogger.debug(`Error in sending events data ${util5.format(er)}`);
2546
3546
  }
2547
3547
  }
2548
3548
  static async executeObservabilityCleanup(funnelData) {
@@ -2592,7 +3592,7 @@ Visit https://automation.browserstack.com/builds/${process.env[BROWSERSTACK_TEST
2592
3592
  if (!filePath) {
2593
3593
  return null;
2594
3594
  }
2595
- const content = fs8.readFileSync(filePath, "utf8");
3595
+ const content = fs10.readFileSync(filePath, "utf8");
2596
3596
  const data = JSON.parse(content);
2597
3597
  this.removeFunnelDataFile(filePath);
2598
3598
  return data;
@@ -2601,7 +3601,7 @@ Visit https://automation.browserstack.com/builds/${process.env[BROWSERSTACK_TEST
2601
3601
  if (!filePath) {
2602
3602
  return;
2603
3603
  }
2604
- fs8.rmSync(filePath, { force: true });
3604
+ fs10.rmSync(filePath, { force: true });
2605
3605
  }
2606
3606
  };
2607
3607
  void BStackCleanup.startCleanup();