@wdio/browserstack-service 8.45.0 → 8.47.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/README.md +44 -11
- package/build/Percy/Percy-Handler.d.ts.map +1 -1
- package/build/Percy/Percy-Handler.js +4 -1
- package/build/Percy/Percy.d.ts.map +1 -1
- package/build/Percy/Percy.js +2 -1
- package/build/accessibility-handler.d.ts +9 -3
- package/build/accessibility-handler.d.ts.map +1 -1
- package/build/accessibility-handler.js +52 -11
- package/build/cleanup.js +3 -3
- package/build/cli/apiUtils.d.ts +14 -0
- package/build/cli/apiUtils.d.ts.map +1 -0
- package/build/cli/apiUtils.js +24 -0
- package/build/cli/cliLogger.d.ts +16 -0
- package/build/cli/cliLogger.d.ts.map +1 -0
- package/build/cli/cliLogger.js +70 -0
- package/build/cli/cliUtils.d.ts +46 -0
- package/build/cli/cliUtils.d.ts.map +1 -0
- package/build/cli/cliUtils.js +430 -0
- package/build/cli/eventDispatcher.d.ts +28 -0
- package/build/cli/eventDispatcher.d.ts.map +1 -0
- package/build/cli/eventDispatcher.js +48 -0
- package/build/cli/frameworks/automationFramework.d.ts +89 -0
- package/build/cli/frameworks/automationFramework.d.ts.map +1 -0
- package/build/cli/frameworks/automationFramework.js +131 -0
- package/build/cli/frameworks/constants/automationFrameworkConstants.d.ts +18 -0
- package/build/cli/frameworks/constants/automationFrameworkConstants.d.ts.map +1 -0
- package/build/cli/frameworks/constants/automationFrameworkConstants.js +17 -0
- package/build/cli/frameworks/constants/testFrameworkConstants.d.ts +43 -0
- package/build/cli/frameworks/constants/testFrameworkConstants.d.ts.map +1 -0
- package/build/cli/frameworks/constants/testFrameworkConstants.js +42 -0
- package/build/cli/frameworks/testFramework.d.ts +89 -0
- package/build/cli/frameworks/testFramework.d.ts.map +1 -0
- package/build/cli/frameworks/testFramework.js +125 -0
- package/build/cli/frameworks/wdioAutomationFramework.d.ts +28 -0
- package/build/cli/frameworks/wdioAutomationFramework.d.ts.map +1 -0
- package/build/cli/frameworks/wdioAutomationFramework.js +66 -0
- package/build/cli/frameworks/wdioMochaTestFramework.d.ts +82 -0
- package/build/cli/frameworks/wdioMochaTestFramework.d.ts.map +1 -0
- package/build/cli/frameworks/wdioMochaTestFramework.js +319 -0
- package/build/cli/grpcClient.d.ts +70 -0
- package/build/cli/grpcClient.d.ts.map +1 -0
- package/build/cli/grpcClient.js +419 -0
- package/build/cli/index.d.ts +148 -0
- package/build/cli/index.d.ts.map +1 -0
- package/build/cli/index.js +415 -0
- package/build/cli/instances/automationFrameworkInstance.d.ts +33 -0
- package/build/cli/instances/automationFrameworkInstance.d.ts.map +1 -0
- package/build/cli/instances/automationFrameworkInstance.js +44 -0
- package/build/cli/instances/testFrameworkInstance.d.ts +62 -0
- package/build/cli/instances/testFrameworkInstance.d.ts.map +1 -0
- package/build/cli/instances/testFrameworkInstance.js +96 -0
- package/build/cli/instances/trackedContext.d.ts +32 -0
- package/build/cli/instances/trackedContext.d.ts.map +1 -0
- package/build/cli/instances/trackedContext.js +47 -0
- package/build/cli/instances/trackedInstance.d.ts +40 -0
- package/build/cli/instances/trackedInstance.d.ts.map +1 -0
- package/build/cli/instances/trackedInstance.js +63 -0
- package/build/cli/modules/accessibilityModule.d.ts +33 -0
- package/build/cli/modules/accessibilityModule.d.ts.map +1 -0
- package/build/cli/modules/accessibilityModule.js +347 -0
- package/build/cli/modules/automateModule.d.ts +26 -0
- package/build/cli/modules/automateModule.d.ts.map +1 -0
- package/build/cli/modules/automateModule.js +222 -0
- package/build/cli/modules/baseModule.d.ts +33 -0
- package/build/cli/modules/baseModule.d.ts.map +1 -0
- package/build/cli/modules/baseModule.js +51 -0
- package/build/cli/modules/observabilityModule.d.ts +22 -0
- package/build/cli/modules/observabilityModule.d.ts.map +1 -0
- package/build/cli/modules/observabilityModule.js +45 -0
- package/build/cli/modules/percyModule.d.ts +19 -0
- package/build/cli/modules/percyModule.d.ts.map +1 -0
- package/build/cli/modules/percyModule.js +77 -0
- package/build/cli/modules/testHubModule.d.ts +30 -0
- package/build/cli/modules/testHubModule.d.ts.map +1 -0
- package/build/cli/modules/testHubModule.js +232 -0
- package/build/cli/modules/webdriverIOModule.d.ts +29 -0
- package/build/cli/modules/webdriverIOModule.d.ts.map +1 -0
- package/build/cli/modules/webdriverIOModule.js +128 -0
- package/build/cli/states/automationFrameworkState.d.ts +65 -0
- package/build/cli/states/automationFrameworkState.d.ts.map +1 -0
- package/build/cli/states/automationFrameworkState.js +61 -0
- package/build/cli/states/hookState.d.ts +45 -0
- package/build/cli/states/hookState.d.ts.map +1 -0
- package/build/cli/states/hookState.js +43 -0
- package/build/cli/states/testFrameworkState.d.ts +125 -0
- package/build/cli/states/testFrameworkState.d.ts.map +1 -0
- package/build/cli/states/testFrameworkState.js +115 -0
- package/build/constants.d.ts +13 -0
- package/build/constants.d.ts.map +1 -1
- package/build/constants.js +21 -1
- package/build/crash-reporter.d.ts.map +1 -1
- package/build/crash-reporter.js +4 -3
- package/build/exitHandler.d.ts.map +1 -1
- package/build/exitHandler.js +47 -5
- package/build/insights-handler.d.ts +8 -0
- package/build/insights-handler.d.ts.map +1 -1
- package/build/insights-handler.js +57 -1
- package/build/instrumentation/funnelInstrumentation.d.ts.map +1 -1
- package/build/instrumentation/funnelInstrumentation.js +5 -4
- package/build/instrumentation/performance/constants.d.ts +8 -0
- package/build/instrumentation/performance/constants.d.ts.map +1 -1
- package/build/instrumentation/performance/constants.js +9 -1
- package/build/instrumentation/performance/performance-tester.d.ts.map +1 -1
- package/build/instrumentation/performance/performance-tester.js +5 -3
- package/build/launcher.d.ts.map +1 -1
- package/build/launcher.js +94 -51
- package/build/request-handler.js +1 -1
- package/build/service.d.ts +3 -1
- package/build/service.d.ts.map +1 -1
- package/build/service.js +244 -69
- package/build/testOps/requestUtils.js +3 -2
- package/build/types.d.ts +36 -3
- package/build/types.d.ts.map +1 -1
- package/build/util.d.ts +13 -1
- package/build/util.d.ts.map +1 -1
- package/build/util.js +173 -27
- package/package.json +7 -4
- package/tsconfig.prod.tsbuildinfo +1 -1
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import { platform, arch, homedir } from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import util, { promisify } from 'node:util';
|
|
6
|
+
import { exec } from 'node:child_process';
|
|
7
|
+
import yauzl from 'yauzl';
|
|
8
|
+
import got from 'got';
|
|
9
|
+
import { threadId } from 'node:worker_threads';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const { version: bstackServiceVersion } = require('../../package.json');
|
|
13
|
+
import { isNullOrEmpty, nestedKeyValue, createDir, isWritable, setReadWriteAccess, isTrue, nodeRequest, getBrowserStackUser, getBrowserStackKey, isFalse, isTurboScale, shouldAddServiceVersion, } from '../util.js';
|
|
14
|
+
import PerformanceTester from '../instrumentation/performance/performance-tester.js';
|
|
15
|
+
import { EVENTS as PerformanceEvents } from '../instrumentation/performance/constants.js';
|
|
16
|
+
import { BStackLogger as logger } from './cliLogger.js';
|
|
17
|
+
import { UPDATED_CLI_ENDPOINT } from '../constants.js';
|
|
18
|
+
import { TestFrameworkConstants } from './frameworks/constants/testFrameworkConstants.js';
|
|
19
|
+
import APIUtils from './apiUtils.js';
|
|
20
|
+
export class CLIUtils {
|
|
21
|
+
static automationFrameworkDetail = {};
|
|
22
|
+
static testFrameworkDetail = {};
|
|
23
|
+
static CLISupportedFrameworks = ['mocha'];
|
|
24
|
+
static isDevelopmentEnv() {
|
|
25
|
+
return process.env.BROWSERSTACK_CLI_ENV === 'development';
|
|
26
|
+
}
|
|
27
|
+
static getCLIParamsForDevEnv() {
|
|
28
|
+
return {
|
|
29
|
+
id: process.env.BROWSERSTACK_CLI_ENV || '',
|
|
30
|
+
listen: `unix:/tmp/sdk-platform-${process.env.BROWSERSTACK_CLI_ENV}.sock`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build config object for binary session request
|
|
35
|
+
* @returns {string}
|
|
36
|
+
* @throws {Error}
|
|
37
|
+
*/
|
|
38
|
+
static getBinConfig(config, capabilities, options, buildTag) {
|
|
39
|
+
const modifiedOpts = { ...options };
|
|
40
|
+
if (modifiedOpts.opts) {
|
|
41
|
+
modifiedOpts.browserStackLocalOptions = modifiedOpts.opts;
|
|
42
|
+
delete modifiedOpts.opts;
|
|
43
|
+
}
|
|
44
|
+
modifiedOpts.testContextOptions = {
|
|
45
|
+
skipSessionName: isFalse(modifiedOpts.setSessionName),
|
|
46
|
+
skipSessionStatus: isFalse(modifiedOpts.setSessionStatus),
|
|
47
|
+
sessionNameOmitTestTitle: modifiedOpts.sessionNameOmitTestTitle || false,
|
|
48
|
+
sessionNamePrependTopLevelSuiteTitle: modifiedOpts.sessionNamePrependTopLevelSuiteTitle || false,
|
|
49
|
+
sessionNameFormat: modifiedOpts.sessionNameFormat || ''
|
|
50
|
+
};
|
|
51
|
+
const commonBstackOptions = (() => {
|
|
52
|
+
const caps = config.capabilities;
|
|
53
|
+
if (caps &&
|
|
54
|
+
!Array.isArray(caps) &&
|
|
55
|
+
typeof caps === 'object' &&
|
|
56
|
+
'bstack:options' in caps) {
|
|
57
|
+
// Cast after guard to satisfy TypeScript
|
|
58
|
+
return caps['bstack:options'] || {};
|
|
59
|
+
}
|
|
60
|
+
return {};
|
|
61
|
+
})();
|
|
62
|
+
const isNonBstackA11y = isTurboScale(options) || !shouldAddServiceVersion(config, options.testObservability);
|
|
63
|
+
const observabilityOptions = options.testObservabilityOptions || {};
|
|
64
|
+
const binconfig = {
|
|
65
|
+
userName: observabilityOptions.user || config.user,
|
|
66
|
+
accessKey: observabilityOptions.key || config.key,
|
|
67
|
+
platforms: [],
|
|
68
|
+
isNonBstackA11yWDIO: isNonBstackA11y,
|
|
69
|
+
...modifiedOpts,
|
|
70
|
+
...commonBstackOptions,
|
|
71
|
+
};
|
|
72
|
+
binconfig.buildName = observabilityOptions.buildName || binconfig.buildName;
|
|
73
|
+
binconfig.projectName = observabilityOptions.projectName || binconfig.projectName;
|
|
74
|
+
binconfig.buildTag = this.getObservabilityBuildTags(observabilityOptions, buildTag) || [];
|
|
75
|
+
let caps = capabilities;
|
|
76
|
+
if (capabilities && !Array.isArray(capabilities)) {
|
|
77
|
+
caps = [capabilities];
|
|
78
|
+
}
|
|
79
|
+
if (Array.isArray(caps)) {
|
|
80
|
+
for (const cap of caps) {
|
|
81
|
+
const platform = {};
|
|
82
|
+
const capability = cap;
|
|
83
|
+
Object.keys(capability)
|
|
84
|
+
.filter((key) => (key !== 'bstack:options'))
|
|
85
|
+
.forEach((key) => {
|
|
86
|
+
if (binconfig[key] === undefined) {
|
|
87
|
+
platform[key] = capability[key];
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
if (capability['bstack:options']) {
|
|
91
|
+
Object.keys(capability['bstack:options'])
|
|
92
|
+
.forEach((key) => {
|
|
93
|
+
if (binconfig[key] === undefined) {
|
|
94
|
+
platform[key] = capability['bstack:options'][key];
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
binconfig.platforms.push(platform);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return JSON.stringify(binconfig);
|
|
102
|
+
}
|
|
103
|
+
static getSdkVersion() {
|
|
104
|
+
return bstackServiceVersion;
|
|
105
|
+
}
|
|
106
|
+
static getSdkLanguage() {
|
|
107
|
+
return 'ECMAScript';
|
|
108
|
+
}
|
|
109
|
+
static async setupCliPath(config) {
|
|
110
|
+
logger.debug('Configuring Cli path.');
|
|
111
|
+
const developmentBinaryPath = process.env.SDK_CLI_BIN_PATH || null;
|
|
112
|
+
if (!isNullOrEmpty(developmentBinaryPath)) {
|
|
113
|
+
logger.debug(`Development Cli Path: ${developmentBinaryPath}`);
|
|
114
|
+
return developmentBinaryPath;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const cliDir = this.getCliDir();
|
|
118
|
+
if (isNullOrEmpty(cliDir)) {
|
|
119
|
+
throw new Error('No writable directory available for the CLI');
|
|
120
|
+
}
|
|
121
|
+
const existingCliPath = this.getExistingCliPath(cliDir);
|
|
122
|
+
const finalBinaryPath = await this.checkAndUpdateCli(existingCliPath, cliDir, config);
|
|
123
|
+
logger.debug(`Resolved binary path: ${finalBinaryPath}`);
|
|
124
|
+
return finalBinaryPath;
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
logger.debug(`Error in setting up cli path directory, Exception: ${util.format(err)}`);
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
static async checkAndUpdateCli(existingCliPath, cliDir, config) {
|
|
132
|
+
PerformanceTester.start(PerformanceEvents.SDK_CLI_CHECK_UPDATE);
|
|
133
|
+
logger.info(`Current CLI Path Found: ${existingCliPath}`);
|
|
134
|
+
const queryParams = {
|
|
135
|
+
sdk_version: CLIUtils.getSdkVersion(),
|
|
136
|
+
os: platform(),
|
|
137
|
+
os_arch: arch(),
|
|
138
|
+
cli_version: '0',
|
|
139
|
+
sdk_language: this.getSdkLanguage(),
|
|
140
|
+
};
|
|
141
|
+
if (!isNullOrEmpty(existingCliPath)) {
|
|
142
|
+
queryParams.cli_version = await this.runShellCommand(`${existingCliPath} version`);
|
|
143
|
+
}
|
|
144
|
+
const response = await this.requestToUpdateCLI(queryParams, config);
|
|
145
|
+
if (nestedKeyValue(response, ['updated_cli_version'])) {
|
|
146
|
+
logger.debug(`Need to update binary, current binary version: ${queryParams.cli_version}`);
|
|
147
|
+
const browserStackBinaryUrl = process.env.BROWSERSTACK_BINARY_URL || null;
|
|
148
|
+
if (!isNullOrEmpty(browserStackBinaryUrl)) {
|
|
149
|
+
logger.debug(`Using BROWSERSTACK_BINARY_URL: ${browserStackBinaryUrl}`);
|
|
150
|
+
response.url = browserStackBinaryUrl;
|
|
151
|
+
}
|
|
152
|
+
const finalBinaryPath = await this.downloadLatestBinary(nestedKeyValue(response, ['url']), cliDir);
|
|
153
|
+
PerformanceTester.end(PerformanceEvents.SDK_CLI_CHECK_UPDATE);
|
|
154
|
+
return finalBinaryPath;
|
|
155
|
+
}
|
|
156
|
+
PerformanceTester.end(PerformanceEvents.SDK_CLI_CHECK_UPDATE);
|
|
157
|
+
return existingCliPath;
|
|
158
|
+
}
|
|
159
|
+
static getCliDir() {
|
|
160
|
+
const writableDir = this.getWritableDir();
|
|
161
|
+
try {
|
|
162
|
+
if (isNullOrEmpty(writableDir)) {
|
|
163
|
+
throw new Error('No writable directory available for the CLI');
|
|
164
|
+
}
|
|
165
|
+
const cliDirPath = path.join(writableDir, 'cli');
|
|
166
|
+
if (!fs.existsSync(cliDirPath)) {
|
|
167
|
+
createDir(cliDirPath);
|
|
168
|
+
}
|
|
169
|
+
return cliDirPath;
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
logger.error(`Error in getting writable directory, writableDir=${util.format(err)}`);
|
|
173
|
+
return '';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
static getWritableDir() {
|
|
177
|
+
const writableDirOptions = [
|
|
178
|
+
process.env.BROWSERSTACK_FILES_DIR,
|
|
179
|
+
path.join(homedir(), '.browserstack'),
|
|
180
|
+
path.join('tmp', '.browserstack'),
|
|
181
|
+
];
|
|
182
|
+
for (const path of writableDirOptions) {
|
|
183
|
+
if (isNullOrEmpty(path)) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
if (fs.existsSync(path)) {
|
|
188
|
+
logger.debug(`File ${path} already exist`);
|
|
189
|
+
if (!isWritable(path)) {
|
|
190
|
+
logger.debug(`Giving write permission to ${path}`);
|
|
191
|
+
const success = setReadWriteAccess(path);
|
|
192
|
+
if (!isTrue(success)) {
|
|
193
|
+
logger.warn(`Unable to provide write permission to ${path}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
logger.debug(`File does not exist: ${path}`);
|
|
199
|
+
createDir(path);
|
|
200
|
+
logger.debug(`Giving write permission to ${path}`);
|
|
201
|
+
const success = setReadWriteAccess(path);
|
|
202
|
+
if (!isTrue(success)) {
|
|
203
|
+
logger.warn(`Unable to provide write permission to ${path}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return path;
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
logger.error(`Unable to get writable directory, exception ${util.format(err)}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
static getExistingCliPath(cliDir) {
|
|
215
|
+
try {
|
|
216
|
+
// Check if the path exists and is a directory
|
|
217
|
+
if (!fs.existsSync(cliDir) || !fs.statSync(cliDir).isDirectory()) {
|
|
218
|
+
return '';
|
|
219
|
+
}
|
|
220
|
+
// List all files in the directory that start with "binary-"
|
|
221
|
+
const allBinaries = fs
|
|
222
|
+
.readdirSync(cliDir)
|
|
223
|
+
.map((file) => path.join(cliDir, file))
|
|
224
|
+
.filter((filePath) => fs.statSync(filePath).isFile() && path.basename(filePath).startsWith('binary-'));
|
|
225
|
+
if (allBinaries.length > 0) {
|
|
226
|
+
// Get the latest binary by comparing the last modified time
|
|
227
|
+
const latestBinary = allBinaries
|
|
228
|
+
.map((filePath) => ({
|
|
229
|
+
filePath,
|
|
230
|
+
mtime: fs.statSync(filePath).mtime,
|
|
231
|
+
}))
|
|
232
|
+
.reduce((latest, current) => {
|
|
233
|
+
if (!latest || !latest.mtime) {
|
|
234
|
+
return current;
|
|
235
|
+
}
|
|
236
|
+
if (current.mtime > latest.mtime) {
|
|
237
|
+
return current;
|
|
238
|
+
}
|
|
239
|
+
return latest;
|
|
240
|
+
}, null);
|
|
241
|
+
return latestBinary ? latestBinary.filePath : '';
|
|
242
|
+
}
|
|
243
|
+
return ''; // No binary present
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
logger.error(`Error while reading CLI path: ${util.format(err)}`);
|
|
247
|
+
return '';
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
static requestToUpdateCLI = async (queryParams, config) => {
|
|
251
|
+
const params = new URLSearchParams(queryParams);
|
|
252
|
+
const requestInit = {
|
|
253
|
+
headers: {
|
|
254
|
+
Authorization: `Basic ${Buffer.from(`${getBrowserStackUser(config)}:${getBrowserStackKey(config)}`).toString('base64')}`,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
const response = await nodeRequest('GET', `${UPDATED_CLI_ENDPOINT}?${params.toString()}`, requestInit, APIUtils.BROWSERSTACK_AUTOMATE_API_URL);
|
|
258
|
+
logger.debug(`response ${JSON.stringify(response)}`);
|
|
259
|
+
return response;
|
|
260
|
+
};
|
|
261
|
+
static runShellCommand(cmdCommand, workingDir = '') {
|
|
262
|
+
return new Promise((resolve) => {
|
|
263
|
+
const process = exec(cmdCommand, { cwd: workingDir, timeout: 5000 }, (error, stdout, stderr) => {
|
|
264
|
+
if (error) {
|
|
265
|
+
resolve(stderr.trim() || 'SHELL_EXECUTE_ERROR');
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
resolve(stdout.trim());
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
// Ensure the process is killed if it exceeds the timeout
|
|
272
|
+
process.on('error', () => {
|
|
273
|
+
resolve('SHELL_EXECUTE_ERROR');
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
static downloadLatestBinary = async (binDownloadUrl, cliDir) => {
|
|
278
|
+
PerformanceTester.start(PerformanceEvents.SDK_CLI_DOWNLOAD);
|
|
279
|
+
logger.debug(`Downloading SDK binary from: ${binDownloadUrl}`);
|
|
280
|
+
try {
|
|
281
|
+
const zipFilePath = path.join(cliDir, 'downloaded_file.zip');
|
|
282
|
+
const downloadedFileStream = fs.createWriteStream(zipFilePath);
|
|
283
|
+
return new Promise((resolve, reject) => {
|
|
284
|
+
const binaryName = null;
|
|
285
|
+
const stream = got.stream(binDownloadUrl, {
|
|
286
|
+
followRedirect: true
|
|
287
|
+
});
|
|
288
|
+
stream.pipe(downloadedFileStream);
|
|
289
|
+
downloadedFileStream.on('error', function (err) {
|
|
290
|
+
logger.error('Got Error while downloading percy binary file' + err);
|
|
291
|
+
PerformanceTester.end(PerformanceEvents.SDK_CLI_DOWNLOAD, false, util.format(err));
|
|
292
|
+
reject(err);
|
|
293
|
+
});
|
|
294
|
+
stream.on('error', (err) => {
|
|
295
|
+
logger.error(`Got Error in cli binary downloading request ${util.format(err)}`);
|
|
296
|
+
PerformanceTester.end(PerformanceEvents.SDK_CLI_DOWNLOAD, false, util.format(err));
|
|
297
|
+
reject(err);
|
|
298
|
+
});
|
|
299
|
+
CLIUtils.downloadFileStream(downloadedFileStream, binaryName, zipFilePath, cliDir, resolve, reject);
|
|
300
|
+
PerformanceTester.end(PerformanceEvents.SDK_CLI_DOWNLOAD);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
PerformanceTester.end(PerformanceEvents.SDK_CLI_DOWNLOAD, false, util.format(err));
|
|
305
|
+
logger.debug(`Failed to download binary, Exception: ${util.format(err)}`);
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
static downloadFileStream(downloadedFileStream, binaryName, zipFilePath, cliDir, resolve, reject) {
|
|
310
|
+
downloadedFileStream.on('close', async function () {
|
|
311
|
+
const yauzlOpenPromise = promisify(yauzl.open);
|
|
312
|
+
try {
|
|
313
|
+
const zipfile = await yauzlOpenPromise(zipFilePath, { lazyEntries: true });
|
|
314
|
+
zipfile.readEntry();
|
|
315
|
+
zipfile.on('entry', async (entry) => {
|
|
316
|
+
if (!binaryName) {
|
|
317
|
+
binaryName = entry.fileName;
|
|
318
|
+
}
|
|
319
|
+
if (/\/$/.test(entry.fileName)) {
|
|
320
|
+
// Directory file names end with '/'.
|
|
321
|
+
zipfile.readEntry();
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
// file entry
|
|
325
|
+
const writeStream = fs.createWriteStream(path.join(cliDir, entry.fileName));
|
|
326
|
+
const openReadStreamPromise = promisify(zipfile.openReadStream).bind(zipfile);
|
|
327
|
+
try {
|
|
328
|
+
const readStream = await openReadStreamPromise(entry);
|
|
329
|
+
readStream.on('end', function () {
|
|
330
|
+
writeStream.close();
|
|
331
|
+
zipfile.readEntry();
|
|
332
|
+
});
|
|
333
|
+
readStream.pipe(writeStream);
|
|
334
|
+
}
|
|
335
|
+
catch (zipErr) {
|
|
336
|
+
reject(zipErr);
|
|
337
|
+
}
|
|
338
|
+
if (entry.fileName === binaryName) {
|
|
339
|
+
zipfile.close();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
zipfile.on('error', (zipErr) => {
|
|
344
|
+
reject(zipErr);
|
|
345
|
+
});
|
|
346
|
+
zipfile.once('end', () => {
|
|
347
|
+
fsp.unlink(zipFilePath)
|
|
348
|
+
.catch(() => {
|
|
349
|
+
logger.warn(`Failed to delete zip file: ${zipFilePath}`);
|
|
350
|
+
});
|
|
351
|
+
fsp.chmod(`${cliDir}/${binaryName}`, '0755')
|
|
352
|
+
.then(() => {
|
|
353
|
+
resolve(`${cliDir}/${binaryName}`);
|
|
354
|
+
}).catch((err) => {
|
|
355
|
+
reject(err);
|
|
356
|
+
});
|
|
357
|
+
zipfile.close();
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
reject(err);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
static getTestFrameworkDetail() {
|
|
366
|
+
if (process.env.BROWSERSTACK_TEST_FRAMEWORK_DETAIL) {
|
|
367
|
+
return JSON.parse(process.env.BROWSERSTACK_TEST_FRAMEWORK_DETAIL);
|
|
368
|
+
}
|
|
369
|
+
return this.testFrameworkDetail;
|
|
370
|
+
}
|
|
371
|
+
static getAutomationFrameworkDetail() {
|
|
372
|
+
if (process.env.BROWSERSTACK_AUTOMATION_FRAMEWORK_DETAIL) {
|
|
373
|
+
return JSON.parse(process.env.BROWSERSTACK_AUTOMATION_FRAMEWORK_DETAIL);
|
|
374
|
+
}
|
|
375
|
+
return this.automationFrameworkDetail;
|
|
376
|
+
}
|
|
377
|
+
static setFrameworkDetail(testFramework, automationFramework) {
|
|
378
|
+
if (!testFramework || !automationFramework) {
|
|
379
|
+
logger.debug(`Test or Automation framework not provided testFramework=${testFramework}, automationFramework=${automationFramework}`);
|
|
380
|
+
}
|
|
381
|
+
this.testFrameworkDetail = {
|
|
382
|
+
name: testFramework,
|
|
383
|
+
version: { [testFramework]: CLIUtils.getSdkVersion() },
|
|
384
|
+
};
|
|
385
|
+
this.automationFrameworkDetail = {
|
|
386
|
+
name: automationFramework,
|
|
387
|
+
version: { [automationFramework]: CLIUtils.getSdkVersion() },
|
|
388
|
+
};
|
|
389
|
+
process.env.BROWSERSTACK_AUTOMATION_FRAMEWORK_DETAIL = JSON.stringify(this.automationFrameworkDetail);
|
|
390
|
+
process.env.BROWSERSTACK_TEST_FRAMEWORK_DETAIL = JSON.stringify(this.testFrameworkDetail);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Get the current instance name using thread id and processId
|
|
394
|
+
* @returns {string}
|
|
395
|
+
*/
|
|
396
|
+
static getCurrentInstanceName() {
|
|
397
|
+
return `${process.pid}:${threadId}`;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
*
|
|
401
|
+
* @param {TestFrameworkState | AutomationFrameworkState} frameworkState
|
|
402
|
+
* @param {HookState} hookState
|
|
403
|
+
* @returns {string}
|
|
404
|
+
*/
|
|
405
|
+
static getHookRegistryKey(frameworkState, hookState) {
|
|
406
|
+
return `${frameworkState}:${hookState}`;
|
|
407
|
+
}
|
|
408
|
+
static matchHookRegex(hookState) {
|
|
409
|
+
const pattern = new RegExp(TestFrameworkConstants.HOOK_REGEX);
|
|
410
|
+
return pattern.test(hookState);
|
|
411
|
+
}
|
|
412
|
+
static getObservabilityBuildTags(observabilityOptions, bstackBuildTag) {
|
|
413
|
+
if (process.env.TEST_OBSERVABILITY_BUILD_TAG) {
|
|
414
|
+
return process.env.TEST_OBSERVABILITY_BUILD_TAG.split(',');
|
|
415
|
+
}
|
|
416
|
+
if (observabilityOptions.buildTag) {
|
|
417
|
+
return observabilityOptions.buildTag;
|
|
418
|
+
}
|
|
419
|
+
if (bstackBuildTag) {
|
|
420
|
+
return [bstackBuildTag];
|
|
421
|
+
}
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
static checkCLISupportedFrameworks(framework) {
|
|
425
|
+
if (framework === undefined) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
return this.CLISupportedFrameworks.includes(framework);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventDispatcher - Singleton class for event handling
|
|
3
|
+
*/
|
|
4
|
+
declare class EventDispatcher {
|
|
5
|
+
#private;
|
|
6
|
+
observers: Record<string, Function[]>;
|
|
7
|
+
constructor();
|
|
8
|
+
/**
|
|
9
|
+
* Get the EventDispatcher singleton instance
|
|
10
|
+
* @returns {EventDispatcher} The singleton instance
|
|
11
|
+
*/
|
|
12
|
+
static getInstance(): EventDispatcher;
|
|
13
|
+
/**
|
|
14
|
+
* Add event observer
|
|
15
|
+
* @param {string} event - Event name
|
|
16
|
+
* @param {Function} callback - Callback function
|
|
17
|
+
*/
|
|
18
|
+
registerObserver(hookRegistryKey: string, callback: Function): void;
|
|
19
|
+
/**
|
|
20
|
+
* Notify registered observers on an event
|
|
21
|
+
* @param {string} event - Event name
|
|
22
|
+
* @param {*} data - Event data
|
|
23
|
+
*/
|
|
24
|
+
notifyObserver(event: string, args: unknown): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export declare const eventDispatcher: EventDispatcher;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=eventDispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventDispatcher.d.ts","sourceRoot":"","sources":["../../src/cli/eventDispatcher.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAM,eAAe;;IAGjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;;IAMrC;;;KAGC;IACD,MAAM,CAAC,WAAW;IAOlB;;;;KAIC;IACD,gBAAgB,CAAC,eAAe,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ;IAQ5D;;;;KAIC;IACK,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;CAQpD;AAGD,eAAO,MAAM,eAAe,iBAAgC,CAAA"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventDispatcher - Singleton class for event handling
|
|
3
|
+
*/
|
|
4
|
+
class EventDispatcher {
|
|
5
|
+
static #instance = null;
|
|
6
|
+
observers;
|
|
7
|
+
constructor() {
|
|
8
|
+
this.observers = {};
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Get the EventDispatcher singleton instance
|
|
12
|
+
* @returns {EventDispatcher} The singleton instance
|
|
13
|
+
*/
|
|
14
|
+
static getInstance() {
|
|
15
|
+
if (!EventDispatcher.#instance) {
|
|
16
|
+
EventDispatcher.#instance = new EventDispatcher();
|
|
17
|
+
}
|
|
18
|
+
return EventDispatcher.#instance;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Add event observer
|
|
22
|
+
* @param {string} event - Event name
|
|
23
|
+
* @param {Function} callback - Callback function
|
|
24
|
+
*/
|
|
25
|
+
registerObserver(hookRegistryKey, callback) {
|
|
26
|
+
if (!this.observers[hookRegistryKey]) {
|
|
27
|
+
this.observers[hookRegistryKey] = [];
|
|
28
|
+
}
|
|
29
|
+
this.observers[hookRegistryKey].push(callback);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Notify registered observers on an event
|
|
33
|
+
* @param {string} event - Event name
|
|
34
|
+
* @param {*} data - Event data
|
|
35
|
+
*/
|
|
36
|
+
async notifyObserver(event, args) {
|
|
37
|
+
if (this.observers[event]) {
|
|
38
|
+
for (const callback of this.observers[event]) {
|
|
39
|
+
await callback(args);
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Create the singleton instance
|
|
46
|
+
export const eventDispatcher = EventDispatcher.getInstance();
|
|
47
|
+
// Object.freeze to prevent modification of the instance
|
|
48
|
+
Object.freeze(eventDispatcher);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type AutomationFrameworkInstance from '../instances/automationFrameworkInstance.js';
|
|
2
|
+
import type TrackedContext from '../instances/trackedContext.js';
|
|
3
|
+
/**
|
|
4
|
+
* AutomationFramework - Automation Framework abstract class
|
|
5
|
+
*/
|
|
6
|
+
export default class AutomationFramework {
|
|
7
|
+
#private;
|
|
8
|
+
static instances: Map<any, any>;
|
|
9
|
+
static KEY_AUTOMATION_SESSIONS: string;
|
|
10
|
+
static KEY_NON_BROWSERSTACK_AUTOMATION_SESSIONS: string;
|
|
11
|
+
/**
|
|
12
|
+
* Constructor for the AutomationFramework
|
|
13
|
+
* @param {string} automationFrameworkName - Name of the automation framework
|
|
14
|
+
* @param {string} automationFrameworkVersion - Version of the automation framework
|
|
15
|
+
*/
|
|
16
|
+
constructor(automationFrameworkName: string, automationFrameworkVersion: string);
|
|
17
|
+
/**
|
|
18
|
+
* Get the automation framework name
|
|
19
|
+
* @returns {string} The name of the automation framework
|
|
20
|
+
*/
|
|
21
|
+
getAutomationFrameworkName(): string;
|
|
22
|
+
/**
|
|
23
|
+
* Get the automation framework version
|
|
24
|
+
* @returns {string} The version of the automation framework
|
|
25
|
+
*/
|
|
26
|
+
getAutomationFrameworkVersion(): string;
|
|
27
|
+
/**
|
|
28
|
+
* Track an event
|
|
29
|
+
* @param {Object}
|
|
30
|
+
* @param {Object}
|
|
31
|
+
* @param {Object}
|
|
32
|
+
* @returns {void}
|
|
33
|
+
*/
|
|
34
|
+
trackEvent(automationFrameworkState: State, hookState: State, args?: unknown): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
*
|
|
37
|
+
* @param {*} instance
|
|
38
|
+
* @param {*} automationFrameworkState
|
|
39
|
+
* @param {*} hookState
|
|
40
|
+
* @param {*} args
|
|
41
|
+
*/
|
|
42
|
+
runHooks(instance: AutomationFrameworkInstance, automationFrameworkState: State, hookState: State, args?: unknown): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Register an observer
|
|
45
|
+
* @returns {void}
|
|
46
|
+
*/
|
|
47
|
+
static registerObserver(automationFrameworkState: State, hookState: State, callback: Function): void;
|
|
48
|
+
/**
|
|
49
|
+
* Set the tracked instance
|
|
50
|
+
* @param {TrackedInstance} context - The context
|
|
51
|
+
* @param {TrackedInstance} instance - The instance
|
|
52
|
+
* @returns {void}
|
|
53
|
+
*/
|
|
54
|
+
static setTrackedInstance(context: TrackedContext, instance: AutomationFrameworkInstance): void;
|
|
55
|
+
/**
|
|
56
|
+
* Get the tracked instance
|
|
57
|
+
* @returns {TrackedInstance} The tracked instance
|
|
58
|
+
*/
|
|
59
|
+
static getTrackedInstance(): any;
|
|
60
|
+
/**
|
|
61
|
+
* Set the state
|
|
62
|
+
* @param {TrackedInstance} instance - The instance
|
|
63
|
+
* @param {string} key - The key
|
|
64
|
+
* @param {*} value - The value
|
|
65
|
+
* @returns
|
|
66
|
+
*/
|
|
67
|
+
static setState(instance: AutomationFrameworkInstance, key: string, value: unknown): void;
|
|
68
|
+
/**
|
|
69
|
+
* Get the state
|
|
70
|
+
* @param {TrackedInstance} instance - The instance
|
|
71
|
+
* @param {string} key - The key
|
|
72
|
+
* @returns {*} The state
|
|
73
|
+
*/
|
|
74
|
+
static getState(instance: AutomationFrameworkInstance, key: string): any;
|
|
75
|
+
static isAutomationSession(instance: AutomationFrameworkInstance): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Set the driver for the automation framework instance
|
|
78
|
+
* @param {AutomationFrameworkInstance} instance - The automation framework instance
|
|
79
|
+
* @param {*} driver - The driver object
|
|
80
|
+
*/
|
|
81
|
+
static setDriver(instance: AutomationFrameworkInstance, driver: unknown): void;
|
|
82
|
+
/**
|
|
83
|
+
* Get the driver from the automation framework instance
|
|
84
|
+
* @param {AutomationFrameworkInstance} instance - The automation framework instance
|
|
85
|
+
* @returns {*} The driver object or null
|
|
86
|
+
*/
|
|
87
|
+
static getDriver(instance: AutomationFrameworkInstance): unknown;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=automationFramework.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"automationFramework.d.ts","sourceRoot":"","sources":["../../../src/cli/frameworks/automationFramework.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,2BAA2B,MAAM,6CAA6C,CAAA;AAC1F,OAAO,KAAK,cAAc,MAAM,gCAAgC,CAAA;AAGhE;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,mBAAmB;;IAIpC,MAAM,CAAC,SAAS,gBAAY;IAC5B,MAAM,CAAC,uBAAuB,SAAwB;IACtD,MAAM,CAAC,wCAAwC,SAAyC;IAExF;;;;IAIA;gBACY,uBAAuB,EAAE,MAAM,EAAE,0BAA0B,EAAE,MAAM;IAK/E;;;KAGC;IACD,0BAA0B;IAI1B;;;KAGC;IACD,6BAA6B;IAI7B;;;;;;KAMC;IACK,UAAU,CAAC,wBAAwB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,GAAE,OAAY;IAItF;;;;;;KAMC;IACK,QAAQ,CAAC,QAAQ,EAAE,2BAA2B,EAAE,wBAAwB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,GAAE,OAAY;IAO3H;;;KAGC;IACD,MAAM,CAAC,gBAAgB,CAAC,wBAAwB,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ;IAI7F;;;;;KAKC;IACD,MAAM,CAAC,kBAAkB,CAAC,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,2BAA2B;IAKxF;;;KAGC;IACD,MAAM,CAAC,kBAAkB;IAMzB;;;;;;KAMC;IACD,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,2BAA2B,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;IAIlF;;;;;KAKC;IACD,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,2BAA2B,EAAE,GAAG,EAAE,MAAM;IAIlE,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,2BAA2B,GAAG,OAAO;IAI1E;;;;OAIG;IACH,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,2BAA2B,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAQ9E;;;;OAIG;IACH,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,2BAA2B,GAAG,OAAO;CAKnE"}
|