@wdio/browserstack-service 9.27.0 → 9.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/bstackLogger.d.ts +1 -0
- package/build/bstackLogger.d.ts.map +1 -1
- package/build/cleanup.js +215 -72
- package/build/cli/cliUtils.d.ts +3 -2
- package/build/cli/cliUtils.d.ts.map +1 -1
- package/build/cli/frameworks/wdioMochaTestFramework.d.ts.map +1 -1
- package/build/cli/grpcClient.d.ts.map +1 -1
- package/build/cli/index.d.ts +5 -0
- package/build/cli/index.d.ts.map +1 -1
- package/build/config.d.ts +1 -1
- package/build/config.d.ts.map +1 -1
- package/build/constants.d.ts +4 -0
- package/build/constants.d.ts.map +1 -1
- package/build/crash-reporter.d.ts +1 -1
- package/build/crash-reporter.d.ts.map +1 -1
- package/build/index.js +410 -130
- package/build/instrumentation/performance/performance-tester.d.ts.map +1 -1
- package/build/launcher.d.ts +1 -0
- package/build/launcher.d.ts.map +1 -1
- package/build/service.d.ts.map +1 -1
- package/build/testHub/utils.d.ts +2 -2
- package/build/testHub/utils.d.ts.map +1 -1
- package/build/types.d.ts +8 -0
- package/build/types.d.ts.map +1 -1
- package/build/util.d.ts +1 -0
- package/build/util.d.ts.map +1 -1
- package/package.json +8 -9
package/build/index.js
CHANGED
|
@@ -23,7 +23,7 @@ var init_package = __esm({
|
|
|
23
23
|
"package.json"() {
|
|
24
24
|
package_default = {
|
|
25
25
|
name: "@wdio/browserstack-service",
|
|
26
|
-
version: "9.
|
|
26
|
+
version: "9.27.1",
|
|
27
27
|
description: "WebdriverIO service for better Browserstack integration",
|
|
28
28
|
author: "Adam Bjerstedt <abjerstedt@gmail.com>",
|
|
29
29
|
homepage: "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-browserstack-service",
|
|
@@ -70,12 +70,11 @@ var init_package = __esm({
|
|
|
70
70
|
"browserstack-local": "^1.5.1",
|
|
71
71
|
chalk: "^5.3.0",
|
|
72
72
|
"csv-writer": "^1.6.0",
|
|
73
|
-
"formdata-node": "5.0.1",
|
|
74
73
|
"git-repo-info": "^2.1.1",
|
|
75
74
|
gitconfiglocal: "^2.1.0",
|
|
76
75
|
glob: "^11.0.0",
|
|
77
|
-
tar: "^7.5.
|
|
78
|
-
undici: "^6.
|
|
76
|
+
tar: "^7.5.11",
|
|
77
|
+
undici: "^6.24.0",
|
|
79
78
|
uuid: "^11.1.0",
|
|
80
79
|
webdriverio: "workspace:*",
|
|
81
80
|
"winston-transport": "^4.5.0",
|
|
@@ -98,7 +97,7 @@ var init_package = __esm({
|
|
|
98
97
|
});
|
|
99
98
|
|
|
100
99
|
// src/constants.ts
|
|
101
|
-
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;
|
|
100
|
+
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, BROWSERSTACK_TEST_PLAN_ID, 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, BINARY_BUSY_ERROR_CODES, MAX_SPAWN_RETRIES, SPAWN_RETRY_DELAY_MS, WDIO_NAMING_PREFIX, UPDATED_CLI_ENDPOINT;
|
|
102
101
|
var init_constants = __esm({
|
|
103
102
|
"src/constants.ts"() {
|
|
104
103
|
"use strict";
|
|
@@ -122,8 +121,7 @@ var init_constants = __esm({
|
|
|
122
121
|
DEFAULT_OPTIONS = {
|
|
123
122
|
setSessionName: true,
|
|
124
123
|
setSessionStatus: true,
|
|
125
|
-
testObservability: true
|
|
126
|
-
accessibility: false
|
|
124
|
+
testObservability: true
|
|
127
125
|
};
|
|
128
126
|
consoleHolder = Object.assign({}, console);
|
|
129
127
|
APP_ALLY_ISSUES_ENDPOINT = "api/v1/issues";
|
|
@@ -136,7 +134,8 @@ var init_constants = __esm({
|
|
|
136
134
|
DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS = 5e3;
|
|
137
135
|
DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS = 100;
|
|
138
136
|
BSTACK_SERVICE_VERSION = bstackServiceVersion;
|
|
139
|
-
NOT_ALLOWED_KEYS_IN_CAPS = ["includeTagsInTestingScope", "excludeTagsInTestingScope"];
|
|
137
|
+
NOT_ALLOWED_KEYS_IN_CAPS = ["includeTagsInTestingScope", "excludeTagsInTestingScope", "testManagementOptions"];
|
|
138
|
+
BROWSERSTACK_TEST_PLAN_ID = "BROWSERSTACK_TEST_PLAN_ID";
|
|
140
139
|
LOGS_FILE = "logs/bstack-wdio-service.log";
|
|
141
140
|
CLI_DEBUG_LOGS_FILE = "log/sdk-cli-debug.log";
|
|
142
141
|
UPLOAD_LOGS_ENDPOINT = "client-logs/upload";
|
|
@@ -182,6 +181,9 @@ var init_constants = __esm({
|
|
|
182
181
|
MAX_GIT_META_DATA_SIZE_IN_BYTES = 64 * 1024;
|
|
183
182
|
GIT_META_DATA_TRUNCATED = "...[TRUNCATED]";
|
|
184
183
|
CLI_STOP_TIMEOUT = 5e3;
|
|
184
|
+
BINARY_BUSY_ERROR_CODES = ["ETXTBSY", "EBUSY"];
|
|
185
|
+
MAX_SPAWN_RETRIES = 3;
|
|
186
|
+
SPAWN_RETRY_DELAY_MS = 1e3;
|
|
185
187
|
WDIO_NAMING_PREFIX = "WebdriverIO-";
|
|
186
188
|
UPDATED_CLI_ENDPOINT = "sdk/v1/update_cli";
|
|
187
189
|
}
|
|
@@ -635,6 +637,7 @@ var init_cliUtils = __esm({
|
|
|
635
637
|
modifiedOpts.browserStackLocalOptions = modifiedOpts.opts;
|
|
636
638
|
delete modifiedOpts.opts;
|
|
637
639
|
}
|
|
640
|
+
delete modifiedOpts.testManagementOptions;
|
|
638
641
|
modifiedOpts.testContextOptions = {
|
|
639
642
|
skipSessionName: isFalse(modifiedOpts.setSessionName),
|
|
640
643
|
skipSessionStatus: isFalse(modifiedOpts.setSessionStatus),
|
|
@@ -653,6 +656,8 @@ var init_cliUtils = __esm({
|
|
|
653
656
|
options.testObservability
|
|
654
657
|
);
|
|
655
658
|
const observabilityOptions = options.testObservabilityOptions || {};
|
|
659
|
+
const testManagementOptions = options.testManagementOptions || {};
|
|
660
|
+
const testPlanId = typeof testManagementOptions.testPlanId === "string" ? testManagementOptions.testPlanId.trim() : "";
|
|
656
661
|
const binconfig = {
|
|
657
662
|
userName: observabilityOptions.user || config.user,
|
|
658
663
|
accessKey: observabilityOptions.key || config.key,
|
|
@@ -664,26 +669,26 @@ var init_cliUtils = __esm({
|
|
|
664
669
|
binconfig.buildName = observabilityOptions.buildName || binconfig.buildName;
|
|
665
670
|
binconfig.projectName = observabilityOptions.projectName || binconfig.projectName;
|
|
666
671
|
binconfig.buildTag = this.getObservabilityBuildTags(observabilityOptions, buildTag) || [];
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
672
|
+
if (testPlanId.length > 0) {
|
|
673
|
+
binconfig.testManagementOptions = {
|
|
674
|
+
testPlanId
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
const caps = Array.isArray(capabilities) ? capabilities : [capabilities];
|
|
678
|
+
for (const cap of caps) {
|
|
679
|
+
const platform4 = {};
|
|
680
|
+
const capability = cap;
|
|
681
|
+
Object.keys(capability).filter((key) => key !== "bstack:options").forEach((key) => {
|
|
682
|
+
platform4[key] = capability[key];
|
|
683
|
+
});
|
|
684
|
+
if (capability["bstack:options"]) {
|
|
685
|
+
Object.keys(
|
|
686
|
+
capability["bstack:options"]
|
|
687
|
+
).forEach((key) => {
|
|
688
|
+
platform4[key] = capability["bstack:options"][key];
|
|
677
689
|
});
|
|
678
|
-
if (capability["bstack:options"]) {
|
|
679
|
-
Object.keys(
|
|
680
|
-
capability["bstack:options"]
|
|
681
|
-
).forEach((key) => {
|
|
682
|
-
platform4[key] = capability["bstack:options"][key];
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
binconfig.platforms.push(platform4);
|
|
686
690
|
}
|
|
691
|
+
binconfig.platforms.push(platform4);
|
|
687
692
|
}
|
|
688
693
|
return JSON.stringify(binconfig);
|
|
689
694
|
}
|
|
@@ -742,9 +747,20 @@ var init_cliUtils = __esm({
|
|
|
742
747
|
sdk_language: this.getSdkLanguage()
|
|
743
748
|
};
|
|
744
749
|
if (!isNullOrEmpty(existingCliPath)) {
|
|
745
|
-
|
|
750
|
+
if (this.isBinaryBusy(existingCliPath)) {
|
|
751
|
+
BStackLogger.warn(`Existing binary is currently in use, skipping update: ${existingCliPath}`);
|
|
752
|
+
PerformanceTester.end(EVENTS.SDK_CLI_CHECK_UPDATE);
|
|
753
|
+
return existingCliPath;
|
|
754
|
+
}
|
|
755
|
+
const version3 = await this.runShellCommand(
|
|
746
756
|
`${existingCliPath} version`
|
|
747
757
|
);
|
|
758
|
+
if (version3.toLowerCase().includes("text file busy")) {
|
|
759
|
+
BStackLogger.warn(`Binary busy during version check, skipping update: ${existingCliPath}`);
|
|
760
|
+
PerformanceTester.end(EVENTS.SDK_CLI_CHECK_UPDATE);
|
|
761
|
+
return existingCliPath;
|
|
762
|
+
}
|
|
763
|
+
queryParams.cli_version = version3;
|
|
748
764
|
}
|
|
749
765
|
const response = await this.requestToUpdateCLI(queryParams, config);
|
|
750
766
|
if (nestedKeyValue(response, ["updated_cli_version"])) {
|
|
@@ -860,6 +876,29 @@ var init_cliUtils = __esm({
|
|
|
860
876
|
return "";
|
|
861
877
|
}
|
|
862
878
|
}
|
|
879
|
+
static isBinaryBusy(binaryPath) {
|
|
880
|
+
if (isNullOrEmpty(binaryPath)) {
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
if (platform() === "darwin") {
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
if (!fs2.existsSync(binaryPath)) {
|
|
887
|
+
return false;
|
|
888
|
+
}
|
|
889
|
+
try {
|
|
890
|
+
const fd = fs2.openSync(binaryPath, "r+");
|
|
891
|
+
fs2.closeSync(fd);
|
|
892
|
+
return false;
|
|
893
|
+
} catch (err) {
|
|
894
|
+
if (BINARY_BUSY_ERROR_CODES.includes(err.code)) {
|
|
895
|
+
BStackLogger.debug(`Binary is busy: ${binaryPath}`);
|
|
896
|
+
return true;
|
|
897
|
+
}
|
|
898
|
+
BStackLogger.debug(`Error checking if binary is busy: ${err.message}`);
|
|
899
|
+
return false;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
863
902
|
static requestToUpdateCLI = async (queryParams, config) => {
|
|
864
903
|
const params = new URLSearchParams(queryParams);
|
|
865
904
|
const requestInit = {
|
|
@@ -980,8 +1019,11 @@ ${Date.now()}
|
|
|
980
1019
|
const cleanupTemporaryDownloads = (maxAgeMs = CLI_LOCK_TIMEOUT_MS) => {
|
|
981
1020
|
try {
|
|
982
1021
|
const now2 = Date.now();
|
|
1022
|
+
const tmpExtractRe = /\.tmp\.\d+$/;
|
|
983
1023
|
for (const entry of fs2.readdirSync(cliDir)) {
|
|
984
|
-
|
|
1024
|
+
const isDownloadZip = entry.startsWith(CLI_DOWNLOAD_TMP_PREFIX) && entry.endsWith(CLI_DOWNLOAD_TMP_SUFFIX);
|
|
1025
|
+
const isExtractTmp = tmpExtractRe.test(entry);
|
|
1026
|
+
if (!isDownloadZip && !isExtractTmp) {
|
|
985
1027
|
continue;
|
|
986
1028
|
}
|
|
987
1029
|
const filePath = path2.join(cliDir, entry);
|
|
@@ -998,13 +1040,13 @@ ${Date.now()}
|
|
|
998
1040
|
fs2.unlinkSync(filePath);
|
|
999
1041
|
} catch (err) {
|
|
1000
1042
|
BStackLogger.debug(
|
|
1001
|
-
`Failed to delete temp CLI
|
|
1043
|
+
`Failed to delete temp CLI file ${filePath}: ${util.format(err)}`
|
|
1002
1044
|
);
|
|
1003
1045
|
}
|
|
1004
1046
|
}
|
|
1005
1047
|
} catch (err) {
|
|
1006
1048
|
BStackLogger.debug(
|
|
1007
|
-
`Failed to scan temp CLI
|
|
1049
|
+
`Failed to scan temp CLI files in ${cliDir}: ${util.format(err)}`
|
|
1008
1050
|
);
|
|
1009
1051
|
}
|
|
1010
1052
|
};
|
|
@@ -1050,7 +1092,6 @@ ${Date.now()}
|
|
|
1050
1092
|
);
|
|
1051
1093
|
const downloadedFileStream = fs2.createWriteStream(zipFilePath);
|
|
1052
1094
|
return new Promise((resolve, reject) => {
|
|
1053
|
-
const binaryName = null;
|
|
1054
1095
|
const processDownload = async () => {
|
|
1055
1096
|
const abortController = new AbortController();
|
|
1056
1097
|
const timeout = setTimeout(
|
|
@@ -1084,7 +1125,6 @@ ${Date.now()}
|
|
|
1084
1125
|
nodeStream.pipe(downloadedFileStream);
|
|
1085
1126
|
_CLIUtils.downloadFileStream(
|
|
1086
1127
|
downloadedFileStream,
|
|
1087
|
-
binaryName,
|
|
1088
1128
|
zipFilePath,
|
|
1089
1129
|
cliDir,
|
|
1090
1130
|
(result) => {
|
|
@@ -1118,40 +1158,104 @@ ${Date.now()}
|
|
|
1118
1158
|
return null;
|
|
1119
1159
|
}
|
|
1120
1160
|
};
|
|
1121
|
-
static downloadFileStream(downloadedFileStream,
|
|
1161
|
+
static downloadFileStream(downloadedFileStream, zipFilePath, cliDir, resolve, reject) {
|
|
1122
1162
|
downloadedFileStream.on("close", async function() {
|
|
1123
1163
|
const yauzlOpenPromise = promisify(yauzl.open);
|
|
1124
1164
|
try {
|
|
1125
1165
|
const zipfile = await yauzlOpenPromise(zipFilePath, {
|
|
1126
1166
|
lazyEntries: true
|
|
1127
1167
|
});
|
|
1168
|
+
let resolvedBinaryPath = null;
|
|
1128
1169
|
zipfile.readEntry();
|
|
1129
1170
|
zipfile.on("entry", async (entry) => {
|
|
1130
|
-
if (!binaryName) {
|
|
1131
|
-
binaryName = entry.fileName;
|
|
1132
|
-
}
|
|
1133
1171
|
if (/\/$/.test(entry.fileName)) {
|
|
1134
1172
|
zipfile.readEntry();
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
const candidatePath = path2.join(cliDir, entry.fileName);
|
|
1176
|
+
const resolvedCandidate = path2.resolve(candidatePath);
|
|
1177
|
+
const resolvedDir = path2.resolve(cliDir) + path2.sep;
|
|
1178
|
+
if (!resolvedCandidate.startsWith(resolvedDir)) {
|
|
1179
|
+
zipfile.close();
|
|
1180
|
+
reject(new Error(`Zip-slip detected: entry "${entry.fileName}" resolves outside ${cliDir}`));
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
const isBinaryEntry = path2.basename(entry.fileName).startsWith("binary-");
|
|
1184
|
+
if (!isBinaryEntry) {
|
|
1185
|
+
const directStream = fs2.createWriteStream(candidatePath);
|
|
1186
|
+
directStream.on("error", (writeErr) => {
|
|
1187
|
+
zipfile.close();
|
|
1188
|
+
reject(writeErr);
|
|
1189
|
+
});
|
|
1190
|
+
const openReadStreamPromise2 = promisify(
|
|
1140
1191
|
zipfile.openReadStream
|
|
1141
1192
|
).bind(zipfile);
|
|
1142
1193
|
try {
|
|
1143
|
-
const readStream = await
|
|
1194
|
+
const readStream = await openReadStreamPromise2(entry);
|
|
1144
1195
|
readStream.on("end", function() {
|
|
1145
|
-
|
|
1146
|
-
zipfile.readEntry();
|
|
1196
|
+
directStream.end();
|
|
1197
|
+
directStream.on("close", () => zipfile.readEntry());
|
|
1147
1198
|
});
|
|
1148
|
-
readStream.pipe(
|
|
1199
|
+
readStream.pipe(directStream);
|
|
1149
1200
|
} catch (zipErr) {
|
|
1201
|
+
zipfile.close();
|
|
1150
1202
|
reject(zipErr);
|
|
1151
1203
|
}
|
|
1152
|
-
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
const finalPath = candidatePath;
|
|
1207
|
+
const tempPath = path2.join(cliDir, `${entry.fileName}.tmp.${process.pid}`);
|
|
1208
|
+
const writeStream = fs2.createWriteStream(tempPath);
|
|
1209
|
+
let writeStreamErrored = false;
|
|
1210
|
+
writeStream.on("error", (writeErr) => {
|
|
1211
|
+
writeStreamErrored = true;
|
|
1212
|
+
fsp.unlink(tempPath).catch(() => {
|
|
1213
|
+
});
|
|
1214
|
+
zipfile.close();
|
|
1215
|
+
reject(writeErr);
|
|
1216
|
+
});
|
|
1217
|
+
writeStream.on("close", async () => {
|
|
1218
|
+
if (writeStreamErrored) {
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
try {
|
|
1222
|
+
await fsp.chmod(tempPath, "0755");
|
|
1223
|
+
try {
|
|
1224
|
+
await fsp.rename(tempPath, finalPath);
|
|
1225
|
+
} catch (renameErr) {
|
|
1226
|
+
if (renameErr.code !== "EXDEV") {
|
|
1227
|
+
throw renameErr;
|
|
1228
|
+
}
|
|
1229
|
+
BStackLogger.warn(`Atomic rename failed (cross-device), falling back to copy: ${renameErr.message}`);
|
|
1230
|
+
await fsp.copyFile(tempPath, finalPath);
|
|
1231
|
+
await fsp.unlink(tempPath).catch(() => {
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
if (!resolvedBinaryPath) {
|
|
1235
|
+
resolvedBinaryPath = finalPath;
|
|
1236
|
+
}
|
|
1237
|
+
zipfile.readEntry();
|
|
1238
|
+
} catch (err) {
|
|
1239
|
+
await fsp.unlink(tempPath).catch(() => {
|
|
1240
|
+
});
|
|
1153
1241
|
zipfile.close();
|
|
1242
|
+
reject(err);
|
|
1154
1243
|
}
|
|
1244
|
+
});
|
|
1245
|
+
const openReadStreamPromise = promisify(
|
|
1246
|
+
zipfile.openReadStream
|
|
1247
|
+
).bind(zipfile);
|
|
1248
|
+
try {
|
|
1249
|
+
const readStream = await openReadStreamPromise(entry);
|
|
1250
|
+
readStream.on("end", function() {
|
|
1251
|
+
writeStream.end();
|
|
1252
|
+
});
|
|
1253
|
+
readStream.pipe(writeStream);
|
|
1254
|
+
} catch (zipErr) {
|
|
1255
|
+
fsp.unlink(tempPath).catch(() => {
|
|
1256
|
+
});
|
|
1257
|
+
zipfile.close();
|
|
1258
|
+
reject(zipErr);
|
|
1155
1259
|
}
|
|
1156
1260
|
});
|
|
1157
1261
|
zipfile.on("error", (zipErr) => {
|
|
@@ -1159,16 +1263,15 @@ ${Date.now()}
|
|
|
1159
1263
|
});
|
|
1160
1264
|
zipfile.once("end", () => {
|
|
1161
1265
|
fsp.unlink(zipFilePath).catch(() => {
|
|
1162
|
-
BStackLogger.warn(
|
|
1163
|
-
`Failed to delete zip file: ${zipFilePath}`
|
|
1164
|
-
);
|
|
1165
|
-
});
|
|
1166
|
-
fsp.chmod(`${cliDir}/${binaryName}`, "0755").then(() => {
|
|
1167
|
-
resolve(`${cliDir}/${binaryName}`);
|
|
1168
|
-
}).catch((err) => {
|
|
1169
|
-
reject(err);
|
|
1266
|
+
BStackLogger.warn(`Failed to delete zip file: ${zipFilePath}`);
|
|
1170
1267
|
});
|
|
1268
|
+
if (!resolvedBinaryPath) {
|
|
1269
|
+
zipfile.close();
|
|
1270
|
+
reject(new Error("No binary-* entry found in zip; cannot complete CLI binary extraction"));
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1171
1273
|
zipfile.close();
|
|
1274
|
+
resolve(resolvedBinaryPath);
|
|
1172
1275
|
});
|
|
1173
1276
|
} catch (err) {
|
|
1174
1277
|
reject(err);
|
|
@@ -1525,6 +1628,7 @@ var init_performance_tester = __esm({
|
|
|
1525
1628
|
static end(event, success = true, failure, details = {}) {
|
|
1526
1629
|
performance.mark(event + "-end");
|
|
1527
1630
|
performance.measure(event, event + "-start", event + "-end");
|
|
1631
|
+
delete this.eventsMap[event + "-start"];
|
|
1528
1632
|
this.details[event] = Object.assign({ success, failure: util2.format(failure) }, Object.assign(Object.assign({
|
|
1529
1633
|
clientWorkerId: _PerformanceTester.getClientWorkerId(),
|
|
1530
1634
|
worker: _PerformanceTester.getProcessId(),
|
|
@@ -2131,8 +2235,17 @@ var init_crash_reporter = __esm({
|
|
|
2131
2235
|
for (const prop in obj) {
|
|
2132
2236
|
if (keys.includes(prop.toLowerCase())) {
|
|
2133
2237
|
obj[prop] = "[REDACTED]";
|
|
2134
|
-
} else if (typeof obj[prop] === "object") {
|
|
2238
|
+
} else if (typeof obj[prop] === "object" && obj[prop] !== null) {
|
|
2135
2239
|
this.recursivelyRedactKeysFromObject(obj[prop], keys);
|
|
2240
|
+
} else if (typeof obj[prop] === "string") {
|
|
2241
|
+
try {
|
|
2242
|
+
const parsed = JSON.parse(obj[prop]);
|
|
2243
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
2244
|
+
this.recursivelyRedactKeysFromObject(parsed, keys);
|
|
2245
|
+
obj[prop] = JSON.stringify(parsed);
|
|
2246
|
+
}
|
|
2247
|
+
} catch {
|
|
2248
|
+
}
|
|
2136
2249
|
}
|
|
2137
2250
|
}
|
|
2138
2251
|
}
|
|
@@ -2608,10 +2721,8 @@ import path5 from "node:path";
|
|
|
2608
2721
|
import util3 from "node:util";
|
|
2609
2722
|
import gitRepoInfo from "git-repo-info";
|
|
2610
2723
|
import gitconfig from "gitconfiglocal";
|
|
2611
|
-
import { FormData as FormData2 } from "formdata-node";
|
|
2612
2724
|
import { performance as performance2 } from "node:perf_hooks";
|
|
2613
2725
|
import { create } from "tar";
|
|
2614
|
-
import { fileFromPath } from "formdata-node/file-from-path";
|
|
2615
2726
|
function getBrowserDescription(cap) {
|
|
2616
2727
|
cap = cap || {};
|
|
2617
2728
|
if (cap["bstack:options"]) {
|
|
@@ -3181,6 +3292,25 @@ function getObservabilityBuildTags(options, bstackBuildTag) {
|
|
|
3181
3292
|
}
|
|
3182
3293
|
return [];
|
|
3183
3294
|
}
|
|
3295
|
+
function getTestPlanId(options) {
|
|
3296
|
+
if (process.env[BROWSERSTACK_TEST_PLAN_ID]) {
|
|
3297
|
+
return process.env[BROWSERSTACK_TEST_PLAN_ID];
|
|
3298
|
+
}
|
|
3299
|
+
const CLI_ARG = "--browserstack.testManagementOptions.testPlanId";
|
|
3300
|
+
const argIndex = process.argv.indexOf(CLI_ARG);
|
|
3301
|
+
if (argIndex !== -1 && process.argv[argIndex + 1]) {
|
|
3302
|
+
return process.argv[argIndex + 1];
|
|
3303
|
+
}
|
|
3304
|
+
const argWithEquals = process.argv.find((arg) => arg.startsWith(`${CLI_ARG}=`));
|
|
3305
|
+
if (argWithEquals) {
|
|
3306
|
+
return argWithEquals.split("=")[1];
|
|
3307
|
+
}
|
|
3308
|
+
const testPlanId = options.testManagementOptions?.testPlanId;
|
|
3309
|
+
if (typeof testPlanId === "string" && testPlanId.trim().length > 0) {
|
|
3310
|
+
return testPlanId.trim();
|
|
3311
|
+
}
|
|
3312
|
+
return void 0;
|
|
3313
|
+
}
|
|
3184
3314
|
function getBrowserStackUser(config) {
|
|
3185
3315
|
if (process.env.BROWSERSTACK_USERNAME) {
|
|
3186
3316
|
return process.env.BROWSERSTACK_USERNAME;
|
|
@@ -3226,8 +3356,14 @@ function getFailureObject(error) {
|
|
|
3226
3356
|
};
|
|
3227
3357
|
}
|
|
3228
3358
|
async function uploadLogs(user, key, clientBuildUuid) {
|
|
3359
|
+
const eventName = EVENTS.SDK_UPLOAD_LOGS;
|
|
3360
|
+
let success = true;
|
|
3361
|
+
let failure;
|
|
3362
|
+
PerformanceTester.start(eventName);
|
|
3229
3363
|
try {
|
|
3230
3364
|
if (!user || !key) {
|
|
3365
|
+
success = false;
|
|
3366
|
+
failure = "skipped: missing_credentials";
|
|
3231
3367
|
BStackLogger2.debug("Uploading logs failed due to no credentials");
|
|
3232
3368
|
return;
|
|
3233
3369
|
}
|
|
@@ -3239,10 +3375,20 @@ async function uploadLogs(user, key, clientBuildUuid) {
|
|
|
3239
3375
|
CLI_DEBUG_LOGS_FILE
|
|
3240
3376
|
].filter((f) => fs6.existsSync(f));
|
|
3241
3377
|
const copiedFileNames = [];
|
|
3378
|
+
const archiveAddFailures = [];
|
|
3242
3379
|
for (const f of filesToArchive) {
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3380
|
+
try {
|
|
3381
|
+
const dest = path5.join(tmpDir, path5.basename(f));
|
|
3382
|
+
fs6.copyFileSync(f, dest);
|
|
3383
|
+
copiedFileNames.push(path5.basename(f));
|
|
3384
|
+
} catch (copyErr) {
|
|
3385
|
+
const msg = copyErr?.message || String(copyErr);
|
|
3386
|
+
archiveAddFailures.push(`${path5.basename(f)}: ${msg}`);
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
if (archiveAddFailures.length > 0 && failure === void 0) {
|
|
3390
|
+
success = false;
|
|
3391
|
+
failure = `archive_add_failed [${archiveAddFailures.length}]: ${archiveAddFailures.join("; ")}`.substring(0, 300);
|
|
3246
3392
|
}
|
|
3247
3393
|
await create(
|
|
3248
3394
|
{
|
|
@@ -3261,8 +3407,8 @@ async function uploadLogs(user, key, clientBuildUuid) {
|
|
|
3261
3407
|
dest.on("finish", resolve);
|
|
3262
3408
|
dest.on("error", reject);
|
|
3263
3409
|
});
|
|
3264
|
-
const formData = new
|
|
3265
|
-
const file = await
|
|
3410
|
+
const formData = new FormData();
|
|
3411
|
+
const file = await fs6.openAsBlob(tarGzPath, { type: "application/x-gzip" });
|
|
3266
3412
|
formData.append("data", file, "logs.tar.gz");
|
|
3267
3413
|
formData.append("clientBuildUuid", clientBuildUuid);
|
|
3268
3414
|
const auth = Buffer.from(`${user}:${key}`).toString("base64");
|
|
@@ -3289,10 +3435,21 @@ async function uploadLogs(user, key, clientBuildUuid) {
|
|
|
3289
3435
|
if (fs6.existsSync(CLI_DEBUG_LOGS_FILE)) {
|
|
3290
3436
|
fs6.unlinkSync(CLI_DEBUG_LOGS_FILE);
|
|
3291
3437
|
}
|
|
3438
|
+
if (!response) {
|
|
3439
|
+
success = false;
|
|
3440
|
+
failure = "upload_no_response";
|
|
3441
|
+
} else if (response.status && response.status !== "success") {
|
|
3442
|
+
success = false;
|
|
3443
|
+
failure = `upload_status: ${response.status}${response.message ? " - " + String(response.message).slice(0, 200) : ""}`;
|
|
3444
|
+
}
|
|
3292
3445
|
return response;
|
|
3293
3446
|
} catch (error) {
|
|
3447
|
+
success = false;
|
|
3448
|
+
failure = `uploadLogs exception: ${getErrorString(error)}`;
|
|
3294
3449
|
BStackLogger2.error(`Error while uploading logs: ${getErrorString(error)}`);
|
|
3295
3450
|
return null;
|
|
3451
|
+
} finally {
|
|
3452
|
+
PerformanceTester.end(eventName, success, failure);
|
|
3296
3453
|
}
|
|
3297
3454
|
}
|
|
3298
3455
|
function truncateString(field, truncateSizeInBytes) {
|
|
@@ -3689,7 +3846,10 @@ var init_util = __esm({
|
|
|
3689
3846
|
},
|
|
3690
3847
|
product_map: getProductMapForBuildStartCall(bStackConfig, accessibilityAutomation),
|
|
3691
3848
|
config: {},
|
|
3692
|
-
test_orchestration: OrchestrationUtils.getInstance(config)?.getBuildStartData() || {}
|
|
3849
|
+
test_orchestration: OrchestrationUtils.getInstance(config)?.getBuildStartData() || {},
|
|
3850
|
+
test_management: {
|
|
3851
|
+
test_plan_id: getTestPlanId(options)
|
|
3852
|
+
}
|
|
3693
3853
|
};
|
|
3694
3854
|
if (accessibilityAutomation && (isTurboScale(options) || data.browserstackAutomation === false)) {
|
|
3695
3855
|
data.accessibility.settings ??= {};
|
|
@@ -3718,6 +3878,7 @@ var init_util = __esm({
|
|
|
3718
3878
|
const jsonResponse = await response.json();
|
|
3719
3879
|
delete data?.accessibility?.settings?.includeEncodedExtension;
|
|
3720
3880
|
BStackLogger2.debug(`[Start_Build] Success response: ${JSON.stringify(jsonResponse)}`);
|
|
3881
|
+
BStackLogger2.debug(`Test Plan Id sent in request: ${getTestPlanId(options)}`);
|
|
3721
3882
|
process.env[TESTOPS_BUILD_COMPLETED_ENV] = "true";
|
|
3722
3883
|
if (jsonResponse.jwt) {
|
|
3723
3884
|
process.env[BROWSERSTACK_TESTHUB_JWT] = jsonResponse.jwt;
|
|
@@ -4113,14 +4274,18 @@ var init_bstackLogger = __esm({
|
|
|
4113
4274
|
static logFilePath = path6.join(process.cwd(), LOGS_FILE);
|
|
4114
4275
|
static logFolderPath = path6.join(process.cwd(), "logs");
|
|
4115
4276
|
static logFileStream;
|
|
4277
|
+
static redactCredentials(logMessage) {
|
|
4278
|
+
return logMessage.replace(/(["']?(?:username|userName|accesskey|accessKey|user|key)["']?\s*[:=]\s*["']?)([^"'\s,}]+)/gi, "$1").replace(/([?&](?:username|userName|access_key|accesskey|accessKey|user|key)=)([^&#\s]+)/gi, "$1");
|
|
4279
|
+
}
|
|
4116
4280
|
static logToFile(logMessage, logLevel) {
|
|
4117
4281
|
try {
|
|
4282
|
+
const redactedMessage = this.redactCredentials(logMessage);
|
|
4118
4283
|
if (!this.logFileStream) {
|
|
4119
4284
|
this.ensureLogsFolder();
|
|
4120
4285
|
this.logFileStream = fs7.createWriteStream(this.logFilePath, { flags: "a" });
|
|
4121
4286
|
}
|
|
4122
4287
|
if (this.logFileStream && this.logFileStream.writable) {
|
|
4123
|
-
this.logFileStream.write(this.formatLog(
|
|
4288
|
+
this.logFileStream.write(this.formatLog(redactedMessage, logLevel));
|
|
4124
4289
|
}
|
|
4125
4290
|
} catch (error) {
|
|
4126
4291
|
log2.debug(`Failed to log to file. Error ${error}`);
|
|
@@ -4131,28 +4296,33 @@ var init_bstackLogger = __esm({
|
|
|
4131
4296
|
`;
|
|
4132
4297
|
}
|
|
4133
4298
|
static info(message) {
|
|
4134
|
-
this.
|
|
4135
|
-
|
|
4299
|
+
const redactedMessage = this.redactCredentials(message);
|
|
4300
|
+
this.logToFile(redactedMessage, "info");
|
|
4301
|
+
log2.info(redactedMessage);
|
|
4136
4302
|
}
|
|
4137
4303
|
static error(message) {
|
|
4138
|
-
this.
|
|
4139
|
-
|
|
4304
|
+
const redactedMessage = this.redactCredentials(message);
|
|
4305
|
+
this.logToFile(redactedMessage, "error");
|
|
4306
|
+
log2.error(redactedMessage);
|
|
4140
4307
|
}
|
|
4141
4308
|
static debug(message, param) {
|
|
4142
|
-
this.
|
|
4309
|
+
const redactedMessage = this.redactCredentials(message);
|
|
4310
|
+
this.logToFile(redactedMessage, "debug");
|
|
4143
4311
|
if (param) {
|
|
4144
|
-
log2.debug(
|
|
4312
|
+
log2.debug(redactedMessage, param);
|
|
4145
4313
|
} else {
|
|
4146
|
-
log2.debug(
|
|
4314
|
+
log2.debug(redactedMessage);
|
|
4147
4315
|
}
|
|
4148
4316
|
}
|
|
4149
4317
|
static warn(message) {
|
|
4150
|
-
this.
|
|
4151
|
-
|
|
4318
|
+
const redactedMessage = this.redactCredentials(message);
|
|
4319
|
+
this.logToFile(redactedMessage, "warn");
|
|
4320
|
+
log2.warn(redactedMessage);
|
|
4152
4321
|
}
|
|
4153
4322
|
static trace(message) {
|
|
4154
|
-
this.
|
|
4155
|
-
|
|
4323
|
+
const redactedMessage = this.redactCredentials(message);
|
|
4324
|
+
this.logToFile(redactedMessage, "trace");
|
|
4325
|
+
log2.trace(redactedMessage);
|
|
4156
4326
|
}
|
|
4157
4327
|
static clearLogger() {
|
|
4158
4328
|
if (this.logFileStream) {
|
|
@@ -4183,13 +4353,14 @@ var init_utils = __esm({
|
|
|
4183
4353
|
init_bstackLogger();
|
|
4184
4354
|
init_util();
|
|
4185
4355
|
getProductMap = (config) => {
|
|
4186
|
-
|
|
4187
|
-
observability
|
|
4188
|
-
accessibility
|
|
4189
|
-
percy
|
|
4190
|
-
automate
|
|
4191
|
-
app_automate
|
|
4192
|
-
|
|
4356
|
+
const entries = [
|
|
4357
|
+
["observability", config.testObservability.enabled],
|
|
4358
|
+
["accessibility", config.accessibility],
|
|
4359
|
+
["percy", config.percy],
|
|
4360
|
+
["automate", config.automate],
|
|
4361
|
+
["app_automate", config.appAutomate]
|
|
4362
|
+
];
|
|
4363
|
+
return Object.fromEntries(entries.filter(([, v]) => v !== null));
|
|
4193
4364
|
};
|
|
4194
4365
|
shouldProcessEventForTesthub = (eventType) => {
|
|
4195
4366
|
if (isTrue(process.env[BROWSERSTACK_OBSERVABILITY])) {
|
|
@@ -4237,13 +4408,14 @@ var init_utils = __esm({
|
|
|
4237
4408
|
}
|
|
4238
4409
|
};
|
|
4239
4410
|
getProductMapForBuildStartCall = (config, accessibilityAutomation) => {
|
|
4240
|
-
|
|
4241
|
-
observability
|
|
4242
|
-
accessibility
|
|
4243
|
-
percy
|
|
4244
|
-
automate
|
|
4245
|
-
app_automate
|
|
4246
|
-
|
|
4411
|
+
const entries = [
|
|
4412
|
+
["observability", config.testObservability.enabled],
|
|
4413
|
+
["accessibility", accessibilityAutomation],
|
|
4414
|
+
["percy", config.percy],
|
|
4415
|
+
["automate", config.automate],
|
|
4416
|
+
["app_automate", config.appAutomate]
|
|
4417
|
+
];
|
|
4418
|
+
return Object.fromEntries(entries.filter(([, v]) => v !== null));
|
|
4247
4419
|
};
|
|
4248
4420
|
}
|
|
4249
4421
|
});
|
|
@@ -4341,7 +4513,7 @@ import {
|
|
|
4341
4513
|
DriverInitRequestConstructor,
|
|
4342
4514
|
FetchDriverExecuteParamsEventRequestConstructor
|
|
4343
4515
|
} from "@browserstack/wdio-browserstack-service";
|
|
4344
|
-
var GrpcClient;
|
|
4516
|
+
var GRPC_MESSAGE_LIMIT, GrpcClient;
|
|
4345
4517
|
var init_grpcClient = __esm({
|
|
4346
4518
|
"src/cli/grpcClient.ts"() {
|
|
4347
4519
|
"use strict";
|
|
@@ -4349,6 +4521,7 @@ var init_grpcClient = __esm({
|
|
|
4349
4521
|
init_performance_tester();
|
|
4350
4522
|
init_constants2();
|
|
4351
4523
|
init_cliLogger();
|
|
4524
|
+
GRPC_MESSAGE_LIMIT = 20 * 1024 * 1024;
|
|
4352
4525
|
GrpcClient = class _GrpcClient {
|
|
4353
4526
|
static #instance = null;
|
|
4354
4527
|
binSessionId;
|
|
@@ -4410,17 +4583,20 @@ var init_grpcClient = __esm({
|
|
|
4410
4583
|
if (!listenAddress) {
|
|
4411
4584
|
throw new Error("Unable to determine gRPC server listen address");
|
|
4412
4585
|
}
|
|
4586
|
+
const channelOptions = {
|
|
4587
|
+
"grpc.keepalive_time_ms": 1e4,
|
|
4588
|
+
"grpc.max_send_message_length": GRPC_MESSAGE_LIMIT,
|
|
4589
|
+
"grpc.max_receive_message_length": GRPC_MESSAGE_LIMIT
|
|
4590
|
+
};
|
|
4413
4591
|
this.channel = new grpcChannel(
|
|
4414
4592
|
listenAddress,
|
|
4415
4593
|
grpcCredentials.createInsecure(),
|
|
4416
|
-
|
|
4417
|
-
"grpc.keepalive_time_ms": 1e4
|
|
4418
|
-
}
|
|
4594
|
+
channelOptions
|
|
4419
4595
|
);
|
|
4420
4596
|
this.client = new SDKClient(
|
|
4421
4597
|
listenAddress,
|
|
4422
4598
|
grpcCredentials.createInsecure(),
|
|
4423
|
-
|
|
4599
|
+
channelOptions
|
|
4424
4600
|
);
|
|
4425
4601
|
this.logger.info(`Connected to gRPC server at ${listenAddress}`);
|
|
4426
4602
|
}
|
|
@@ -5660,12 +5836,12 @@ var init_automateModule = __esm({
|
|
|
5660
5836
|
async onAfterTest(args) {
|
|
5661
5837
|
this.logger.debug("onAfterTest: inside automate module after test hook!");
|
|
5662
5838
|
const instace = args.instance;
|
|
5663
|
-
const { error, passed } = args.result;
|
|
5839
|
+
const { error, passed, skipped } = args.result;
|
|
5664
5840
|
const _failReasons = [];
|
|
5665
|
-
if (!passed) {
|
|
5841
|
+
if (!passed && !skipped) {
|
|
5666
5842
|
_failReasons.push(error && error.message || "Unknown Error");
|
|
5667
5843
|
}
|
|
5668
|
-
const status = passed ? "passed" : "failed";
|
|
5844
|
+
const status = passed || skipped ? "passed" : "failed";
|
|
5669
5845
|
const reason = _failReasons.length > 0 ? _failReasons.join("\n") : void 0;
|
|
5670
5846
|
const autoInstance = AutomationFramework.getTrackedInstance();
|
|
5671
5847
|
const sessionId = AutomationFramework.getState(autoInstance, AutomationFrameworkConstants.KEY_FRAMEWORK_SESSION_ID);
|
|
@@ -6063,13 +6239,17 @@ var init_wdioMochaTestFramework = __esm({
|
|
|
6063
6239
|
}
|
|
6064
6240
|
loadTestResult(instance, args) {
|
|
6065
6241
|
const results = args.result;
|
|
6066
|
-
const { error, passed } = results;
|
|
6242
|
+
const { error, passed, skipped } = results;
|
|
6067
6243
|
let result = "passed";
|
|
6068
6244
|
let failure = null;
|
|
6069
6245
|
let failureReason = null;
|
|
6070
6246
|
let failureType = null;
|
|
6071
6247
|
if (!passed) {
|
|
6072
|
-
|
|
6248
|
+
if (skipped) {
|
|
6249
|
+
result = "skipped";
|
|
6250
|
+
} else {
|
|
6251
|
+
result = error && error.message && error.message.includes("sync skip; aborting execution") ? "ignore" : "failed";
|
|
6252
|
+
}
|
|
6073
6253
|
if (error && result !== "skipped") {
|
|
6074
6254
|
failure = [{ backtrace: [removeAnsiColors(error.message), removeAnsiColors(error.stack || "")] }];
|
|
6075
6255
|
failureReason = removeAnsiColors(error.message);
|
|
@@ -9049,6 +9229,7 @@ var init_cli = __esm({
|
|
|
9049
9229
|
init_accessibilityModule();
|
|
9050
9230
|
init_util();
|
|
9051
9231
|
init_observabilityModule();
|
|
9232
|
+
init_crash_reporter();
|
|
9052
9233
|
init_percyModule();
|
|
9053
9234
|
init_apiUtils();
|
|
9054
9235
|
BrowserstackCLI = class _BrowserstackCLI {
|
|
@@ -9125,7 +9306,9 @@ var init_cli = __esm({
|
|
|
9125
9306
|
await this.start();
|
|
9126
9307
|
this.logger.debug("startMain: main-process started");
|
|
9127
9308
|
const response = await GrpcClient.getInstance().startBinSession(this.wdioConfig);
|
|
9128
|
-
|
|
9309
|
+
const redactedStartResponse = JSON.parse(JSON.stringify(response));
|
|
9310
|
+
CrashReporter.recursivelyRedactKeysFromObject(redactedStartResponse, ["user", "username", "key", "accesskey", "password"]);
|
|
9311
|
+
BStackLogger.debug(`start: startBinSession response=${JSON.stringify(redactedStartResponse)}`);
|
|
9129
9312
|
this.loadModules(response);
|
|
9130
9313
|
this.isMainConnected = true;
|
|
9131
9314
|
}
|
|
@@ -9158,6 +9341,17 @@ var init_cli = __esm({
|
|
|
9158
9341
|
}
|
|
9159
9342
|
this.modules[ObservabilityModule.MODULE_NAME] = new ObservabilityModule(startBinResponse.observability);
|
|
9160
9343
|
}
|
|
9344
|
+
if (startBinResponse.testhub.errors && startBinResponse.testhub.errors.length > 0) {
|
|
9345
|
+
try {
|
|
9346
|
+
const errors = JSON.parse(Buffer.from(startBinResponse.testhub.errors).toString());
|
|
9347
|
+
for (const [code, detail] of Object.entries(errors)) {
|
|
9348
|
+
const { message } = detail;
|
|
9349
|
+
BStackLogger.error(`[Build] ${code}: ${message}`);
|
|
9350
|
+
}
|
|
9351
|
+
} catch (e) {
|
|
9352
|
+
BStackLogger.debug(`Failed to parse testhub errors: ${e}`);
|
|
9353
|
+
}
|
|
9354
|
+
}
|
|
9161
9355
|
this.modules[TestHubModule.MODULE_NAME] = new TestHubModule(startBinResponse.testhub);
|
|
9162
9356
|
if (startBinResponse.accessibility?.success) {
|
|
9163
9357
|
process.env[BROWSERSTACK_ACCESSIBILITY] = "true";
|
|
@@ -9202,12 +9396,61 @@ var init_cli = __esm({
|
|
|
9202
9396
|
return Promise.resolve();
|
|
9203
9397
|
}
|
|
9204
9398
|
const SDK_CLI_BIN_PATH = await this.getCliBinPath();
|
|
9399
|
+
if (!SDK_CLI_BIN_PATH) {
|
|
9400
|
+
throw new Error("Failed to get CLI binary path. CLI setup failed.");
|
|
9401
|
+
}
|
|
9205
9402
|
const cmd = [SDK_CLI_BIN_PATH, "sdk"];
|
|
9206
9403
|
this.logger.debug(`spawning command='${cmd}'`);
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9404
|
+
for (let attempt = 1; attempt <= MAX_SPAWN_RETRIES; attempt++) {
|
|
9405
|
+
try {
|
|
9406
|
+
await this._spawnAndAwaitReady(cmd);
|
|
9407
|
+
if (attempt > 1) {
|
|
9408
|
+
this.logger.info(`Spawn succeeded on attempt ${attempt}/${MAX_SPAWN_RETRIES}`);
|
|
9409
|
+
}
|
|
9410
|
+
PerformanceTester.end(EVENTS.SDK_CLI_START);
|
|
9411
|
+
return;
|
|
9412
|
+
} catch (err) {
|
|
9413
|
+
const isBusy = BINARY_BUSY_ERROR_CODES.includes(err.code) || err.message && (err.message.toLowerCase().includes("text file busy") || err.message.toLowerCase().includes("being used by another process"));
|
|
9414
|
+
if (isBusy && attempt < MAX_SPAWN_RETRIES) {
|
|
9415
|
+
this.logger.warn(
|
|
9416
|
+
`Spawn attempt ${attempt}/${MAX_SPAWN_RETRIES} failed with busy error, retrying in ${SPAWN_RETRY_DELAY_MS}ms: ${err.message}`
|
|
9417
|
+
);
|
|
9418
|
+
if (this.process) {
|
|
9419
|
+
this.process.removeAllListeners();
|
|
9420
|
+
this.process.stdout?.removeAllListeners();
|
|
9421
|
+
this.process.stderr?.removeAllListeners();
|
|
9422
|
+
this.process = null;
|
|
9423
|
+
}
|
|
9424
|
+
await new Promise((r) => setTimeout(r, SPAWN_RETRY_DELAY_MS));
|
|
9425
|
+
} else {
|
|
9426
|
+
PerformanceTester.end(EVENTS.SDK_CLI_START, false, err);
|
|
9427
|
+
throw err;
|
|
9428
|
+
}
|
|
9429
|
+
}
|
|
9430
|
+
}
|
|
9431
|
+
}
|
|
9432
|
+
/**
|
|
9433
|
+
* Spawn the CLI process and wait until it emits "ready".
|
|
9434
|
+
* Handles both synchronous spawn errors and async error/close events.
|
|
9435
|
+
*/
|
|
9436
|
+
_spawnAndAwaitReady(cmd) {
|
|
9210
9437
|
return new Promise((resolve, reject) => {
|
|
9438
|
+
let settled = false;
|
|
9439
|
+
const settle = (fn, val) => {
|
|
9440
|
+
if (settled) {
|
|
9441
|
+
return;
|
|
9442
|
+
}
|
|
9443
|
+
settled = true;
|
|
9444
|
+
fn(val);
|
|
9445
|
+
};
|
|
9446
|
+
try {
|
|
9447
|
+
this.process = spawn3(cmd[0], cmd.slice(1), {
|
|
9448
|
+
env: process.env
|
|
9449
|
+
});
|
|
9450
|
+
} catch (syncErr) {
|
|
9451
|
+
settle(reject, syncErr);
|
|
9452
|
+
return;
|
|
9453
|
+
}
|
|
9211
9454
|
const cliOut = {};
|
|
9212
9455
|
this.process.stdout.on("data", (data) => {
|
|
9213
9456
|
const lines = data.toString().trim().split("\n");
|
|
@@ -9220,8 +9463,7 @@ var init_cli = __esm({
|
|
|
9220
9463
|
}
|
|
9221
9464
|
if (line.toLowerCase().includes("ready")) {
|
|
9222
9465
|
this.loadCliParams(cliOut);
|
|
9223
|
-
|
|
9224
|
-
resolve();
|
|
9466
|
+
settle(resolve);
|
|
9225
9467
|
return;
|
|
9226
9468
|
}
|
|
9227
9469
|
}
|
|
@@ -9230,14 +9472,11 @@ var init_cli = __esm({
|
|
|
9230
9472
|
this.logger.error(`CLI stderr: ${data.toString().trim()}`);
|
|
9231
9473
|
});
|
|
9232
9474
|
this.process.on("error", (err) => {
|
|
9233
|
-
|
|
9234
|
-
PerformanceTester.end(EVENTS.SDK_CLI_START, false, err);
|
|
9235
|
-
reject(new Error(`Failed to start CLI process: ${err.message}`));
|
|
9475
|
+
settle(reject, err);
|
|
9236
9476
|
});
|
|
9237
9477
|
this.process.on("close", (code) => {
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
}
|
|
9478
|
+
const msg = code !== 0 ? `CLI process exited with code ${code}` : "CLI process exited cleanly before emitting ready";
|
|
9479
|
+
settle(reject, new Error(msg));
|
|
9241
9480
|
});
|
|
9242
9481
|
});
|
|
9243
9482
|
}
|
|
@@ -9317,7 +9556,9 @@ var init_cli = __esm({
|
|
|
9317
9556
|
this.logger.info(`Starting as child process with session ID: ${binSessionId}`);
|
|
9318
9557
|
GrpcClient.getInstance().connect();
|
|
9319
9558
|
const response = await GrpcClient.getInstance().connectBinSession();
|
|
9320
|
-
|
|
9559
|
+
const redactedResponse = JSON.parse(JSON.stringify(response));
|
|
9560
|
+
CrashReporter.recursivelyRedactKeysFromObject(redactedResponse, ["user", "username", "key", "accesskey", "password"]);
|
|
9561
|
+
this.logger.info(`Connected to bin session: ${JSON.stringify(redactedResponse)}`);
|
|
9321
9562
|
this.loadModules(response);
|
|
9322
9563
|
this.isChildConnected = true;
|
|
9323
9564
|
PerformanceTester.end(EVENTS.SDK_CONNECT_BIN_SESSION);
|
|
@@ -9385,7 +9626,9 @@ var init_cli = __esm({
|
|
|
9385
9626
|
setConfig(response) {
|
|
9386
9627
|
try {
|
|
9387
9628
|
this.config = JSON.parse(response.config);
|
|
9388
|
-
|
|
9629
|
+
const redactedConfig = JSON.parse(response.config);
|
|
9630
|
+
CrashReporter.recursivelyRedactKeysFromObject(redactedConfig, ["user", "username", "key", "accesskey", "password"]);
|
|
9631
|
+
this.logger.debug(`loadModules: config=${JSON.stringify(redactedConfig)}`);
|
|
9389
9632
|
} catch (error) {
|
|
9390
9633
|
this.logger.error(`setConfig: error=${util10.format(error)}`);
|
|
9391
9634
|
}
|
|
@@ -10644,7 +10887,7 @@ var BrowserStackConfig = class _BrowserStackConfig {
|
|
|
10644
10887
|
this.accessKey = config.key;
|
|
10645
10888
|
this.testObservability = new testOpsConfig_default(options.testObservability !== false, !isUndefined(options.testObservability));
|
|
10646
10889
|
this.percy = options.percy || false;
|
|
10647
|
-
this.accessibility = options.accessibility
|
|
10890
|
+
this.accessibility = options.accessibility;
|
|
10648
10891
|
this.app = options.app;
|
|
10649
10892
|
this.appAutomate = !isUndefined(options.app);
|
|
10650
10893
|
this.automate = !this.appAutomate;
|
|
@@ -11215,7 +11458,7 @@ var BrowserstackLauncherService = class {
|
|
|
11215
11458
|
BStackLogger2.debug(`_options data: ${JSON.stringify(_options)}`);
|
|
11216
11459
|
BStackLogger2.debug(`webdriver capabilities data: ${JSON.stringify(capabilities)}`);
|
|
11217
11460
|
const configCopy = JSON.parse(JSON.stringify(_config));
|
|
11218
|
-
CrashReporter.recursivelyRedactKeysFromObject(configCopy, ["user", "key", "accesskey", "password"]);
|
|
11461
|
+
CrashReporter.recursivelyRedactKeysFromObject(configCopy, ["user", "username", "key", "accesskey", "password"]);
|
|
11219
11462
|
BStackLogger2.debug(`_config data: ${JSON.stringify(configCopy)}`);
|
|
11220
11463
|
if (Array.isArray(capabilities)) {
|
|
11221
11464
|
capabilities.flatMap((c) => {
|
|
@@ -11311,7 +11554,7 @@ var BrowserstackLauncherService = class {
|
|
|
11311
11554
|
_projectName;
|
|
11312
11555
|
_buildTag;
|
|
11313
11556
|
_buildIdentifier;
|
|
11314
|
-
_accessibilityAutomation
|
|
11557
|
+
_accessibilityAutomation;
|
|
11315
11558
|
_percy;
|
|
11316
11559
|
_percyBestPlatformCaps;
|
|
11317
11560
|
browserStackConfig;
|
|
@@ -11451,7 +11694,7 @@ var BrowserstackLauncherService = class {
|
|
|
11451
11694
|
this._updateObjectTypeCaps(capabilities, "goog:chromeOptions", overrideOptions);
|
|
11452
11695
|
}
|
|
11453
11696
|
if (buildStartResponse?.accessibility) {
|
|
11454
|
-
if (this._accessibilityAutomation
|
|
11697
|
+
if (isUndefined(this._accessibilityAutomation)) {
|
|
11455
11698
|
this.browserStackConfig.accessibility = buildStartResponse.accessibility.success;
|
|
11456
11699
|
this._accessibilityAutomation = buildStartResponse.accessibility.success;
|
|
11457
11700
|
this._options.accessibility = buildStartResponse.accessibility.success;
|
|
@@ -11472,6 +11715,7 @@ var BrowserstackLauncherService = class {
|
|
|
11472
11715
|
} else if (isAccessibilityAutomationSession(this._accessibilityAutomation)) {
|
|
11473
11716
|
this._updateObjectTypeCaps(capabilities, "accessibilityOptions", {});
|
|
11474
11717
|
}
|
|
11718
|
+
this._removeCliOnlyCapabilityOptions(capabilities);
|
|
11475
11719
|
if (shouldSetupPercy) {
|
|
11476
11720
|
try {
|
|
11477
11721
|
const bestPlatformPercyCaps = getBestPlatformForPercySnapshot(capabilities);
|
|
@@ -11749,13 +11993,40 @@ Visit https://automation.browserstack.com/builds/${process.env[BROWSERSTACK_TEST
|
|
|
11749
11993
|
}
|
|
11750
11994
|
async _uploadServiceLogs() {
|
|
11751
11995
|
const clientBuildUuid = this._getClientBuildUuid();
|
|
11752
|
-
await
|
|
11753
|
-
|
|
11754
|
-
|
|
11755
|
-
|
|
11756
|
-
|
|
11996
|
+
const response = await uploadLogs(getBrowserStackUser(this._config), getBrowserStackKey(this._config), clientBuildUuid);
|
|
11997
|
+
if (response) {
|
|
11998
|
+
BStackLogger2.info(`Upload response: ${JSON.stringify(response, null, 2)}`);
|
|
11999
|
+
BStackLogger2.logToFile(`Response - ${format4(response)}`, "debug");
|
|
12000
|
+
}
|
|
12001
|
+
}
|
|
12002
|
+
_removeCliOnlyCapabilityOptions(capabilities) {
|
|
12003
|
+
if (!capabilities || typeof capabilities !== "object") {
|
|
12004
|
+
return;
|
|
12005
|
+
}
|
|
12006
|
+
const strip = (capability) => {
|
|
12007
|
+
const capabilityRecord = capability;
|
|
12008
|
+
const bstackOptions = capabilityRecord["bstack:options"];
|
|
12009
|
+
if (bstackOptions && typeof bstackOptions === "object") {
|
|
12010
|
+
NOT_ALLOWED_KEYS_IN_CAPS.forEach((key) => delete bstackOptions[key]);
|
|
11757
12011
|
}
|
|
11758
|
-
|
|
12012
|
+
NOT_ALLOWED_KEYS_IN_CAPS.forEach((key) => delete capabilityRecord[`browserstack.${key}`]);
|
|
12013
|
+
const alwaysMatch = capability.alwaysMatch;
|
|
12014
|
+
if (alwaysMatch && typeof alwaysMatch === "object") {
|
|
12015
|
+
strip(alwaysMatch);
|
|
12016
|
+
}
|
|
12017
|
+
};
|
|
12018
|
+
if (Array.isArray(capabilities)) {
|
|
12019
|
+
capabilities.flatMap((c) => {
|
|
12020
|
+
if (Object.values(c).length > 0 && Object.values(c).every((c2) => typeof c2 === "object" && c2.capabilities)) {
|
|
12021
|
+
return Object.values(c).map((o) => o.capabilities);
|
|
12022
|
+
}
|
|
12023
|
+
return c;
|
|
12024
|
+
}).forEach(strip);
|
|
12025
|
+
} else {
|
|
12026
|
+
Object.entries(capabilities).forEach(([, caps]) => {
|
|
12027
|
+
strip(caps.capabilities);
|
|
12028
|
+
});
|
|
12029
|
+
}
|
|
11759
12030
|
}
|
|
11760
12031
|
_updateObjectTypeCaps(capabilities, capType, value) {
|
|
11761
12032
|
try {
|
|
@@ -12546,11 +12817,20 @@ var BrowserstackService = class {
|
|
|
12546
12817
|
PerformanceTester.end(EVENTS.SDK_FIND_NEAREST_HUB, false, "Hub URL not found");
|
|
12547
12818
|
}
|
|
12548
12819
|
}
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12820
|
+
try {
|
|
12821
|
+
if (BrowserstackCLI.getInstance().isRunning()) {
|
|
12822
|
+
await BrowserstackCLI.getInstance().getAutomationFramework().trackEvent(AutomationFrameworkState.CREATE, HookState.PRE, { caps: capabilities });
|
|
12823
|
+
const instance = AutomationFramework.getTrackedInstance();
|
|
12824
|
+
const caps = AutomationFramework.getState(instance, AutomationFrameworkConstants.KEY_CAPABILITIES);
|
|
12825
|
+
Object.assign(capabilities, caps);
|
|
12826
|
+
const bstackOptions = capabilities["bstack:options"];
|
|
12827
|
+
if (bstackOptions && typeof bstackOptions === "object") {
|
|
12828
|
+
NOT_ALLOWED_KEYS_IN_CAPS.forEach((key) => delete bstackOptions[key]);
|
|
12829
|
+
}
|
|
12830
|
+
NOT_ALLOWED_KEYS_IN_CAPS.forEach((key) => delete capabilities[`browserstack.${key}`]);
|
|
12831
|
+
}
|
|
12832
|
+
} catch (err) {
|
|
12833
|
+
BStackLogger2.error(`Error while tracking automation framework event: ${err}`);
|
|
12554
12834
|
}
|
|
12555
12835
|
} catch (err) {
|
|
12556
12836
|
BStackLogger2.error(`Error while connecting to Browserstack CLI: ${err}`);
|
|
@@ -12727,8 +13007,8 @@ var BrowserstackService = class {
|
|
|
12727
13007
|
}
|
|
12728
13008
|
async afterTest(test, context, results) {
|
|
12729
13009
|
this._specsRan = true;
|
|
12730
|
-
const { error, passed } = results;
|
|
12731
|
-
if (!passed) {
|
|
13010
|
+
const { error, passed, skipped } = results;
|
|
13011
|
+
if (!passed && !skipped) {
|
|
12732
13012
|
const testError = error && error.message || "Unknown Error";
|
|
12733
13013
|
this._failReasons.push(testError);
|
|
12734
13014
|
this._pureTestFailReasons.push(testError);
|