@wdio/browserstack-service 9.27.1 → 9.28.0
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/caCert.d.ts +7 -0
- package/build/caCert.d.ts.map +1 -0
- package/build/cleanup.js +406 -164
- package/build/cli/cliUtils.d.ts +10 -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 +13 -0
- package/build/cli/index.d.ts.map +1 -1
- package/build/config.d.ts +4 -4
- package/build/config.d.ts.map +1 -1
- package/build/constants.d.ts +15 -0
- package/build/constants.d.ts.map +1 -1
- package/build/fetchWrapper.d.ts +18 -2
- package/build/fetchWrapper.d.ts.map +1 -1
- package/build/index.js +1289 -714
- package/build/insights-handler.d.ts.map +1 -1
- 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/reporter.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 +16 -0
- package/build/types.d.ts.map +1 -1
- package/build/util.d.ts +23 -0
- package/build/util.d.ts.map +1 -1
- package/package.json +6 -7
package/build/cleanup.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// src/util.ts
|
|
2
2
|
import { hostname as hostname2, platform as platform3, type as type2, version as version2, arch as arch3, tmpdir } from "node:os";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
|
-
import
|
|
4
|
+
import fs8 from "node:fs";
|
|
5
5
|
import zlib from "node:zlib";
|
|
6
6
|
import { format, promisify as promisify2 } from "node:util";
|
|
7
|
-
import
|
|
7
|
+
import path7 from "node:path";
|
|
8
8
|
import util3 from "node:util";
|
|
9
9
|
import gitRepoInfo from "git-repo-info";
|
|
10
10
|
import gitconfig from "gitconfiglocal";
|
|
11
|
-
import { FormData } from "formdata-node";
|
|
12
11
|
import { performance as performance2 } from "node:perf_hooks";
|
|
13
12
|
|
|
14
13
|
// src/logPatcher.ts
|
|
@@ -54,12 +53,12 @@ var logPatcher_default = logPatcher;
|
|
|
54
53
|
|
|
55
54
|
// src/instrumentation/performance/performance-tester.ts
|
|
56
55
|
import { createObjectCsvWriter } from "csv-writer";
|
|
57
|
-
import
|
|
56
|
+
import fs5 from "node:fs";
|
|
58
57
|
import fsPromise from "node:fs/promises";
|
|
59
58
|
import { performance, PerformanceObserver } from "node:perf_hooks";
|
|
60
59
|
import util2 from "node:util";
|
|
61
60
|
import worker from "node:worker_threads";
|
|
62
|
-
import
|
|
61
|
+
import path5 from "node:path";
|
|
63
62
|
import { arch as arch2, hostname, platform as platform2, type, version } from "node:os";
|
|
64
63
|
|
|
65
64
|
// src/bstackLogger.ts
|
|
@@ -71,7 +70,7 @@ import logger from "@wdio/logger";
|
|
|
71
70
|
// package.json
|
|
72
71
|
var package_default = {
|
|
73
72
|
name: "@wdio/browserstack-service",
|
|
74
|
-
version: "9.27.
|
|
73
|
+
version: "9.27.2",
|
|
75
74
|
description: "WebdriverIO service for better Browserstack integration",
|
|
76
75
|
author: "Adam Bjerstedt <abjerstedt@gmail.com>",
|
|
77
76
|
homepage: "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-browserstack-service",
|
|
@@ -118,7 +117,6 @@ var package_default = {
|
|
|
118
117
|
"browserstack-local": "^1.5.1",
|
|
119
118
|
chalk: "^5.3.0",
|
|
120
119
|
"csv-writer": "^1.6.0",
|
|
121
|
-
"formdata-node": "5.0.1",
|
|
122
120
|
"git-repo-info": "^2.1.1",
|
|
123
121
|
gitconfiglocal: "^2.1.0",
|
|
124
122
|
glob: "^11.0.0",
|
|
@@ -149,6 +147,7 @@ var consoleHolder = Object.assign({}, console);
|
|
|
149
147
|
var APP_ALLY_ISSUES_ENDPOINT = "api/v1/issues";
|
|
150
148
|
var APP_ALLY_ISSUES_SUMMARY_ENDPOINT = "api/v1/issues-summary";
|
|
151
149
|
var BSTACK_SERVICE_VERSION = bstackServiceVersion;
|
|
150
|
+
var BROWSERSTACK_TEST_PLAN_ID = "BROWSERSTACK_TEST_PLAN_ID";
|
|
152
151
|
var LOGS_FILE = "logs/bstack-wdio-service.log";
|
|
153
152
|
var SMART_SELECTION_MODE_RELEVANT_FIRST = "relevantFirst";
|
|
154
153
|
var SMART_SELECTION_MODE_RELEVANT_ONLY = "relevantOnly";
|
|
@@ -162,6 +161,7 @@ var BROWSERSTACK_ACCESSIBILITY = "BROWSERSTACK_ACCESSIBILITY";
|
|
|
162
161
|
var BROWSERSTACK_OBSERVABILITY = "BROWSERSTACK_OBSERVABILITY";
|
|
163
162
|
var MAX_GIT_META_DATA_SIZE_IN_BYTES = 64 * 1024;
|
|
164
163
|
var GIT_META_DATA_TRUNCATED = "...[TRUNCATED]";
|
|
164
|
+
var BINARY_BUSY_ERROR_CODES = ["ETXTBSY", "EBUSY"];
|
|
165
165
|
var WDIO_NAMING_PREFIX = "WebdriverIO-";
|
|
166
166
|
var UPDATED_CLI_ENDPOINT = "sdk/v1/update_cli";
|
|
167
167
|
|
|
@@ -266,10 +266,10 @@ var APIUtils = class {
|
|
|
266
266
|
};
|
|
267
267
|
|
|
268
268
|
// src/cli/cliUtils.ts
|
|
269
|
-
import
|
|
269
|
+
import fs4 from "node:fs";
|
|
270
270
|
import fsp from "node:fs/promises";
|
|
271
271
|
import { platform, arch, homedir } from "node:os";
|
|
272
|
-
import
|
|
272
|
+
import path4 from "node:path";
|
|
273
273
|
import util, { promisify } from "node:util";
|
|
274
274
|
import { exec } from "node:child_process";
|
|
275
275
|
import { Readable } from "node:stream";
|
|
@@ -277,7 +277,20 @@ import yauzl from "yauzl";
|
|
|
277
277
|
import { threadId } from "node:worker_threads";
|
|
278
278
|
|
|
279
279
|
// src/fetchWrapper.ts
|
|
280
|
-
import { fetch as undiciFetch, ProxyAgent } from "undici";
|
|
280
|
+
import { fetch as undiciFetch, ProxyAgent, Agent as Agent2 } from "undici";
|
|
281
|
+
|
|
282
|
+
// src/caCert.ts
|
|
283
|
+
import { setGlobalDispatcher, Agent } from "undici";
|
|
284
|
+
import tls from "node:tls";
|
|
285
|
+
import fs2 from "node:fs";
|
|
286
|
+
import os from "node:os";
|
|
287
|
+
import path2 from "node:path";
|
|
288
|
+
var mergedCa;
|
|
289
|
+
function getMergedCa() {
|
|
290
|
+
return mergedCa;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/fetchWrapper.ts
|
|
281
294
|
var ResponseError = class extends Error {
|
|
282
295
|
response;
|
|
283
296
|
constructor(message, res) {
|
|
@@ -285,26 +298,56 @@ var ResponseError = class extends Error {
|
|
|
285
298
|
this.response = res;
|
|
286
299
|
}
|
|
287
300
|
};
|
|
288
|
-
|
|
289
|
-
|
|
301
|
+
var dispatcherCache = /* @__PURE__ */ new Map();
|
|
302
|
+
function getDispatcher(proxyUrl, connectTimeoutMs) {
|
|
303
|
+
const ca = getMergedCa();
|
|
304
|
+
const key = `${proxyUrl ?? "direct"}:${connectTimeoutMs ?? "default"}:${ca ? "ca" : "noca"}`;
|
|
305
|
+
let dispatcher = dispatcherCache.get(key);
|
|
306
|
+
if (!dispatcher) {
|
|
307
|
+
const connect = {};
|
|
308
|
+
if (connectTimeoutMs) {
|
|
309
|
+
connect.timeout = connectTimeoutMs;
|
|
310
|
+
}
|
|
311
|
+
if (proxyUrl) {
|
|
312
|
+
dispatcher = new ProxyAgent({ uri: proxyUrl, connect, ...ca ? { requestTls: { ca } } : {} });
|
|
313
|
+
} else {
|
|
314
|
+
if (ca) {
|
|
315
|
+
connect.ca = ca;
|
|
316
|
+
}
|
|
317
|
+
dispatcher = new Agent2({ connect });
|
|
318
|
+
}
|
|
319
|
+
dispatcherCache.set(key, dispatcher);
|
|
320
|
+
}
|
|
321
|
+
return dispatcher;
|
|
322
|
+
}
|
|
323
|
+
async function fetchWrap(input, init, options) {
|
|
324
|
+
const res = await _fetch(input, init, options);
|
|
290
325
|
if (!res.ok) {
|
|
291
326
|
throw new ResponseError(`Error response from server ${res.status}: ${await res.text()}`, res);
|
|
292
327
|
}
|
|
293
328
|
return res;
|
|
294
329
|
}
|
|
295
|
-
function _fetch(input, init) {
|
|
330
|
+
function _fetch(input, init, options) {
|
|
331
|
+
const connectTimeoutMs = options?.connectTimeoutMs;
|
|
296
332
|
const proxyUrl = process.env.HTTP_PROXY || process.env.HTTPS_PROXY;
|
|
297
333
|
if (proxyUrl) {
|
|
298
334
|
const noProxy = process.env.NO_PROXY && process.env.NO_PROXY.trim() ? process.env.NO_PROXY.trim().split(/[\s,;]+/) : [];
|
|
299
335
|
const request = new Request(input);
|
|
300
336
|
const url = new URL(request.url);
|
|
301
337
|
if (!noProxy.some((str) => url.hostname.endsWith(str))) {
|
|
338
|
+
const dispatcher = getDispatcher(proxyUrl, connectTimeoutMs);
|
|
302
339
|
return undiciFetch(
|
|
303
340
|
request.url,
|
|
304
|
-
{ ...init, dispatcher
|
|
341
|
+
{ ...init, dispatcher }
|
|
305
342
|
);
|
|
306
343
|
}
|
|
307
344
|
}
|
|
345
|
+
if (connectTimeoutMs) {
|
|
346
|
+
return undiciFetch(
|
|
347
|
+
new Request(input).url,
|
|
348
|
+
{ ...init, dispatcher: getDispatcher(void 0, connectTimeoutMs) }
|
|
349
|
+
);
|
|
350
|
+
}
|
|
308
351
|
return fetch(input, init);
|
|
309
352
|
}
|
|
310
353
|
|
|
@@ -456,20 +499,20 @@ var DISPATCHER_EVENTS = {
|
|
|
456
499
|
};
|
|
457
500
|
|
|
458
501
|
// src/cli/cliLogger.ts
|
|
459
|
-
import
|
|
460
|
-
import
|
|
502
|
+
import path3 from "node:path";
|
|
503
|
+
import fs3 from "node:fs";
|
|
461
504
|
import chalk2 from "chalk";
|
|
462
505
|
import logger2 from "@wdio/logger";
|
|
463
506
|
var log2 = logger2("@wdio/browserstack-service/cli");
|
|
464
507
|
var BStackLogger2 = class {
|
|
465
|
-
static logFilePath =
|
|
466
|
-
static logFolderPath =
|
|
508
|
+
static logFilePath = path3.join(process.cwd(), LOGS_FILE);
|
|
509
|
+
static logFolderPath = path3.join(process.cwd(), "logs");
|
|
467
510
|
static logFileStream;
|
|
468
511
|
static logToFile(logMessage, logLevel) {
|
|
469
512
|
try {
|
|
470
513
|
if (!this.logFileStream) {
|
|
471
514
|
this.ensureLogsFolder();
|
|
472
|
-
this.logFileStream =
|
|
515
|
+
this.logFileStream = fs3.createWriteStream(this.logFilePath, { flags: "a" });
|
|
473
516
|
}
|
|
474
517
|
if (this.logFileStream && this.logFileStream.writable) {
|
|
475
518
|
this.logFileStream.write(this.formatLog(logMessage, logLevel));
|
|
@@ -513,13 +556,13 @@ var BStackLogger2 = class {
|
|
|
513
556
|
this.logFileStream = null;
|
|
514
557
|
}
|
|
515
558
|
static clearLogFile() {
|
|
516
|
-
if (
|
|
517
|
-
|
|
559
|
+
if (fs3.existsSync(this.logFilePath)) {
|
|
560
|
+
fs3.truncateSync(this.logFilePath);
|
|
518
561
|
}
|
|
519
562
|
}
|
|
520
563
|
static ensureLogsFolder() {
|
|
521
|
-
if (!
|
|
522
|
-
|
|
564
|
+
if (!fs3.existsSync(this.logFolderPath)) {
|
|
565
|
+
fs3.mkdirSync(this.logFolderPath);
|
|
523
566
|
}
|
|
524
567
|
}
|
|
525
568
|
};
|
|
@@ -574,6 +617,9 @@ var CLI_LOCK_POLL_MS = 1e3;
|
|
|
574
617
|
var CLI_DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
575
618
|
var CLI_DOWNLOAD_TMP_PREFIX = "downloaded_file_";
|
|
576
619
|
var CLI_DOWNLOAD_TMP_SUFFIX = ".zip";
|
|
620
|
+
var CLI_CONNECT_TIMEOUT_MS = Number(process.env.BROWSERSTACK_CLI_CONNECT_TIMEOUT_MS) || 60 * 1e3;
|
|
621
|
+
var CLI_FETCH_MAX_ATTEMPTS = 3;
|
|
622
|
+
var CLI_FETCH_RETRY_BASE_DELAY_MS = 500;
|
|
577
623
|
var CLIUtils = class _CLIUtils {
|
|
578
624
|
static automationFrameworkDetail = {};
|
|
579
625
|
static testFrameworkDetail = {};
|
|
@@ -598,6 +644,7 @@ var CLIUtils = class _CLIUtils {
|
|
|
598
644
|
modifiedOpts.browserStackLocalOptions = modifiedOpts.opts;
|
|
599
645
|
delete modifiedOpts.opts;
|
|
600
646
|
}
|
|
647
|
+
delete modifiedOpts.testManagementOptions;
|
|
601
648
|
modifiedOpts.testContextOptions = {
|
|
602
649
|
skipSessionName: isFalse(modifiedOpts.setSessionName),
|
|
603
650
|
skipSessionStatus: isFalse(modifiedOpts.setSessionStatus),
|
|
@@ -616,6 +663,8 @@ var CLIUtils = class _CLIUtils {
|
|
|
616
663
|
options.testObservability
|
|
617
664
|
);
|
|
618
665
|
const observabilityOptions = options.testObservabilityOptions || {};
|
|
666
|
+
const testManagementOptions = options.testManagementOptions || {};
|
|
667
|
+
const testPlanId = typeof testManagementOptions.testPlanId === "string" ? testManagementOptions.testPlanId.trim() : "";
|
|
619
668
|
const binconfig = {
|
|
620
669
|
userName: observabilityOptions.user || config.user,
|
|
621
670
|
accessKey: observabilityOptions.key || config.key,
|
|
@@ -627,26 +676,26 @@ var CLIUtils = class _CLIUtils {
|
|
|
627
676
|
binconfig.buildName = observabilityOptions.buildName || binconfig.buildName;
|
|
628
677
|
binconfig.projectName = observabilityOptions.projectName || binconfig.projectName;
|
|
629
678
|
binconfig.buildTag = this.getObservabilityBuildTags(observabilityOptions, buildTag) || [];
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
679
|
+
if (testPlanId.length > 0) {
|
|
680
|
+
binconfig.testManagementOptions = {
|
|
681
|
+
testPlanId
|
|
682
|
+
};
|
|
633
683
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
684
|
+
const caps = Array.isArray(capabilities) ? capabilities : [capabilities];
|
|
685
|
+
for (const cap of caps) {
|
|
686
|
+
const platform4 = {};
|
|
687
|
+
const capability = cap;
|
|
688
|
+
Object.keys(capability).filter((key) => key !== "bstack:options").forEach((key) => {
|
|
689
|
+
platform4[key] = capability[key];
|
|
690
|
+
});
|
|
691
|
+
if (capability["bstack:options"]) {
|
|
692
|
+
Object.keys(
|
|
693
|
+
capability["bstack:options"]
|
|
694
|
+
).forEach((key) => {
|
|
695
|
+
platform4[key] = capability["bstack:options"][key];
|
|
640
696
|
});
|
|
641
|
-
if (capability["bstack:options"]) {
|
|
642
|
-
Object.keys(
|
|
643
|
-
capability["bstack:options"]
|
|
644
|
-
).forEach((key) => {
|
|
645
|
-
platform4[key] = capability["bstack:options"][key];
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
binconfig.platforms.push(platform4);
|
|
649
697
|
}
|
|
698
|
+
binconfig.platforms.push(platform4);
|
|
650
699
|
}
|
|
651
700
|
return JSON.stringify(binconfig);
|
|
652
701
|
}
|
|
@@ -677,7 +726,7 @@ var CLIUtils = class _CLIUtils {
|
|
|
677
726
|
BStackLogger2.debug(`Resolved binary path: ${finalBinaryPath}`);
|
|
678
727
|
return finalBinaryPath;
|
|
679
728
|
} catch (err) {
|
|
680
|
-
BStackLogger2.
|
|
729
|
+
BStackLogger2.warn(
|
|
681
730
|
`Error in setting up cli path directory, Exception: ${util.format(err)}`
|
|
682
731
|
);
|
|
683
732
|
}
|
|
@@ -688,7 +737,7 @@ var CLIUtils = class _CLIUtils {
|
|
|
688
737
|
BStackLogger2.debug(
|
|
689
738
|
`Worker process detected, skipping CLI update. Using existing: ${existingCliPath}`
|
|
690
739
|
);
|
|
691
|
-
if (existingCliPath &&
|
|
740
|
+
if (existingCliPath && fs4.existsSync(existingCliPath)) {
|
|
692
741
|
return existingCliPath;
|
|
693
742
|
}
|
|
694
743
|
BStackLogger2.warn(
|
|
@@ -705,9 +754,20 @@ var CLIUtils = class _CLIUtils {
|
|
|
705
754
|
sdk_language: this.getSdkLanguage()
|
|
706
755
|
};
|
|
707
756
|
if (!isNullOrEmpty(existingCliPath)) {
|
|
708
|
-
|
|
757
|
+
if (this.isBinaryBusy(existingCliPath)) {
|
|
758
|
+
BStackLogger2.warn(`Existing binary is currently in use, skipping update: ${existingCliPath}`);
|
|
759
|
+
PerformanceTester.end(EVENTS.SDK_CLI_CHECK_UPDATE);
|
|
760
|
+
return existingCliPath;
|
|
761
|
+
}
|
|
762
|
+
const version3 = await this.runShellCommand(
|
|
709
763
|
`${existingCliPath} version`
|
|
710
764
|
);
|
|
765
|
+
if (version3.toLowerCase().includes("text file busy")) {
|
|
766
|
+
BStackLogger2.warn(`Binary busy during version check, skipping update: ${existingCliPath}`);
|
|
767
|
+
PerformanceTester.end(EVENTS.SDK_CLI_CHECK_UPDATE);
|
|
768
|
+
return existingCliPath;
|
|
769
|
+
}
|
|
770
|
+
queryParams.cli_version = version3;
|
|
711
771
|
}
|
|
712
772
|
const response = await this.requestToUpdateCLI(queryParams, config);
|
|
713
773
|
if (nestedKeyValue(response, ["updated_cli_version"])) {
|
|
@@ -737,8 +797,8 @@ var CLIUtils = class _CLIUtils {
|
|
|
737
797
|
if (isNullOrEmpty(writableDir)) {
|
|
738
798
|
throw new Error("No writable directory available for the CLI");
|
|
739
799
|
}
|
|
740
|
-
const cliDirPath =
|
|
741
|
-
if (!
|
|
800
|
+
const cliDirPath = path4.join(writableDir, "cli");
|
|
801
|
+
if (!fs4.existsSync(cliDirPath)) {
|
|
742
802
|
createDir(cliDirPath);
|
|
743
803
|
}
|
|
744
804
|
return cliDirPath;
|
|
@@ -752,37 +812,37 @@ var CLIUtils = class _CLIUtils {
|
|
|
752
812
|
static getWritableDir() {
|
|
753
813
|
const writableDirOptions = [
|
|
754
814
|
process.env.BROWSERSTACK_FILES_DIR,
|
|
755
|
-
|
|
756
|
-
|
|
815
|
+
path4.join(homedir(), ".browserstack"),
|
|
816
|
+
path4.join("tmp", ".browserstack")
|
|
757
817
|
];
|
|
758
|
-
for (const
|
|
759
|
-
if (isNullOrEmpty(
|
|
818
|
+
for (const path10 of writableDirOptions) {
|
|
819
|
+
if (isNullOrEmpty(path10)) {
|
|
760
820
|
continue;
|
|
761
821
|
}
|
|
762
822
|
try {
|
|
763
|
-
if (
|
|
764
|
-
BStackLogger2.debug(`File ${
|
|
765
|
-
if (!isWritable(
|
|
766
|
-
BStackLogger2.debug(`Giving write permission to ${
|
|
767
|
-
const success = setReadWriteAccess(
|
|
823
|
+
if (fs4.existsSync(path10)) {
|
|
824
|
+
BStackLogger2.debug(`File ${path10} already exist`);
|
|
825
|
+
if (!isWritable(path10)) {
|
|
826
|
+
BStackLogger2.debug(`Giving write permission to ${path10}`);
|
|
827
|
+
const success = setReadWriteAccess(path10);
|
|
768
828
|
if (!isTrue(success)) {
|
|
769
829
|
BStackLogger2.warn(
|
|
770
|
-
`Unable to provide write permission to ${
|
|
830
|
+
`Unable to provide write permission to ${path10}`
|
|
771
831
|
);
|
|
772
832
|
}
|
|
773
833
|
}
|
|
774
834
|
} else {
|
|
775
|
-
BStackLogger2.debug(`File does not exist: ${
|
|
776
|
-
createDir(
|
|
777
|
-
BStackLogger2.debug(`Giving write permission to ${
|
|
778
|
-
const success = setReadWriteAccess(
|
|
835
|
+
BStackLogger2.debug(`File does not exist: ${path10}`);
|
|
836
|
+
createDir(path10);
|
|
837
|
+
BStackLogger2.debug(`Giving write permission to ${path10}`);
|
|
838
|
+
const success = setReadWriteAccess(path10);
|
|
779
839
|
if (!isTrue(success)) {
|
|
780
840
|
BStackLogger2.warn(
|
|
781
|
-
`Unable to provide write permission to ${
|
|
841
|
+
`Unable to provide write permission to ${path10}`
|
|
782
842
|
);
|
|
783
843
|
}
|
|
784
844
|
}
|
|
785
|
-
return
|
|
845
|
+
return path10;
|
|
786
846
|
} catch (err) {
|
|
787
847
|
BStackLogger2.error(
|
|
788
848
|
`Unable to get writable directory, exception ${util.format(err)}`
|
|
@@ -793,16 +853,16 @@ var CLIUtils = class _CLIUtils {
|
|
|
793
853
|
}
|
|
794
854
|
static getExistingCliPath(cliDir) {
|
|
795
855
|
try {
|
|
796
|
-
if (!
|
|
856
|
+
if (!fs4.existsSync(cliDir) || !fs4.statSync(cliDir).isDirectory()) {
|
|
797
857
|
return "";
|
|
798
858
|
}
|
|
799
|
-
const allBinaries =
|
|
800
|
-
(filePath) =>
|
|
859
|
+
const allBinaries = fs4.readdirSync(cliDir).map((file) => path4.join(cliDir, file)).filter(
|
|
860
|
+
(filePath) => fs4.statSync(filePath).isFile() && path4.basename(filePath).startsWith("binary-")
|
|
801
861
|
);
|
|
802
862
|
if (allBinaries.length > 0) {
|
|
803
863
|
const latestBinary = allBinaries.map((filePath) => ({
|
|
804
864
|
filePath,
|
|
805
|
-
mtime:
|
|
865
|
+
mtime: fs4.statSync(filePath).mtime
|
|
806
866
|
})).reduce(
|
|
807
867
|
(latest, current) => {
|
|
808
868
|
if (!latest || !latest.mtime) {
|
|
@@ -823,6 +883,58 @@ var CLIUtils = class _CLIUtils {
|
|
|
823
883
|
return "";
|
|
824
884
|
}
|
|
825
885
|
}
|
|
886
|
+
static isBinaryBusy(binaryPath) {
|
|
887
|
+
if (isNullOrEmpty(binaryPath)) {
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
if (platform() === "darwin") {
|
|
891
|
+
return false;
|
|
892
|
+
}
|
|
893
|
+
if (!fs4.existsSync(binaryPath)) {
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
try {
|
|
897
|
+
const fd = fs4.openSync(binaryPath, "r+");
|
|
898
|
+
fs4.closeSync(fd);
|
|
899
|
+
return false;
|
|
900
|
+
} catch (err) {
|
|
901
|
+
if (BINARY_BUSY_ERROR_CODES.includes(err.code)) {
|
|
902
|
+
BStackLogger2.debug(`Binary is busy: ${binaryPath}`);
|
|
903
|
+
return true;
|
|
904
|
+
}
|
|
905
|
+
BStackLogger2.debug(`Error checking if binary is busy: ${err.message}`);
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* fetch wrapper for CLI network calls that (a) raises undici's connect timeout
|
|
911
|
+
* above the non-overridable 10s default of global fetch and (b) retries a few
|
|
912
|
+
* times with linear backoff on transient connection failures. Both are needed
|
|
913
|
+
* for reliability in constrained CI/containerised networks (SDK-6152).
|
|
914
|
+
*/
|
|
915
|
+
static fetchWithRetry = async (input, init, label) => {
|
|
916
|
+
let lastError;
|
|
917
|
+
for (let attempt = 1; attempt <= CLI_FETCH_MAX_ATTEMPTS; attempt++) {
|
|
918
|
+
try {
|
|
919
|
+
return await _fetch(input, init, { connectTimeoutMs: CLI_CONNECT_TIMEOUT_MS });
|
|
920
|
+
} catch (err) {
|
|
921
|
+
lastError = err;
|
|
922
|
+
const reason = (
|
|
923
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
924
|
+
err?.cause?.code || err?.code || err?.message
|
|
925
|
+
);
|
|
926
|
+
BStackLogger2.warn(
|
|
927
|
+
`${label} attempt ${attempt}/${CLI_FETCH_MAX_ATTEMPTS} failed: ${reason}`
|
|
928
|
+
);
|
|
929
|
+
if (attempt < CLI_FETCH_MAX_ATTEMPTS) {
|
|
930
|
+
await new Promise(
|
|
931
|
+
(resolve) => setTimeout(resolve, CLI_FETCH_RETRY_BASE_DELAY_MS * attempt)
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
throw lastError;
|
|
937
|
+
};
|
|
826
938
|
static requestToUpdateCLI = async (queryParams, config) => {
|
|
827
939
|
const params = new URLSearchParams(queryParams);
|
|
828
940
|
const requestInit = {
|
|
@@ -831,9 +943,10 @@ var CLIUtils = class _CLIUtils {
|
|
|
831
943
|
Authorization: `Basic ${Buffer.from(`${getBrowserStackUser(config)}:${getBrowserStackKey(config)}`).toString("base64")}`
|
|
832
944
|
}
|
|
833
945
|
};
|
|
834
|
-
const response = await
|
|
946
|
+
const response = await this.fetchWithRetry(
|
|
835
947
|
`${APIUtils.BROWSERSTACK_AUTOMATE_API_URL}/${UPDATED_CLI_ENDPOINT}?${params.toString()}`,
|
|
836
|
-
requestInit
|
|
948
|
+
requestInit,
|
|
949
|
+
"update_cli request"
|
|
837
950
|
);
|
|
838
951
|
const jsonResponse = await response.json();
|
|
839
952
|
BStackLogger2.debug(`response ${JSON.stringify(jsonResponse)}`);
|
|
@@ -858,11 +971,11 @@ var CLIUtils = class _CLIUtils {
|
|
|
858
971
|
});
|
|
859
972
|
}
|
|
860
973
|
static downloadLatestBinary = async (binDownloadUrl, cliDir) => {
|
|
861
|
-
const lockPath =
|
|
974
|
+
const lockPath = path4.join(cliDir, "download.lock");
|
|
862
975
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
863
976
|
const parseLockFile = () => {
|
|
864
977
|
try {
|
|
865
|
-
const content =
|
|
978
|
+
const content = fs4.readFileSync(lockPath, "utf8").trim();
|
|
866
979
|
const [pidLine, timestampLine] = content.split("\n");
|
|
867
980
|
const pid = Number.parseInt(pidLine, 10);
|
|
868
981
|
const timestamp = Number.parseInt(timestampLine, 10);
|
|
@@ -886,20 +999,20 @@ var CLIUtils = class _CLIUtils {
|
|
|
886
999
|
const start = Date.now();
|
|
887
1000
|
while (true) {
|
|
888
1001
|
try {
|
|
889
|
-
const fd =
|
|
1002
|
+
const fd = fs4.openSync(lockPath, "wx");
|
|
890
1003
|
try {
|
|
891
|
-
|
|
1004
|
+
fs4.writeFileSync(fd, `${process.pid}
|
|
892
1005
|
${Date.now()}
|
|
893
1006
|
`);
|
|
894
1007
|
} catch {
|
|
895
1008
|
}
|
|
896
1009
|
return () => {
|
|
897
1010
|
try {
|
|
898
|
-
|
|
1011
|
+
fs4.closeSync(fd);
|
|
899
1012
|
} catch {
|
|
900
1013
|
}
|
|
901
1014
|
try {
|
|
902
|
-
|
|
1015
|
+
fs4.unlinkSync(lockPath);
|
|
903
1016
|
} catch {
|
|
904
1017
|
}
|
|
905
1018
|
};
|
|
@@ -915,14 +1028,14 @@ ${Date.now()}
|
|
|
915
1028
|
`Stale CLI download lock detected (pid=${lockMeta.pid}, age=${lockAge}ms). Removing lock.`
|
|
916
1029
|
);
|
|
917
1030
|
try {
|
|
918
|
-
|
|
1031
|
+
fs4.unlinkSync(lockPath);
|
|
919
1032
|
} catch {
|
|
920
1033
|
}
|
|
921
1034
|
continue;
|
|
922
1035
|
}
|
|
923
1036
|
}
|
|
924
1037
|
const existingBinary = _CLIUtils.getExistingCliPath(cliDir);
|
|
925
|
-
if (existingBinary &&
|
|
1038
|
+
if (existingBinary && fs4.existsSync(existingBinary) && fs4.statSync(existingBinary).size > 0) {
|
|
926
1039
|
BStackLogger2.debug(
|
|
927
1040
|
`Binary appeared while waiting for lock: ${existingBinary}`
|
|
928
1041
|
);
|
|
@@ -943,14 +1056,17 @@ ${Date.now()}
|
|
|
943
1056
|
const cleanupTemporaryDownloads = (maxAgeMs = CLI_LOCK_TIMEOUT_MS) => {
|
|
944
1057
|
try {
|
|
945
1058
|
const now = Date.now();
|
|
946
|
-
|
|
947
|
-
|
|
1059
|
+
const tmpExtractRe = /\.tmp\.\d+$/;
|
|
1060
|
+
for (const entry of fs4.readdirSync(cliDir)) {
|
|
1061
|
+
const isDownloadZip = entry.startsWith(CLI_DOWNLOAD_TMP_PREFIX) && entry.endsWith(CLI_DOWNLOAD_TMP_SUFFIX);
|
|
1062
|
+
const isExtractTmp = tmpExtractRe.test(entry);
|
|
1063
|
+
if (!isDownloadZip && !isExtractTmp) {
|
|
948
1064
|
continue;
|
|
949
1065
|
}
|
|
950
|
-
const filePath =
|
|
1066
|
+
const filePath = path4.join(cliDir, entry);
|
|
951
1067
|
let stats;
|
|
952
1068
|
try {
|
|
953
|
-
stats =
|
|
1069
|
+
stats = fs4.statSync(filePath);
|
|
954
1070
|
} catch {
|
|
955
1071
|
continue;
|
|
956
1072
|
}
|
|
@@ -958,16 +1074,16 @@ ${Date.now()}
|
|
|
958
1074
|
continue;
|
|
959
1075
|
}
|
|
960
1076
|
try {
|
|
961
|
-
|
|
1077
|
+
fs4.unlinkSync(filePath);
|
|
962
1078
|
} catch (err) {
|
|
963
1079
|
BStackLogger2.debug(
|
|
964
|
-
`Failed to delete temp CLI
|
|
1080
|
+
`Failed to delete temp CLI file ${filePath}: ${util.format(err)}`
|
|
965
1081
|
);
|
|
966
1082
|
}
|
|
967
1083
|
}
|
|
968
1084
|
} catch (err) {
|
|
969
1085
|
BStackLogger2.debug(
|
|
970
|
-
`Failed to scan temp CLI
|
|
1086
|
+
`Failed to scan temp CLI files in ${cliDir}: ${util.format(err)}`
|
|
971
1087
|
);
|
|
972
1088
|
}
|
|
973
1089
|
};
|
|
@@ -998,7 +1114,7 @@ ${Date.now()}
|
|
|
998
1114
|
}
|
|
999
1115
|
releaseLock = lockResult;
|
|
1000
1116
|
const existingBinary = _CLIUtils.getExistingCliPath(cliDir);
|
|
1001
|
-
if (existingBinary &&
|
|
1117
|
+
if (existingBinary && fs4.existsSync(existingBinary) && fs4.statSync(existingBinary).size > 0) {
|
|
1002
1118
|
BStackLogger2.debug(
|
|
1003
1119
|
`Binary already exists after acquiring lock: ${existingBinary}`
|
|
1004
1120
|
);
|
|
@@ -1007,13 +1123,12 @@ ${Date.now()}
|
|
|
1007
1123
|
return existingBinary;
|
|
1008
1124
|
}
|
|
1009
1125
|
cleanupTemporaryDownloads();
|
|
1010
|
-
const zipFilePath =
|
|
1126
|
+
const zipFilePath = path4.join(
|
|
1011
1127
|
cliDir,
|
|
1012
1128
|
`${CLI_DOWNLOAD_TMP_PREFIX}${process.pid}_${Date.now()}${CLI_DOWNLOAD_TMP_SUFFIX}`
|
|
1013
1129
|
);
|
|
1014
|
-
const downloadedFileStream =
|
|
1130
|
+
const downloadedFileStream = fs4.createWriteStream(zipFilePath);
|
|
1015
1131
|
return new Promise((resolve, reject) => {
|
|
1016
|
-
const binaryName = null;
|
|
1017
1132
|
const processDownload = async () => {
|
|
1018
1133
|
const abortController = new AbortController();
|
|
1019
1134
|
const timeout = setTimeout(
|
|
@@ -1022,9 +1137,11 @@ ${Date.now()}
|
|
|
1022
1137
|
);
|
|
1023
1138
|
let response;
|
|
1024
1139
|
try {
|
|
1025
|
-
response = await _fetch(
|
|
1026
|
-
|
|
1027
|
-
|
|
1140
|
+
response = await _fetch(
|
|
1141
|
+
binDownloadUrl,
|
|
1142
|
+
{ signal: abortController.signal },
|
|
1143
|
+
{ connectTimeoutMs: CLI_CONNECT_TIMEOUT_MS }
|
|
1144
|
+
);
|
|
1028
1145
|
} finally {
|
|
1029
1146
|
clearTimeout(timeout);
|
|
1030
1147
|
}
|
|
@@ -1047,7 +1164,6 @@ ${Date.now()}
|
|
|
1047
1164
|
nodeStream.pipe(downloadedFileStream);
|
|
1048
1165
|
_CLIUtils.downloadFileStream(
|
|
1049
1166
|
downloadedFileStream,
|
|
1050
|
-
binaryName,
|
|
1051
1167
|
zipFilePath,
|
|
1052
1168
|
cliDir,
|
|
1053
1169
|
(result) => {
|
|
@@ -1081,40 +1197,104 @@ ${Date.now()}
|
|
|
1081
1197
|
return null;
|
|
1082
1198
|
}
|
|
1083
1199
|
};
|
|
1084
|
-
static downloadFileStream(downloadedFileStream,
|
|
1200
|
+
static downloadFileStream(downloadedFileStream, zipFilePath, cliDir, resolve, reject) {
|
|
1085
1201
|
downloadedFileStream.on("close", async function() {
|
|
1086
1202
|
const yauzlOpenPromise = promisify(yauzl.open);
|
|
1087
1203
|
try {
|
|
1088
1204
|
const zipfile = await yauzlOpenPromise(zipFilePath, {
|
|
1089
1205
|
lazyEntries: true
|
|
1090
1206
|
});
|
|
1207
|
+
let resolvedBinaryPath = null;
|
|
1091
1208
|
zipfile.readEntry();
|
|
1092
1209
|
zipfile.on("entry", async (entry) => {
|
|
1093
|
-
if (!binaryName) {
|
|
1094
|
-
binaryName = entry.fileName;
|
|
1095
|
-
}
|
|
1096
1210
|
if (/\/$/.test(entry.fileName)) {
|
|
1097
1211
|
zipfile.readEntry();
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
const candidatePath = path4.join(cliDir, entry.fileName);
|
|
1215
|
+
const resolvedCandidate = path4.resolve(candidatePath);
|
|
1216
|
+
const resolvedDir = path4.resolve(cliDir) + path4.sep;
|
|
1217
|
+
if (!resolvedCandidate.startsWith(resolvedDir)) {
|
|
1218
|
+
zipfile.close();
|
|
1219
|
+
reject(new Error(`Zip-slip detected: entry "${entry.fileName}" resolves outside ${cliDir}`));
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
const isBinaryEntry = path4.basename(entry.fileName).startsWith("binary-");
|
|
1223
|
+
if (!isBinaryEntry) {
|
|
1224
|
+
const directStream = fs4.createWriteStream(candidatePath);
|
|
1225
|
+
directStream.on("error", (writeErr) => {
|
|
1226
|
+
zipfile.close();
|
|
1227
|
+
reject(writeErr);
|
|
1228
|
+
});
|
|
1229
|
+
const openReadStreamPromise2 = promisify(
|
|
1103
1230
|
zipfile.openReadStream
|
|
1104
1231
|
).bind(zipfile);
|
|
1105
1232
|
try {
|
|
1106
|
-
const readStream = await
|
|
1233
|
+
const readStream = await openReadStreamPromise2(entry);
|
|
1107
1234
|
readStream.on("end", function() {
|
|
1108
|
-
|
|
1109
|
-
zipfile.readEntry();
|
|
1235
|
+
directStream.end();
|
|
1236
|
+
directStream.on("close", () => zipfile.readEntry());
|
|
1110
1237
|
});
|
|
1111
|
-
readStream.pipe(
|
|
1238
|
+
readStream.pipe(directStream);
|
|
1112
1239
|
} catch (zipErr) {
|
|
1240
|
+
zipfile.close();
|
|
1113
1241
|
reject(zipErr);
|
|
1114
1242
|
}
|
|
1115
|
-
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
const finalPath = candidatePath;
|
|
1246
|
+
const tempPath = path4.join(cliDir, `${entry.fileName}.tmp.${process.pid}`);
|
|
1247
|
+
const writeStream = fs4.createWriteStream(tempPath);
|
|
1248
|
+
let writeStreamErrored = false;
|
|
1249
|
+
writeStream.on("error", (writeErr) => {
|
|
1250
|
+
writeStreamErrored = true;
|
|
1251
|
+
fsp.unlink(tempPath).catch(() => {
|
|
1252
|
+
});
|
|
1253
|
+
zipfile.close();
|
|
1254
|
+
reject(writeErr);
|
|
1255
|
+
});
|
|
1256
|
+
writeStream.on("close", async () => {
|
|
1257
|
+
if (writeStreamErrored) {
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
try {
|
|
1261
|
+
await fsp.chmod(tempPath, "0755");
|
|
1262
|
+
try {
|
|
1263
|
+
await fsp.rename(tempPath, finalPath);
|
|
1264
|
+
} catch (renameErr) {
|
|
1265
|
+
if (renameErr.code !== "EXDEV") {
|
|
1266
|
+
throw renameErr;
|
|
1267
|
+
}
|
|
1268
|
+
BStackLogger2.warn(`Atomic rename failed (cross-device), falling back to copy: ${renameErr.message}`);
|
|
1269
|
+
await fsp.copyFile(tempPath, finalPath);
|
|
1270
|
+
await fsp.unlink(tempPath).catch(() => {
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
if (!resolvedBinaryPath) {
|
|
1274
|
+
resolvedBinaryPath = finalPath;
|
|
1275
|
+
}
|
|
1276
|
+
zipfile.readEntry();
|
|
1277
|
+
} catch (err) {
|
|
1278
|
+
await fsp.unlink(tempPath).catch(() => {
|
|
1279
|
+
});
|
|
1116
1280
|
zipfile.close();
|
|
1281
|
+
reject(err);
|
|
1117
1282
|
}
|
|
1283
|
+
});
|
|
1284
|
+
const openReadStreamPromise = promisify(
|
|
1285
|
+
zipfile.openReadStream
|
|
1286
|
+
).bind(zipfile);
|
|
1287
|
+
try {
|
|
1288
|
+
const readStream = await openReadStreamPromise(entry);
|
|
1289
|
+
readStream.on("end", function() {
|
|
1290
|
+
writeStream.end();
|
|
1291
|
+
});
|
|
1292
|
+
readStream.pipe(writeStream);
|
|
1293
|
+
} catch (zipErr) {
|
|
1294
|
+
fsp.unlink(tempPath).catch(() => {
|
|
1295
|
+
});
|
|
1296
|
+
zipfile.close();
|
|
1297
|
+
reject(zipErr);
|
|
1118
1298
|
}
|
|
1119
1299
|
});
|
|
1120
1300
|
zipfile.on("error", (zipErr) => {
|
|
@@ -1122,16 +1302,15 @@ ${Date.now()}
|
|
|
1122
1302
|
});
|
|
1123
1303
|
zipfile.once("end", () => {
|
|
1124
1304
|
fsp.unlink(zipFilePath).catch(() => {
|
|
1125
|
-
BStackLogger2.warn(
|
|
1126
|
-
`Failed to delete zip file: ${zipFilePath}`
|
|
1127
|
-
);
|
|
1128
|
-
});
|
|
1129
|
-
fsp.chmod(`${cliDir}/${binaryName}`, "0755").then(() => {
|
|
1130
|
-
resolve(`${cliDir}/${binaryName}`);
|
|
1131
|
-
}).catch((err) => {
|
|
1132
|
-
reject(err);
|
|
1305
|
+
BStackLogger2.warn(`Failed to delete zip file: ${zipFilePath}`);
|
|
1133
1306
|
});
|
|
1307
|
+
if (!resolvedBinaryPath) {
|
|
1308
|
+
zipfile.close();
|
|
1309
|
+
reject(new Error("No binary-* entry found in zip; cannot complete CLI binary extraction"));
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1134
1312
|
zipfile.close();
|
|
1313
|
+
resolve(resolvedBinaryPath);
|
|
1135
1314
|
});
|
|
1136
1315
|
} catch (err) {
|
|
1137
1316
|
reject(err);
|
|
@@ -1246,11 +1425,11 @@ var PerformanceTester = class _PerformanceTester {
|
|
|
1246
1425
|
static browser;
|
|
1247
1426
|
static scenarioThatRan;
|
|
1248
1427
|
static jsonReportDirName = "performance-report";
|
|
1249
|
-
static jsonReportDirPath =
|
|
1428
|
+
static jsonReportDirPath = path5.join(process.cwd(), "logs", this.jsonReportDirName);
|
|
1250
1429
|
static jsonReportFileName = `${this.jsonReportDirPath}/performance-report-${_PerformanceTester.getProcessId()}.json`;
|
|
1251
1430
|
static startMonitoring(csvName = "performance-report.csv") {
|
|
1252
|
-
if (!
|
|
1253
|
-
|
|
1431
|
+
if (!fs5.existsSync(this.jsonReportDirPath)) {
|
|
1432
|
+
fs5.mkdirSync(this.jsonReportDirPath, { recursive: true });
|
|
1254
1433
|
}
|
|
1255
1434
|
this._observer = new PerformanceObserver((list) => {
|
|
1256
1435
|
list.getEntries().filter((entry) => entry.entryType === "measure").forEach(
|
|
@@ -1324,10 +1503,10 @@ var PerformanceTester = class _PerformanceTester {
|
|
|
1324
1503
|
this.started = false;
|
|
1325
1504
|
this.generateCSV(this._events);
|
|
1326
1505
|
const content = this.generateReport(this._events);
|
|
1327
|
-
const dir =
|
|
1506
|
+
const dir = path5.join(process.cwd(), filename);
|
|
1328
1507
|
try {
|
|
1329
1508
|
await fsPromise.writeFile(dir, content);
|
|
1330
|
-
BStackLogger.info(`Performance report is at ${
|
|
1509
|
+
BStackLogger.info(`Performance report is at ${path5}`);
|
|
1331
1510
|
} catch (err) {
|
|
1332
1511
|
BStackLogger.error(`Error in writing html ${util2.format(err)}`);
|
|
1333
1512
|
}
|
|
@@ -1468,6 +1647,7 @@ var PerformanceTester = class _PerformanceTester {
|
|
|
1468
1647
|
static end(event, success = true, failure, details = {}) {
|
|
1469
1648
|
performance.mark(event + "-end");
|
|
1470
1649
|
performance.measure(event, event + "-start", event + "-end");
|
|
1650
|
+
delete this.eventsMap[event + "-start"];
|
|
1471
1651
|
this.details[event] = Object.assign({ success, failure: util2.format(failure) }, Object.assign(Object.assign({
|
|
1472
1652
|
clientWorkerId: _PerformanceTester.getClientWorkerId(),
|
|
1473
1653
|
worker: _PerformanceTester.getProcessId(),
|
|
@@ -1497,7 +1677,7 @@ var PerformanceTester = class _PerformanceTester {
|
|
|
1497
1677
|
this.start(EVENTS.SDK_KEY_METRICS_PREPARATION);
|
|
1498
1678
|
let measures = [];
|
|
1499
1679
|
if (await fsPromise.access(this.jsonReportDirPath).then(() => true).catch(() => false)) {
|
|
1500
|
-
const files = (await fsPromise.readdir(this.jsonReportDirPath)).map((file) =>
|
|
1680
|
+
const files = (await fsPromise.readdir(this.jsonReportDirPath)).map((file) => path5.resolve(this.jsonReportDirPath, file));
|
|
1501
1681
|
measures = (await Promise.all(files.map((file) => fsPromise.readFile(file, "utf-8")))).map((el) => `[${el.slice(0, -1)}]`).map((el) => JSON.parse(el)).flat();
|
|
1502
1682
|
}
|
|
1503
1683
|
BStackLogger.debug(`[Performance Upload] Total events from files: ${measures.length}`);
|
|
@@ -1574,7 +1754,7 @@ var PerformanceTester = class _PerformanceTester {
|
|
|
1574
1754
|
if (await fsPromise.access(this.jsonReportDirPath).then(() => true, () => false)) {
|
|
1575
1755
|
const files = await fsPromise.readdir(this.jsonReportDirPath);
|
|
1576
1756
|
for (const file of files) {
|
|
1577
|
-
await fsPromise.unlink(
|
|
1757
|
+
await fsPromise.unlink(path5.join(this.jsonReportDirPath, file));
|
|
1578
1758
|
}
|
|
1579
1759
|
BStackLogger.debug(`[Performance Upload] Cleaned up ${files.length} temporary report files`);
|
|
1580
1760
|
}
|
|
@@ -1619,17 +1799,18 @@ var logBuildError = (error, product = "") => {
|
|
|
1619
1799
|
}
|
|
1620
1800
|
};
|
|
1621
1801
|
var getProductMapForBuildStartCall = (config, accessibilityAutomation) => {
|
|
1622
|
-
|
|
1623
|
-
observability
|
|
1624
|
-
accessibility
|
|
1625
|
-
percy
|
|
1626
|
-
automate
|
|
1627
|
-
app_automate
|
|
1628
|
-
|
|
1802
|
+
const entries = [
|
|
1803
|
+
["observability", config.testObservability.enabled],
|
|
1804
|
+
["accessibility", accessibilityAutomation],
|
|
1805
|
+
["percy", config.percy],
|
|
1806
|
+
["automate", config.automate],
|
|
1807
|
+
["app_automate", config.appAutomate]
|
|
1808
|
+
];
|
|
1809
|
+
return Object.fromEntries(entries.filter(([, v]) => v !== null));
|
|
1629
1810
|
};
|
|
1630
1811
|
|
|
1631
1812
|
// src/testorchestration/testorcherstrationutils.ts
|
|
1632
|
-
import
|
|
1813
|
+
import fs6 from "node:fs";
|
|
1633
1814
|
var RUN_SMART_SELECTION = "runSmartSelection";
|
|
1634
1815
|
var ALLOWED_ORCHESTRATION_KEYS = [
|
|
1635
1816
|
RUN_SMART_SELECTION
|
|
@@ -1859,13 +2040,13 @@ var OrchestrationUtils = class _OrchestrationUtils {
|
|
|
1859
2040
|
* @returns Formatted list of repository configurations
|
|
1860
2041
|
*/
|
|
1861
2042
|
_loadSourceFromFile(filePath) {
|
|
1862
|
-
if (!
|
|
2043
|
+
if (!fs6.existsSync(filePath)) {
|
|
1863
2044
|
BStackLogger.error(`Source file '${filePath}' does not exist.`);
|
|
1864
2045
|
return [];
|
|
1865
2046
|
}
|
|
1866
2047
|
let data = null;
|
|
1867
2048
|
try {
|
|
1868
|
-
const fileContent =
|
|
2049
|
+
const fileContent = fs6.readFileSync(filePath, "utf8");
|
|
1869
2050
|
data = JSON.parse(fileContent);
|
|
1870
2051
|
} catch (error) {
|
|
1871
2052
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2448,12 +2629,11 @@ var usageStats_default = UsageStats;
|
|
|
2448
2629
|
|
|
2449
2630
|
// src/util.ts
|
|
2450
2631
|
import { create } from "tar";
|
|
2451
|
-
import { fileFromPath } from "formdata-node/file-from-path";
|
|
2452
2632
|
|
|
2453
2633
|
// src/scripts/accessibility-scripts.ts
|
|
2454
|
-
import
|
|
2455
|
-
import
|
|
2456
|
-
import
|
|
2634
|
+
import path6 from "node:path";
|
|
2635
|
+
import fs7 from "node:fs";
|
|
2636
|
+
import os2 from "node:os";
|
|
2457
2637
|
var AccessibilityScripts = class _AccessibilityScripts {
|
|
2458
2638
|
static instance = null;
|
|
2459
2639
|
performScan = null;
|
|
@@ -2467,7 +2647,7 @@ var AccessibilityScripts = class _AccessibilityScripts {
|
|
|
2467
2647
|
// don't allow to create instances from it other than through `checkAndGetInstance`
|
|
2468
2648
|
constructor() {
|
|
2469
2649
|
this.browserstackFolderPath = this.getWritableDir();
|
|
2470
|
-
this.commandsPath =
|
|
2650
|
+
this.commandsPath = path6.join(this.browserstackFolderPath, "commands.json");
|
|
2471
2651
|
}
|
|
2472
2652
|
static checkAndGetInstance() {
|
|
2473
2653
|
if (!_AccessibilityScripts.instance) {
|
|
@@ -2479,17 +2659,17 @@ var AccessibilityScripts = class _AccessibilityScripts {
|
|
|
2479
2659
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2480
2660
|
getWritableDir() {
|
|
2481
2661
|
const orderedPaths = [
|
|
2482
|
-
|
|
2662
|
+
path6.join(os2.homedir(), ".browserstack"),
|
|
2483
2663
|
process.cwd(),
|
|
2484
|
-
|
|
2664
|
+
os2.tmpdir()
|
|
2485
2665
|
];
|
|
2486
2666
|
for (const orderedPath of orderedPaths) {
|
|
2487
2667
|
try {
|
|
2488
|
-
if (
|
|
2489
|
-
|
|
2668
|
+
if (fs7.existsSync(orderedPath)) {
|
|
2669
|
+
fs7.accessSync(orderedPath);
|
|
2490
2670
|
return orderedPath;
|
|
2491
2671
|
}
|
|
2492
|
-
|
|
2672
|
+
fs7.mkdirSync(orderedPath, { recursive: true });
|
|
2493
2673
|
return orderedPath;
|
|
2494
2674
|
} catch (error) {
|
|
2495
2675
|
}
|
|
@@ -2498,8 +2678,8 @@ var AccessibilityScripts = class _AccessibilityScripts {
|
|
|
2498
2678
|
}
|
|
2499
2679
|
readFromExistingFile() {
|
|
2500
2680
|
try {
|
|
2501
|
-
if (
|
|
2502
|
-
const data =
|
|
2681
|
+
if (fs7.existsSync(this.commandsPath)) {
|
|
2682
|
+
const data = fs7.readFileSync(this.commandsPath, "utf8");
|
|
2503
2683
|
if (data) {
|
|
2504
2684
|
this.update(JSON.parse(data));
|
|
2505
2685
|
}
|
|
@@ -2522,10 +2702,10 @@ var AccessibilityScripts = class _AccessibilityScripts {
|
|
|
2522
2702
|
}
|
|
2523
2703
|
}
|
|
2524
2704
|
store() {
|
|
2525
|
-
if (!
|
|
2526
|
-
|
|
2705
|
+
if (!fs7.existsSync(this.browserstackFolderPath)) {
|
|
2706
|
+
fs7.mkdirSync(this.browserstackFolderPath);
|
|
2527
2707
|
}
|
|
2528
|
-
|
|
2708
|
+
fs7.writeFileSync(this.commandsPath, JSON.stringify({
|
|
2529
2709
|
commands: this.commandsToWrap,
|
|
2530
2710
|
scripts: {
|
|
2531
2711
|
scan: this.performScan,
|
|
@@ -2682,7 +2862,10 @@ var launchTestSession = PerformanceTester.measureWrapper(TESTHUB_EVENTS.START, o
|
|
|
2682
2862
|
},
|
|
2683
2863
|
product_map: getProductMapForBuildStartCall(bStackConfig, accessibilityAutomation),
|
|
2684
2864
|
config: {},
|
|
2685
|
-
test_orchestration: OrchestrationUtils.getInstance(config)?.getBuildStartData() || {}
|
|
2865
|
+
test_orchestration: OrchestrationUtils.getInstance(config)?.getBuildStartData() || {},
|
|
2866
|
+
test_management: {
|
|
2867
|
+
test_plan_id: getTestPlanId(options)
|
|
2868
|
+
}
|
|
2686
2869
|
};
|
|
2687
2870
|
if (accessibilityAutomation && (isTurboScale(options) || data.browserstackAutomation === false)) {
|
|
2688
2871
|
data.accessibility.settings ??= {};
|
|
@@ -2711,6 +2894,7 @@ var launchTestSession = PerformanceTester.measureWrapper(TESTHUB_EVENTS.START, o
|
|
|
2711
2894
|
const jsonResponse = await response.json();
|
|
2712
2895
|
delete data?.accessibility?.settings?.includeEncodedExtension;
|
|
2713
2896
|
BStackLogger.debug(`[Start_Build] Success response: ${JSON.stringify(jsonResponse)}`);
|
|
2897
|
+
BStackLogger.debug(`Test Plan Id sent in request: ${getTestPlanId(options)}`);
|
|
2714
2898
|
process.env[TESTOPS_BUILD_COMPLETED_ENV] = "true";
|
|
2715
2899
|
if (jsonResponse.jwt) {
|
|
2716
2900
|
process.env[BROWSERSTACK_TESTHUB_JWT] = jsonResponse.jwt;
|
|
@@ -3220,7 +3404,7 @@ function getObservabilityBuild(options, bstackBuildName) {
|
|
|
3220
3404
|
if (options.testObservabilityOptions && options.testObservabilityOptions.buildName) {
|
|
3221
3405
|
return options.testObservabilityOptions.buildName;
|
|
3222
3406
|
}
|
|
3223
|
-
return bstackBuildName ||
|
|
3407
|
+
return bstackBuildName || path7.basename(path7.resolve(process.cwd()));
|
|
3224
3408
|
}
|
|
3225
3409
|
function getObservabilityBuildTags(options, bstackBuildTag) {
|
|
3226
3410
|
if (process.env.TEST_OBSERVABILITY_BUILD_TAG) {
|
|
@@ -3234,6 +3418,25 @@ function getObservabilityBuildTags(options, bstackBuildTag) {
|
|
|
3234
3418
|
}
|
|
3235
3419
|
return [];
|
|
3236
3420
|
}
|
|
3421
|
+
function getTestPlanId(options) {
|
|
3422
|
+
if (process.env[BROWSERSTACK_TEST_PLAN_ID]) {
|
|
3423
|
+
return process.env[BROWSERSTACK_TEST_PLAN_ID];
|
|
3424
|
+
}
|
|
3425
|
+
const CLI_ARG = "--browserstack.testManagementOptions.testPlanId";
|
|
3426
|
+
const argIndex = process.argv.indexOf(CLI_ARG);
|
|
3427
|
+
if (argIndex !== -1 && process.argv[argIndex + 1]) {
|
|
3428
|
+
return process.argv[argIndex + 1];
|
|
3429
|
+
}
|
|
3430
|
+
const argWithEquals = process.argv.find((arg) => arg.startsWith(`${CLI_ARG}=`));
|
|
3431
|
+
if (argWithEquals) {
|
|
3432
|
+
return argWithEquals.split("=")[1];
|
|
3433
|
+
}
|
|
3434
|
+
const testPlanId = options.testManagementOptions?.testPlanId;
|
|
3435
|
+
if (typeof testPlanId === "string" && testPlanId.trim().length > 0) {
|
|
3436
|
+
return testPlanId.trim();
|
|
3437
|
+
}
|
|
3438
|
+
return void 0;
|
|
3439
|
+
}
|
|
3237
3440
|
function getBrowserStackUser(config) {
|
|
3238
3441
|
if (process.env.BROWSERSTACK_USERNAME) {
|
|
3239
3442
|
return process.env.BROWSERSTACK_USERNAME;
|
|
@@ -3295,6 +3498,45 @@ var getPlatformVersion = o11yErrorHandler(function getPlatformVersion2(caps, use
|
|
|
3295
3498
|
}
|
|
3296
3499
|
return void 0;
|
|
3297
3500
|
});
|
|
3501
|
+
var getResolvedDeviceName = o11yErrorHandler(function getResolvedDeviceName2(driverCaps, requestedCaps) {
|
|
3502
|
+
const flattenMultiremote = (caps) => {
|
|
3503
|
+
if (!caps) {
|
|
3504
|
+
return [];
|
|
3505
|
+
}
|
|
3506
|
+
const obj = caps;
|
|
3507
|
+
if (obj["deviceModel"] || obj["appium:deviceModel"] || obj["deviceName"] || obj["bstack:options"]) {
|
|
3508
|
+
return [caps];
|
|
3509
|
+
}
|
|
3510
|
+
return Object.values(obj).filter((v) => v !== null && typeof v === "object" && "capabilities" in v).map((v) => v.capabilities).filter(Boolean);
|
|
3511
|
+
};
|
|
3512
|
+
const sources = [
|
|
3513
|
+
...flattenMultiremote(driverCaps),
|
|
3514
|
+
...flattenMultiremote(requestedCaps)
|
|
3515
|
+
];
|
|
3516
|
+
if (!sources.length) {
|
|
3517
|
+
return void 0;
|
|
3518
|
+
}
|
|
3519
|
+
const pickString = (obj, key) => {
|
|
3520
|
+
const v = obj?.[key];
|
|
3521
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
3522
|
+
};
|
|
3523
|
+
const paths = [
|
|
3524
|
+
(c) => pickString(c, "deviceModel"),
|
|
3525
|
+
(c) => pickString(c, "appium:deviceModel"),
|
|
3526
|
+
(c) => pickString(c["bstack:options"], "deviceName"),
|
|
3527
|
+
(c) => pickString(c, "appium:deviceName"),
|
|
3528
|
+
(c) => pickString(c, "deviceName")
|
|
3529
|
+
];
|
|
3530
|
+
for (const path10 of paths) {
|
|
3531
|
+
for (const src of sources) {
|
|
3532
|
+
const v = path10(src);
|
|
3533
|
+
if (v) {
|
|
3534
|
+
return v;
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
return void 0;
|
|
3539
|
+
});
|
|
3298
3540
|
var isObjectEmpty = (objectName) => {
|
|
3299
3541
|
return objectName && Object.keys(objectName).length === 0 && objectName.constructor === Object;
|
|
3300
3542
|
};
|
|
@@ -3456,28 +3698,28 @@ function nestedKeyValue(hash, keys) {
|
|
|
3456
3698
|
return keys.reduce((hash2, key) => isHash(hash2) ? hash2[key] : void 0, hash);
|
|
3457
3699
|
}
|
|
3458
3700
|
function removeDir(dir) {
|
|
3459
|
-
const list =
|
|
3701
|
+
const list = fs8.readdirSync(dir);
|
|
3460
3702
|
for (let i = 0; i < list.length; i++) {
|
|
3461
|
-
const filename =
|
|
3462
|
-
const stat =
|
|
3703
|
+
const filename = path7.join(dir, list[i]);
|
|
3704
|
+
const stat = fs8.statSync(filename);
|
|
3463
3705
|
if (filename === "." || filename === "..") {
|
|
3464
3706
|
} else if (stat.isDirectory()) {
|
|
3465
3707
|
removeDir(filename);
|
|
3466
3708
|
} else {
|
|
3467
|
-
|
|
3709
|
+
fs8.unlinkSync(filename);
|
|
3468
3710
|
}
|
|
3469
3711
|
}
|
|
3470
|
-
|
|
3712
|
+
fs8.rmdirSync(dir);
|
|
3471
3713
|
}
|
|
3472
3714
|
function createDir(dir) {
|
|
3473
|
-
if (
|
|
3715
|
+
if (fs8.existsSync(dir)) {
|
|
3474
3716
|
removeDir(dir);
|
|
3475
3717
|
}
|
|
3476
|
-
|
|
3718
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
3477
3719
|
}
|
|
3478
3720
|
function isWritable(dirPath) {
|
|
3479
3721
|
try {
|
|
3480
|
-
|
|
3722
|
+
fs8.accessSync(dirPath, fs8.constants.W_OK);
|
|
3481
3723
|
return true;
|
|
3482
3724
|
} catch {
|
|
3483
3725
|
return false;
|
|
@@ -3485,7 +3727,7 @@ function isWritable(dirPath) {
|
|
|
3485
3727
|
}
|
|
3486
3728
|
function setReadWriteAccess(dirPath) {
|
|
3487
3729
|
try {
|
|
3488
|
-
|
|
3730
|
+
fs8.chmodSync(dirPath, 438);
|
|
3489
3731
|
BStackLogger.debug(`Directory ${dirPath} is now read/write accessible.`);
|
|
3490
3732
|
} catch (err) {
|
|
3491
3733
|
BStackLogger.error(`Failed to set directory access: ${err.stack}`);
|
|
@@ -3493,19 +3735,19 @@ function setReadWriteAccess(dirPath) {
|
|
|
3493
3735
|
}
|
|
3494
3736
|
|
|
3495
3737
|
// src/cleanup.ts
|
|
3496
|
-
import
|
|
3738
|
+
import fs11 from "node:fs";
|
|
3497
3739
|
import util5 from "node:util";
|
|
3498
3740
|
|
|
3499
3741
|
// src/instrumentation/funnelInstrumentation.ts
|
|
3500
|
-
import
|
|
3742
|
+
import os3 from "node:os";
|
|
3501
3743
|
import util4, { format as format2 } from "node:util";
|
|
3502
|
-
import
|
|
3503
|
-
import
|
|
3744
|
+
import path9 from "node:path";
|
|
3745
|
+
import fs10 from "node:fs";
|
|
3504
3746
|
|
|
3505
3747
|
// src/data-store.ts
|
|
3506
|
-
import
|
|
3507
|
-
import
|
|
3508
|
-
var workersDataDirPath =
|
|
3748
|
+
import path8 from "node:path";
|
|
3749
|
+
import fs9 from "node:fs";
|
|
3750
|
+
var workersDataDirPath = path8.join(process.cwd(), "logs", "worker_data");
|
|
3509
3751
|
|
|
3510
3752
|
// src/instrumentation/funnelInstrumentation.ts
|
|
3511
3753
|
function redactCredentialsFromFunnelData(data) {
|
|
@@ -3611,7 +3853,7 @@ Visit https://automation.browserstack.com/builds/${process.env[BROWSERSTACK_TEST
|
|
|
3611
3853
|
if (!filePath) {
|
|
3612
3854
|
return null;
|
|
3613
3855
|
}
|
|
3614
|
-
const content =
|
|
3856
|
+
const content = fs11.readFileSync(filePath, "utf8");
|
|
3615
3857
|
const data = JSON.parse(content);
|
|
3616
3858
|
this.removeFunnelDataFile(filePath);
|
|
3617
3859
|
return data;
|
|
@@ -3620,7 +3862,7 @@ Visit https://automation.browserstack.com/builds/${process.env[BROWSERSTACK_TEST
|
|
|
3620
3862
|
if (!filePath) {
|
|
3621
3863
|
return;
|
|
3622
3864
|
}
|
|
3623
|
-
|
|
3865
|
+
fs11.rmSync(filePath, { force: true });
|
|
3624
3866
|
}
|
|
3625
3867
|
};
|
|
3626
3868
|
void BStackCleanup.startCleanup();
|