Skip to content

Commit 89eaf83

Browse files
authored
Merge pull request #736 from browserstack/HST_722_ats
[🔨 WIP 🔨] Support running on ATS grid
2 parents 5d419bc + a17f89c commit 89eaf83

File tree

13 files changed

+357
-16
lines changed

13 files changed

+357
-16
lines changed

bin/commands/info.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ module.exports = function info(args, rawArgs) {
3838
'User-Agent': utils.getUserAgent(),
3939
},
4040
};
41+
42+
if (Constants.turboScaleObj.enabled) {
43+
options.url = `${config.turboScaleBuildsUrl}/${buildId}`;
44+
}
45+
4146
request.get(options, function (err, resp, body) {
4247
let message = null;
4348
let messageType = null;

bin/commands/runs.js

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const {
3636
checkAccessibilityPlatform,
3737
supportFileCleanup
3838
} = require('../accessibility-automation/helper');
39+
const { isTurboScaleSession, getTurboScaleGridDetails, patchCypressConfigFileContent, atsFileCleanup } = require('../helpers/atsHelper');
3940

4041
module.exports = function run(args, rawArgs) {
4142

@@ -66,6 +67,8 @@ module.exports = function run(args, rawArgs) {
6667
const [isTestObservabilitySession, isBrowserstackInfra] = setTestObservabilityFlags(bsConfig);
6768
const checkAccessibility = checkAccessibilityPlatform(bsConfig);
6869
const isAccessibilitySession = bsConfig.run_settings.accessibility || checkAccessibility;
70+
const turboScaleSession = isTurboScaleSession(bsConfig);
71+
Constants.turboScaleObj.enabled = turboScaleSession;
6972

7073
utils.setUsageReportingFlag(bsConfig, args.disableUsageReporting);
7174

@@ -77,7 +80,7 @@ module.exports = function run(args, rawArgs) {
7780
// accept the access key from command line or env variable if provided
7881
utils.setAccessKey(bsConfig, args);
7982

80-
let buildReportData = !isBrowserstackInfra ? null : await getInitialDetails(bsConfig, args, rawArgs);
83+
let buildReportData = (turboScaleSession || !isBrowserstackInfra) ? null : await getInitialDetails(bsConfig, args, rawArgs);
8184

8285
// accept the build name from command line if provided
8386
utils.setBuildName(bsConfig, args);
@@ -147,6 +150,29 @@ module.exports = function run(args, rawArgs) {
147150
if (isAccessibilitySession && isBrowserstackInfra) {
148151
await createAccessibilityTestRun(bsConfig);
149152
}
153+
154+
if (turboScaleSession) {
155+
// Local is only required in case user is running on trial grid and wants to access private website.
156+
// Even then, it will be spawned separately via browserstack-cli ats connect-grid command and not via browserstack-cypress-cli
157+
// Hence whenever running on ATS, need to make local as false
158+
bsConfig.connection_settings.local = false;
159+
160+
const gridDetails = await getTurboScaleGridDetails(bsConfig, args, rawArgs);
161+
162+
if (gridDetails && Object.keys(gridDetails).length > 0) {
163+
Constants.turboScaleObj.gridDetails = gridDetails;
164+
Constants.turboScaleObj.gridUrl = gridDetails.cypressUrl;
165+
Constants.turboScaleObj.uploadUrl = gridDetails.cypressUrl + '/upload';
166+
Constants.turboScaleObj.buildUrl = gridDetails.cypressUrl + '/build';
167+
168+
logger.debug(`Automate TurboScale Grid URL set to ${gridDetails.url}`);
169+
170+
patchCypressConfigFileContent(bsConfig);
171+
} else {
172+
process.exitCode = Constants.ERROR_EXIT_CODE;
173+
return;
174+
}
175+
}
150176
}
151177

152178
const { packagesInstalled } = !isBrowserstackInfra ? false : await packageInstaller.packageSetupAndInstaller(bsConfig, config.packageDirName, {markBlockStart, markBlockEnd});
@@ -186,7 +212,7 @@ module.exports = function run(args, rawArgs) {
186212

187213
//get the number of spec files
188214
markBlockStart('getNumberOfSpecFiles');
189-
let specFiles = utils.getNumberOfSpecFiles(bsConfig, args, cypressConfigFile);
215+
let specFiles = utils.getNumberOfSpecFiles(bsConfig, args, cypressConfigFile, turboScaleSession);
190216
markBlockEnd('getNumberOfSpecFiles');
191217

192218
bsConfig['run_settings']['video_config'] = utils.getVideoConfig(cypressConfigFile, bsConfig);
@@ -251,6 +277,11 @@ module.exports = function run(args, rawArgs) {
251277
if (process.env.BROWSERSTACK_TEST_ACCESSIBILITY === 'true') {
252278
supportFileCleanup();
253279
}
280+
281+
if (turboScaleSession) {
282+
atsFileCleanup(bsConfig);
283+
}
284+
254285
// Set config args for enforce_settings
255286
if ( !utils.isUndefinedOrFalse(bsConfig.run_settings.enforce_settings) ) {
256287
markBlockStart('setEnforceSettingsConfig');
@@ -310,13 +341,13 @@ module.exports = function run(args, rawArgs) {
310341
logger.debug("Completed polling of build status");
311342

312343
// stop the Local instance
313-
await utils.stopLocalBinary(bsConfig, bs_local, args, rawArgs, buildReportData);
344+
if (!turboScaleSession) await utils.stopLocalBinary(bsConfig, bs_local, args, rawArgs, buildReportData);
314345

315346
// waiting for 5 secs for upload to complete (as a safety measure)
316347
await new Promise(resolve => setTimeout(resolve, 5000));
317348

318349
// download build artifacts
319-
if (exitCode != Constants.BUILD_FAILED_EXIT_CODE) {
350+
if (exitCode != Constants.BUILD_FAILED_EXIT_CODE && !turboScaleSession) {
320351
if (utils.nonEmptyArray(bsConfig.run_settings.downloads)) {
321352
logger.debug("Downloading build artifacts");
322353
await downloadBuildArtifacts(bsConfig, data.build_id, args, rawArgs, buildReportData);
@@ -328,7 +359,7 @@ module.exports = function run(args, rawArgs) {
328359
markBlockEnd('postBuild');
329360
utils.handleSyncExit(exitCode, data.dashboard_url);
330361
});
331-
} else {
362+
} else if(!turboScaleSession){
332363
let stacktraceUrl = getStackTraceUrl();
333364
downloadBuildStacktrace(stacktraceUrl).then((message) => {
334365
utils.sendUsageReport(bsConfig, args, message, Constants.messageTypes.SUCCESS, null, buildReportData, rawArgs);
@@ -344,7 +375,7 @@ module.exports = function run(args, rawArgs) {
344375
});
345376
}
346377
});
347-
} else if (utils.nonEmptyArray(bsConfig.run_settings.downloads)) {
378+
} else if (utils.nonEmptyArray(bsConfig.run_settings.downloads && !turboScaleSession)) {
348379
logger.info(Constants.userMessages.ASYNC_DOWNLOADS.replace('<build-id>', data.build_id));
349380
}
350381

bin/commands/stop.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,19 @@ module.exports = function stop(args, rawArgs) {
1919
// accept the access key from command line if provided
2020
utils.setAccessKey(bsConfig, args);
2121

22-
let buildReportData = await getInitialDetails(bsConfig, args, rawArgs);
23-
2422
utils.setUsageReportingFlag(bsConfig, args.disableUsageReporting);
2523

2624
// set cypress config filename
2725
utils.setCypressConfigFilename(bsConfig, args);
2826

2927
let buildId = args._[1];
28+
let buildReportData = null;
3029

31-
await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs, buildReportData);
30+
if (!Constants.turboScaleObj.enabled) {
31+
buildReportData = await getInitialDetails(bsConfig, args, rawArgs);
32+
}
3233

34+
await utils.stopBrowserStackBuild(bsConfig, args, buildId, rawArgs, buildReportData);
3335
}).catch(function (err) {
3436
logger.error(err);
3537
utils.setUsageReportingFlag(null, args.disableUsageReporting);

bin/helpers/atsHelper.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
const path = require('path');
2+
const fs = require('fs')
3+
4+
const request = require('request'),
5+
logger = require('./logger').winstonLogger,
6+
utils = require('./utils'),
7+
config = require('./config');
8+
Constants = require('./constants');
9+
10+
exports.isTurboScaleSession = (bsConfig) => {
11+
// env var will override config
12+
if (process.env.BROWSERSTACK_TURBOSCALE && process.env.BROWSERSTACK_TURBOSCALE === 'true') {
13+
return true;
14+
}
15+
16+
if (bsConfig.run_settings && bsConfig.run_settings.turboScale) {
17+
return true;
18+
}
19+
20+
return false;
21+
};
22+
23+
exports.getTurboScaleOptions = (bsConfig) => {
24+
if (bsConfig.run_settings && bsConfig.run_settings.turboScaleOptions) {
25+
return bsConfig.run_settings.turboScaleOptions;
26+
}
27+
28+
return {};
29+
};
30+
31+
exports.getTurboScaleGridName = (bsConfig) => {
32+
// env var will override config
33+
if (process.env.BROWSERSTACK_TURBOSCALE_GRID_NAME) {
34+
return process.env.BROWSERSTACK_TURBOSCALE_GRID_NAME;
35+
}
36+
37+
if (bsConfig.run_settings && bsConfig.run_settings.turboScaleOptions && bsConfig.run_settings.turboScaleOptions.gridName) {
38+
return bsConfig.run_settings.turboScaleOptions.gridName;
39+
}
40+
41+
return 'NO_GRID_NAME_PASSED';
42+
};
43+
44+
exports.getTurboScaleGridDetails = async (bsConfig, args, rawArgs) => {
45+
try {
46+
const gridName = this.getTurboScaleGridName(bsConfig);
47+
48+
return new Promise((resolve, reject) => {
49+
let options = {
50+
url: `${config.turboScaleAPIUrl}/grids/${gridName}`,
51+
auth: {
52+
username: bsConfig.auth.username,
53+
password: bsConfig.auth.access_key,
54+
},
55+
headers: {
56+
'User-Agent': utils.getUserAgent(),
57+
}
58+
};
59+
let responseData = {};
60+
request.get(options, function (err, resp, data) {
61+
if(err) {
62+
logger.warn(utils.formatRequest(err, resp, data));
63+
utils.sendUsageReport(bsConfig, args, err, Constants.messageTypes.ERROR, 'get_ats_details_failed', null, rawArgs);
64+
resolve({});
65+
} else {
66+
try {
67+
responseData = JSON.parse(data);
68+
} catch (e) {
69+
responseData = {};
70+
}
71+
if(resp.statusCode != 200) {
72+
logger.warn(`Warn: Get Automate TurboScale Details Request failed with status code ${resp.statusCode}`);
73+
utils.sendUsageReport(bsConfig, args, responseData["error"], Constants.messageTypes.ERROR, 'get_ats_details_failed', null, rawArgs);
74+
resolve({});
75+
}
76+
resolve(responseData);
77+
}
78+
});
79+
});
80+
} catch (err) {
81+
logger.error(`Failed to find TurboScale Grid: ${err}: ${err.stack}`);
82+
}
83+
};
84+
85+
exports.patchCypressConfigFileContent = (bsConfig) => {
86+
try {
87+
let cypressConfigFileData = fs.readFileSync(path.resolve(bsConfig.run_settings.cypress_config_file)).toString();
88+
const patchedConfigFileData = cypressConfigFileData + '\n\n' + `
89+
let originalFunction = module.exports.e2e.setupNodeEvents;
90+
91+
module.exports.e2e.setupNodeEvents = (on, config) => {
92+
const bstackOn = require("./cypressPatch.js")(on);
93+
if (originalFunction !== null && originalFunction !== undefined) {
94+
originalFunction(bstackOn, config);
95+
}
96+
}
97+
`
98+
99+
let confPath = bsConfig.run_settings.cypress_config_file;
100+
let patchedConfPathList = confPath.split(path.sep);
101+
patchedConfPathList[patchedConfPathList.length - 1] = 'patched_ats_config_file.js'
102+
const patchedConfPath = patchedConfPathList.join(path.sep);
103+
104+
bsConfig.run_settings.patched_cypress_config_file = patchedConfPath;
105+
106+
fs.writeFileSync(path.resolve(bsConfig.run_settings.patched_cypress_config_file), patchedConfigFileData);
107+
} catch(e) {
108+
logger.error(`Encountered an error when trying to patch ATS Cypress Config File ${e}`);
109+
return {};
110+
}
111+
}
112+
113+
exports.atsFileCleanup = (bsConfig) => {
114+
const filePath = path.resolve(bsConfig.run_settings.patched_cypress_config_file);
115+
if(fs.existsSync(filePath)){
116+
fs.unlinkSync(filePath);
117+
}
118+
}

bin/helpers/build.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ const createBuild = (bsConfig, zip) => {
2323
body: data
2424
}
2525

26+
if (Constants.turboScaleObj.enabled) {
27+
options.url = Constants.turboScaleObj.buildUrl;
28+
}
29+
2630
request.post(options, function (err, resp, body) {
2731
if (err) {
2832
logger.error(utils.formatRequest(err, resp, body));

bin/helpers/checkUploaded.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ const checkUploadedMd5 = (bsConfig, args, instrumentBlocks) => {
121121
body: JSON.stringify(data)
122122
};
123123

124+
if (Constants.turboScaleObj.enabled) {
125+
options.url = config.turboScaleMd5Sum;
126+
}
127+
124128
instrumentBlocks.markBlockStart("checkAlreadyUploaded.railsCheck");
125129
request.post(options, function (err, resp, body) {
126130
if (err) {

bin/helpers/config.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ config.packageFileName = "bstackPackages.tar.gz";
2424
config.packageDirName = "tmpBstackPackages";
2525
config.retries = 5;
2626
config.networkErrorExitCode = 2;
27-
config.compiledConfigJsDirName = 'tmpBstackCompiledJs'
28-
config.configJsonFileName = 'tmpCypressConfig.json'
27+
config.compiledConfigJsDirName = 'tmpBstackCompiledJs';
28+
config.configJsonFileName = 'tmpCypressConfig.json';
29+
30+
// turboScale
31+
config.turboScaleMd5Sum = `${config.turboScaleUrl}/md5sumcheck`;
32+
config.turboScaleBuildsUrl = `${config.turboScaleUrl}/builds`;
2933

3034
module.exports = config;

bin/helpers/config.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33
"rails_host": "https://api.browserstack.com",
44
"dashboardUrl": "https://automate.browserstack.com/dashboard/v2/builds/",
55
"usageReportingUrl": "https://eds.browserstack.com:443/send_event_cy_internal",
6-
"localTestingListUrl": "https://www.browserstack.com/local/v1/list"
6+
"localTestingListUrl": "https://www.browserstack.com/local/v1/list",
7+
"turboScaleUrl": "https://grid.browserstack.com/packages/cypress",
8+
"turboScaleAPIUrl": "https://api.browserstack.com/automate-turboscale/v1"
79
}

bin/helpers/constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,8 @@ const CYPRESS_CONFIG_FILE_NAMES = Object.keys(CYPRESS_CONFIG_FILE_MAPPING);
441441

442442
const CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS = ['js', 'ts', 'cjs', 'mjs']
443443

444+
const turboScaleObj = {};
445+
444446
module.exports = Object.freeze({
445447
syncCLI,
446448
userMessages,
@@ -453,6 +455,7 @@ module.exports = Object.freeze({
453455
hashingOptions,
454456
packageInstallerOptions,
455457
specFileTypes,
458+
turboScaleObj,
456459
DEFAULT_CYPRESS_SPEC_PATH,
457460
DEFAULT_CYPRESS_10_SPEC_PATH,
458461
SPEC_TOTAL_CHAR_LIMIT,

bin/helpers/sync/syncSpecsLogs.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ let terminalWidth = (process.stdout.columns) * 0.9;
2424
let lineSeparator = Constants.syncCLI.DEFAULT_LINE_SEP;
2525
if (!isNaN(terminalWidth)) lineSeparator = "\n" + "-".repeat(terminalWidth);
2626

27-
let getOptions = (auth, build_id) => {
28-
return {
27+
let getOptions = (auth, build_id) => {
28+
const options = {
2929
url: `${config.buildUrlV2}${build_id}`,
3030
auth: {
3131
user: auth.username,
@@ -37,6 +37,12 @@ let getOptions = (auth, build_id) => {
3737
"User-Agent": utils.getUserAgent()
3838
}
3939
};
40+
41+
if (Constants.turboScaleObj.enabled) {
42+
options.url = `${config.turboScaleBuildsUrl}/${build_id}`;
43+
}
44+
45+
return options;
4046
}
4147

4248
let getTableConfig = (termWidth) => {

0 commit comments

Comments
 (0)