@wdio/browserstack-service
Version:
WebdriverIO service for better Browserstack integration
1,310 lines (1,300 loc) • 520 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __decorateClass = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i = decorators.length - 1, decorator; i >= 0; i--)
if (decorator = decorators[i])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp(target, key, result);
return result;
};
// package.json
var package_default;
var init_package = __esm({
"package.json"() {
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, BROWSER_DESCRIPTION, VALID_APP_EXTENSION, DEFAULT_OPTIONS, consoleHolder, APP_ALLY_ISSUES_ENDPOINT, APP_ALLY_ISSUES_SUMMARY_ENDPOINT, DATA_EVENT_ENDPOINT, DATA_BATCH_ENDPOINT, DATA_SCREENSHOT_ENDPOINT, DATA_BATCH_SIZE, DATA_BATCH_INTERVAL, DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS, DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS, BSTACK_SERVICE_VERSION, NOT_ALLOWED_KEYS_IN_CAPS, LOGS_FILE, CLI_DEBUG_LOGS_FILE, UPLOAD_LOGS_ENDPOINT, PERCY_LOGS_FILE, PERCY_DOM_CHANGING_COMMANDS_ENDPOINTS, CAPTURE_MODES, LOG_KIND_USAGE_MAP, SUPPORTED_BROWSERS_FOR_AI, TCG_URL, TCG_INFO, SMART_SELECTION_MODE_RELEVANT_FIRST, SMART_SELECTION_MODE_RELEVANT_ONLY, BROWSERSTACK_TESTHUB_JWT, BSTACK_TCG_AUTH_RESULT, TESTOPS_SCREENSHOT_ENV, BROWSERSTACK_TESTHUB_UUID, TEST_ANALYTICS_ID, PERF_MEASUREMENT_ENV, RERUN_TESTS_ENV, RERUN_ENV, TESTOPS_BUILD_COMPLETED_ENV, BROWSERSTACK_PERCY, BROWSERSTACK_ACCESSIBILITY, BROWSERSTACK_OBSERVABILITY, BROWSERSTACK_TEST_REPORTING, TEST_REPORTING_PROJECT_NAME, MAX_GIT_META_DATA_SIZE_IN_BYTES, GIT_META_DATA_TRUNCATED, CLI_STOP_TIMEOUT, WDIO_NAMING_PREFIX, UPDATED_CLI_ENDPOINT;
var init_constants = __esm({
"src/constants.ts"() {
"use strict";
init_package();
bstackServiceVersion = package_default.version;
BROWSER_DESCRIPTION = [
"device",
"os",
"osVersion",
"os_version",
"browserName",
"browser",
"browserVersion",
"browser_version"
];
VALID_APP_EXTENSION = [
".apk",
".aab",
".ipa"
];
DEFAULT_OPTIONS = {
setSessionName: true,
setSessionStatus: true,
testObservability: true,
accessibility: false
};
consoleHolder = Object.assign({}, console);
APP_ALLY_ISSUES_ENDPOINT = "api/v1/issues";
APP_ALLY_ISSUES_SUMMARY_ENDPOINT = "api/v1/issues-summary";
DATA_EVENT_ENDPOINT = "api/v1/event";
DATA_BATCH_ENDPOINT = "api/v1/batch";
DATA_SCREENSHOT_ENDPOINT = "api/v1/screenshots";
DATA_BATCH_SIZE = 1e3;
DATA_BATCH_INTERVAL = 2e3;
DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS = 5e3;
DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS = 100;
BSTACK_SERVICE_VERSION = bstackServiceVersion;
NOT_ALLOWED_KEYS_IN_CAPS = ["includeTagsInTestingScope", "excludeTagsInTestingScope"];
LOGS_FILE = "logs/bstack-wdio-service.log";
CLI_DEBUG_LOGS_FILE = "log/sdk-cli-debug.log";
UPLOAD_LOGS_ENDPOINT = "client-logs/upload";
PERCY_LOGS_FILE = "logs/percy.log";
PERCY_DOM_CHANGING_COMMANDS_ENDPOINTS = [
"/session/:sessionId/url",
"/session/:sessionId/forward",
"/session/:sessionId/back",
"/session/:sessionId/refresh",
"/session/:sessionId/screenshot",
"/session/:sessionId/actions",
"/session/:sessionId/appium/device/shake"
];
CAPTURE_MODES = ["click", "auto", "screenshot", "manual", "testcase"];
LOG_KIND_USAGE_MAP = {
"TEST_LOG": "log",
"TEST_SCREENSHOT": "screenshot",
"TEST_STEP": "step",
"HTTP": "http"
};
SUPPORTED_BROWSERS_FOR_AI = ["chrome", "microsoftedge", "firefox"];
TCG_URL = "https://tcg.browserstack.com";
TCG_INFO = {
tcgRegion: "use",
tcgUrl: TCG_URL
};
SMART_SELECTION_MODE_RELEVANT_FIRST = "relevantFirst";
SMART_SELECTION_MODE_RELEVANT_ONLY = "relevantOnly";
BROWSERSTACK_TESTHUB_JWT = "BROWSERSTACK_TESTHUB_JWT";
BSTACK_TCG_AUTH_RESULT = "BSTACK_TCG_AUTH_RESULT";
TESTOPS_SCREENSHOT_ENV = "BS_TESTOPS_ALLOW_SCREENSHOTS";
BROWSERSTACK_TESTHUB_UUID = "BROWSERSTACK_TESTHUB_UUID";
TEST_ANALYTICS_ID = "TEST_ANALYTICS_ID";
PERF_MEASUREMENT_ENV = "BROWSERSTACK_O11Y_PERF_MEASUREMENT";
RERUN_TESTS_ENV = "BROWSERSTACK_RERUN_TESTS";
RERUN_ENV = "BROWSERSTACK_RERUN";
TESTOPS_BUILD_COMPLETED_ENV = "BS_TESTOPS_BUILD_COMPLETED";
BROWSERSTACK_PERCY = "BROWSERSTACK_PERCY";
BROWSERSTACK_ACCESSIBILITY = "BROWSERSTACK_ACCESSIBILITY";
BROWSERSTACK_OBSERVABILITY = "BROWSERSTACK_OBSERVABILITY";
BROWSERSTACK_TEST_REPORTING = "BROWSERSTACK_TEST_REPORTING";
TEST_REPORTING_PROJECT_NAME = "TEST_REPORTING_PROJECT_NAME";
MAX_GIT_META_DATA_SIZE_IN_BYTES = 64 * 1024;
GIT_META_DATA_TRUNCATED = "...[TRUNCATED]";
CLI_STOP_TIMEOUT = 5e3;
WDIO_NAMING_PREFIX = "WebdriverIO-";
UPDATED_CLI_ENDPOINT = "sdk/v1/update_cli";
}
});
// src/logPatcher.ts
import Transport from "winston-transport";
var LOG_LEVELS, logPatcher, logPatcher_default;
var init_logPatcher = __esm({
"src/logPatcher.ts"() {
"use strict";
LOG_LEVELS = {
INFO: "INFO",
ERROR: "ERROR",
DEBUG: "DEBUG",
TRACE: "TRACE",
WARN: "WARN"
};
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);
};
};
logPatcher_default = logPatcher;
}
});
// src/cli/apiUtils.ts
var APIUtils;
var init_apiUtils = __esm({
"src/cli/apiUtils.ts"() {
"use strict";
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/fetchWrapper.ts
import { fetch as undiciFetch, ProxyAgent } from "undici";
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 url3 = new URL(request.url);
if (!noProxy.some((str) => url3.hostname.endsWith(str))) {
return undiciFetch(
request.url,
{ ...init, dispatcher: new ProxyAgent(proxyUrl) }
);
}
}
return fetch(input, init);
}
var ResponseError;
var init_fetchWrapper = __esm({
"src/fetchWrapper.ts"() {
"use strict";
ResponseError = class extends Error {
response;
constructor(message, res) {
super(message);
this.response = res;
}
};
}
});
// src/instrumentation/performance/constants.ts
var EVENTS, TESTHUB_EVENTS, AUTOMATE_EVENTS, A11Y_EVENTS, PERCY_EVENTS, O11Y_EVENTS, HOOK_EVENTS, TURBOSCALE_EVENTS, APP_AUTOMATE_EVENTS, DRIVER_EVENT, FRAMEWORK_EVENTS, CONFIG_EVENTS, AI_EVENTS, DISPATCHER_EVENTS;
var init_constants2 = __esm({
"src/instrumentation/performance/constants.ts"() {
"use strict";
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"
};
TESTHUB_EVENTS = {
START: `${EVENTS.SDK_TESTHUB}:start`,
STOP: `${EVENTS.SDK_TESTHUB}:stop`
};
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`
};
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`
};
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`
};
O11Y_EVENTS = {
SYNC: `${EVENTS.SDK_O11Y}:sync`,
TAKE_SCREENSHOT: `${EVENTS.SDK_O11Y}:driver-takeScreenShot`,
PRINT_BUILDLINK: `${EVENTS.SDK_O11Y}:print-buildlink`
};
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`
};
TURBOSCALE_EVENTS = {
HUB_MANAGEMENT: `${EVENTS.SDK_TURBOSCALE}:hub-management`,
PRINT_BUILDLINK: `${EVENTS.SDK_TURBOSCALE}:print-buildlink`
};
APP_AUTOMATE_EVENTS = {
APP_UPLOAD: `${EVENTS.SDK_APP_AUTOMATE}:app-upload`
};
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
};
FRAMEWORK_EVENTS = {
INIT: EVENTS.SDK_AUTOMATION_FRAMEWORK_INIT,
START: EVENTS.SDK_AUTOMATION_FRAMEWORK_START,
STOP: EVENTS.SDK_AUTOMATION_FRAMEWORK_STOP
};
CONFIG_EVENTS = {
ACCESSIBILITY: EVENTS.SDK_ACCESSIBILITY_CONFIG,
OBSERVABILITY: EVENTS.SDK_OBSERVABILITY_CONFIG
};
AI_EVENTS = {
SELF_HEAL_STEP: EVENTS.SDK_AI_SELF_HEAL_STEP,
SELF_HEAL_GET_RESULT: EVENTS.SDK_AI_SELF_HEAL_GET_RESULT
};
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 path from "node:path";
import fs from "node:fs";
import chalk from "chalk";
import logger from "@wdio/logger";
var log, BStackLogger;
var init_cliLogger = __esm({
"src/cli/cliLogger.ts"() {
"use strict";
init_constants();
init_util();
log = logger("@wdio/browserstack-service/cli");
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/frameworks/constants/testFrameworkConstants.ts
var TestFrameworkConstants;
var init_testFrameworkConstants = __esm({
"src/cli/frameworks/constants/testFrameworkConstants.ts"() {
"use strict";
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
import fs2 from "node:fs";
import fsp from "node:fs/promises";
import { platform, arch, homedir } from "node:os";
import path2 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";
var CLI_LOCK_TIMEOUT_MS, CLI_LOCK_POLL_MS, CLI_DOWNLOAD_TIMEOUT_MS, CLI_DOWNLOAD_TMP_PREFIX, CLI_DOWNLOAD_TMP_SUFFIX, CLIUtils;
var init_cliUtils = __esm({
"src/cli/cliUtils.ts"() {
"use strict";
init_fetchWrapper();
init_util();
init_performance_tester();
init_constants2();
init_cliLogger();
init_constants();
init_testFrameworkConstants();
init_apiUtils();
CLI_LOCK_TIMEOUT_MS = 5 * 60 * 1e3;
CLI_LOCK_POLL_MS = 1e3;
CLI_DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
CLI_DOWNLOAD_TMP_PREFIX = "downloaded_file_";
CLI_DOWNLOAD_TMP_SUFFIX = ".zip";
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) {
BStackLogger.debug("Configuring Cli path.");
const developmentBinaryPath = process.env.SDK_CLI_BIN_PATH || null;
if (!isNullOrEmpty(developmentBinaryPath)) {
BStackLogger.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
);
BStackLogger.debug(`Resolved binary path: ${finalBinaryPath}`);
return finalBinaryPath;
} catch (err) {
BStackLogger.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) {
BStackLogger.debug(
`Worker process detected, skipping CLI update. Using existing: ${existingCliPath}`
);
if (existingCliPath && fs2.existsSync(existingCliPath)) {
return existingCliPath;
}
BStackLogger.warn(
"Worker process has no existing CLI binary, attempting download as fallback."
);
}
PerformanceTester.start(EVENTS.SDK_CLI_CHECK_UPDATE);
BStackLogger.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"])) {
BStackLogger.debug(
`Need to update binary, current binary version: ${queryParams.cli_version}`
);
const browserStackBinaryUrl = process.env.BROWSERSTACK_BINARY_URL || null;
if (!isNullOrEmpty(browserStackBinaryUrl)) {
BStackLogger.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 = path2.join(writableDir, "cli");
if (!fs2.existsSync(cliDirPath)) {
createDir(cliDirPath);
}
return cliDirPath;
} catch (err) {
BStackLogger.error(
`Error in getting writable directory, writableDir=${util.format(err)}`
);
return "";
}
}
static getWritableDir() {
const writableDirOptions = [
process.env.BROWSERSTACK_FILES_DIR,
path2.join(homedir(), ".browserstack"),
path2.join("tmp", ".browserstack")
];
for (const path21 of writableDirOptions) {
if (isNullOrEmpty(path21)) {
continue;
}
try {
if (fs2.existsSync(path21)) {
BStackLogger.debug(`File ${path21} already exist`);
if (!isWritable(path21)) {
BStackLogger.debug(`Giving write permission to ${path21}`);
const success = setReadWriteAccess(path21);
if (!isTrue(success)) {
BStackLogger.warn(
`Unable to provide write permission to ${path21}`
);
}
}
} else {
BStackLogger.debug(`File does not exist: ${path21}`);
createDir(path21);
BStackLogger.debug(`Giving write permission to ${path21}`);
const success = setReadWriteAccess(path21);
if (!isTrue(success)) {
BStackLogger.warn(
`Unable to provide write permission to ${path21}`
);
}
}
return path21;
} catch (err) {
BStackLogger.error(
`Unable to get writable directory, exception ${util.format(err)}`
);
}
}
return null;
}
static getExistingCliPath(cliDir) {
try {
if (!fs2.existsSync(cliDir) || !fs2.statSync(cliDir).isDirectory()) {
return "";
}
const allBinaries = fs2.readdirSync(cliDir).map((file) => path2.join(cliDir, file)).filter(
(filePath) => fs2.statSync(filePath).isFile() && path2.basename(filePath).startsWith("binary-")
);
if (allBinaries.length > 0) {
const latestBinary = allBinaries.map((filePath) => ({
filePath,
mtime: fs2.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) {
BStackLogger.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();
BStackLogger.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 = path2.join(cliDir, "download.lock");
const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
const parseLockFile = () => {
try {
const content = fs2.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 = fs2.openSync(lockPath, "wx");
try {
fs2.writeFileSync(fd, `${process.pid}
${Date.now()}
`);
} catch {
}
return () => {
try {
fs2.closeSync(fd);
} catch {
}
try {
fs2.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) {
BStackLogger.warn(
`Stale CLI download lock detected (pid=${lockMeta.pid}, age=${lockAge}ms). Removing lock.`
);
try {
fs2.unlinkSync(lockPath);
} catch {
}
continue;
}
}
const existingBinary = _CLIUtils.getExistingCliPath(cliDir);
if (existingBinary && fs2.existsSync(existingBinary) && fs2.statSync(existingBinary).size > 0) {
BStackLogger.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 sleep2(pollMs);
continue;
}
throw e;
}
}
};
const cleanupTemporaryDownloads = (maxAgeMs = CLI_LOCK_TIMEOUT_MS) => {
try {
const now2 = Date.now();
for (const entry of fs2.readdirSync(cliDir)) {
if (!entry.startsWith(CLI_DOWNLOAD_TMP_PREFIX) || !entry.endsWith(CLI_DOWNLOAD_TMP_SUFFIX)) {
continue;
}
const filePath = path2.join(cliDir, entry);
let stats;
try {
stats = fs2.statSync(filePath);
} catch {
continue;
}
if (now2 - stats.mtimeMs < maxAgeMs) {
continue;
}
try {
fs2.unlinkSync(filePath);
} catch (err) {
BStackLogger.debug(
`Failed to delete temp CLI zip file ${filePath}: ${util.format(err)}`
);
}
}
} catch (err) {
BStackLogger.debug(
`Failed to scan temp CLI downloads in ${cliDir}: ${util.format(err)}`
);
}
};
PerformanceTester.start(EVENTS.SDK_CLI_DOWNLOAD);
BStackLogger.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 && fs2.existsSync(existingBinary) && fs2.statSync(existingBinary).size > 0) {
BStackLogger.debug(
`Binary already exists after acquiring lock: ${existingBinary}`
);
endDownload();
releaseLock();
return existingBinary;
}
cleanupTemporaryDownloads();
const zipFilePath = path2.join(
cliDir,
`${CLI_DOWNLOAD_TMP_PREFIX}${process.pid}_${Date.now()}${CLI_DOWNLOAD_TMP_SUFFIX}`
);
const downloadedFileStream = fs2.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) {
BStackLogger.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) {
BStackLogger.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));
BStackLogger.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 = fs2.createWriteStream(
path2.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(() => {
BStackLogger.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) {
BStackLogger.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
import { createObjectCsvWriter } from "csv-writer";
import fs3 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 path3 from "node:path";
import { arch as arch2, hostname, platform as platform2, type, version } from "node:os";
var PerformanceTester;
var init_performance_tester = __esm({
"src/instrumentation/performance/performance-tester.ts"() {
"use strict";
init_bstackLogger();
init_constants();
init_apiUtils();
init_cliUtils();
init_constants2();
init_fetchWrapper();
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 = path3.join(process.cwd(), "logs", this.jsonReportDirName);
static jsonReportFileName = `${this.jsonReportDirPath}/performance-report-${_PerformanceTester.getProcessId()}.json`;
static startMonitoring(csvName = "performance-report.csv") {
if (!fs3.existsSync(this.jsonReportDirPath)) {
fs3.mkdirSync(this.jsonReportDirPath, {