@wdio/browserstack-service 9.27.1 → 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/cleanup.js +182 -57
- 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/index.js +366 -111
- 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 +6 -7
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.27.
|
|
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,7 +70,6 @@ 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",
|
|
@@ -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(),
|
|
@@ -2617,10 +2721,8 @@ import path5 from "node:path";
|
|
|
2617
2721
|
import util3 from "node:util";
|
|
2618
2722
|
import gitRepoInfo from "git-repo-info";
|
|
2619
2723
|
import gitconfig from "gitconfiglocal";
|
|
2620
|
-
import { FormData as FormData2 } from "formdata-node";
|
|
2621
2724
|
import { performance as performance2 } from "node:perf_hooks";
|
|
2622
2725
|
import { create } from "tar";
|
|
2623
|
-
import { fileFromPath } from "formdata-node/file-from-path";
|
|
2624
2726
|
function getBrowserDescription(cap) {
|
|
2625
2727
|
cap = cap || {};
|
|
2626
2728
|
if (cap["bstack:options"]) {
|
|
@@ -3190,6 +3292,25 @@ function getObservabilityBuildTags(options, bstackBuildTag) {
|
|
|
3190
3292
|
}
|
|
3191
3293
|
return [];
|
|
3192
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
|
+
}
|
|
3193
3314
|
function getBrowserStackUser(config) {
|
|
3194
3315
|
if (process.env.BROWSERSTACK_USERNAME) {
|
|
3195
3316
|
return process.env.BROWSERSTACK_USERNAME;
|
|
@@ -3235,8 +3356,14 @@ function getFailureObject(error) {
|
|
|
3235
3356
|
};
|
|
3236
3357
|
}
|
|
3237
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);
|
|
3238
3363
|
try {
|
|
3239
3364
|
if (!user || !key) {
|
|
3365
|
+
success = false;
|
|
3366
|
+
failure = "skipped: missing_credentials";
|
|
3240
3367
|
BStackLogger2.debug("Uploading logs failed due to no credentials");
|
|
3241
3368
|
return;
|
|
3242
3369
|
}
|
|
@@ -3248,10 +3375,20 @@ async function uploadLogs(user, key, clientBuildUuid) {
|
|
|
3248
3375
|
CLI_DEBUG_LOGS_FILE
|
|
3249
3376
|
].filter((f) => fs6.existsSync(f));
|
|
3250
3377
|
const copiedFileNames = [];
|
|
3378
|
+
const archiveAddFailures = [];
|
|
3251
3379
|
for (const f of filesToArchive) {
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
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);
|
|
3255
3392
|
}
|
|
3256
3393
|
await create(
|
|
3257
3394
|
{
|
|
@@ -3270,8 +3407,8 @@ async function uploadLogs(user, key, clientBuildUuid) {
|
|
|
3270
3407
|
dest.on("finish", resolve);
|
|
3271
3408
|
dest.on("error", reject);
|
|
3272
3409
|
});
|
|
3273
|
-
const formData = new
|
|
3274
|
-
const file = await
|
|
3410
|
+
const formData = new FormData();
|
|
3411
|
+
const file = await fs6.openAsBlob(tarGzPath, { type: "application/x-gzip" });
|
|
3275
3412
|
formData.append("data", file, "logs.tar.gz");
|
|
3276
3413
|
formData.append("clientBuildUuid", clientBuildUuid);
|
|
3277
3414
|
const auth = Buffer.from(`${user}:${key}`).toString("base64");
|
|
@@ -3298,10 +3435,21 @@ async function uploadLogs(user, key, clientBuildUuid) {
|
|
|
3298
3435
|
if (fs6.existsSync(CLI_DEBUG_LOGS_FILE)) {
|
|
3299
3436
|
fs6.unlinkSync(CLI_DEBUG_LOGS_FILE);
|
|
3300
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
|
+
}
|
|
3301
3445
|
return response;
|
|
3302
3446
|
} catch (error) {
|
|
3447
|
+
success = false;
|
|
3448
|
+
failure = `uploadLogs exception: ${getErrorString(error)}`;
|
|
3303
3449
|
BStackLogger2.error(`Error while uploading logs: ${getErrorString(error)}`);
|
|
3304
3450
|
return null;
|
|
3451
|
+
} finally {
|
|
3452
|
+
PerformanceTester.end(eventName, success, failure);
|
|
3305
3453
|
}
|
|
3306
3454
|
}
|
|
3307
3455
|
function truncateString(field, truncateSizeInBytes) {
|
|
@@ -3698,7 +3846,10 @@ var init_util = __esm({
|
|
|
3698
3846
|
},
|
|
3699
3847
|
product_map: getProductMapForBuildStartCall(bStackConfig, accessibilityAutomation),
|
|
3700
3848
|
config: {},
|
|
3701
|
-
test_orchestration: OrchestrationUtils.getInstance(config)?.getBuildStartData() || {}
|
|
3849
|
+
test_orchestration: OrchestrationUtils.getInstance(config)?.getBuildStartData() || {},
|
|
3850
|
+
test_management: {
|
|
3851
|
+
test_plan_id: getTestPlanId(options)
|
|
3852
|
+
}
|
|
3702
3853
|
};
|
|
3703
3854
|
if (accessibilityAutomation && (isTurboScale(options) || data.browserstackAutomation === false)) {
|
|
3704
3855
|
data.accessibility.settings ??= {};
|
|
@@ -3727,6 +3878,7 @@ var init_util = __esm({
|
|
|
3727
3878
|
const jsonResponse = await response.json();
|
|
3728
3879
|
delete data?.accessibility?.settings?.includeEncodedExtension;
|
|
3729
3880
|
BStackLogger2.debug(`[Start_Build] Success response: ${JSON.stringify(jsonResponse)}`);
|
|
3881
|
+
BStackLogger2.debug(`Test Plan Id sent in request: ${getTestPlanId(options)}`);
|
|
3730
3882
|
process.env[TESTOPS_BUILD_COMPLETED_ENV] = "true";
|
|
3731
3883
|
if (jsonResponse.jwt) {
|
|
3732
3884
|
process.env[BROWSERSTACK_TESTHUB_JWT] = jsonResponse.jwt;
|
|
@@ -4201,13 +4353,14 @@ var init_utils = __esm({
|
|
|
4201
4353
|
init_bstackLogger();
|
|
4202
4354
|
init_util();
|
|
4203
4355
|
getProductMap = (config) => {
|
|
4204
|
-
|
|
4205
|
-
observability
|
|
4206
|
-
accessibility
|
|
4207
|
-
percy
|
|
4208
|
-
automate
|
|
4209
|
-
app_automate
|
|
4210
|
-
|
|
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));
|
|
4211
4364
|
};
|
|
4212
4365
|
shouldProcessEventForTesthub = (eventType) => {
|
|
4213
4366
|
if (isTrue(process.env[BROWSERSTACK_OBSERVABILITY])) {
|
|
@@ -4255,13 +4408,14 @@ var init_utils = __esm({
|
|
|
4255
4408
|
}
|
|
4256
4409
|
};
|
|
4257
4410
|
getProductMapForBuildStartCall = (config, accessibilityAutomation) => {
|
|
4258
|
-
|
|
4259
|
-
observability
|
|
4260
|
-
accessibility
|
|
4261
|
-
percy
|
|
4262
|
-
automate
|
|
4263
|
-
app_automate
|
|
4264
|
-
|
|
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));
|
|
4265
4419
|
};
|
|
4266
4420
|
}
|
|
4267
4421
|
});
|
|
@@ -4359,7 +4513,7 @@ import {
|
|
|
4359
4513
|
DriverInitRequestConstructor,
|
|
4360
4514
|
FetchDriverExecuteParamsEventRequestConstructor
|
|
4361
4515
|
} from "@browserstack/wdio-browserstack-service";
|
|
4362
|
-
var GrpcClient;
|
|
4516
|
+
var GRPC_MESSAGE_LIMIT, GrpcClient;
|
|
4363
4517
|
var init_grpcClient = __esm({
|
|
4364
4518
|
"src/cli/grpcClient.ts"() {
|
|
4365
4519
|
"use strict";
|
|
@@ -4367,6 +4521,7 @@ var init_grpcClient = __esm({
|
|
|
4367
4521
|
init_performance_tester();
|
|
4368
4522
|
init_constants2();
|
|
4369
4523
|
init_cliLogger();
|
|
4524
|
+
GRPC_MESSAGE_LIMIT = 20 * 1024 * 1024;
|
|
4370
4525
|
GrpcClient = class _GrpcClient {
|
|
4371
4526
|
static #instance = null;
|
|
4372
4527
|
binSessionId;
|
|
@@ -4428,17 +4583,20 @@ var init_grpcClient = __esm({
|
|
|
4428
4583
|
if (!listenAddress) {
|
|
4429
4584
|
throw new Error("Unable to determine gRPC server listen address");
|
|
4430
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
|
+
};
|
|
4431
4591
|
this.channel = new grpcChannel(
|
|
4432
4592
|
listenAddress,
|
|
4433
4593
|
grpcCredentials.createInsecure(),
|
|
4434
|
-
|
|
4435
|
-
"grpc.keepalive_time_ms": 1e4
|
|
4436
|
-
}
|
|
4594
|
+
channelOptions
|
|
4437
4595
|
);
|
|
4438
4596
|
this.client = new SDKClient(
|
|
4439
4597
|
listenAddress,
|
|
4440
4598
|
grpcCredentials.createInsecure(),
|
|
4441
|
-
|
|
4599
|
+
channelOptions
|
|
4442
4600
|
);
|
|
4443
4601
|
this.logger.info(`Connected to gRPC server at ${listenAddress}`);
|
|
4444
4602
|
}
|
|
@@ -5678,12 +5836,12 @@ var init_automateModule = __esm({
|
|
|
5678
5836
|
async onAfterTest(args) {
|
|
5679
5837
|
this.logger.debug("onAfterTest: inside automate module after test hook!");
|
|
5680
5838
|
const instace = args.instance;
|
|
5681
|
-
const { error, passed } = args.result;
|
|
5839
|
+
const { error, passed, skipped } = args.result;
|
|
5682
5840
|
const _failReasons = [];
|
|
5683
|
-
if (!passed) {
|
|
5841
|
+
if (!passed && !skipped) {
|
|
5684
5842
|
_failReasons.push(error && error.message || "Unknown Error");
|
|
5685
5843
|
}
|
|
5686
|
-
const status = passed ? "passed" : "failed";
|
|
5844
|
+
const status = passed || skipped ? "passed" : "failed";
|
|
5687
5845
|
const reason = _failReasons.length > 0 ? _failReasons.join("\n") : void 0;
|
|
5688
5846
|
const autoInstance = AutomationFramework.getTrackedInstance();
|
|
5689
5847
|
const sessionId = AutomationFramework.getState(autoInstance, AutomationFrameworkConstants.KEY_FRAMEWORK_SESSION_ID);
|
|
@@ -6081,13 +6239,17 @@ var init_wdioMochaTestFramework = __esm({
|
|
|
6081
6239
|
}
|
|
6082
6240
|
loadTestResult(instance, args) {
|
|
6083
6241
|
const results = args.result;
|
|
6084
|
-
const { error, passed } = results;
|
|
6242
|
+
const { error, passed, skipped } = results;
|
|
6085
6243
|
let result = "passed";
|
|
6086
6244
|
let failure = null;
|
|
6087
6245
|
let failureReason = null;
|
|
6088
6246
|
let failureType = null;
|
|
6089
6247
|
if (!passed) {
|
|
6090
|
-
|
|
6248
|
+
if (skipped) {
|
|
6249
|
+
result = "skipped";
|
|
6250
|
+
} else {
|
|
6251
|
+
result = error && error.message && error.message.includes("sync skip; aborting execution") ? "ignore" : "failed";
|
|
6252
|
+
}
|
|
6091
6253
|
if (error && result !== "skipped") {
|
|
6092
6254
|
failure = [{ backtrace: [removeAnsiColors(error.message), removeAnsiColors(error.stack || "")] }];
|
|
6093
6255
|
failureReason = removeAnsiColors(error.message);
|
|
@@ -9179,6 +9341,17 @@ var init_cli = __esm({
|
|
|
9179
9341
|
}
|
|
9180
9342
|
this.modules[ObservabilityModule.MODULE_NAME] = new ObservabilityModule(startBinResponse.observability);
|
|
9181
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
|
+
}
|
|
9182
9355
|
this.modules[TestHubModule.MODULE_NAME] = new TestHubModule(startBinResponse.testhub);
|
|
9183
9356
|
if (startBinResponse.accessibility?.success) {
|
|
9184
9357
|
process.env[BROWSERSTACK_ACCESSIBILITY] = "true";
|
|
@@ -9223,12 +9396,61 @@ var init_cli = __esm({
|
|
|
9223
9396
|
return Promise.resolve();
|
|
9224
9397
|
}
|
|
9225
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
|
+
}
|
|
9226
9402
|
const cmd = [SDK_CLI_BIN_PATH, "sdk"];
|
|
9227
9403
|
this.logger.debug(`spawning command='${cmd}'`);
|
|
9228
|
-
|
|
9229
|
-
|
|
9230
|
-
|
|
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) {
|
|
9231
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
|
+
}
|
|
9232
9454
|
const cliOut = {};
|
|
9233
9455
|
this.process.stdout.on("data", (data) => {
|
|
9234
9456
|
const lines = data.toString().trim().split("\n");
|
|
@@ -9241,8 +9463,7 @@ var init_cli = __esm({
|
|
|
9241
9463
|
}
|
|
9242
9464
|
if (line.toLowerCase().includes("ready")) {
|
|
9243
9465
|
this.loadCliParams(cliOut);
|
|
9244
|
-
|
|
9245
|
-
resolve();
|
|
9466
|
+
settle(resolve);
|
|
9246
9467
|
return;
|
|
9247
9468
|
}
|
|
9248
9469
|
}
|
|
@@ -9251,14 +9472,11 @@ var init_cli = __esm({
|
|
|
9251
9472
|
this.logger.error(`CLI stderr: ${data.toString().trim()}`);
|
|
9252
9473
|
});
|
|
9253
9474
|
this.process.on("error", (err) => {
|
|
9254
|
-
|
|
9255
|
-
PerformanceTester.end(EVENTS.SDK_CLI_START, false, err);
|
|
9256
|
-
reject(new Error(`Failed to start CLI process: ${err.message}`));
|
|
9475
|
+
settle(reject, err);
|
|
9257
9476
|
});
|
|
9258
9477
|
this.process.on("close", (code) => {
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
}
|
|
9478
|
+
const msg = code !== 0 ? `CLI process exited with code ${code}` : "CLI process exited cleanly before emitting ready";
|
|
9479
|
+
settle(reject, new Error(msg));
|
|
9262
9480
|
});
|
|
9263
9481
|
});
|
|
9264
9482
|
}
|
|
@@ -10669,7 +10887,7 @@ var BrowserStackConfig = class _BrowserStackConfig {
|
|
|
10669
10887
|
this.accessKey = config.key;
|
|
10670
10888
|
this.testObservability = new testOpsConfig_default(options.testObservability !== false, !isUndefined(options.testObservability));
|
|
10671
10889
|
this.percy = options.percy || false;
|
|
10672
|
-
this.accessibility = options.accessibility
|
|
10890
|
+
this.accessibility = options.accessibility;
|
|
10673
10891
|
this.app = options.app;
|
|
10674
10892
|
this.appAutomate = !isUndefined(options.app);
|
|
10675
10893
|
this.automate = !this.appAutomate;
|
|
@@ -11336,7 +11554,7 @@ var BrowserstackLauncherService = class {
|
|
|
11336
11554
|
_projectName;
|
|
11337
11555
|
_buildTag;
|
|
11338
11556
|
_buildIdentifier;
|
|
11339
|
-
_accessibilityAutomation
|
|
11557
|
+
_accessibilityAutomation;
|
|
11340
11558
|
_percy;
|
|
11341
11559
|
_percyBestPlatformCaps;
|
|
11342
11560
|
browserStackConfig;
|
|
@@ -11476,7 +11694,7 @@ var BrowserstackLauncherService = class {
|
|
|
11476
11694
|
this._updateObjectTypeCaps(capabilities, "goog:chromeOptions", overrideOptions);
|
|
11477
11695
|
}
|
|
11478
11696
|
if (buildStartResponse?.accessibility) {
|
|
11479
|
-
if (this._accessibilityAutomation
|
|
11697
|
+
if (isUndefined(this._accessibilityAutomation)) {
|
|
11480
11698
|
this.browserStackConfig.accessibility = buildStartResponse.accessibility.success;
|
|
11481
11699
|
this._accessibilityAutomation = buildStartResponse.accessibility.success;
|
|
11482
11700
|
this._options.accessibility = buildStartResponse.accessibility.success;
|
|
@@ -11497,6 +11715,7 @@ var BrowserstackLauncherService = class {
|
|
|
11497
11715
|
} else if (isAccessibilityAutomationSession(this._accessibilityAutomation)) {
|
|
11498
11716
|
this._updateObjectTypeCaps(capabilities, "accessibilityOptions", {});
|
|
11499
11717
|
}
|
|
11718
|
+
this._removeCliOnlyCapabilityOptions(capabilities);
|
|
11500
11719
|
if (shouldSetupPercy) {
|
|
11501
11720
|
try {
|
|
11502
11721
|
const bestPlatformPercyCaps = getBestPlatformForPercySnapshot(capabilities);
|
|
@@ -11774,13 +11993,40 @@ Visit https://automation.browserstack.com/builds/${process.env[BROWSERSTACK_TEST
|
|
|
11774
11993
|
}
|
|
11775
11994
|
async _uploadServiceLogs() {
|
|
11776
11995
|
const clientBuildUuid = this._getClientBuildUuid();
|
|
11777
|
-
await
|
|
11778
|
-
|
|
11779
|
-
|
|
11780
|
-
|
|
11781
|
-
|
|
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]);
|
|
11782
12011
|
}
|
|
11783
|
-
|
|
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
|
+
}
|
|
11784
12030
|
}
|
|
11785
12031
|
_updateObjectTypeCaps(capabilities, capType, value) {
|
|
11786
12032
|
try {
|
|
@@ -12571,11 +12817,20 @@ var BrowserstackService = class {
|
|
|
12571
12817
|
PerformanceTester.end(EVENTS.SDK_FIND_NEAREST_HUB, false, "Hub URL not found");
|
|
12572
12818
|
}
|
|
12573
12819
|
}
|
|
12574
|
-
|
|
12575
|
-
|
|
12576
|
-
|
|
12577
|
-
|
|
12578
|
-
|
|
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}`);
|
|
12579
12834
|
}
|
|
12580
12835
|
} catch (err) {
|
|
12581
12836
|
BStackLogger2.error(`Error while connecting to Browserstack CLI: ${err}`);
|
|
@@ -12752,8 +13007,8 @@ var BrowserstackService = class {
|
|
|
12752
13007
|
}
|
|
12753
13008
|
async afterTest(test, context, results) {
|
|
12754
13009
|
this._specsRan = true;
|
|
12755
|
-
const { error, passed } = results;
|
|
12756
|
-
if (!passed) {
|
|
13010
|
+
const { error, passed, skipped } = results;
|
|
13011
|
+
if (!passed && !skipped) {
|
|
12757
13012
|
const testError = error && error.message || "Unknown Error";
|
|
12758
13013
|
this._failReasons.push(testError);
|
|
12759
13014
|
this._pureTestFailReasons.push(testError);
|