UNPKG

@wdio/browserstack-service

Version:
1,433 lines (1,419 loc) 125 kB
// src/util.ts import { hostname as hostname2, platform as platform3, type as type2, version as version2, arch as arch3, tmpdir } from "node:os"; import crypto from "node:crypto"; import fs7 from "node:fs"; import zlib from "node:zlib"; import { format, promisify as promisify2 } from "node:util"; import path6 from "node:path"; import util3 from "node:util"; import gitRepoInfo from "git-repo-info"; import gitconfig from "gitconfiglocal"; import { FormData } from "formdata-node"; import { performance as performance2 } from "node:perf_hooks"; // src/logPatcher.ts import Transport from "winston-transport"; var LOG_LEVELS = { INFO: "INFO", ERROR: "ERROR", DEBUG: "DEBUG", TRACE: "TRACE", WARN: "WARN" }; var logPatcher = class extends Transport { logToTestOps = (level = LOG_LEVELS.INFO, message = [""]) => { process.emit(`bs:addLog:${process.pid}`, { timestamp: (/* @__PURE__ */ new Date()).toISOString(), level: level.toUpperCase(), message: `"${message.join(", ")}"`, kind: "TEST_LOG", http_response: {} }); }; /* Patching this would show user an extended trace on their cli */ trace = (...message) => { this.logToTestOps(LOG_LEVELS.TRACE, message); }; debug = (...message) => { this.logToTestOps(LOG_LEVELS.DEBUG, message); }; info = (...message) => { this.logToTestOps(LOG_LEVELS.INFO, message); }; warn = (...message) => { this.logToTestOps(LOG_LEVELS.WARN, message); }; error = (...message) => { this.logToTestOps(LOG_LEVELS.ERROR, message); }; log = (...message) => { this.logToTestOps(LOG_LEVELS.INFO, message); }; }; var logPatcher_default = logPatcher; // src/instrumentation/performance/performance-tester.ts import { createObjectCsvWriter } from "csv-writer"; import fs4 from "node:fs"; import fsPromise from "node:fs/promises"; import { performance, PerformanceObserver } from "node:perf_hooks"; import util2 from "node:util"; import worker from "node:worker_threads"; import path4 from "node:path"; import { arch as arch2, hostname, platform as platform2, type, version } from "node:os"; // src/bstackLogger.ts import path from "node:path"; import fs from "node:fs"; import chalk from "chalk"; import logger from "@wdio/logger"; // package.json var package_default = { name: "@wdio/browserstack-service", version: "9.23.3", description: "WebdriverIO service for better Browserstack integration", author: "Adam Bjerstedt <abjerstedt@gmail.com>", homepage: "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-browserstack-service", license: "MIT", engines: { node: ">=18.20.0" }, repository: { type: "git", url: "git+https://github.com/webdriverio/webdriverio.git", directory: "packages/wdio-browserstack-service" }, keywords: [ "webdriverio", "wdio", "browserstack", "wdio-service" ], bugs: { url: "https://github.com/webdriverio/webdriverio/issues" }, type: "module", types: "./build/index.d.ts", exports: { ".": { types: "./build/index.d.ts", import: "./build/index.js" }, "./cleanup": { import: "./build/cleanup.js", source: "./src/cleanup.ts" } }, typeScriptVersion: "3.8.3", dependencies: { "@browserstack/ai-sdk-node": "1.5.17", "@browserstack/wdio-browserstack-service": "^2.0.2", "@percy/appium-app": "^2.0.9", "@percy/selenium-webdriver": "^2.2.2", "@types/gitconfiglocal": "^2.0.1", "@wdio/logger": "workspace:*", "@wdio/reporter": "workspace:*", "@wdio/types": "workspace:*", "browserstack-local": "^1.5.1", chalk: "^5.3.0", "csv-writer": "^1.6.0", "formdata-node": "5.0.1", "git-repo-info": "^2.1.1", gitconfiglocal: "^2.1.0", glob: "^11.0.0", tar: "^7.5.7", undici: "^6.21.3", uuid: "^11.1.0", webdriverio: "workspace:*", "winston-transport": "^4.5.0", yauzl: "^3.0.0" }, peerDependencies: { "@wdio/cli": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" }, devDependencies: { "@types/node": "^20.1.0", "@types/tar": "^6.1.13", "@types/yauzl": "^2.10.3", "@wdio/globals": "workspace:*" }, publishConfig: { access: "public" } }; // src/constants.ts var bstackServiceVersion = package_default.version; var consoleHolder = Object.assign({}, console); var APP_ALLY_ISSUES_ENDPOINT = "api/v1/issues"; var APP_ALLY_ISSUES_SUMMARY_ENDPOINT = "api/v1/issues-summary"; var BSTACK_SERVICE_VERSION = bstackServiceVersion; var LOGS_FILE = "logs/bstack-wdio-service.log"; var SMART_SELECTION_MODE_RELEVANT_FIRST = "relevantFirst"; var SMART_SELECTION_MODE_RELEVANT_ONLY = "relevantOnly"; var BROWSERSTACK_TESTHUB_JWT = "BROWSERSTACK_TESTHUB_JWT"; var TESTOPS_SCREENSHOT_ENV = "BS_TESTOPS_ALLOW_SCREENSHOTS"; var BROWSERSTACK_TESTHUB_UUID = "BROWSERSTACK_TESTHUB_UUID"; var PERF_MEASUREMENT_ENV = "BROWSERSTACK_O11Y_PERF_MEASUREMENT"; var RERUN_ENV = "BROWSERSTACK_RERUN"; var TESTOPS_BUILD_COMPLETED_ENV = "BS_TESTOPS_BUILD_COMPLETED"; var BROWSERSTACK_ACCESSIBILITY = "BROWSERSTACK_ACCESSIBILITY"; var BROWSERSTACK_OBSERVABILITY = "BROWSERSTACK_OBSERVABILITY"; var MAX_GIT_META_DATA_SIZE_IN_BYTES = 64 * 1024; var GIT_META_DATA_TRUNCATED = "...[TRUNCATED]"; var WDIO_NAMING_PREFIX = "WebdriverIO-"; var UPDATED_CLI_ENDPOINT = "sdk/v1/update_cli"; // src/bstackLogger.ts var log = logger("@wdio/browserstack-service"); var BStackLogger = class { static logFilePath = path.join(process.cwd(), LOGS_FILE); static logFolderPath = path.join(process.cwd(), "logs"); static logFileStream; static logToFile(logMessage, logLevel) { try { if (!this.logFileStream) { this.ensureLogsFolder(); this.logFileStream = fs.createWriteStream(this.logFilePath, { flags: "a" }); } if (this.logFileStream && this.logFileStream.writable) { this.logFileStream.write(this.formatLog(logMessage, logLevel)); } } catch (error) { log.debug(`Failed to log to file. Error ${error}`); } } static formatLog(logMessage, level) { return `${chalk.gray((/* @__PURE__ */ new Date()).toISOString())} ${chalk[COLORS[level]](level.toUpperCase())} ${chalk.whiteBright("@wdio/browserstack-service")} ${logMessage} `; } static info(message) { this.logToFile(message, "info"); log.info(message); } static error(message) { this.logToFile(message, "error"); log.error(message); } static debug(message, param) { this.logToFile(message, "debug"); if (param) { log.debug(message, param); } else { log.debug(message); } } static warn(message) { this.logToFile(message, "warn"); log.warn(message); } static trace(message) { this.logToFile(message, "trace"); log.trace(message); } static clearLogger() { if (this.logFileStream) { this.logFileStream.end(); } this.logFileStream = null; } static clearLogFile() { if (fs.existsSync(this.logFilePath)) { fs.truncateSync(this.logFilePath); } } static ensureLogsFolder() { if (!fs.existsSync(this.logFolderPath)) { fs.mkdirSync(this.logFolderPath); } } }; // src/cli/apiUtils.ts var APIUtils = class { static FUNNEL_INSTRUMENTATION_URL = "https://api.browserstack.com/sdk/v1/event"; static BROWSERSTACK_AUTOMATE_API_URL = "https://api.browserstack.com"; static BROWSERSTACK_AA_API_URL = "https://api.browserstack.com"; static BROWSERSTACK_PERCY_API_URL = "https://api.browserstack.com"; static BROWSERSTACK_AUTOMATE_API_CLOUD_URL = "https://api-cloud.browserstack.com"; static BROWSERSTACK_AA_API_CLOUD_URL = "https://api-cloud.browserstack.com"; static APP_ALLY_ENDPOINT = "https://app-accessibility.browserstack.com/automate"; static DATA_ENDPOINT = "https://collector-observability.browserstack.com"; static UPLOAD_LOGS_ADDRESS = "https://upload-observability.browserstack.com"; static EDS_URL = "https://eds.browserstack.com"; static updateURLSForGRR(apis) { this.FUNNEL_INSTRUMENTATION_URL = `${apis.automate.api}/sdk/v1/event`; this.BROWSERSTACK_AUTOMATE_API_URL = apis.automate.api; this.BROWSERSTACK_AA_API_URL = apis.appAutomate.api; this.BROWSERSTACK_PERCY_API_URL = apis.percy.api; this.BROWSERSTACK_AUTOMATE_API_CLOUD_URL = apis.automate.upload; this.BROWSERSTACK_AA_API_CLOUD_URL = apis.appAutomate.upload; this.APP_ALLY_ENDPOINT = `${apis.appAccessibility.api}/automate`; this.DATA_ENDPOINT = apis.observability.api; this.UPLOAD_LOGS_ADDRESS = apis.observability.upload; this.EDS_URL = apis.edsInstrumentation.api; } }; // src/cli/cliUtils.ts import fs3 from "node:fs"; import fsp from "node:fs/promises"; import { platform, arch, homedir } from "node:os"; import path3 from "node:path"; import util, { promisify } from "node:util"; import { exec } from "node:child_process"; import { Readable } from "node:stream"; import yauzl from "yauzl"; import { threadId } from "node:worker_threads"; // src/fetchWrapper.ts import { fetch as undiciFetch, ProxyAgent } from "undici"; var ResponseError = class extends Error { response; constructor(message, res) { super(message); this.response = res; } }; async function fetchWrap(input, init) { const res = await _fetch(input, init); if (!res.ok) { throw new ResponseError(`Error response from server ${res.status}: ${await res.text()}`, res); } return res; } function _fetch(input, init) { const proxyUrl = process.env.HTTP_PROXY || process.env.HTTPS_PROXY; if (proxyUrl) { const noProxy = process.env.NO_PROXY && process.env.NO_PROXY.trim() ? process.env.NO_PROXY.trim().split(/[\s,;]+/) : []; const request = new Request(input); const url = new URL(request.url); if (!noProxy.some((str) => url.hostname.endsWith(str))) { return undiciFetch( request.url, { ...init, dispatcher: new ProxyAgent(proxyUrl) } ); } } return fetch(input, init); } // src/instrumentation/performance/constants.ts var EVENTS = { SDK_SETUP: "sdk:setup", SDK_CLEANUP: "sdk:cleanup", SDK_PRE_TEST: "sdk:pre-test", SDK_TEST: "sdk:test", SDK_POST_TEST: "sdk:post-test", SDK_HOOK: "sdk:hook", SDK_DRIVER: "sdk:driver", SDK_A11Y: "sdk:a11y", SDK_O11Y: "sdk:o11y", SDK_AUTO_CAPTURE: "sdk:auto-capture", SDK_PROXY_SETUP: "sdk:proxy-setup", SDK_TESTHUB: "sdk:testhub", SDK_AUTOMATE: "sdk:automate", SDK_APP_AUTOMATE: "sdk:app-automate", SDK_TURBOSCALE: "sdk:turboscale", SDK_PERCY: "sdk:percy", SDK_PRE_INITIALIZE: "sdk:driver:pre-initialization", SDK_POST_INITIALIZE: "sdk:driver:post-initialization", SDK_CLI_CHECK_UPDATE: "sdk:cli:check-update", SDK_CLI_DOWNLOAD: "sdk:cli:download", SDK_CLI_ON_BOOTSTRAP: "sdk:cli:on-bootstrap", SDK_CLI_ON_CONNECT: "sdk:cli:on-connect", SDK_CLI_START: "sdk:cli:start", SDK_CLI_ON_STOP: "sdk:cli:on-stop", SDK_CONNECT_BIN_SESSION: "sdk:connectBinSession", SDK_START_BIN_SESSION: "sdk:startBinSession", // New events from Python SDK SDK_DRIVER_INIT: "sdk:driverInit", SDK_FIND_NEAREST_HUB: "sdk:findNearestHub", SDK_AUTOMATION_FRAMEWORK_INIT: "sdk:automationFrameworkInit", SDK_AUTOMATION_FRAMEWORK_START: "sdk:automationFrameworkStart", SDK_AUTOMATION_FRAMEWORK_STOP: "sdk:automationFrameworkStop", SDK_ACCESSIBILITY_CONFIG: "sdk:accessibilityConfig", SDK_OBSERVABILITY_CONFIG: "sdk:observabilityConfig", SDK_AI_SELF_HEAL_STEP: "sdk:aiSelfHealStep", SDK_AI_SELF_HEAL_GET_RESULT: "sdk:aiSelfHealGetResult", SDK_TEST_FRAMEWORK_EVENT: "sdk:testFrameworkEvent", SDK_TEST_SESSION_EVENT: "sdk:testSessionEvent", SDK_CLI_LOG_CREATED_EVENT: "sdk:cli:logCreatedEvent", SDK_CLI_ENQUEUE_TEST_EVENT: "sdk:cli:enqueueTestEvent", SDK_ON_STOP: "sdk:onStop", SDK_SEND_LOGS: "sdk:sendlogs", // Funnel Events SDK_FUNNEL_TEST_ATTEMPTED: "sdk:funnel:test-attempted", SDK_FUNNEL_TEST_SUCCESSFUL: "sdk:funnel:test-successful", // Log Upload Events SDK_UPLOAD_LOGS: "sdk:upload-logs", // Key Metrics Events SDK_SEND_KEY_METRICS: "sdk:send-key-metrics", SDK_KEY_METRICS_PREPARATION: "sdk:key-metrics:preparation", SDK_KEY_METRICS_UPLOAD: "sdk:key-metrics:upload", // CLI Binary Events SDK_CLI_DOWNLOAD_BINARY: "sdk:cli:download-binary", SDK_CLI_BINARY_VERIFICATION: "sdk:cli:binary-verification", // Cleanup & Shutdown Events (tracking gap between driver:quit and funnel:test-successful) SDK_LISTENER_WORKER_END: "sdk:listener:worker-end", SDK_PERCY_TEARDOWN: "sdk:percy:teardown", SDK_WORKER_SAVE_DATA: "sdk:worker:save-data", SDK_PERFORMANCE_REPORT_GEN: "sdk:performance:report-generation", SDK_PERFORMANCE_JSON_WRITE: "sdk:performance:json-write", SDK_PERFORMANCE_HTML_GEN: "sdk:performance:html-generation", // Device Allocation Event (tracking gap between beforeSession and before hooks) SDK_DEVICE_ALLOCATION: "sdk:device-allocation" }; var TESTHUB_EVENTS = { START: `${EVENTS.SDK_TESTHUB}:start`, STOP: `${EVENTS.SDK_TESTHUB}:stop` }; var AUTOMATE_EVENTS = { KEEP_ALIVE: `${EVENTS.SDK_AUTOMATE}:keep-alive`, HUB_MANAGEMENT: `${EVENTS.SDK_AUTOMATE}:hub-management`, LOCAL_START: `${EVENTS.SDK_AUTOMATE}:local-start`, LOCAL_STOP: `${EVENTS.SDK_AUTOMATE}:local-stop`, DRIVER_MANAGE: `${EVENTS.SDK_AUTOMATE}:driver-manage`, SESSION_NAME: `${EVENTS.SDK_AUTOMATE}:session-name`, SESSION_STATUS: `${EVENTS.SDK_AUTOMATE}:session-status`, SESSION_ANNOTATION: `${EVENTS.SDK_AUTOMATE}:session-annotation`, IDLE_TIMEOUT: `${EVENTS.SDK_AUTOMATE}:idle-timeout`, GENERATE_CI_ARTIFACT: `${EVENTS.SDK_AUTOMATE}:ci-artifacts`, PRINT_BUILDLINK: `${EVENTS.SDK_AUTOMATE}:print-buildlink` }; var A11Y_EVENTS = { PERFORM_SCAN: `${EVENTS.SDK_A11Y}:driver-performscan`, SAVE_RESULTS: `${EVENTS.SDK_A11Y}:save-results`, GET_RESULTS: `${EVENTS.SDK_A11Y}:get-accessibility-results`, GET_RESULTS_SUMMARY: `${EVENTS.SDK_A11Y}:get-accessibility-results-summary` }; var PERCY_EVENTS = { DOWNLOAD: `${EVENTS.SDK_PERCY}:download`, SCREENSHOT: `${EVENTS.SDK_PERCY}:screenshot`, START: `${EVENTS.SDK_PERCY}:start`, STOP: `${EVENTS.SDK_PERCY}:stop`, AUTO_CAPTURE: `${EVENTS.SDK_PERCY}:auto-capture`, SNAPSHOT: `${EVENTS.SDK_PERCY}:snapshot`, SCREENSHOT_APP: `${EVENTS.SDK_PERCY}:screenshot-app` }; var O11Y_EVENTS = { SYNC: `${EVENTS.SDK_O11Y}:sync`, TAKE_SCREENSHOT: `${EVENTS.SDK_O11Y}:driver-takeScreenShot`, PRINT_BUILDLINK: `${EVENTS.SDK_O11Y}:print-buildlink` }; var HOOK_EVENTS = { BEFORE_EACH: `${EVENTS.SDK_HOOK}:before-each`, AFTER_EACH: `${EVENTS.SDK_HOOK}:after-each`, AFTER_ALL: `${EVENTS.SDK_HOOK}:after-all`, BEFORE_ALL: `${EVENTS.SDK_HOOK}:before-all`, BEFORE: `${EVENTS.SDK_HOOK}:before`, AFTER: `${EVENTS.SDK_HOOK}:after` }; var TURBOSCALE_EVENTS = { HUB_MANAGEMENT: `${EVENTS.SDK_TURBOSCALE}:hub-management`, PRINT_BUILDLINK: `${EVENTS.SDK_TURBOSCALE}:print-buildlink` }; var APP_AUTOMATE_EVENTS = { APP_UPLOAD: `${EVENTS.SDK_APP_AUTOMATE}:app-upload` }; var DRIVER_EVENT = { QUIT: `${EVENTS.SDK_DRIVER}:quit`, GET: `${EVENTS.SDK_DRIVER}:get`, PRE_EXECUTE: `${EVENTS.SDK_DRIVER}:pre-execute`, POST_EXECUTE: `${EVENTS.SDK_DRIVER}:post-execute`, INIT: EVENTS.SDK_DRIVER_INIT, PRE_INITIALIZE: EVENTS.SDK_PRE_INITIALIZE, POST_INITIALIZE: EVENTS.SDK_POST_INITIALIZE }; var FRAMEWORK_EVENTS = { INIT: EVENTS.SDK_AUTOMATION_FRAMEWORK_INIT, START: EVENTS.SDK_AUTOMATION_FRAMEWORK_START, STOP: EVENTS.SDK_AUTOMATION_FRAMEWORK_STOP }; var CONFIG_EVENTS = { ACCESSIBILITY: EVENTS.SDK_ACCESSIBILITY_CONFIG, OBSERVABILITY: EVENTS.SDK_OBSERVABILITY_CONFIG }; var AI_EVENTS = { SELF_HEAL_STEP: EVENTS.SDK_AI_SELF_HEAL_STEP, SELF_HEAL_GET_RESULT: EVENTS.SDK_AI_SELF_HEAL_GET_RESULT }; var DISPATCHER_EVENTS = { TEST_FRAMEWORK: EVENTS.SDK_TEST_FRAMEWORK_EVENT, TEST_SESSION: EVENTS.SDK_TEST_SESSION_EVENT, LOG_CREATED: EVENTS.SDK_CLI_LOG_CREATED_EVENT, ENQUEUE_TEST: EVENTS.SDK_CLI_ENQUEUE_TEST_EVENT }; // src/cli/cliLogger.ts import path2 from "node:path"; import fs2 from "node:fs"; import chalk2 from "chalk"; import logger2 from "@wdio/logger"; var log2 = logger2("@wdio/browserstack-service/cli"); var BStackLogger2 = class { static logFilePath = path2.join(process.cwd(), LOGS_FILE); static logFolderPath = path2.join(process.cwd(), "logs"); static logFileStream; static logToFile(logMessage, logLevel) { try { if (!this.logFileStream) { this.ensureLogsFolder(); this.logFileStream = fs2.createWriteStream(this.logFilePath, { flags: "a" }); } if (this.logFileStream && this.logFileStream.writable) { this.logFileStream.write(this.formatLog(logMessage, logLevel)); } } catch (error) { log2.debug(`Failed to log to file. Error ${error}`); } } static formatLog(logMessage, level) { return `${chalk2.gray((/* @__PURE__ */ new Date()).toISOString())} ${chalk2[COLORS[level]](level.toUpperCase())} ${chalk2.whiteBright("@wdio/browserstack-service")} ${logMessage} `; } static info(message) { this.logToFile(message, "info"); log2.info(message); } static error(message) { this.logToFile(message, "error"); log2.error(message); } static debug(message, param) { this.logToFile(message, "debug"); if (param) { log2.debug(message, param); } else { log2.debug(message); } } static warn(message) { this.logToFile(message, "warn"); log2.warn(message); } static trace(message) { this.logToFile(message, "trace"); log2.trace(message); } static clearLogger() { if (this.logFileStream) { this.logFileStream.end(); } this.logFileStream = null; } static clearLogFile() { if (fs2.existsSync(this.logFilePath)) { fs2.truncateSync(this.logFilePath); } } static ensureLogsFolder() { if (!fs2.existsSync(this.logFolderPath)) { fs2.mkdirSync(this.logFolderPath); } } }; // src/cli/frameworks/constants/testFrameworkConstants.ts var TestFrameworkConstants = { KEY_TEST_UUID: "test_uuid", KEY_TEST_ID: "test_id", KEY_TEST_NAME: "test_name", KEY_TEST_FILE_PATH: "test_file_path", KEY_TEST_TAGS: "test_tags", KEY_TEST_RESULT: "test_result", KEY_TEST_RESULT_AT: "test_result_at", KEY_TEST_STARTED_AT: "test_started_at", KEY_TEST_ENDED_AT: "test_ended_at", KEY_TEST_LOCATION: "test_location", KEY_TEST_SCOPE: "test_scope", KEY_TEST_SCOPES: "test_scopes", KEY_TEST_FRAMEWORK_NAME: "test_framework_name", KEY_TEST_FRAMEWORK_VERSION: "test_framework_version", KEY_TEST_CODE: "test_code", KEY_TEST_RERUN_NAME: "test_rerun_name", KEY_PLATFORM_INDEX: "platform_index", KEY_TEST_FAILURE: "test_failure", KEY_TEST_FAILURE_TYPE: "test_failure_type", KEY_TEST_FAILURE_REASON: "test_failure_reason", KEY_TEST_LOGS: "test_logs", KEY_TEST_META: "test_meta", KEY_TEST_DEFERRED: "test_deferred", KEY_SESSION_NAME: "test_session_name", KEY_AUTOMATE_SESSION_NAME: "automate_session_name", KEY_AUTOMATE_SESSION_STATUS: "automate_session_status", KEY_AUTOMATE_SESSION_REASON: "automate_session_reason", KEY_EVENT_STARTED_AT: "event_started_at", KEY_EVENT_ENDED_AT: "event_ended_at", KEY_HOOK_ID: "hook_id", KEY_HOOK_RESULT: "hook_result", KEY_HOOK_LOGS: "hook_logs", KEY_HOOK_NAME: "hook_name", KEY_HOOKS_STARTED: "test_hooks_started", KEY_HOOKS_FINISHED: "test_hooks_finished", DEFAULT_TEST_RESULT: "pending", DEFAULT_HOOK_RESULT: "pending", KIND_SCREENSHOT: "TEST_SCREENSHOT", KIND_LOG: "TEST_LOG", HOOK_REGEX: "^(BEFORE_|AFTER_)" }; // src/cli/cliUtils.ts var CLI_LOCK_TIMEOUT_MS = 5 * 60 * 1e3; var CLI_LOCK_POLL_MS = 1e3; var CLI_DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3; var CLI_DOWNLOAD_TMP_PREFIX = "downloaded_file_"; var CLI_DOWNLOAD_TMP_SUFFIX = ".zip"; var CLIUtils = class _CLIUtils { static automationFrameworkDetail = {}; static testFrameworkDetail = {}; static CLISupportedFrameworks = ["mocha"]; static isDevelopmentEnv() { return process.env.BROWSERSTACK_CLI_ENV === "development"; } static getCLIParamsForDevEnv() { return { id: process.env.BROWSERSTACK_CLI_ENV || "", listen: `unix:/tmp/sdk-platform-${process.env.BROWSERSTACK_CLI_ENV}.sock` }; } /** * Build config object for binary session request * @returns {string} * @throws {Error} */ static getBinConfig(config, capabilities, options, buildTag) { const modifiedOpts = { ...options }; if (modifiedOpts.opts) { modifiedOpts.browserStackLocalOptions = modifiedOpts.opts; delete modifiedOpts.opts; } modifiedOpts.testContextOptions = { skipSessionName: isFalse(modifiedOpts.setSessionName), skipSessionStatus: isFalse(modifiedOpts.setSessionStatus), sessionNameOmitTestTitle: modifiedOpts.sessionNameOmitTestTitle || false, sessionNamePrependTopLevelSuiteTitle: modifiedOpts.sessionNamePrependTopLevelSuiteTitle || false, sessionNameFormat: modifiedOpts.sessionNameFormat || "" }; const commonBstackOptions = (() => { if (capabilities && !Array.isArray(capabilities) && typeof capabilities === "object" && "bstack:options" in capabilities) { return capabilities["bstack:options"] || {}; } return {}; })(); const isNonBstackA11y = isTurboScale(options) || !shouldAddServiceVersion( config, options.testObservability ); const observabilityOptions = options.testObservabilityOptions || {}; const binconfig = { userName: observabilityOptions.user || config.user, accessKey: observabilityOptions.key || config.key, platforms: [], isNonBstackA11yWDIO: isNonBstackA11y, ...modifiedOpts, ...commonBstackOptions }; binconfig.buildName = observabilityOptions.buildName || binconfig.buildName; binconfig.projectName = observabilityOptions.projectName || binconfig.projectName; binconfig.buildTag = this.getObservabilityBuildTags(observabilityOptions, buildTag) || []; let caps = capabilities; if (capabilities && !Array.isArray(capabilities)) { caps = [capabilities]; } if (Array.isArray(caps)) { for (const cap of caps) { const platform4 = {}; const capability = cap; Object.keys(capability).filter((key) => key !== "bstack:options").forEach((key) => { platform4[key] = capability[key]; }); if (capability["bstack:options"]) { Object.keys( capability["bstack:options"] ).forEach((key) => { platform4[key] = capability["bstack:options"][key]; }); } binconfig.platforms.push(platform4); } } return JSON.stringify(binconfig); } static getSdkVersion() { return BSTACK_SERVICE_VERSION; } static getSdkLanguage() { return "ECMAScript"; } static async setupCliPath(config) { BStackLogger2.debug("Configuring Cli path."); const developmentBinaryPath = process.env.SDK_CLI_BIN_PATH || null; if (!isNullOrEmpty(developmentBinaryPath)) { BStackLogger2.debug(`Development Cli Path: ${developmentBinaryPath}`); return developmentBinaryPath; } try { const cliDir = this.getCliDir(); if (isNullOrEmpty(cliDir)) { throw new Error("No writable directory available for the CLI"); } const existingCliPath = this.getExistingCliPath(cliDir); const finalBinaryPath = await this.checkAndUpdateCli( existingCliPath, cliDir, config ); BStackLogger2.debug(`Resolved binary path: ${finalBinaryPath}`); return finalBinaryPath; } catch (err) { BStackLogger2.debug( `Error in setting up cli path directory, Exception: ${util.format(err)}` ); } return null; } static async checkAndUpdateCli(existingCliPath, cliDir, config) { if (process.env.BROWSERSTACK_TESTHUB_JWT) { BStackLogger2.debug( `Worker process detected, skipping CLI update. Using existing: ${existingCliPath}` ); if (existingCliPath && fs3.existsSync(existingCliPath)) { return existingCliPath; } BStackLogger2.warn( "Worker process has no existing CLI binary, attempting download as fallback." ); } PerformanceTester.start(EVENTS.SDK_CLI_CHECK_UPDATE); BStackLogger2.info(`Current CLI Path Found: ${existingCliPath}`); const queryParams = { sdk_version: _CLIUtils.getSdkVersion(), os: platform(), os_arch: arch(), cli_version: "0", sdk_language: this.getSdkLanguage() }; if (!isNullOrEmpty(existingCliPath)) { queryParams.cli_version = await this.runShellCommand( `${existingCliPath} version` ); } const response = await this.requestToUpdateCLI(queryParams, config); if (nestedKeyValue(response, ["updated_cli_version"])) { BStackLogger2.debug( `Need to update binary, current binary version: ${queryParams.cli_version}` ); const browserStackBinaryUrl = process.env.BROWSERSTACK_BINARY_URL || null; if (!isNullOrEmpty(browserStackBinaryUrl)) { BStackLogger2.debug( `Using BROWSERSTACK_BINARY_URL: ${browserStackBinaryUrl}` ); response.url = browserStackBinaryUrl; } const finalBinaryPath = await this.downloadLatestBinary( nestedKeyValue(response, ["url"]), cliDir ); PerformanceTester.end(EVENTS.SDK_CLI_CHECK_UPDATE); return finalBinaryPath; } PerformanceTester.end(EVENTS.SDK_CLI_CHECK_UPDATE); return existingCliPath; } static getCliDir() { const writableDir = this.getWritableDir(); try { if (isNullOrEmpty(writableDir)) { throw new Error("No writable directory available for the CLI"); } const cliDirPath = path3.join(writableDir, "cli"); if (!fs3.existsSync(cliDirPath)) { createDir(cliDirPath); } return cliDirPath; } catch (err) { BStackLogger2.error( `Error in getting writable directory, writableDir=${util.format(err)}` ); return ""; } } static getWritableDir() { const writableDirOptions = [ process.env.BROWSERSTACK_FILES_DIR, path3.join(homedir(), ".browserstack"), path3.join("tmp", ".browserstack") ]; for (const path9 of writableDirOptions) { if (isNullOrEmpty(path9)) { continue; } try { if (fs3.existsSync(path9)) { BStackLogger2.debug(`File ${path9} already exist`); if (!isWritable(path9)) { BStackLogger2.debug(`Giving write permission to ${path9}`); const success = setReadWriteAccess(path9); if (!isTrue(success)) { BStackLogger2.warn( `Unable to provide write permission to ${path9}` ); } } } else { BStackLogger2.debug(`File does not exist: ${path9}`); createDir(path9); BStackLogger2.debug(`Giving write permission to ${path9}`); const success = setReadWriteAccess(path9); if (!isTrue(success)) { BStackLogger2.warn( `Unable to provide write permission to ${path9}` ); } } return path9; } catch (err) { BStackLogger2.error( `Unable to get writable directory, exception ${util.format(err)}` ); } } return null; } static getExistingCliPath(cliDir) { try { if (!fs3.existsSync(cliDir) || !fs3.statSync(cliDir).isDirectory()) { return ""; } const allBinaries = fs3.readdirSync(cliDir).map((file) => path3.join(cliDir, file)).filter( (filePath) => fs3.statSync(filePath).isFile() && path3.basename(filePath).startsWith("binary-") ); if (allBinaries.length > 0) { const latestBinary = allBinaries.map((filePath) => ({ filePath, mtime: fs3.statSync(filePath).mtime })).reduce( (latest, current) => { if (!latest || !latest.mtime) { return current; } if (current.mtime > latest.mtime) { return current; } return latest; }, null ); return latestBinary ? latestBinary.filePath : ""; } return ""; } catch (err) { BStackLogger2.error(`Error while reading CLI path: ${util.format(err)}`); return ""; } } static requestToUpdateCLI = async (queryParams, config) => { const params = new URLSearchParams(queryParams); const requestInit = { method: "GET", headers: { Authorization: `Basic ${Buffer.from(`${getBrowserStackUser(config)}:${getBrowserStackKey(config)}`).toString("base64")}` } }; const response = await _fetch( `${APIUtils.BROWSERSTACK_AUTOMATE_API_URL}/${UPDATED_CLI_ENDPOINT}?${params.toString()}`, requestInit ); const jsonResponse = await response.json(); BStackLogger2.debug(`response ${JSON.stringify(jsonResponse)}`); return jsonResponse; }; static runShellCommand(cmdCommand, workingDir = "") { return new Promise((resolve) => { const process2 = exec( cmdCommand, { cwd: workingDir, timeout: 5e3 }, (error, stdout, stderr) => { if (error) { resolve(stderr.trim() || "SHELL_EXECUTE_ERROR"); } else { resolve(stdout.trim()); } } ); process2.on("error", () => { resolve("SHELL_EXECUTE_ERROR"); }); }); } static downloadLatestBinary = async (binDownloadUrl, cliDir) => { const lockPath = path3.join(cliDir, "download.lock"); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const parseLockFile = () => { try { const content = fs3.readFileSync(lockPath, "utf8").trim(); const [pidLine, timestampLine] = content.split("\n"); const pid = Number.parseInt(pidLine, 10); const timestamp = Number.parseInt(timestampLine, 10); if (!Number.isFinite(pid) || !Number.isFinite(timestamp)) { return null; } return { pid, timestamp }; } catch { return null; } }; const isProcessRunning = (pid) => { try { process.kill(pid, 0); return true; } catch { return false; } }; const acquireLock = async (timeoutMs = CLI_LOCK_TIMEOUT_MS, pollMs = CLI_LOCK_POLL_MS) => { const start = Date.now(); while (true) { try { const fd = fs3.openSync(lockPath, "wx"); try { fs3.writeFileSync(fd, `${process.pid} ${Date.now()} `); } catch { } return () => { try { fs3.closeSync(fd); } catch { } try { fs3.unlinkSync(lockPath); } catch { } }; } catch (e) { const error = e; if (error.code === "EEXIST") { const lockMeta = parseLockFile(); if (lockMeta) { const lockAge = Date.now() - lockMeta.timestamp; const running = isProcessRunning(lockMeta.pid); if (!running || lockAge > timeoutMs) { BStackLogger2.warn( `Stale CLI download lock detected (pid=${lockMeta.pid}, age=${lockAge}ms). Removing lock.` ); try { fs3.unlinkSync(lockPath); } catch { } continue; } } const existingBinary = _CLIUtils.getExistingCliPath(cliDir); if (existingBinary && fs3.existsSync(existingBinary) && fs3.statSync(existingBinary).size > 0) { BStackLogger2.debug( `Binary appeared while waiting for lock: ${existingBinary}` ); return { alreadyExists: existingBinary }; } if (Date.now() - start > timeoutMs) { throw new Error( `Timeout waiting for lock: ${lockPath}` ); } await sleep(pollMs); continue; } throw e; } } }; const cleanupTemporaryDownloads = (maxAgeMs = CLI_LOCK_TIMEOUT_MS) => { try { const now = Date.now(); for (const entry of fs3.readdirSync(cliDir)) { if (!entry.startsWith(CLI_DOWNLOAD_TMP_PREFIX) || !entry.endsWith(CLI_DOWNLOAD_TMP_SUFFIX)) { continue; } const filePath = path3.join(cliDir, entry); let stats; try { stats = fs3.statSync(filePath); } catch { continue; } if (now - stats.mtimeMs < maxAgeMs) { continue; } try { fs3.unlinkSync(filePath); } catch (err) { BStackLogger2.debug( `Failed to delete temp CLI zip file ${filePath}: ${util.format(err)}` ); } } } catch (err) { BStackLogger2.debug( `Failed to scan temp CLI downloads in ${cliDir}: ${util.format(err)}` ); } }; PerformanceTester.start(EVENTS.SDK_CLI_DOWNLOAD); BStackLogger2.debug(`Downloading SDK binary from: ${binDownloadUrl}`); let downloadEnded = false; const endDownload = (success = true, errMsg) => { if (downloadEnded) { return; } downloadEnded = true; if (success) { PerformanceTester.end(EVENTS.SDK_CLI_DOWNLOAD); return; } PerformanceTester.end( EVENTS.SDK_CLI_DOWNLOAD, false, errMsg ); }; let releaseLock; try { const lockResult = await acquireLock(); if (typeof lockResult !== "function") { endDownload(); return lockResult.alreadyExists; } releaseLock = lockResult; const existingBinary = _CLIUtils.getExistingCliPath(cliDir); if (existingBinary && fs3.existsSync(existingBinary) && fs3.statSync(existingBinary).size > 0) { BStackLogger2.debug( `Binary already exists after acquiring lock: ${existingBinary}` ); endDownload(); releaseLock(); return existingBinary; } cleanupTemporaryDownloads(); const zipFilePath = path3.join( cliDir, `${CLI_DOWNLOAD_TMP_PREFIX}${process.pid}_${Date.now()}${CLI_DOWNLOAD_TMP_SUFFIX}` ); const downloadedFileStream = fs3.createWriteStream(zipFilePath); return new Promise((resolve, reject) => { const binaryName = null; const processDownload = async () => { const abortController = new AbortController(); const timeout = setTimeout( () => abortController.abort(), CLI_DOWNLOAD_TIMEOUT_MS ); let response; try { response = await _fetch(binDownloadUrl, { signal: abortController.signal }); } finally { clearTimeout(timeout); } if (!response.body) { throw new Error("No response body received"); } downloadedFileStream.on("error", function(err) { BStackLogger2.error( `Got Error while downloading cli binary file: ${err}` ); endDownload(false, util.format(err)); releaseLock?.(); reject(err); }); try { const arrayBuffer = await response.arrayBuffer(); const nodeStream = Readable.from([ new Uint8Array(arrayBuffer) ]); nodeStream.pipe(downloadedFileStream); _CLIUtils.downloadFileStream( downloadedFileStream, binaryName, zipFilePath, cliDir, (result) => { endDownload(); releaseLock?.(); resolve(result); }, (err) => { endDownload(false, util.format(err)); releaseLock?.(); reject(err); } ); } catch (err) { BStackLogger2.error( `Got Error in cli binary downloading request ${util.format(err)}` ); endDownload(false, util.format(err)); releaseLock?.(); reject(err); } }; processDownload(); }); } catch (err) { releaseLock?.(); endDownload(false, util.format(err)); BStackLogger2.debug( `Failed to download binary, Exception: ${util.format(err)}` ); return null; } }; static downloadFileStream(downloadedFileStream, binaryName, zipFilePath, cliDir, resolve, reject) { downloadedFileStream.on("close", async function() { const yauzlOpenPromise = promisify(yauzl.open); try { const zipfile = await yauzlOpenPromise(zipFilePath, { lazyEntries: true }); zipfile.readEntry(); zipfile.on("entry", async (entry) => { if (!binaryName) { binaryName = entry.fileName; } if (/\/$/.test(entry.fileName)) { zipfile.readEntry(); } else { const writeStream = fs3.createWriteStream( path3.join(cliDir, entry.fileName) ); const openReadStreamPromise = promisify( zipfile.openReadStream ).bind(zipfile); try { const readStream = await openReadStreamPromise(entry); readStream.on("end", function() { writeStream.close(); zipfile.readEntry(); }); readStream.pipe(writeStream); } catch (zipErr) { reject(zipErr); } if (entry.fileName === binaryName) { zipfile.close(); } } }); zipfile.on("error", (zipErr) => { reject(zipErr); }); zipfile.once("end", () => { fsp.unlink(zipFilePath).catch(() => { BStackLogger2.warn( `Failed to delete zip file: ${zipFilePath}` ); }); fsp.chmod(`${cliDir}/${binaryName}`, "0755").then(() => { resolve(`${cliDir}/${binaryName}`); }).catch((err) => { reject(err); }); zipfile.close(); }); } catch (err) { reject(err); } }); } static getTestFrameworkDetail() { if (process.env.BROWSERSTACK_TEST_FRAMEWORK_DETAIL) { return JSON.parse(process.env.BROWSERSTACK_TEST_FRAMEWORK_DETAIL); } return this.testFrameworkDetail; } static getAutomationFrameworkDetail() { if (process.env.BROWSERSTACK_AUTOMATION_FRAMEWORK_DETAIL) { return JSON.parse( process.env.BROWSERSTACK_AUTOMATION_FRAMEWORK_DETAIL ); } return this.automationFrameworkDetail; } static setFrameworkDetail(testFramework, automationFramework) { if (!testFramework || !automationFramework) { BStackLogger2.debug( `Test or Automation framework not provided testFramework=${testFramework}, automationFramework=${automationFramework}` ); } this.testFrameworkDetail = { name: testFramework, version: { [testFramework]: _CLIUtils.getSdkVersion() } }; this.automationFrameworkDetail = { name: automationFramework, version: { [automationFramework]: _CLIUtils.getSdkVersion() } }; process.env.BROWSERSTACK_AUTOMATION_FRAMEWORK_DETAIL = JSON.stringify( this.automationFrameworkDetail ); process.env.BROWSERSTACK_TEST_FRAMEWORK_DETAIL = JSON.stringify( this.testFrameworkDetail ); } /** * Get the current instance name using thread id and processId * @returns {string} */ static getCurrentInstanceName() { return `${process.pid}:${threadId}`; } /** * Generate a unique client worker identifier combining thread ID and process ID. * This identifier is used to track worker-specific events and performance metrics * across distributed test execution. Format matches the Python SDK implementation * for consistency across SDKs. * * Format: "threadId-processId" * * @param context - Optional execution context with threadId and processId * @returns Worker ID string in format "threadId-processId" * @example * const workerId = CLIUtils.getClientWorkerId() // Returns "1-12345" * const workerId = CLIUtils.getClientWorkerId({ threadId: 123, processId: 456 }) // Returns "123-456" */ static getClientWorkerId(context) { const workerThreadId = context?.threadId?.toString() || threadId.toString(); const workerProcessId = context?.processId?.toString() || process.pid.toString(); return `${workerThreadId}-${workerProcessId}`; } /** * * @param {TestFrameworkState | AutomationFrameworkState} frameworkState * @param {HookState} hookState * @returns {string} */ static getHookRegistryKey(frameworkState, hookState) { return `${frameworkState}:${hookState}`; } static matchHookRegex(hookState) { const pattern = new RegExp(TestFrameworkConstants.HOOK_REGEX); return pattern.test(hookState); } static getObservabilityBuildTags(observabilityOptions, bstackBuildTag) { if (process.env.TEST_OBSERVABILITY_BUILD_TAG) { return process.env.TEST_OBSERVABILITY_BUILD_TAG.split(","); } if (observabilityOptions.buildTag) { return observabilityOptions.buildTag; } if (bstackBuildTag) { return [bstackBuildTag]; } return []; } static checkCLISupportedFrameworks(framework) { if (framework === void 0) { return false; } return this.CLISupportedFrameworks.includes(framework); } }; // src/instrumentation/performance/performance-tester.ts var PerformanceTester = class _PerformanceTester { static _observer; static _csvWriter; static _events = []; static _measuredEvents = []; static _hasStoppedGeneration = false; static _stopGenerateCallCount = 0; static started = false; static details = {}; static eventsMap = {}; static browser; static scenarioThatRan; static jsonReportDirName = "performance-report"; static jsonReportDirPath = path4.join(process.cwd(), "logs", this.jsonReportDirName); static jsonReportFileName = `${this.jsonReportDirPath}/performance-report-${_PerformanceTester.getProcessId()}.json`; static startMonitoring(csvName = "performance-report.csv") { if (!fs4.existsSync(this.jsonReportDirPath)) { fs4.mkdirSync(this.jsonReportDirPath, { recursive: true }); } this._observer = new PerformanceObserver((list) => { list.getEntries().filter((entry) => entry.entryType === "measure").forEach( (entry) => { let finalEntry = entry.toJSON(); try { if (typeof finalEntry.startTime === "number" && typeof performance.timeOrigin === "number") { const originalStartTime = finalEntry.startTime; finalEntry.startTime = performance.timeOrigin + finalEntry.startTime; BStackLogger.debug(`Timestamp conversion for ${entry.name}: ${originalStartTime} -> ${finalEntry.startTime} (timeOrigin: ${performance.timeOrigin})`); } } catch (e) { BStackLogger.debug(`Error converting startTime to epoch: ${util2.format(e)}`); } if (this.details[entry.name]) { finalEntry = Object.assign(finalEntry, this.details[entry.name]); } delete this.details[entry.name]; this._measuredEvents.push(finalEntry); } ); if (process.env[PERF_MEASUREMENT_ENV]) { list.getEntries().forEach((entry) => this._events.push(entry)); } }); const entryTypes = ["measure"]; if (process.env[PERF_MEASUREMENT_ENV]) { entryTypes.push("function"); } this._observer.observe({ buffered: true, entryTypes }); this.started = true; if (process.env[PERF_MEASUREMENT_ENV]) { this._csvWriter = createObjectCsvWriter({ path: csvName, header: [ { id: "name", title: "Function Name" }, { id: "time", title: "Execution Time (ms)" } ] }); } } static calculateTimes(methods) { const times = {}; this._events.map((entry) => { if (!times[entry.name]) { times[entry.name] = 0; } times[entry.name] += entry.duration; }); const timeTaken = methods.reduce((a, c) => { return times[c] + (a || 0); }, 0); BStackLogger.debug(`Time for ${methods} is ${timeTaken}`); return timeTaken; } static async stopAndGenerate(filename = "performance-own.html") { if (!this.started) { return; } try { const eventsJson = JSON.stringify(this._measuredEvents); const finalJSONStr = eventsJson.slice(1, -1) + ","; await fsPromise.appendFile(this.jsonReportFileName, finalJSONStr); } catch (er) { BStackLogger.debug(`Failed to write events of the worker to ${this.jsonReportFileName}: ${util2.format(er)}`); } this._observer.disconnect(); if (!process.env[PERF_MEASUREMENT_ENV]) { return; } this.started = false; this.generateCSV(this._events); const content = this.generateReport(this._events); const dir = path4.join(process.cwd(), filename); try { await fsPromise.writeFile(dir, content); BStackLogger.info(`Performance report is at ${path4}`); } catch (err) { BStackLogger.error(`Error in writing html ${util2.format(err)}`); } } static generateReport(entries) { let html = "<!DOCTYPE html><html><head><title>Performance Report</title></head><body>"; html += "<h1>Performance Report</h1>"; html += "<table><thead><tr><th>Function Name</th><th>Duration (ms)</th></tr></thead><tbody>"; entries.forEach((entry) => { html += `<tr><td>${entry.name}</td><td>${entry.duration}</td></tr>`; }); html += "</tbody></table></body></html>"; return html; } static generateCSV(entries) { const times = {}; entries.map((entry) => { if (!times[entry.name]) { times[entry.name] = 0; } times[entry.name] += entry.duration; return { name: entry.name, time: entry.duration }; }); const dat = Object.entries(times).map(([key, value]) => { return { name: key, time: value }; }); this._csvWriter.writeRecords(dat).then(() => BStackLogger.info("Performance CSV report generated successfully")).catch((error) => console.error(error)); } static Measure(label, details = {}) { const self = this; return (target, key, descriptor) => { const originalMethod = descriptor.value; if (descriptor.value) { descriptor.value = function(...args) { return _PerformanceTester.measure.apply(self, [label, originalMethod, { methodName: key.toString(), ...details }, args, this]); }; } }; } static measureWrapper(name, fn, details = {}) { const self = this; details.worker = _PerformanceTester.getProcessId(); details.testName = _PerformanceTester.scenarioThatRan && _PerformanceTester.scenarioThatRan[_PerformanceTester.scenarioThatRan.length - 1]; details.platform = _PerformanceTester.browser?.sessionId; return function(...args) { return self.measure(name, fn, details, args); }; } static isEnabled() { return !(process.env.BROWSERSTACK_SDK_INSTRUMENTATION === "false"); } static measure(label, fn, details = {}, args, thisArg = null) { if (!this.started || !this.isEnabled()) { return fn.apply(thisArg, args); } const uniqueId = `${Date.now()}-${Math.random().toString(36).substring(7)}`; const startMark = `${label}-start-${uniqueId}`; const endMark = `${label}-end-${uniqueId}`; performance.mark(startMark); const detailsWithContext = { ...details, measurementId: uniqueId }; try { const returnVal = fn.apply(thisArg, args); if (returnVal instanceof Promise) { return new Promise((resolve, reject) => { returnVal.then((v) => { performance.mark(endMark); performance.measure(label, startMark, endMark); this.details[label] = Object.assign({ success: true, failure: void 0 }, Object.assign(Object.assign({ clientWorkerId: _PerformanceTester.getClientWorkerId(), worker: _PerformanceTester.getProcessId(), platform: _PerformanceTester.browser?.sessionId, testName: _PerformanceTester.scenarioThatRan?.pop() }, detailsWithContext), this.details[label] || {})); resolve(v); }).catch((e) => { performance.mark(endMark); performance.measure(label, startMark, endMark); this.details[label] = Object.assign({ success: false, failure: util2.format(e) }, Object.assign(Object.assign({ clientWorkerId: _PerformanceTester.getClientWorkerId(), worker: _PerformanceTester.getProcessId(), platform: _PerformanceTester.browser?.sessionId, testName: _PerformanceTester.scenarioThatRan?.pop() }, detailsWithContext), this.details[label] || {})); reject(e); }); }); } performance.mark(endMark); performance.measure(label, startMark, endMark); this.details[label] = Object.assign({ success: true, failure: void 0 }, Object.assign(Object.assign({ clientWorkerId: _PerformanceTester.getClientWorkerId(), worker: _PerformanceTester.getProcessId(), platform: _PerformanceTester.browser?.sessionId, testName: _Performan