@wdio/browserstack-service
Version:
WebdriverIO service for better Browserstack integration
1,441 lines (1,424 loc) • 256 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
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;
};
// src/launcher.ts
import fs11 from "node:fs";
import { readFile } from "node:fs/promises";
import path12 from "node:path";
import { promisify as promisify2, format as format3 } from "node:util";
import { performance as performance3, PerformanceObserver as PerformanceObserver2 } from "node:perf_hooks";
import os5 from "node:os";
import { SevereServiceError } from "webdriverio";
import * as BrowserstackLocalLauncher from "browserstack-local";
// package.json
var package_default = {
name: "@wdio/browserstack-service",
version: "9.19.1",
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",
"@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",
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/yauzl": "^2.10.3",
"@wdio/globals": "workspace:*"
},
publishConfig: {
access: "public"
}
};
// src/constants.ts
var bstackServiceVersion = package_default.version;
var BROWSER_DESCRIPTION = [
"device",
"os",
"osVersion",
"os_version",
"browserName",
"browser",
"browserVersion",
"browser_version"
];
var VALID_APP_EXTENSION = [
".apk",
".aab",
".ipa"
];
var DEFAULT_OPTIONS = {
setSessionName: true,
setSessionStatus: true,
testObservability: true,
accessibility: false
};
var consoleHolder = Object.assign({}, console);
var DATA_ENDPOINT = "https://collector-observability.browserstack.com";
var APP_ALLY_ENDPOINT = "https://app-accessibility.browserstack.com/automate";
var APP_ALLY_ISSUES_ENDPOINT = "api/v1/issues";
var APP_ALLY_ISSUES_SUMMARY_ENDPOINT = "api/v1/issues-summary";
var DATA_EVENT_ENDPOINT = "api/v1/event";
var DATA_BATCH_ENDPOINT = "api/v1/batch";
var DATA_SCREENSHOT_ENDPOINT = "api/v1/screenshots";
var DATA_BATCH_SIZE = 1e3;
var DATA_BATCH_INTERVAL = 2e3;
var DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS = 5e3;
var DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS = 100;
var BSTACK_SERVICE_VERSION = bstackServiceVersion;
var NOT_ALLOWED_KEYS_IN_CAPS = ["includeTagsInTestingScope", "excludeTagsInTestingScope"];
var LOGS_FILE = "logs/bstack-wdio-service.log";
var UPLOAD_LOGS_ADDRESS = "https://upload-observability.browserstack.com";
var UPLOAD_LOGS_ENDPOINT = "client-logs/upload";
var PERCY_LOGS_FILE = "logs/percy.log";
var 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"
];
var CAPTURE_MODES = ["click", "auto", "screenshot", "manual", "testcase"];
var LOG_KIND_USAGE_MAP = {
"TEST_LOG": "log",
"TEST_SCREENSHOT": "screenshot",
"TEST_STEP": "step",
"HTTP": "http"
};
var FUNNEL_INSTRUMENTATION_URL = "https://api.browserstack.com/sdk/v1/event";
var EDS_URL = "https://eds.browserstack.com";
var SUPPORTED_BROWSERS_FOR_AI = ["chrome", "microsoftedge", "firefox"];
var TCG_URL = "https://tcg.browserstack.com";
var TCG_INFO = {
tcgRegion: "use",
tcgUrl: TCG_URL
};
var BROWSERSTACK_TESTHUB_JWT = "BROWSERSTACK_TESTHUB_JWT";
var BSTACK_TCG_AUTH_RESULT = "BSTACK_TCG_AUTH_RESULT";
var TESTOPS_SCREENSHOT_ENV = "BS_TESTOPS_ALLOW_SCREENSHOTS";
var BROWSERSTACK_TESTHUB_UUID = "BROWSERSTACK_TESTHUB_UUID";
var TEST_ANALYTICS_ID = "TEST_ANALYTICS_ID";
var PERF_MEASUREMENT_ENV = "BROWSERSTACK_O11Y_PERF_MEASUREMENT";
var RERUN_TESTS_ENV = "BROWSERSTACK_RERUN_TESTS";
var RERUN_ENV = "BROWSERSTACK_RERUN";
var TESTOPS_BUILD_COMPLETED_ENV = "BS_TESTOPS_BUILD_COMPLETED";
var BROWSERSTACK_PERCY = "BROWSERSTACK_PERCY";
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]";
// src/bstackLogger.ts
import path4 from "node:path";
import fs4 from "node:fs";
import chalk from "chalk";
import logger from "@wdio/logger";
// src/util.ts
import { hostname as hostname2, platform as platform2, type as type2, version as version2, arch as arch2 } from "node:os";
import crypto from "node:crypto";
import fs3 from "node:fs";
import zlib from "node:zlib";
import { format, promisify } from "node:util";
import path3 from "node:path";
import util2 from "node:util";
import gitRepoInfo from "git-repo-info";
import gitconfig from "gitconfiglocal";
import { FormData as FormData2 } 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 fs from "node:fs";
import fsPromise from "node:fs/promises";
import { performance, PerformanceObserver } from "node:perf_hooks";
import util from "node:util";
import worker from "node:worker_threads";
import path from "node:path";
import { arch, hostname, platform, type, version } from "node:os";
// 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 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);
}
// src/instrumentation/performance/performance-tester.ts
var PerformanceTester = class _PerformanceTester {
static _observer;
static _csvWriter;
static _events = [];
static _measuredEvents = [];
static started = false;
static details = {};
static eventsMap = {};
static browser;
static scenarioThatRan;
static jsonReportDirName = "performance-report";
static jsonReportDirPath = path.join(process.cwd(), "logs", this.jsonReportDirName);
static jsonReportFileName = `${this.jsonReportDirPath}/performance-report-${_PerformanceTester.getProcessId()}.json`;
static startMonitoring(csvName = "performance-report.csv") {
if (!fs.existsSync(this.jsonReportDirPath)) {
fs.mkdirSync(this.jsonReportDirPath, { recursive: true });
}
this._observer = new PerformanceObserver((list) => {
list.getEntries().filter((entry) => entry.entryType === "measure").forEach(
(entry) => {
let finalEntry = entry;
finalEntry = entry.toJSON();
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}: ${util.format(er)}`);
}
this._observer.disconnect();
if (!process.env[PERF_MEASUREMENT_ENV]) {
return;
}
await _PerformanceTester.sleep(2e3);
this.started = false;
this.generateCSV(this._events);
const content = this.generateReport(this._events);
const dir = path.join(process.cwd(), filename);
try {
await fsPromise.writeFile(dir, content);
BStackLogger.info(`Performance report is at ${path}`);
} catch (err) {
BStackLogger.error(`Error in writing html ${util.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);
}
_PerformanceTester.start(label);
if (this.details) {
this.details[label] = details;
}
try {
const returnVal = fn.apply(thisArg, args);
if (returnVal instanceof Promise) {
return new Promise((resolve, reject) => {
returnVal.then((v) => {
_PerformanceTester.end(label);
resolve(v);
}).catch((e) => {
_PerformanceTester.end(label, false, util.format(e));
reject(e);
});
});
}
_PerformanceTester.end(label);
return returnVal;
} catch (er) {
_PerformanceTester.end(label, false, util.format(er));
throw er;
}
}
static start(event) {
const finalEvent = event + "-start";
if (this.eventsMap[finalEvent]) {
return;
}
performance.mark(finalEvent);
this.eventsMap[finalEvent] = 1;
}
static end(event, success = true, failure, details = {}) {
performance.mark(event + "-end");
performance.measure(event, event + "-start", event + "-end");
this.details[event] = Object.assign({ success, failure: util.format(failure) }, Object.assign(Object.assign({
worker: _PerformanceTester.getProcessId(),
platform: _PerformanceTester.browser?.sessionId,
testName: _PerformanceTester.scenarioThatRan && _PerformanceTester.scenarioThatRan[_PerformanceTester.scenarioThatRan.length - 1]
}, details), this.details[event] || {}));
}
static getProcessId() {
return `${process.pid}-${worker.threadId}`;
}
static sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms));
static async uploadEventsData() {
try {
let measures = [];
if (await fsPromise.access(this.jsonReportDirPath).then(() => true).catch(() => false)) {
const files = (await fsPromise.readdir(this.jsonReportDirPath)).map((file) => path.resolve(this.jsonReportDirPath, file));
measures = (await Promise.all(files.map((file) => fsPromise.readFile(file, "utf-8")))).map((el) => `[${el.slice(0, -1)}]`).map((el) => JSON.parse(el)).flat();
}
if (this._measuredEvents.length > 0) {
measures = measures.concat(this._measuredEvents);
}
const date = /* @__PURE__ */ new Date();
const options = {
timeZone: "UTC",
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
fractionalSecondDigits: 3,
// To include microseconds
hour12: false
};
const formattedDate = new Intl.DateTimeFormat("en-GB", options).formatToParts(date).map(({ type: type3, value }) => type3 === "timeZoneName" ? "Z" : value).join("").replace(",", "T");
const payload = {
event_type: "sdk_events",
data: {
testhub_uuid: process.env.PERF_TESTHUB_UUID || process.env.SDK_RUN_ID,
created_day: formattedDate,
event_name: "SDKFeaturePerformance",
user_data: process.env.PERF_USER_NAME,
host_info: JSON.stringify({
hostname: hostname(),
platform: platform(),
type: type(),
version: version(),
arch: arch()
}),
event_json: { measures, sdkRunId: process.env.SDK_RUN_ID }
}
};
const result = await fetchWrap(`${EDS_URL}/send_sdk_events`, {
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify(payload)
});
BStackLogger.debug(`Successfully uploaded performance events ${util.format(await result.text())}`);
} catch (er) {
BStackLogger.debug(`Failed to upload performance events ${util.format(er)}`);
}
try {
if (await fsPromise.access(this.jsonReportDirPath).then(() => true, () => false)) {
const files = await fsPromise.readdir(this.jsonReportDirPath);
for (const file of files) {
await fsPromise.unlink(path.join(this.jsonReportDirPath, file));
}
}
} catch (er) {
BStackLogger.debug(`Failed to delete performance related files ${util.format(er)}`);
}
}
};
// 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"
};
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`
};
// src/crash-reporter.ts
var CrashReporter = class {
/* User test config for build run minus PII */
static userConfigForReporting = {};
/* User credentials used for reporting crashes in browserstack service */
static credentialsForCrashReportUpload = {};
static setCredentialsForCrashReportUpload(options, config) {
this.credentialsForCrashReportUpload = {
username: getObservabilityUser(options, config),
password: getObservabilityKey(options, config)
};
process.env.CREDENTIALS_FOR_CRASH_REPORTING = JSON.stringify(this.credentialsForCrashReportUpload);
}
static setConfigDetails(userConfig, capabilities, options) {
const configWithoutPII = this.filterPII(userConfig);
const filteredCapabilities = this.filterCapabilities(capabilities);
this.userConfigForReporting = {
framework: userConfig.framework,
services: configWithoutPII.services,
capabilities: filteredCapabilities,
env: {
"BROWSERSTACK_BUILD": process.env.BROWSERSTACK_BUILD,
"BROWSERSTACK_BUILD_NAME": process.env.BROWSERSTACK_BUILD_NAME,
"BUILD_TAG": process.env.BUILD_TAG
}
};
process.env.USER_CONFIG_FOR_REPORTING = JSON.stringify(this.userConfigForReporting);
this.setCredentialsForCrashReportUpload(options, userConfig);
}
static async uploadCrashReport(exception, stackTrace) {
try {
if (!this.credentialsForCrashReportUpload.username || !this.credentialsForCrashReportUpload.password) {
this.credentialsForCrashReportUpload = process.env.CREDENTIALS_FOR_CRASH_REPORTING !== void 0 ? JSON.parse(process.env.CREDENTIALS_FOR_CRASH_REPORTING) : this.credentialsForCrashReportUpload;
}
} catch (error) {
return BStackLogger.error(`[Crash_Report_Upload] Failed to parse user credentials while reporting crash due to ${error}`);
}
if (!this.credentialsForCrashReportUpload.username || !this.credentialsForCrashReportUpload.password) {
return BStackLogger.error("[Crash_Report_Upload] Failed to parse user credentials while reporting crash");
}
try {
if (Object.keys(this.userConfigForReporting).length === 0) {
this.userConfigForReporting = process.env.USER_CONFIG_FOR_REPORTING !== void 0 ? JSON.parse(process.env.USER_CONFIG_FOR_REPORTING) : {};
}
} catch (error) {
BStackLogger.error(`[Crash_Report_Upload] Failed to parse user config while reporting crash due to ${error}`);
this.userConfigForReporting = {};
}
const data = {
hashed_id: process.env[BROWSERSTACK_TESTHUB_UUID],
observability_version: {
frameworkName: "WebdriverIO-" + (this.userConfigForReporting.framework || "null"),
sdkVersion: BSTACK_SERVICE_VERSION
},
exception: {
error: exception.toString(),
stackTrace
},
config: this.userConfigForReporting
};
const url3 = `${DATA_ENDPOINT}/api/v1/analytics`;
const encodedAuth = Buffer.from(`${this.credentialsForCrashReportUpload.username}:${this.credentialsForCrashReportUpload.password}`, "utf8").toString("base64");
const headers = {
...DEFAULT_REQUEST_CONFIG.headers,
Authorization: `Basic ${encodedAuth}`
};
const response = await _fetch(url3, {
method: "POST",
body: JSON.stringify(data),
headers
});
if (response.ok) {
BStackLogger.debug(`[Crash_Report_Upload] Success response: ${JSON.stringify(await response.json())}`);
} else {
BStackLogger.error(`[Crash_Report_Upload] Failed due to ${response.body}`);
}
}
static recursivelyRedactKeysFromObject(obj, keys) {
if (!obj) {
return;
}
if (Array.isArray(obj)) {
obj.map((ele) => this.recursivelyRedactKeysFromObject(ele, keys));
} else {
for (const prop in obj) {
if (keys.includes(prop.toLowerCase())) {
obj[prop] = "[REDACTED]";
} else if (typeof obj[prop] === "object") {
this.recursivelyRedactKeysFromObject(obj[prop], keys);
}
}
}
}
static deletePIIKeysFromObject(obj) {
if (!obj) {
return;
}
["user", "username", "key", "accessKey"].forEach((key) => delete obj[key]);
}
static filterCapabilities(capabilities) {
const capsCopy = JSON.parse(JSON.stringify(capabilities));
this.recursivelyRedactKeysFromObject(capsCopy, ["extensions"]);
return capsCopy;
}
static filterPII(userConfig) {
const configWithoutPII = JSON.parse(JSON.stringify(userConfig));
this.deletePIIKeysFromObject(configWithoutPII);
const finalServices = [];
const initialServices = configWithoutPII.services;
delete configWithoutPII.services;
try {
for (const serviceArray of initialServices) {
if (Array.isArray(serviceArray) && serviceArray.length >= 2 && serviceArray[0] === "browserstack") {
for (let idx = 1; idx < serviceArray.length; idx++) {
this.deletePIIKeysFromObject(serviceArray[idx]);
if (serviceArray[idx]) {
this.deletePIIKeysFromObject(serviceArray[idx].testObservabilityOptions);
}
}
finalServices.push(serviceArray);
break;
}
}
} catch (err) {
BStackLogger.error(`Error in parsing user config PII with error ${err ? err.stack || err : err}`);
return configWithoutPII;
}
configWithoutPII.services = finalServices;
return configWithoutPII;
}
};
// src/testOps/featureStats.ts
var FeatureStats = class _FeatureStats {
triggeredCount = 0;
sentCount = 0;
failedCount = 0;
groups = {};
mark(status, groupId) {
switch (status) {
case "triggered":
this.triggered(groupId);
break;
case "success":
case "sent":
this.sent(groupId);
break;
case "failed":
this.failed(groupId);
break;
default:
BStackLogger.debug("Request to mark usage for unknown status - " + status);
break;
}
}
triggered(groupId) {
this.triggeredCount += 1;
if (groupId) {
this.createGroup(groupId).triggered();
}
}
sent(groupId) {
this.sentCount += 1;
if (groupId) {
this.createGroup(groupId).sent();
}
}
failed(groupId) {
this.failedCount += 1;
if (groupId) {
this.createGroup(groupId).failed();
}
}
success(groupId) {
this.sent(groupId);
}
createGroup(groupId) {
if (!this.groups[groupId]) {
this.groups[groupId] = new _FeatureStats();
}
return this.groups[groupId];
}
getTriggeredCount() {
return this.triggeredCount;
}
getSentCount() {
return this.sentCount;
}
getFailedCount() {
return this.failedCount;
}
getUsageForGroup(groupId) {
return this.groups[groupId] || new _FeatureStats();
}
getOverview() {
return { triggeredCount: this.triggeredCount, sentCount: this.sentCount, failedCount: this.failedCount };
}
getGroups() {
return this.groups;
}
add(featureStats) {
this.triggeredCount += featureStats.getTriggeredCount();
this.sentCount += featureStats.getSentCount();
this.failedCount += featureStats.getFailedCount();
Object.entries(featureStats.getGroups()).forEach(([groupId, group]) => {
this.createGroup(groupId).add(group);
});
}
// omitGroups: true/false -> Include groups or not
// onlyGroups: true/false -> data includes only groups
// nestedGroups: true/false -> groups will be nested in groups if true
toJSON(config = {}) {
const overviewData = !config.onlyGroups ? {
triggeredCount: this.triggeredCount,
sentCount: this.sentCount,
failedCount: this.failedCount
} : {};
const groupsData = {};
if (!config.omitGroups) {
Object.entries(this.groups).forEach(([groupId, group2]) => {
groupsData[groupId] = group2.toJSON();
});
}
const group = config.nestedGroups ? { groups: groupsData } : groupsData;
return {
...overviewData,
...group
};
}
static fromJSON(json) {
const stats = new _FeatureStats();
if (!json || isObjectEmpty(json)) {
return stats;
}
stats.triggeredCount = json.triggeredCount;
stats.sentCount = json.sentCount;
stats.failedCount = json.failedCount;
if (!json.groups) {
return stats;
}
Object.entries(json.groups).forEach(([groupId, group]) => {
stats.groups[groupId] = _FeatureStats.fromJSON(group);
});
return stats;
}
};
var featureStats_default = FeatureStats;
// src/testOps/featureUsage.ts
var FeatureUsage = class {
isTriggered;
status;
error;
constructor(isTriggered) {
if (isTriggered !== void 0) {
this.isTriggered = isTriggered;
}
}
getTriggered() {
return this.isTriggered;
}
setTriggered(triggered) {
this.isTriggered = triggered;
}
setStatus(status) {
this.status = status;
}
setError(error) {
this.error = error;
}
triggered() {
this.isTriggered = true;
}
failed(e) {
this.status = "failed";
this.error = getErrorString(e);
}
success() {
this.status = "success";
}
getStatus() {
return this.status;
}
getError() {
return this.error;
}
toJSON() {
return {
isTriggered: this.isTriggered,
status: this.status,
error: this.error
};
}
};
var featureUsage_default = FeatureUsage;
// src/testOps/testOpsConfig.ts
var TestOpsConfig = class _TestOpsConfig {
constructor(enabled = true, manuallySet = false) {
this.enabled = enabled;
this.manuallySet = manuallySet;
_TestOpsConfig._instance = this;
}
static _instance;
buildStopped = false;
buildHashedId;
static getInstance(...args) {
if (!this._instance) {
this._instance = new _TestOpsConfig(...args);
}
return this._instance;
}
};
var testOpsConfig_default = TestOpsConfig;
// src/testOps/usageStats.ts
var UsageStats = class _UsageStats {
static instance;
testStartedStats;
testFinishedStats;
hookStartedStats;
hookFinishedStats;
cbtSessionStats;
logStats;
launchBuildUsage;
stopBuildUsage;
static getInstance() {
if (!_UsageStats.instance) {
_UsageStats.instance = new _UsageStats();
}
return _UsageStats.instance;
}
constructor() {
this.testStartedStats = new featureStats_default();
this.testFinishedStats = new featureStats_default();
this.hookStartedStats = new featureStats_default();
this.hookFinishedStats = new featureStats_default();
this.cbtSessionStats = new featureStats_default();
this.logStats = new featureStats_default();
this.launchBuildUsage = new featureUsage_default();
this.stopBuildUsage = new featureUsage_default();
}
add(usageStats) {
this.testStartedStats.add(usageStats.testStartedStats);
this.testFinishedStats.add(usageStats.testFinishedStats);
this.hookStartedStats.add(usageStats.hookStartedStats);
this.hookFinishedStats.add(usageStats.hookFinishedStats);
this.cbtSessionStats.add(usageStats.cbtSessionStats);
this.logStats.add(usageStats.logStats);
}
getFormattedData(workersData) {
this.addDataFromWorkers(workersData);
const testOpsConfig = testOpsConfig_default.getInstance();
const usage = {
enabled: testOpsConfig.enabled,
manuallySet: testOpsConfig.manuallySet,
buildHashedId: testOpsConfig.buildHashedId
};
if (!usage.enabled) {
return usage;
}
try {
usage.events = this.getEventsData();
} catch (e) {
BStackLogger.debug("exception in getFormattedData: " + e);
}
return usage;
}
addDataFromWorkers(workersData) {
workersData.map((workerData) => {
try {
const usageStatsForWorker = _UsageStats.fromJSON(workerData.usageStats);
this.add(usageStatsForWorker);
} catch (e) {
BStackLogger.debug("Exception in adding workerData: " + e);
}
});
}
getEventsData() {
return {
buildEvents: {
started: this.launchBuildUsage.toJSON(),
finished: this.stopBuildUsage.toJSON()
},
testEvents: {
started: this.testStartedStats.toJSON(),
finished: this.testFinishedStats.toJSON({ omitGroups: true }),
...this.testFinishedStats.toJSON({ onlyGroups: true })
},
hookEvents: {
started: this.hookStartedStats.toJSON(),
finished: this.hookFinishedStats.toJSON({ omitGroups: true }),
...this.hookFinishedStats.toJSON({ onlyGroups: true })
},
logEvents: this.logStats.toJSON(),
cbtSessionEvents: this.cbtSessionStats.toJSON()
};
}
getDataToSave() {
return {
testEvents: {
started: this.testStartedStats.toJSON(),
finished: this.testFinishedStats.toJSON({ nestedGroups: true })
},
hookEvents: {
started: this.hookStartedStats.toJSON(),
finished: this.hookFinishedStats.toJSON({ nestedGroups: true })
},
logEvents: this.logStats.toJSON({ nestedGroups: true }),
cbtSessionEvents: this.cbtSessionStats.toJSON()
};
}
static fromJSON(data) {
const usageStats = new _UsageStats();
usageStats.testStartedStats = featureStats_default.fromJSON(data.testEvents.started);
usageStats.testFinishedStats = featureStats_default.fromJSON(data.testEvents.finished);
usageStats.hookStartedStats = featureStats_default.fromJSON(data.hookEvents.started);
usageStats.hookFinishedStats = featureStats_default.fromJSON(data.hookEvents.finished);
usageStats.logStats = featureStats_default.fromJSON(data.logEvents);
usageStats.cbtSessionStats = featureStats_default.fromJSON(data.cbtSessionStats);
return usageStats;
}
};
var usageStats_default = UsageStats;
// src/scripts/accessibility-scripts.ts
import path2 from "node:path";
import fs2 from "node:fs";
import os from "node:os";
var AccessibilityScripts = class _AccessibilityScripts {
static instance = null;
performScan = null;
getResults = null;
getResultsSummary = null;
saveTestResults = null;
commandsToWrap = null;
ChromeExtension = {};
browserstackFolderPath = "";
commandsPath = "";
// don't allow to create instances from it other than through `checkAndGetInstance`
constructor() {
this.browserstackFolderPath = this.getWritableDir();
this.commandsPath = path2.join(this.browserstackFolderPath, "commands.json");
}
static checkAndGetInstance() {
if (!_AccessibilityScripts.instance) {
_AccessibilityScripts.instance = new _AccessibilityScripts();
_AccessibilityScripts.instance.readFromExistingFile();
}
return _AccessibilityScripts.instance;
}
/* eslint-disable @typescript-eslint/no-unused-vars */
getWritableDir() {
const orderedPaths = [
path2.join(os.homedir(), ".browserstack"),
process.cwd(),
os.tmpdir()
];
for (const orderedPath of orderedPaths) {
try {
if (fs2.existsSync(orderedPath)) {
fs2.accessSync(orderedPath);
return orderedPath;
}
fs2.mkdirSync(orderedPath, { recursive: true });
return orderedPath;
} catch (error) {
}
}
return "";
}
readFromExistingFile() {
try {
if (fs2.existsSync(this.commandsPath)) {
const data = fs2.readFileSync(this.commandsPath, "utf8");
if (data) {
this.update(JSON.parse(data));
}
}
} catch {
}
}
update(data) {
if (data.scripts) {
this.performScan = data.scripts.scan;
this.getResults = data.scripts.getResults;
this.getResultsSummary = data.scripts.getResultsSummary;
this.saveTestResults = data.scripts.saveResults;
}
if (data.commands && data.commands.length) {
this.commandsToWrap = data.commands;
}
if (data.nonBStackInfraA11yChromeOptions) {
this.ChromeExtension = data.nonBStackInfraA11yChromeOptions;
}
}
store() {
if (!fs2.existsSync(this.browserstackFolderPath)) {
fs2.mkdirSync(this.browserstackFolderPath);
}
fs2.writeFileSync(this.commandsPath, JSON.stringify({
commands: this.commandsToWrap,
scripts: {
scan: this.performScan,
getResults: this.getResults,
getResultsSummary: this.getResultsSummary,
saveResults: this.saveTestResults
},
nonBStackInfraA11yChromeOptions: this.ChromeExtension
}));
}
};
var accessibility_scripts_default = AccessibilityScripts.checkAndGetInstance();
// src/util.ts
var pGitconfig = promisify(gitconfig);
var DEFAULT_REQUEST_CONFIG = {
headers: {
"Content-Type": "application/json",
"X-BSTACK-OBS": "true"
}
};
var COLORS = {
error: "red",
warn: "yellow",
info: "cyanBright",
debug: "green",
trace: "cyan",
progress: "magenta"
};
function getBrowserDescription(cap) {
cap = cap || {};
if (cap["bstack:options"]) {
cap = { ...cap, ...cap["bstack:options"] };
}
return BROWSER_DESCRIPTION.map((k) => cap[k]).filter(Boolean).join(" ");
}
function getBrowserCapabilities(browser, caps, browserName) {
if (!browser.isMultiremote) {
return { ...browser.capabilities, ...caps };
}
const multiCaps = caps;
const globalCap = browserName && browser.getInstance(browserName) ? browser.getInstance(browserName).capabilities : {};
const cap = browserName && multiCaps[browserName] ? multiCaps[browserName].capabilities : {};
return { ...globalCap, ...cap };
}
function isBrowserstackCapability(cap) {
return Boolean(
cap && cap["bstack:options"] && // return false if the only cap in bstack:options is wdioService,
// as that is added by the service and not present in user passed caps
!(Object.keys(cap["bstack:options"]).length === 1 && cap["bstack:options"].wdioService)
);
}
function getParentSuiteName(fullTitle, testSuiteTitle) {
const fullTitleWords = fullTitle.split(" ");
const testSuiteTitleWords = testSuiteTitle.split(" ");
const shortestLength = Math.min(fullTitleWords.length, testSuiteTitleWords.length);
let c = 0;
let parentSuiteName = "";
while (c < shortestLength && fullTitleWords[c] === testSuiteTitleWords[c]) {
parentSuiteName += fullTitleWords[c++] + " ";
}
return parentSuiteName.trim();
}
function processError(error, fn, args) {
BStackLogger.error(`Error in executing ${fn.name} with args ${args}: ${error}`);
let argsString;
try {
argsString = JSON.stringify(args);
} catch {
argsString = util2.inspect(args, { depth: 2 });
}
CrashReporter.uploadCrashReport(`Error in executing ${fn.name} with args ${argsString} : ${error}`, error && error.stack || "unknown error");
}
function o11yErrorHandler(fn) {
return function(...args) {
try {
let functionToHandle = fn;
if (process.env[PERF_MEASUREMENT_ENV]) {
functionToHandle = performance2.timerify(functionToHandle);
}
const result = functionToHandle(...args);
if (result instanceof Promise) {
return result.catch((error) => processError(error, fn, args));
}
return result;
} catch (error) {
processError(error, fn, args);
}
};
}
async function nodeRequest(requestType, apiEndpoint, options, apiUrl, timeout = 12e4) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await _fetch(`${apiUrl}/${apiEndpoint}`, {
method: requestType,
signal: controller.signal,
...options
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
BStackLogger.debug(`Error in firing request ${apiUrl}/${apiEndpoint}: ${format(error)}`);
const isLogUpload = apiEndpoint === UPLOAD_LOGS_ENDPOINT;
if (error && error.response) {
const errorMessageJson = error.response.body ? JSON.parse(error.response.body.toString()) : null;
const errorMessage = errorMessageJson ? errorMessageJson.message : null;
if (errorMessage) {
const message = `${errorMessage} - ${error.stack}`;
if (isLogUpload) {
BStackLogger.debug(message);
} else {
BStackLogger.error(message);
}
}
if (isLogUpload) {
return;
}
throw error;
} else {
if (isLogUpload) {
BStackLogger.debug(`Failed to fire api request due to ${error} - ${error.stack}`);
return;
}
BStackLogger.debug(`Failed to fire api request due to ${error} - ${error.stack}`);
throw error;
}
}
}
function o11yClassErrorHandler(errorClass) {
const prototype = errorClass.prototype;
if (Object.getOwnPropertyNames(prototype).length < 2) {
return errorClass;
}
Object.getOwnPropertyNames(prototype).forEach((methodName) => {
const method = prototype[methodName];
if (typeof method === "function" && methodName !== "constructor" && methodName !== "commandWrapper") {
Object.defineProperty(prototype, methodName, {
writable: true,
value: function(...args) {
try {
const result = (process.env[PERF_MEASUREMENT_ENV] ? performance2.timerify(method) : method).call(this, ...args);
if (result instanceof Promise) {
return result.catch((error) => processError(error, method, args));
}
return result;
} catch (err) {
processError(err, method, args);
}
}
});
}
});
return errorClass;
}
var processTestObservabilityResponse = (response) => {
if (!response.observability) {
handleErrorForObservability(null);
return;
}
if (!response.observability.success) {
handleErrorForObservability(response.observability);
return;
}
process.env[BROWSERSTACK_OBSERVABILITY] = "true";
if (response.observability.options.allow_screenshots) {
process.env[TESTOPS_SCREENSHOT_ENV] = response.observability.options.allow_screenshots.toString();
}
};
var jsonifyAccessibilityArray = (dataArray, keyName, valueName) => {
const result = {};
dataArray.forEach((element) => {
result[element[keyName]] = element[valueName];
});
return result;
};
var processAccessibilityResponse = (response, options) => {
if (!response.accessibility) {
if (options.accessibility === true) {
handleErrorForAccessibility(null);
}
return;
}
if (!response.accessibility.success) {
handleErrorForAccessibility(response.accessibility);
return;
}
if (response.accessibility.options) {
const { accessibilityToken, pollingTimeout, scannerVersion } = jsonifyAccessibilityArray(response.accessibility.options.capabilities, "name", "value");
const result = jsonifyAccessibilityArray(response.accessibility.options.capabilities, "name", "value");
const scriptsJson = {
"scripts": jsonifyAccessibilityArray(response.accessibility.options.scripts, "name", "command"),
"commands": response.accessibility.options.commandsToWrap.commands,
"nonBStackInfraA11yChromeOptions": result["goog:chromeOptions"]
};
if (scannerVersion) {
process.env.BSTACK_A11Y_SCANNER_VERSION = scannerVersion;
BStackLogger.debug(`Accessibility scannerVersion ${scannerVersion}`);
}
if (accessibilityToken) {
process.env.BSTACK_A11Y_JWT = accessibilityToken;
process.env[BROWSERSTACK_ACCESSIBILITY] = "true";
}
if (pollingTimeout) {
process.env.BSTACK_A11Y_POLLING_TIMEOUT = pollingTimeout;
}
if (scriptsJson) {
accessibility_scripts_default.update(scriptsJson);
accessibility_scripts_default.store();
}
}
};
var processLaunchBuildResponse = (response, options) => {
if (options.testObservability) {
processTestObservabilityResponse(response);
}
processAccessibilityResponse(response, options);
};
var launchTestSession = PerformanceTester.measureWrapper(TESTHUB_EVENTS.START, o11yErrorHandler(async function launchTestSession2(options, config, bsConfig, bStackConfig, accessibilityAutomation) {
const launchBuildUsage = usageStats_default.getInstance().launchBuildUsage;
launchBuildUsage.triggered();
const data = {
format: "json",
project_name: getObservabilityProject(options, bsConfig.projectName),
name: getObservabilityBuild(options, bsConfig.buildName),
build_identifier: bsConfig.buildIdentifier,
started_at: (/* @__PURE__ */ new Date()).toISOString(),
tags: getObservabilityBuildTags(options, bsConfig.buildTag),
host_info: {
hostname: hostname2(),
platform: platform2(),
type: type2(),
version: version2(),
arch: arch2()
},
ci_info: getCiInfo(),
build_run_identifier: process.env.BROWSERSTACK_BUILD_RUN_IDENTIFIER,
failed_tests_rerun: process.env[RERUN_ENV] || false,
version_control: await getGitMetaData(),
accessibility: {
settings: options.accessibilityOptions
},
browserstackAutomation: shouldAddServiceVersion(config, options.testObservability),
framework_details: {
frameworkName: "WebdriverIO-" + config.framework,
frameworkVersion: bsConfig.bstackServiceVersion,
sdkVersion: bsConfig.bstackServiceVersion,
language: "ECMAScript",
testFramework: {
name: "WebdriverIO",
version: bsConfig.bstackServiceVersion
}
},
product_map: getProductMapForBuildStartCall(bStackConfig, accessibilityAutomation),
config: {}
};
if (accessibilityAutomation && (isTurboScale(options) || data.browserstackAutomation === false)) {
data.accessibility.settings ??= {};
data.accessibility.settings["includeEncodedExtension"] = true;
}
try {
if (Object.keys(CrashReporter.userConfigForReporting).length === 0) {
CrashReporter.userConfigForReporting = process.env.USER_CONFIG_FOR_REPORTING !== void 0 ? JSON.parse(process.env.USER_CONFIG_FOR_REPORTING) : {};
}
} catch (error) {
return BStackLogger.error(`[Crash_Report_Upload] Failed to parse user config while sending build start event due to ${error}`);
}
data.config = CrashReporter.userConfigForReporting;
try {
const url3 = `${DATA_ENDPOINT}/api/v2/builds`;
const encodedAuth = Buffer.from(`${getObservabilityUser(options, config)}:${getObservabilityKey(options, config)}`, "utf8").toString("base64");
const headers = {
...DEFAULT_REQUEST_CONFIG.headers,
Authorization: `Basic ${encodedAuth}`
};
const response = await _fetch(url3, {
method: "POST",
headers,
body: JSON.stringify(data)
});
const jsonResponse = await response.json();
delete data?.accessibility?.settings?.includeEncodedExtension;
BStackLogger.debug(`[Start_Build] Success response: ${JSON.stringify(jsonResponse)}`);
process.env[TESTOPS_BUILD_COMPLETED_ENV] = "true";
if (jsonResponse.jwt) {
process.env[BROWSERSTACK_TESTHUB_JWT] = jsonResponse.jwt;
}
if (jsonResponse.build_hashed_id) {
process.env[BROWSERSTACK_TESTHUB_UUID] = jsonResponse.build_hashed_id;
testOpsConfig_default.getInstance().buildHashedId = jsonResponse.build_hashed_id;
BStackLogger.info(`Testhub started with id: ${testOpsConfig_default.getInstance()?.buildHashedId}`);
}
processLaunchBuildResponse(jsonResponse, options);
launchBuildUsage.success();
return jsonResponse;
} catch (error) {
BStackLogger.debug(`TestHub build start failed: ${format(error)}`);
if (!error.success) {
launchBuildUsage.failed(error);
logBuildError(error);
return null;
}
}
}));
var validateCapsWithAppA11y = (platformMeta) => {
if (platformMeta?.platform_name && String(platformMeta?.platform_name).toLowerCase() === "android" && (platformMeta?.platform_version && parseInt(platformMeta?.platform_version?.toString()) < 11)) {
BStackLogger.warn("App Accessibility Automation tests are supported on OS version 11 and above for Android devices.");
return false;
}
return true;
};
var validateCapsWithA11y = (deviceName, platformMeta, chromeOptions) => {
try {
if (deviceName) {
BStackLogger.warn("Accessibility Automation will run only on Desktop browsers.");
return false;
}
if (platformMeta?.browser_name?.toLowerCase() !== "chrome") {
BStackLogger.warn("Accessibility Automation will run only on Chrome browsers.");
return false;
}
const browserVersion = platformMeta?.browser_version;
if (!isUndefined(browserVersion) && !(browserVersion === "latest" || parseFloat(browserVersion + "") > 94)) {
BStackLogger.warn("Accessibility Automation will run only on Chrome browser version greater than