@wdio/browserstack-service 9.27.2 → 9.29.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 +237 -118
- package/build/cli/cliUtils.d.ts +7 -0
- package/build/cli/cliUtils.d.ts.map +1 -1
- package/build/cli/index.d.ts +8 -0
- package/build/cli/index.d.ts.map +1 -1
- package/build/config.d.ts +3 -3
- package/build/config.d.ts.map +1 -1
- package/build/constants.d.ts +11 -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 +994 -645
- package/build/insights-handler.d.ts.map +1 -1
- package/build/instrumentation/funnelInstrumentation.d.ts.map +1 -1
- package/build/launcher.d.ts.map +1 -1
- package/build/reporter.d.ts.map +1 -1
- package/build/service.d.ts +1 -0
- package/build/service.d.ts.map +1 -1
- package/build/types.d.ts +11 -0
- package/build/types.d.ts.map +1 -1
- package/build/util.d.ts +22 -0
- package/build/util.d.ts.map +1 -1
- package/package.json +6 -6
package/build/cleanup.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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";
|
|
@@ -53,12 +53,12 @@ var logPatcher_default = logPatcher;
|
|
|
53
53
|
|
|
54
54
|
// src/instrumentation/performance/performance-tester.ts
|
|
55
55
|
import { createObjectCsvWriter } from "csv-writer";
|
|
56
|
-
import
|
|
56
|
+
import fs5 from "node:fs";
|
|
57
57
|
import fsPromise from "node:fs/promises";
|
|
58
58
|
import { performance, PerformanceObserver } from "node:perf_hooks";
|
|
59
59
|
import util2 from "node:util";
|
|
60
60
|
import worker from "node:worker_threads";
|
|
61
|
-
import
|
|
61
|
+
import path5 from "node:path";
|
|
62
62
|
import { arch as arch2, hostname, platform as platform2, type, version } from "node:os";
|
|
63
63
|
|
|
64
64
|
// src/bstackLogger.ts
|
|
@@ -70,7 +70,7 @@ import logger from "@wdio/logger";
|
|
|
70
70
|
// package.json
|
|
71
71
|
var package_default = {
|
|
72
72
|
name: "@wdio/browserstack-service",
|
|
73
|
-
version: "9.
|
|
73
|
+
version: "9.28.0",
|
|
74
74
|
description: "WebdriverIO service for better Browserstack integration",
|
|
75
75
|
author: "Adam Bjerstedt <abjerstedt@gmail.com>",
|
|
76
76
|
homepage: "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-browserstack-service",
|
|
@@ -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 = {};
|
|
@@ -680,7 +726,7 @@ var CLIUtils = class _CLIUtils {
|
|
|
680
726
|
BStackLogger2.debug(`Resolved binary path: ${finalBinaryPath}`);
|
|
681
727
|
return finalBinaryPath;
|
|
682
728
|
} catch (err) {
|
|
683
|
-
BStackLogger2.
|
|
729
|
+
BStackLogger2.warn(
|
|
684
730
|
`Error in setting up cli path directory, Exception: ${util.format(err)}`
|
|
685
731
|
);
|
|
686
732
|
}
|
|
@@ -691,7 +737,7 @@ var CLIUtils = class _CLIUtils {
|
|
|
691
737
|
BStackLogger2.debug(
|
|
692
738
|
`Worker process detected, skipping CLI update. Using existing: ${existingCliPath}`
|
|
693
739
|
);
|
|
694
|
-
if (existingCliPath &&
|
|
740
|
+
if (existingCliPath && fs4.existsSync(existingCliPath)) {
|
|
695
741
|
return existingCliPath;
|
|
696
742
|
}
|
|
697
743
|
BStackLogger2.warn(
|
|
@@ -751,8 +797,8 @@ var CLIUtils = class _CLIUtils {
|
|
|
751
797
|
if (isNullOrEmpty(writableDir)) {
|
|
752
798
|
throw new Error("No writable directory available for the CLI");
|
|
753
799
|
}
|
|
754
|
-
const cliDirPath =
|
|
755
|
-
if (!
|
|
800
|
+
const cliDirPath = path4.join(writableDir, "cli");
|
|
801
|
+
if (!fs4.existsSync(cliDirPath)) {
|
|
756
802
|
createDir(cliDirPath);
|
|
757
803
|
}
|
|
758
804
|
return cliDirPath;
|
|
@@ -766,37 +812,37 @@ var CLIUtils = class _CLIUtils {
|
|
|
766
812
|
static getWritableDir() {
|
|
767
813
|
const writableDirOptions = [
|
|
768
814
|
process.env.BROWSERSTACK_FILES_DIR,
|
|
769
|
-
|
|
770
|
-
|
|
815
|
+
path4.join(homedir(), ".browserstack"),
|
|
816
|
+
path4.join("tmp", ".browserstack")
|
|
771
817
|
];
|
|
772
|
-
for (const
|
|
773
|
-
if (isNullOrEmpty(
|
|
818
|
+
for (const path10 of writableDirOptions) {
|
|
819
|
+
if (isNullOrEmpty(path10)) {
|
|
774
820
|
continue;
|
|
775
821
|
}
|
|
776
822
|
try {
|
|
777
|
-
if (
|
|
778
|
-
BStackLogger2.debug(`File ${
|
|
779
|
-
if (!isWritable(
|
|
780
|
-
BStackLogger2.debug(`Giving write permission to ${
|
|
781
|
-
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);
|
|
782
828
|
if (!isTrue(success)) {
|
|
783
829
|
BStackLogger2.warn(
|
|
784
|
-
`Unable to provide write permission to ${
|
|
830
|
+
`Unable to provide write permission to ${path10}`
|
|
785
831
|
);
|
|
786
832
|
}
|
|
787
833
|
}
|
|
788
834
|
} else {
|
|
789
|
-
BStackLogger2.debug(`File does not exist: ${
|
|
790
|
-
createDir(
|
|
791
|
-
BStackLogger2.debug(`Giving write permission to ${
|
|
792
|
-
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);
|
|
793
839
|
if (!isTrue(success)) {
|
|
794
840
|
BStackLogger2.warn(
|
|
795
|
-
`Unable to provide write permission to ${
|
|
841
|
+
`Unable to provide write permission to ${path10}`
|
|
796
842
|
);
|
|
797
843
|
}
|
|
798
844
|
}
|
|
799
|
-
return
|
|
845
|
+
return path10;
|
|
800
846
|
} catch (err) {
|
|
801
847
|
BStackLogger2.error(
|
|
802
848
|
`Unable to get writable directory, exception ${util.format(err)}`
|
|
@@ -807,16 +853,16 @@ var CLIUtils = class _CLIUtils {
|
|
|
807
853
|
}
|
|
808
854
|
static getExistingCliPath(cliDir) {
|
|
809
855
|
try {
|
|
810
|
-
if (!
|
|
856
|
+
if (!fs4.existsSync(cliDir) || !fs4.statSync(cliDir).isDirectory()) {
|
|
811
857
|
return "";
|
|
812
858
|
}
|
|
813
|
-
const allBinaries =
|
|
814
|
-
(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-")
|
|
815
861
|
);
|
|
816
862
|
if (allBinaries.length > 0) {
|
|
817
863
|
const latestBinary = allBinaries.map((filePath) => ({
|
|
818
864
|
filePath,
|
|
819
|
-
mtime:
|
|
865
|
+
mtime: fs4.statSync(filePath).mtime
|
|
820
866
|
})).reduce(
|
|
821
867
|
(latest, current) => {
|
|
822
868
|
if (!latest || !latest.mtime) {
|
|
@@ -844,12 +890,12 @@ var CLIUtils = class _CLIUtils {
|
|
|
844
890
|
if (platform() === "darwin") {
|
|
845
891
|
return false;
|
|
846
892
|
}
|
|
847
|
-
if (!
|
|
893
|
+
if (!fs4.existsSync(binaryPath)) {
|
|
848
894
|
return false;
|
|
849
895
|
}
|
|
850
896
|
try {
|
|
851
|
-
const fd =
|
|
852
|
-
|
|
897
|
+
const fd = fs4.openSync(binaryPath, "r+");
|
|
898
|
+
fs4.closeSync(fd);
|
|
853
899
|
return false;
|
|
854
900
|
} catch (err) {
|
|
855
901
|
if (BINARY_BUSY_ERROR_CODES.includes(err.code)) {
|
|
@@ -860,6 +906,35 @@ var CLIUtils = class _CLIUtils {
|
|
|
860
906
|
return false;
|
|
861
907
|
}
|
|
862
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
|
+
};
|
|
863
938
|
static requestToUpdateCLI = async (queryParams, config) => {
|
|
864
939
|
const params = new URLSearchParams(queryParams);
|
|
865
940
|
const requestInit = {
|
|
@@ -868,9 +943,10 @@ var CLIUtils = class _CLIUtils {
|
|
|
868
943
|
Authorization: `Basic ${Buffer.from(`${getBrowserStackUser(config)}:${getBrowserStackKey(config)}`).toString("base64")}`
|
|
869
944
|
}
|
|
870
945
|
};
|
|
871
|
-
const response = await
|
|
946
|
+
const response = await this.fetchWithRetry(
|
|
872
947
|
`${APIUtils.BROWSERSTACK_AUTOMATE_API_URL}/${UPDATED_CLI_ENDPOINT}?${params.toString()}`,
|
|
873
|
-
requestInit
|
|
948
|
+
requestInit,
|
|
949
|
+
"update_cli request"
|
|
874
950
|
);
|
|
875
951
|
const jsonResponse = await response.json();
|
|
876
952
|
BStackLogger2.debug(`response ${JSON.stringify(jsonResponse)}`);
|
|
@@ -895,11 +971,11 @@ var CLIUtils = class _CLIUtils {
|
|
|
895
971
|
});
|
|
896
972
|
}
|
|
897
973
|
static downloadLatestBinary = async (binDownloadUrl, cliDir) => {
|
|
898
|
-
const lockPath =
|
|
974
|
+
const lockPath = path4.join(cliDir, "download.lock");
|
|
899
975
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
900
976
|
const parseLockFile = () => {
|
|
901
977
|
try {
|
|
902
|
-
const content =
|
|
978
|
+
const content = fs4.readFileSync(lockPath, "utf8").trim();
|
|
903
979
|
const [pidLine, timestampLine] = content.split("\n");
|
|
904
980
|
const pid = Number.parseInt(pidLine, 10);
|
|
905
981
|
const timestamp = Number.parseInt(timestampLine, 10);
|
|
@@ -923,20 +999,20 @@ var CLIUtils = class _CLIUtils {
|
|
|
923
999
|
const start = Date.now();
|
|
924
1000
|
while (true) {
|
|
925
1001
|
try {
|
|
926
|
-
const fd =
|
|
1002
|
+
const fd = fs4.openSync(lockPath, "wx");
|
|
927
1003
|
try {
|
|
928
|
-
|
|
1004
|
+
fs4.writeFileSync(fd, `${process.pid}
|
|
929
1005
|
${Date.now()}
|
|
930
1006
|
`);
|
|
931
1007
|
} catch {
|
|
932
1008
|
}
|
|
933
1009
|
return () => {
|
|
934
1010
|
try {
|
|
935
|
-
|
|
1011
|
+
fs4.closeSync(fd);
|
|
936
1012
|
} catch {
|
|
937
1013
|
}
|
|
938
1014
|
try {
|
|
939
|
-
|
|
1015
|
+
fs4.unlinkSync(lockPath);
|
|
940
1016
|
} catch {
|
|
941
1017
|
}
|
|
942
1018
|
};
|
|
@@ -952,14 +1028,14 @@ ${Date.now()}
|
|
|
952
1028
|
`Stale CLI download lock detected (pid=${lockMeta.pid}, age=${lockAge}ms). Removing lock.`
|
|
953
1029
|
);
|
|
954
1030
|
try {
|
|
955
|
-
|
|
1031
|
+
fs4.unlinkSync(lockPath);
|
|
956
1032
|
} catch {
|
|
957
1033
|
}
|
|
958
1034
|
continue;
|
|
959
1035
|
}
|
|
960
1036
|
}
|
|
961
1037
|
const existingBinary = _CLIUtils.getExistingCliPath(cliDir);
|
|
962
|
-
if (existingBinary &&
|
|
1038
|
+
if (existingBinary && fs4.existsSync(existingBinary) && fs4.statSync(existingBinary).size > 0) {
|
|
963
1039
|
BStackLogger2.debug(
|
|
964
1040
|
`Binary appeared while waiting for lock: ${existingBinary}`
|
|
965
1041
|
);
|
|
@@ -981,16 +1057,16 @@ ${Date.now()}
|
|
|
981
1057
|
try {
|
|
982
1058
|
const now = Date.now();
|
|
983
1059
|
const tmpExtractRe = /\.tmp\.\d+$/;
|
|
984
|
-
for (const entry of
|
|
1060
|
+
for (const entry of fs4.readdirSync(cliDir)) {
|
|
985
1061
|
const isDownloadZip = entry.startsWith(CLI_DOWNLOAD_TMP_PREFIX) && entry.endsWith(CLI_DOWNLOAD_TMP_SUFFIX);
|
|
986
1062
|
const isExtractTmp = tmpExtractRe.test(entry);
|
|
987
1063
|
if (!isDownloadZip && !isExtractTmp) {
|
|
988
1064
|
continue;
|
|
989
1065
|
}
|
|
990
|
-
const filePath =
|
|
1066
|
+
const filePath = path4.join(cliDir, entry);
|
|
991
1067
|
let stats;
|
|
992
1068
|
try {
|
|
993
|
-
stats =
|
|
1069
|
+
stats = fs4.statSync(filePath);
|
|
994
1070
|
} catch {
|
|
995
1071
|
continue;
|
|
996
1072
|
}
|
|
@@ -998,7 +1074,7 @@ ${Date.now()}
|
|
|
998
1074
|
continue;
|
|
999
1075
|
}
|
|
1000
1076
|
try {
|
|
1001
|
-
|
|
1077
|
+
fs4.unlinkSync(filePath);
|
|
1002
1078
|
} catch (err) {
|
|
1003
1079
|
BStackLogger2.debug(
|
|
1004
1080
|
`Failed to delete temp CLI file ${filePath}: ${util.format(err)}`
|
|
@@ -1038,7 +1114,7 @@ ${Date.now()}
|
|
|
1038
1114
|
}
|
|
1039
1115
|
releaseLock = lockResult;
|
|
1040
1116
|
const existingBinary = _CLIUtils.getExistingCliPath(cliDir);
|
|
1041
|
-
if (existingBinary &&
|
|
1117
|
+
if (existingBinary && fs4.existsSync(existingBinary) && fs4.statSync(existingBinary).size > 0) {
|
|
1042
1118
|
BStackLogger2.debug(
|
|
1043
1119
|
`Binary already exists after acquiring lock: ${existingBinary}`
|
|
1044
1120
|
);
|
|
@@ -1047,11 +1123,11 @@ ${Date.now()}
|
|
|
1047
1123
|
return existingBinary;
|
|
1048
1124
|
}
|
|
1049
1125
|
cleanupTemporaryDownloads();
|
|
1050
|
-
const zipFilePath =
|
|
1126
|
+
const zipFilePath = path4.join(
|
|
1051
1127
|
cliDir,
|
|
1052
1128
|
`${CLI_DOWNLOAD_TMP_PREFIX}${process.pid}_${Date.now()}${CLI_DOWNLOAD_TMP_SUFFIX}`
|
|
1053
1129
|
);
|
|
1054
|
-
const downloadedFileStream =
|
|
1130
|
+
const downloadedFileStream = fs4.createWriteStream(zipFilePath);
|
|
1055
1131
|
return new Promise((resolve, reject) => {
|
|
1056
1132
|
const processDownload = async () => {
|
|
1057
1133
|
const abortController = new AbortController();
|
|
@@ -1061,9 +1137,11 @@ ${Date.now()}
|
|
|
1061
1137
|
);
|
|
1062
1138
|
let response;
|
|
1063
1139
|
try {
|
|
1064
|
-
response = await _fetch(
|
|
1065
|
-
|
|
1066
|
-
|
|
1140
|
+
response = await _fetch(
|
|
1141
|
+
binDownloadUrl,
|
|
1142
|
+
{ signal: abortController.signal },
|
|
1143
|
+
{ connectTimeoutMs: CLI_CONNECT_TIMEOUT_MS }
|
|
1144
|
+
);
|
|
1067
1145
|
} finally {
|
|
1068
1146
|
clearTimeout(timeout);
|
|
1069
1147
|
}
|
|
@@ -1133,17 +1211,17 @@ ${Date.now()}
|
|
|
1133
1211
|
zipfile.readEntry();
|
|
1134
1212
|
return;
|
|
1135
1213
|
}
|
|
1136
|
-
const candidatePath =
|
|
1137
|
-
const resolvedCandidate =
|
|
1138
|
-
const resolvedDir =
|
|
1214
|
+
const candidatePath = path4.join(cliDir, entry.fileName);
|
|
1215
|
+
const resolvedCandidate = path4.resolve(candidatePath);
|
|
1216
|
+
const resolvedDir = path4.resolve(cliDir) + path4.sep;
|
|
1139
1217
|
if (!resolvedCandidate.startsWith(resolvedDir)) {
|
|
1140
1218
|
zipfile.close();
|
|
1141
1219
|
reject(new Error(`Zip-slip detected: entry "${entry.fileName}" resolves outside ${cliDir}`));
|
|
1142
1220
|
return;
|
|
1143
1221
|
}
|
|
1144
|
-
const isBinaryEntry =
|
|
1222
|
+
const isBinaryEntry = path4.basename(entry.fileName).startsWith("binary-");
|
|
1145
1223
|
if (!isBinaryEntry) {
|
|
1146
|
-
const directStream =
|
|
1224
|
+
const directStream = fs4.createWriteStream(candidatePath);
|
|
1147
1225
|
directStream.on("error", (writeErr) => {
|
|
1148
1226
|
zipfile.close();
|
|
1149
1227
|
reject(writeErr);
|
|
@@ -1165,8 +1243,8 @@ ${Date.now()}
|
|
|
1165
1243
|
return;
|
|
1166
1244
|
}
|
|
1167
1245
|
const finalPath = candidatePath;
|
|
1168
|
-
const tempPath =
|
|
1169
|
-
const writeStream =
|
|
1246
|
+
const tempPath = path4.join(cliDir, `${entry.fileName}.tmp.${process.pid}`);
|
|
1247
|
+
const writeStream = fs4.createWriteStream(tempPath);
|
|
1170
1248
|
let writeStreamErrored = false;
|
|
1171
1249
|
writeStream.on("error", (writeErr) => {
|
|
1172
1250
|
writeStreamErrored = true;
|
|
@@ -1347,11 +1425,11 @@ var PerformanceTester = class _PerformanceTester {
|
|
|
1347
1425
|
static browser;
|
|
1348
1426
|
static scenarioThatRan;
|
|
1349
1427
|
static jsonReportDirName = "performance-report";
|
|
1350
|
-
static jsonReportDirPath =
|
|
1428
|
+
static jsonReportDirPath = path5.join(process.cwd(), "logs", this.jsonReportDirName);
|
|
1351
1429
|
static jsonReportFileName = `${this.jsonReportDirPath}/performance-report-${_PerformanceTester.getProcessId()}.json`;
|
|
1352
1430
|
static startMonitoring(csvName = "performance-report.csv") {
|
|
1353
|
-
if (!
|
|
1354
|
-
|
|
1431
|
+
if (!fs5.existsSync(this.jsonReportDirPath)) {
|
|
1432
|
+
fs5.mkdirSync(this.jsonReportDirPath, { recursive: true });
|
|
1355
1433
|
}
|
|
1356
1434
|
this._observer = new PerformanceObserver((list) => {
|
|
1357
1435
|
list.getEntries().filter((entry) => entry.entryType === "measure").forEach(
|
|
@@ -1425,10 +1503,10 @@ var PerformanceTester = class _PerformanceTester {
|
|
|
1425
1503
|
this.started = false;
|
|
1426
1504
|
this.generateCSV(this._events);
|
|
1427
1505
|
const content = this.generateReport(this._events);
|
|
1428
|
-
const dir =
|
|
1506
|
+
const dir = path5.join(process.cwd(), filename);
|
|
1429
1507
|
try {
|
|
1430
1508
|
await fsPromise.writeFile(dir, content);
|
|
1431
|
-
BStackLogger.info(`Performance report is at ${
|
|
1509
|
+
BStackLogger.info(`Performance report is at ${path5}`);
|
|
1432
1510
|
} catch (err) {
|
|
1433
1511
|
BStackLogger.error(`Error in writing html ${util2.format(err)}`);
|
|
1434
1512
|
}
|
|
@@ -1599,7 +1677,7 @@ var PerformanceTester = class _PerformanceTester {
|
|
|
1599
1677
|
this.start(EVENTS.SDK_KEY_METRICS_PREPARATION);
|
|
1600
1678
|
let measures = [];
|
|
1601
1679
|
if (await fsPromise.access(this.jsonReportDirPath).then(() => true).catch(() => false)) {
|
|
1602
|
-
const files = (await fsPromise.readdir(this.jsonReportDirPath)).map((file) =>
|
|
1680
|
+
const files = (await fsPromise.readdir(this.jsonReportDirPath)).map((file) => path5.resolve(this.jsonReportDirPath, file));
|
|
1603
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();
|
|
1604
1682
|
}
|
|
1605
1683
|
BStackLogger.debug(`[Performance Upload] Total events from files: ${measures.length}`);
|
|
@@ -1676,7 +1754,7 @@ var PerformanceTester = class _PerformanceTester {
|
|
|
1676
1754
|
if (await fsPromise.access(this.jsonReportDirPath).then(() => true, () => false)) {
|
|
1677
1755
|
const files = await fsPromise.readdir(this.jsonReportDirPath);
|
|
1678
1756
|
for (const file of files) {
|
|
1679
|
-
await fsPromise.unlink(
|
|
1757
|
+
await fsPromise.unlink(path5.join(this.jsonReportDirPath, file));
|
|
1680
1758
|
}
|
|
1681
1759
|
BStackLogger.debug(`[Performance Upload] Cleaned up ${files.length} temporary report files`);
|
|
1682
1760
|
}
|
|
@@ -1732,7 +1810,7 @@ var getProductMapForBuildStartCall = (config, accessibilityAutomation) => {
|
|
|
1732
1810
|
};
|
|
1733
1811
|
|
|
1734
1812
|
// src/testorchestration/testorcherstrationutils.ts
|
|
1735
|
-
import
|
|
1813
|
+
import fs6 from "node:fs";
|
|
1736
1814
|
var RUN_SMART_SELECTION = "runSmartSelection";
|
|
1737
1815
|
var ALLOWED_ORCHESTRATION_KEYS = [
|
|
1738
1816
|
RUN_SMART_SELECTION
|
|
@@ -1962,13 +2040,13 @@ var OrchestrationUtils = class _OrchestrationUtils {
|
|
|
1962
2040
|
* @returns Formatted list of repository configurations
|
|
1963
2041
|
*/
|
|
1964
2042
|
_loadSourceFromFile(filePath) {
|
|
1965
|
-
if (!
|
|
2043
|
+
if (!fs6.existsSync(filePath)) {
|
|
1966
2044
|
BStackLogger.error(`Source file '${filePath}' does not exist.`);
|
|
1967
2045
|
return [];
|
|
1968
2046
|
}
|
|
1969
2047
|
let data = null;
|
|
1970
2048
|
try {
|
|
1971
|
-
const fileContent =
|
|
2049
|
+
const fileContent = fs6.readFileSync(filePath, "utf8");
|
|
1972
2050
|
data = JSON.parse(fileContent);
|
|
1973
2051
|
} catch (error) {
|
|
1974
2052
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2427,6 +2505,8 @@ var TestOpsConfig = class _TestOpsConfig {
|
|
|
2427
2505
|
this.manuallySet = manuallySet;
|
|
2428
2506
|
_TestOpsConfig._instance = this;
|
|
2429
2507
|
}
|
|
2508
|
+
enabled;
|
|
2509
|
+
manuallySet;
|
|
2430
2510
|
static _instance;
|
|
2431
2511
|
buildStopped = false;
|
|
2432
2512
|
buildHashedId;
|
|
@@ -2553,9 +2633,9 @@ var usageStats_default = UsageStats;
|
|
|
2553
2633
|
import { create } from "tar";
|
|
2554
2634
|
|
|
2555
2635
|
// src/scripts/accessibility-scripts.ts
|
|
2556
|
-
import
|
|
2557
|
-
import
|
|
2558
|
-
import
|
|
2636
|
+
import path6 from "node:path";
|
|
2637
|
+
import fs7 from "node:fs";
|
|
2638
|
+
import os2 from "node:os";
|
|
2559
2639
|
var AccessibilityScripts = class _AccessibilityScripts {
|
|
2560
2640
|
static instance = null;
|
|
2561
2641
|
performScan = null;
|
|
@@ -2569,7 +2649,7 @@ var AccessibilityScripts = class _AccessibilityScripts {
|
|
|
2569
2649
|
// don't allow to create instances from it other than through `checkAndGetInstance`
|
|
2570
2650
|
constructor() {
|
|
2571
2651
|
this.browserstackFolderPath = this.getWritableDir();
|
|
2572
|
-
this.commandsPath =
|
|
2652
|
+
this.commandsPath = path6.join(this.browserstackFolderPath, "commands.json");
|
|
2573
2653
|
}
|
|
2574
2654
|
static checkAndGetInstance() {
|
|
2575
2655
|
if (!_AccessibilityScripts.instance) {
|
|
@@ -2581,17 +2661,17 @@ var AccessibilityScripts = class _AccessibilityScripts {
|
|
|
2581
2661
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2582
2662
|
getWritableDir() {
|
|
2583
2663
|
const orderedPaths = [
|
|
2584
|
-
|
|
2664
|
+
path6.join(os2.homedir(), ".browserstack"),
|
|
2585
2665
|
process.cwd(),
|
|
2586
|
-
|
|
2666
|
+
os2.tmpdir()
|
|
2587
2667
|
];
|
|
2588
2668
|
for (const orderedPath of orderedPaths) {
|
|
2589
2669
|
try {
|
|
2590
|
-
if (
|
|
2591
|
-
|
|
2670
|
+
if (fs7.existsSync(orderedPath)) {
|
|
2671
|
+
fs7.accessSync(orderedPath);
|
|
2592
2672
|
return orderedPath;
|
|
2593
2673
|
}
|
|
2594
|
-
|
|
2674
|
+
fs7.mkdirSync(orderedPath, { recursive: true });
|
|
2595
2675
|
return orderedPath;
|
|
2596
2676
|
} catch (error) {
|
|
2597
2677
|
}
|
|
@@ -2600,8 +2680,8 @@ var AccessibilityScripts = class _AccessibilityScripts {
|
|
|
2600
2680
|
}
|
|
2601
2681
|
readFromExistingFile() {
|
|
2602
2682
|
try {
|
|
2603
|
-
if (
|
|
2604
|
-
const data =
|
|
2683
|
+
if (fs7.existsSync(this.commandsPath)) {
|
|
2684
|
+
const data = fs7.readFileSync(this.commandsPath, "utf8");
|
|
2605
2685
|
if (data) {
|
|
2606
2686
|
this.update(JSON.parse(data));
|
|
2607
2687
|
}
|
|
@@ -2624,10 +2704,10 @@ var AccessibilityScripts = class _AccessibilityScripts {
|
|
|
2624
2704
|
}
|
|
2625
2705
|
}
|
|
2626
2706
|
store() {
|
|
2627
|
-
if (!
|
|
2628
|
-
|
|
2707
|
+
if (!fs7.existsSync(this.browserstackFolderPath)) {
|
|
2708
|
+
fs7.mkdirSync(this.browserstackFolderPath);
|
|
2629
2709
|
}
|
|
2630
|
-
|
|
2710
|
+
fs7.writeFileSync(this.commandsPath, JSON.stringify({
|
|
2631
2711
|
commands: this.commandsToWrap,
|
|
2632
2712
|
scripts: {
|
|
2633
2713
|
scan: this.performScan,
|
|
@@ -3326,7 +3406,7 @@ function getObservabilityBuild(options, bstackBuildName) {
|
|
|
3326
3406
|
if (options.testObservabilityOptions && options.testObservabilityOptions.buildName) {
|
|
3327
3407
|
return options.testObservabilityOptions.buildName;
|
|
3328
3408
|
}
|
|
3329
|
-
return bstackBuildName ||
|
|
3409
|
+
return bstackBuildName || path7.basename(path7.resolve(process.cwd()));
|
|
3330
3410
|
}
|
|
3331
3411
|
function getObservabilityBuildTags(options, bstackBuildTag) {
|
|
3332
3412
|
if (process.env.TEST_OBSERVABILITY_BUILD_TAG) {
|
|
@@ -3420,6 +3500,45 @@ var getPlatformVersion = o11yErrorHandler(function getPlatformVersion2(caps, use
|
|
|
3420
3500
|
}
|
|
3421
3501
|
return void 0;
|
|
3422
3502
|
});
|
|
3503
|
+
var getResolvedDeviceName = o11yErrorHandler(function getResolvedDeviceName2(driverCaps, requestedCaps) {
|
|
3504
|
+
const flattenMultiremote = (caps) => {
|
|
3505
|
+
if (!caps) {
|
|
3506
|
+
return [];
|
|
3507
|
+
}
|
|
3508
|
+
const obj = caps;
|
|
3509
|
+
if (obj["deviceModel"] || obj["appium:deviceModel"] || obj["deviceName"] || obj["bstack:options"]) {
|
|
3510
|
+
return [caps];
|
|
3511
|
+
}
|
|
3512
|
+
return Object.values(obj).filter((v) => v !== null && typeof v === "object" && "capabilities" in v).map((v) => v.capabilities).filter(Boolean);
|
|
3513
|
+
};
|
|
3514
|
+
const sources = [
|
|
3515
|
+
...flattenMultiremote(driverCaps),
|
|
3516
|
+
...flattenMultiremote(requestedCaps)
|
|
3517
|
+
];
|
|
3518
|
+
if (!sources.length) {
|
|
3519
|
+
return void 0;
|
|
3520
|
+
}
|
|
3521
|
+
const pickString = (obj, key) => {
|
|
3522
|
+
const v = obj?.[key];
|
|
3523
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
3524
|
+
};
|
|
3525
|
+
const paths = [
|
|
3526
|
+
(c) => pickString(c, "deviceModel"),
|
|
3527
|
+
(c) => pickString(c, "appium:deviceModel"),
|
|
3528
|
+
(c) => pickString(c["bstack:options"], "deviceName"),
|
|
3529
|
+
(c) => pickString(c, "appium:deviceName"),
|
|
3530
|
+
(c) => pickString(c, "deviceName")
|
|
3531
|
+
];
|
|
3532
|
+
for (const path10 of paths) {
|
|
3533
|
+
for (const src of sources) {
|
|
3534
|
+
const v = path10(src);
|
|
3535
|
+
if (v) {
|
|
3536
|
+
return v;
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
return void 0;
|
|
3541
|
+
});
|
|
3423
3542
|
var isObjectEmpty = (objectName) => {
|
|
3424
3543
|
return objectName && Object.keys(objectName).length === 0 && objectName.constructor === Object;
|
|
3425
3544
|
};
|
|
@@ -3581,28 +3700,28 @@ function nestedKeyValue(hash, keys) {
|
|
|
3581
3700
|
return keys.reduce((hash2, key) => isHash(hash2) ? hash2[key] : void 0, hash);
|
|
3582
3701
|
}
|
|
3583
3702
|
function removeDir(dir) {
|
|
3584
|
-
const list =
|
|
3703
|
+
const list = fs8.readdirSync(dir);
|
|
3585
3704
|
for (let i = 0; i < list.length; i++) {
|
|
3586
|
-
const filename =
|
|
3587
|
-
const stat =
|
|
3705
|
+
const filename = path7.join(dir, list[i]);
|
|
3706
|
+
const stat = fs8.statSync(filename);
|
|
3588
3707
|
if (filename === "." || filename === "..") {
|
|
3589
3708
|
} else if (stat.isDirectory()) {
|
|
3590
3709
|
removeDir(filename);
|
|
3591
3710
|
} else {
|
|
3592
|
-
|
|
3711
|
+
fs8.unlinkSync(filename);
|
|
3593
3712
|
}
|
|
3594
3713
|
}
|
|
3595
|
-
|
|
3714
|
+
fs8.rmdirSync(dir);
|
|
3596
3715
|
}
|
|
3597
3716
|
function createDir(dir) {
|
|
3598
|
-
if (
|
|
3717
|
+
if (fs8.existsSync(dir)) {
|
|
3599
3718
|
removeDir(dir);
|
|
3600
3719
|
}
|
|
3601
|
-
|
|
3720
|
+
fs8.mkdirSync(dir, { recursive: true });
|
|
3602
3721
|
}
|
|
3603
3722
|
function isWritable(dirPath) {
|
|
3604
3723
|
try {
|
|
3605
|
-
|
|
3724
|
+
fs8.accessSync(dirPath, fs8.constants.W_OK);
|
|
3606
3725
|
return true;
|
|
3607
3726
|
} catch {
|
|
3608
3727
|
return false;
|
|
@@ -3610,7 +3729,7 @@ function isWritable(dirPath) {
|
|
|
3610
3729
|
}
|
|
3611
3730
|
function setReadWriteAccess(dirPath) {
|
|
3612
3731
|
try {
|
|
3613
|
-
|
|
3732
|
+
fs8.chmodSync(dirPath, 438);
|
|
3614
3733
|
BStackLogger.debug(`Directory ${dirPath} is now read/write accessible.`);
|
|
3615
3734
|
} catch (err) {
|
|
3616
3735
|
BStackLogger.error(`Failed to set directory access: ${err.stack}`);
|
|
@@ -3618,19 +3737,19 @@ function setReadWriteAccess(dirPath) {
|
|
|
3618
3737
|
}
|
|
3619
3738
|
|
|
3620
3739
|
// src/cleanup.ts
|
|
3621
|
-
import
|
|
3740
|
+
import fs11 from "node:fs";
|
|
3622
3741
|
import util5 from "node:util";
|
|
3623
3742
|
|
|
3624
3743
|
// src/instrumentation/funnelInstrumentation.ts
|
|
3625
|
-
import
|
|
3744
|
+
import os3 from "node:os";
|
|
3626
3745
|
import util4, { format as format2 } from "node:util";
|
|
3627
|
-
import
|
|
3628
|
-
import
|
|
3746
|
+
import path9 from "node:path";
|
|
3747
|
+
import fs10 from "node:fs";
|
|
3629
3748
|
|
|
3630
3749
|
// src/data-store.ts
|
|
3631
|
-
import
|
|
3632
|
-
import
|
|
3633
|
-
var workersDataDirPath =
|
|
3750
|
+
import path8 from "node:path";
|
|
3751
|
+
import fs9 from "node:fs";
|
|
3752
|
+
var workersDataDirPath = path8.join(process.cwd(), "logs", "worker_data");
|
|
3634
3753
|
|
|
3635
3754
|
// src/instrumentation/funnelInstrumentation.ts
|
|
3636
3755
|
function redactCredentialsFromFunnelData(data) {
|
|
@@ -3736,7 +3855,7 @@ Visit https://automation.browserstack.com/builds/${process.env[BROWSERSTACK_TEST
|
|
|
3736
3855
|
if (!filePath) {
|
|
3737
3856
|
return null;
|
|
3738
3857
|
}
|
|
3739
|
-
const content =
|
|
3858
|
+
const content = fs11.readFileSync(filePath, "utf8");
|
|
3740
3859
|
const data = JSON.parse(content);
|
|
3741
3860
|
this.removeFunnelDataFile(filePath);
|
|
3742
3861
|
return data;
|
|
@@ -3745,7 +3864,7 @@ Visit https://automation.browserstack.com/builds/${process.env[BROWSERSTACK_TEST
|
|
|
3745
3864
|
if (!filePath) {
|
|
3746
3865
|
return;
|
|
3747
3866
|
}
|
|
3748
|
-
|
|
3867
|
+
fs11.rmSync(filePath, { force: true });
|
|
3749
3868
|
}
|
|
3750
3869
|
};
|
|
3751
3870
|
void BStackCleanup.startCleanup();
|