Skip to content

Commit ff9be4d

Browse files
karanshah-browserstacksuryartroshan04
authoredMar 12, 2021
Improve Local Testing experience (#114)
* Added local_mode and cli args. Calculate local_identifier based on local_mode. * Adding Local Binary start and stop logic * Adding demand message for local-identifier and local-mode options * Adding log messages for sync and local mode * upgrading yargs and putting requiredArgs * passed already failing 12 cases * added new test cases for the modified code * added new test cases and passed already failing test cases * removed unwanted files * Checking local identifier running in always on mode * local inferred, local mode inferred, sync inferred. - Adding checks and params for local, local mode, and sync inferred. * passed all failing tests and added new specs * Adding local start and stop error * Updated validation messages for local cli * Send the value of local_mode not the true/false * Bug fixes for callback stop and start binary * Removing bshost. * Adding local_config_file for passing configuration for local binary * Updating inferred event logic - If the user passed anything explicitly, then it is not inferred. - This is to understand which of the "auto-fallback" flows does CLI fall in. - Inverted the logic of the *_inferred events. * updating user agent to 1.7.2 * written extra specs and passed failing spec * removed unwanted log files * covered new functions under test * Modified local unit tests * Fixing Unit test cases * removed local object creation * Indentation and minor null pointer fixes. * Adding placeholders for local_mode and local_config_file in default json * Local Start fail error. * Fix Local Inferred logic * Fixed local_mode not set to always-on when local_identifier is supplied in browserstack.json * Send local_identifier_error when local_identifier not running in stopBinary * Local mode and id validation * Added test cases for local testing bug fixes * Warning message in case of local_mode is invalid * Updated message for invalid local config file * Fixed cli args precendence on-demand bug * Updating version to 1.8.0 Co-authored-by: Surya Tripathi <raj.surya19@gmail.com> Co-authored-by: roshan <roshannikam04@gmail.com>
·
v1.32.8v1.8.0
1 parent 69da94e commit ff9be4d

File tree

11 files changed

+970
-69
lines changed

11 files changed

+970
-69
lines changed
 

‎bin/commands/runs.js

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,16 @@ module.exports = function run(args) {
4040
utils.setTestEnvs(bsConfig, args);
4141

4242
//accept the local from env variable if provided
43-
utils.setLocal(bsConfig);
43+
utils.setLocal(bsConfig, args);
44+
45+
// set Local Mode (on-demand/ always-on)
46+
utils.setLocalMode(bsConfig, args);
4447

4548
//accept the local identifier from env variable if provided
46-
utils.setLocalIdentifier(bsConfig);
49+
utils.setLocalIdentifier(bsConfig, args);
50+
51+
// set Local Config File
52+
utils.setLocalConfigFile(bsConfig, args);
4753

4854
// run test in headed mode
4955
utils.setHeaded(bsConfig, args);
@@ -61,8 +67,12 @@ module.exports = function run(args) {
6167
return archiver.archive(bsConfig.run_settings, config.fileName, args.exclude).then(function (data) {
6268

6369
// Uploaded zip file
64-
return zipUploader.zipUpload(bsConfig, config.fileName).then(function (zip) {
70+
return zipUploader.zipUpload(bsConfig, config.fileName).then(async function (zip) {
6571
// Create build
72+
73+
//setup Local Testing
74+
let bs_local = await utils.setupLocalTesting(bsConfig, args);
75+
6676
return build.createBuild(bsConfig, zip).then(function (data) {
6777
let message = `${data.message}! ${Constants.userMessages.BUILD_CREATED} with build id: ${data.build_id}`;
6878
let dashboardLink = `${Constants.userMessages.VISIT_DASHBOARD} ${data.dashboard_url}`;
@@ -82,7 +92,11 @@ module.exports = function run(args) {
8292
}
8393

8494
if (args.sync) {
85-
syncRunner.pollBuildStatus(bsConfig, data).then((exitCode) => {
95+
syncRunner.pollBuildStatus(bsConfig, data).then(async (exitCode) => {
96+
97+
// stop the Local instance
98+
await utils.stopLocalBinary(bsConfig, bs_local, args);
99+
86100
// Generate custom report!
87101
reportGenerator(bsConfig, data.build_id, args, function(){
88102
utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null);
@@ -96,17 +110,24 @@ module.exports = function run(args) {
96110
if(!args.sync) logger.info(Constants.userMessages.EXIT_SYNC_CLI_MESSAGE.replace("<build-id>",data.build_id));
97111
utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null);
98112
return;
99-
}).catch(function (err) {
113+
}).catch(async function (err) {
100114
// Build creation failed
101115
logger.error(err);
116+
// stop the Local instance
117+
await utils.stopLocalBinary(bsConfig, bs_local, args);
118+
102119
utils.sendUsageReport(bsConfig, args, err, Constants.messageTypes.ERROR, 'build_failed');
103120
});
104121
}).catch(function (err) {
105-
// Zip Upload failed
122+
// Zip Upload failed | Local Start failed
106123
logger.error(err);
107-
logger.error(Constants.userMessages.ZIP_UPLOAD_FAILED);
108-
fileHelpers.deleteZip();
109-
utils.sendUsageReport(bsConfig, args, `${err}\n${Constants.userMessages.ZIP_UPLOAD_FAILED}`, Constants.messageTypes.ERROR, 'zip_upload_failed');
124+
if(err === Constants.userMessages.LOCAL_START_FAILED){
125+
utils.sendUsageReport(bsConfig, args, `${err}\n${Constants.userMessages.LOCAL_START_FAILED}`, Constants.messageTypes.ERROR, 'local_start_failed');
126+
} else {
127+
logger.error(Constants.userMessages.ZIP_UPLOAD_FAILED);
128+
fileHelpers.deleteZip();
129+
utils.sendUsageReport(bsConfig, args, `${err}\n${Constants.userMessages.ZIP_UPLOAD_FAILED}`, Constants.messageTypes.ERROR, 'zip_upload_failed');
130+
}
110131
});
111132
}).catch(function (err) {
112133
// Zipping failed

‎bin/helpers/capabilityHelper.js

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,47 @@ const caps = (bsConfig, zip) => {
4242
reject("Test suite is empty");
4343
}
4444

45+
// Inferred settings
46+
if(bsConfig.connection_settings){
47+
if (bsConfig.connection_settings.local_mode_inferred) {
48+
obj.local_mode_inferred = bsConfig.connection_settings.local_mode_inferred;
49+
}
50+
51+
if (bsConfig.connection_settings.local_inferred) {
52+
obj.local_inferred = bsConfig.connection_settings.local_inferred;
53+
}
54+
55+
if (bsConfig.connection_settings.sync_inferred) {
56+
obj.sync_inferred = bsConfig.connection_settings.sync_inferred;
57+
logger.info('Setting "sync" mode to enable Local testing.');
58+
}
59+
}
60+
4561
// Local
4662
obj.local = false;
47-
if (bsConfig.connection_settings && bsConfig.connection_settings.local === true) obj.local = true;
48-
logger.info(`Local is set to: ${obj.local} (${obj.local ? Constants.userMessages.LOCAL_TRUE : Constants.userMessages.LOCAL_FALSE})`);
63+
if (bsConfig.connection_settings && bsConfig.connection_settings.local === true) {
64+
obj.local = true;
65+
}
66+
67+
obj.localMode = null;
68+
// Local Mode
69+
if (obj.local === true && bsConfig.connection_settings.local_mode) {
70+
obj.localMode = bsConfig.connection_settings.local_mode;
71+
if (bsConfig.connection_settings.user_defined_local_mode_warning) {
72+
logger.warn(Constants.userMessages.INVALID_LOCAL_MODE_WARNING);
73+
}
74+
logger.info(`Local testing set up in ${obj.localMode} mode.`);
75+
}
4976

5077
// Local Identifier
5178
obj.localIdentifier = null;
5279
if (obj.local === true && (bsConfig.connection_settings.localIdentifier || bsConfig.connection_settings.local_identifier)) {
5380
obj.localIdentifier = bsConfig.connection_settings.localIdentifier || bsConfig.connection_settings.local_identifier;
54-
logger.log(`Local Identifier is set to: ${obj.localIdentifier}`);
81+
logger.info(`Local testing identifier: ${obj.localIdentifier}`);
5582
}
5683

84+
logger.info(`Local is set to: ${obj.local} (${obj.local ? Constants.userMessages.LOCAL_TRUE : Constants.userMessages.LOCAL_FALSE})`);
85+
5786
// Project name
5887
obj.project = "project-name";
5988
// Build name
@@ -94,9 +123,9 @@ const caps = (bsConfig, zip) => {
94123

95124
if(obj.parallels === Constants.cliMessages.RUN.DEFAULT_PARALLEL_MESSAGE) obj.parallels = undefined
96125

97-
if (obj.project) logger.log(`Project name is: ${obj.project}`);
126+
if (obj.project) logger.info(`Project name is: ${obj.project}`);
98127

99-
if (obj.customBuildName) logger.log(`Build name is: ${obj.customBuildName}`);
128+
if (obj.customBuildName) logger.info(`Build name is: ${obj.customBuildName}`);
100129

101130
if (obj.callbackURL) logger.info(`callback url is : ${obj.callbackURL}`);
102131

@@ -132,6 +161,16 @@ const validate = (bsConfig, args) => {
132161
// if parallels specified via arguments validate only arguments
133162
if (!Utils.isUndefined(args) && !Utils.isUndefined(args.parallels) && !Utils.isParallelValid(args.parallels)) reject(Constants.validationMessages.INVALID_PARALLELS_CONFIGURATION);
134163

164+
// validate local args i.e --local-mode and --local-identifier
165+
166+
if( Utils.searchForOption('--local-identifier') && (Utils.isUndefined(args.localIdentifier) || (!Utils.isUndefined(args.localIdentifier) && !args.localIdentifier.trim()))) reject(Constants.validationMessages.INVALID_CLI_LOCAL_IDENTIFIER);
167+
168+
if( Utils.getLocalFlag(bsConfig.connection_settings) && (Utils.isUndefined(bsConfig["connection_settings"]["local_identifier"]) || ( !Utils.isUndefined(bsConfig["connection_settings"]["local_identifier"]) && !bsConfig["connection_settings"]["local_identifier"].trim()))) reject(Constants.validationMessages.INVALID_LOCAL_IDENTIFIER);
169+
170+
if( Utils.searchForOption('--local-mode') && ( Utils.isUndefined(args.localMode) || (!Utils.isUndefined(args.localMode) && !["always-on","on-demand"].includes(args.localMode)))) reject(Constants.validationMessages.INVALID_LOCAL_MODE);
171+
172+
if( Utils.searchForOption('--local-config-file') && ( Utils.isUndefined(args.localConfigFile) || (!Utils.isUndefined(args.localConfigFile) && !fs.existsSync(args.localConfigFile)))) reject(Constants.validationMessages.INVALID_LOCAL_CONFIG_FILE);
173+
135174
// validate if config file provided exists or not when cypress_config_file provided
136175
// validate the cypressProjectDir key otherwise.
137176
let cypressConfigFilePath = bsConfig.run_settings.cypressConfigFilePath;

‎bin/helpers/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
"uploadUrl": "https://api-cloud.browserstack.com/automate-frameworks/cypress/upload",
33
"rails_host": "https://api.browserstack.com",
44
"dashboardUrl": "https://automate.browserstack.com/dashboard/v2/builds/",
5-
"usageReportingUrl": "https://eds.browserstack.com:443/send_event_cy_internal"
5+
"usageReportingUrl": "https://eds.browserstack.com:443/send_event_cy_internal",
6+
"localTestingListUrl": "https://www.browserstack.com/local/v1/list"
67
}

‎bin/helpers/constants.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ const userMessages = {
3737
FATAL_NETWORK_ERROR: `fatal: unable to access '${config.buildUrl}': Could not resolve host: ${config.rails_host}`,
3838
RETRY_LIMIT_EXCEEDED: `Max retries exceeded trying to connect to the host (retries: ${config.retries})`,
3939
CHECK_DASHBOARD_AT: "Please check the build status at: ",
40-
CYPRESS_VERSION_CHANGED: "Your build will run using Cypress <actualVersion> instead of Cypress <preferredVersion>. Read more about supported versions here: http://browserstack.com/docs/automate/cypress/supported-versions"
40+
CYPRESS_VERSION_CHANGED: "Your build will run using Cypress <actualVersion> instead of Cypress <preferredVersion>. Read more about supported versions here: http://browserstack.com/docs/automate/cypress/supported-versions",
41+
LOCAL_START_FAILED: "Local Testing setup failed.",
42+
LOCAL_STOP_FAILED: "Local Binary stop failed.",
43+
INVALID_LOCAL_MODE_WARNING: "Invalid value specified for local_mode. local_mode: (\"always-on\" | \"on-demand\"). For more info, check out https://www.browserstack.com/docs/automate/cypress/cli-reference"
4144
};
4245

4346
const validationMessages = {
@@ -57,7 +60,11 @@ const validationMessages = {
5760
INVALID_CYPRESS_JSON: "cypress.json is not a valid json",
5861
INVALID_DEFAULT_AUTH_PARAMS: "Your username and access key are required to run your tests on BrowserStack. Learn more at https://www.browserstack.com/docs/automate/cypress/authentication",
5962
LOCAL_NOT_SET: "To test <baseUrlValue> on BrowserStack, you will have to set up Local testing. Read more here: https://www.browserstack.com/docs/automate/cypress/local-testing",
60-
INCORRECT_DIRECTORY_STRUCTURE: "No tests to run. Note that your Cypress tests should be in the same directory where the cypress.json exists."
63+
INCORRECT_DIRECTORY_STRUCTURE: "No tests to run. Note that your Cypress tests should be in the same directory where the cypress.json exists.",
64+
INVALID_CLI_LOCAL_IDENTIFIER: "When using --local-identifier, a value needs to be supplied. \n--local-identifier <String>.\nFor more info, check out https://www.browserstack.com/docs/automate/cypress/cli-reference",
65+
INVALID_LOCAL_MODE: "When using --local-mode, a value needs to be supplied. \n--local-mode (\"always-on\" | \"on-demand\").\nFor more info, check out https://www.browserstack.com/docs/automate/cypress/cli-reference",
66+
INVALID_LOCAL_CONFIG_FILE: "Using --local-config-file requires an input of the form /path/to/config-file.yml.\nFor more info, check out https://www.browserstack.com/docs/automate/cypress/cli-reference",
67+
INVALID_LOCAL_IDENTIFIER: "Invalid value specified for local_identifier. For more info, check out https://www.browserstack.com/docs/automate/cypress/cli-reference"
6168
};
6269

6370
const cliMessages = {
@@ -94,6 +101,10 @@ const cliMessages = {
94101
SYNC_DESCRIPTION: "Makes the run command in sync",
95102
BUILD_REPORT_MESSAGE: "See the entire build report here",
96103
HEADED: "Run your tests in a headed browser instead of a headless browser",
104+
LOCAL: "Accepted values: (true | false) - create a local testing connection to let you test staging and localhost websites, or sites behind proxies; learn more at browserstack.com/local-testing",
105+
LOCAL_MODE: 'Accepted values: ("always-on" | "on-demand") - if you choose to keep the binary "always-on", it will speed up your tests by keeping the Local connection warmed up in the background; otherwise, you can choose to have it spawn and killed for every build',
106+
LOCAL_IDENTIFIER: "Accepted values: String - assign an identifier to your Local process instance",
107+
LOCAL_CONFIG_FILE: "Accepted values: String - path to local config-file to your Local process instance. Learn more at https://www.browserstack.com/local-testing/binary-params"
97108
},
98109
COMMON: {
99110
DISABLE_USAGE_REPORTING: "Disable usage reporting",

‎bin/helpers/usageReporting.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,5 +231,6 @@ function send(args) {
231231
}
232232

233233
module.exports = {
234-
send
235-
}
234+
send,
235+
cli_version_and_path,
236+
};

‎bin/helpers/utils.js

Lines changed: 242 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ const os = require("os");
33
const path = require("path");
44
const fs = require("fs");
55
const glob = require('glob');
6+
const getmac = require('getmac').default;
7+
const { v4: uuidv4 } = require('uuid');
8+
const browserstack = require('browserstack-local');
69

710
const usageReporting = require("./usageReporting"),
811
logger = require("./logger").winstonLogger,
@@ -11,6 +14,8 @@ const usageReporting = require("./usageReporting"),
1114
syncCliLogger = require("../helpers/logger").syncCliLogger,
1215
config = require("../helpers/config");
1316

17+
const request = require('request');
18+
1419
exports.validateBstackJson = (bsConfigPath) => {
1520
return new Promise(function (resolve, reject) {
1621
try {
@@ -33,34 +38,46 @@ exports.getErrorCodeFromMsg = (errMsg) => {
3338
let errorCode = null;
3439
switch (errMsg) {
3540
case Constants.validationMessages.EMPTY_BROWSERSTACK_JSON:
36-
errorCode = "bstack_json_invalid_empty";
41+
errorCode = 'bstack_json_invalid_empty';
3742
break;
3843
case Constants.validationMessages.INCORRECT_AUTH_PARAMS:
39-
errorCode = "bstack_json_invalid_missing_keys";
44+
errorCode = 'bstack_json_invalid_missing_keys';
4045
break;
4146
case Constants.validationMessages.EMPTY_BROWSER_LIST:
42-
errorCode = "bstack_json_invalid_no_browsers";
47+
errorCode = 'bstack_json_invalid_no_browsers';
4348
break;
4449
case Constants.validationMessages.EMPTY_RUN_SETTINGS:
45-
errorCode = "bstack_json_invalid_no_run_settings";
50+
errorCode = 'bstack_json_invalid_no_run_settings';
4651
break;
4752
case Constants.validationMessages.EMPTY_CYPRESS_PROJ_DIR:
48-
errorCode = "bstack_json_invalid_no_cypress_proj_dir";
53+
errorCode = 'bstack_json_invalid_no_cypress_proj_dir';
4954
break;
5055
case Constants.validationMessages.INVALID_DEFAULT_AUTH_PARAMS:
51-
errorCode = "bstack_json_default_auth_keys";
56+
errorCode = 'bstack_json_default_auth_keys';
5257
break;
5358
case Constants.validationMessages.INVALID_PARALLELS_CONFIGURATION:
54-
errorCode = "invalid_parallels_specified";
59+
errorCode = 'invalid_parallels_specified';
60+
break;
61+
case Constants.validationMessages.INVALID_LOCAL_IDENTIFIER:
62+
errorCode = 'invalid_local_identifier';
63+
break;
64+
case Constants.validationMessages.INVALID_CLI_LOCAL_IDENTIFIER:
65+
errorCode = 'invalid_local_identifier';
66+
break;
67+
case Constants.validationMessages.INVALID_LOCAL_MODE:
68+
errorCode = 'invalid_local_mode';
69+
break;
70+
case Constants.validationMessages.INVALID_LOCAL_CONFIG_FILE:
71+
errorCode = 'invalid_local_config_file';
5572
break;
5673
case Constants.validationMessages.LOCAL_NOT_SET:
57-
errorCode = "cypress_json_base_url_no_local";
74+
errorCode = 'cypress_json_base_url_no_local';
5875
break;
5976
case Constants.validationMessages.INCORRECT_DIRECTORY_STRUCTURE:
60-
errorCode = "invalid_directory_structure";
77+
errorCode = 'invalid_directory_structure';
6178
break;
6279
case Constants.validationMessages.INVALID_CYPRESS_CONFIG_FILE:
63-
errorCode = "invalid_cypress_config_file";
80+
errorCode = 'invalid_cypress_config_file';
6481
break;
6582
}
6683
if (
@@ -247,7 +264,7 @@ exports.isParallelValid = (value) => {
247264
}
248265

249266
exports.getUserAgent = () => {
250-
return `BStack-Cypress-CLI/1.5.1 (${os.arch()}/${os.platform()}/${os.release()})`;
267+
return `BStack-Cypress-CLI/1.8.0 (${os.arch()}/${os.platform()}/${os.release()})`;
251268
};
252269

253270
exports.isAbsolute = (configPath) => {
@@ -311,25 +328,228 @@ exports.getLocalFlag = (connectionSettings) => {
311328
);
312329
};
313330

314-
exports.setLocal = (bsConfig) => {
315-
if (!this.isUndefined(process.env.BROWSERSTACK_LOCAL)) {
331+
exports.setLocal = (bsConfig, args) => {
332+
let localInferred = !(this.searchForOption('--local-mode'));
333+
if (!this.isUndefined(args.local)) {
334+
let local = false;
335+
if (String(args.local).toLowerCase() === 'true') {
336+
local = true;
337+
}
338+
bsConfig['connection_settings']['local'] = local;
339+
} else if (!this.isUndefined(process.env.BROWSERSTACK_LOCAL)) {
316340
let local = false;
317-
if (String(process.env.BROWSERSTACK_LOCAL).toLowerCase() === "true")
341+
if (String(process.env.BROWSERSTACK_LOCAL).toLowerCase() === 'true') {
318342
local = true;
319-
bsConfig["connection_settings"]["local"] = local;
343+
}
344+
bsConfig['connection_settings']['local'] = local;
320345
logger.info(
321-
"Reading local setting from the environment variable BROWSERSTACK_LOCAL"
346+
'Reading local setting from the environment variable BROWSERSTACK_LOCAL'
322347
);
348+
} else if (
349+
this.isUndefined(bsConfig['connection_settings']['local']) &&
350+
( !this.isUndefined(args.localMode) || !this.isUndefined(bsConfig['connection_settings']['local_mode']) )
351+
) {
352+
bsConfig['connection_settings']['local'] = true;
353+
bsConfig.connection_settings.local_inferred = localInferred;
323354
}
324355
};
325356

326-
exports.setLocalIdentifier = (bsConfig) => {
327-
if (!this.isUndefined(process.env.BROWSERSTACK_LOCAL_IDENTIFIER)) {
357+
exports.setLocalIdentifier = (bsConfig, args) => {
358+
if (!this.isUndefined(args.localIdentifier)){
359+
bsConfig["connection_settings"]["local_identifier"] = args.localIdentifier;
360+
bsConfig['connection_settings']['local_mode'] = "always-on";
361+
} else if (!this.isUndefined(process.env.BROWSERSTACK_LOCAL_IDENTIFIER)) {
328362
bsConfig["connection_settings"]["local_identifier"] =
329363
process.env.BROWSERSTACK_LOCAL_IDENTIFIER;
330364
logger.info(
331365
"Reading local identifier from the environment variable BROWSERSTACK_LOCAL_IDENTIFIER"
332366
);
367+
bsConfig['connection_settings']['local_mode'] = 'always-on';
368+
} else if (
369+
bsConfig['connection_settings']['local'] &&
370+
!this.isUndefined(bsConfig["connection_settings"]["local_identifier"])
371+
){
372+
bsConfig['connection_settings']['local_mode'] = 'always-on';
373+
} else if (
374+
bsConfig['connection_settings']['local'] &&
375+
this.isUndefined(bsConfig["connection_settings"]["local_identifier"])
376+
){
377+
bsConfig["connection_settings"]["local_identifier"] = this.generateLocalIdentifier(bsConfig['connection_settings']['local_mode']);
378+
}
379+
};
380+
381+
exports.setLocalMode = (bsConfig, args) => {
382+
if(String(bsConfig["connection_settings"]["local"]).toLowerCase() === "true"){
383+
let local_mode = 'on-demand';
384+
385+
let localModeUndefined= this.isUndefined(bsConfig["connection_settings"]["local_mode"]);
386+
387+
if (!this.isUndefined(args.localMode)) {
388+
if(String(args.localMode) === "always-on"){
389+
local_mode = 'always-on';
390+
}
391+
} else if (!localModeUndefined && !["always-on", "on-demand"].includes(bsConfig['connection_settings']['local_mode'])) {
392+
bsConfig.connection_settings.user_defined_local_mode_warning = bsConfig['connection_settings']['local_mode'];
393+
} else if (
394+
!this.isUndefined(bsConfig['connection_settings']['local_mode']) &&
395+
String(bsConfig['connection_settings']['local_mode']).toLowerCase() ===
396+
'always-on'
397+
) {
398+
local_mode = 'always-on';
399+
}
400+
bsConfig['connection_settings']['local_mode'] = local_mode;
401+
if (this.isUndefined(args.sync) || !args.sync ){
402+
bsConfig['connection_settings']['sync_inferred'] = true;
403+
}
404+
args.sync = true;
405+
406+
let localModeInferred = !(this.searchForOption('--local-mode'));
407+
408+
if (localModeInferred && localModeUndefined) {
409+
bsConfig.connection_settings.local_mode_inferred = local_mode;
410+
}
411+
}
412+
};
413+
414+
exports.setupLocalTesting = (bsConfig, args) => {
415+
return new Promise(async (resolve, reject) => {
416+
if( bsConfig['connection_settings'] && bsConfig['connection_settings']['local'] && String(bsConfig['connection_settings']['local']) === "true" ){
417+
let localIdentifierRunning = await this.checkLocalIdentifierRunning(
418+
bsConfig, bsConfig['connection_settings']['local_identifier']
419+
);
420+
if (!localIdentifierRunning){
421+
var bs_local = this.getLocalBinary();
422+
var bs_local_args = this.setLocalArgs(bsConfig, args);
423+
let that = this;
424+
logger.info('Setting up Local testing...');
425+
bs_local.start(bs_local_args, function (localStartError) {
426+
if (that.isUndefined(localStartError)) {
427+
resolve(bs_local);
428+
} else {
429+
let message = `name: ${localStartError.name}, message: ${localStartError.message}, extra: ${localStartError.extra}`,
430+
errorCode = "local_start_error";
431+
that.sendUsageReport(
432+
bsConfig,
433+
args,
434+
message,
435+
Constants.messageTypes.ERROR,
436+
errorCode
437+
);
438+
reject(Constants.userMessages.LOCAL_START_FAILED);
439+
}
440+
});
441+
} else {
442+
resolve();
443+
}
444+
} else {
445+
resolve();
446+
}
447+
});
448+
};
449+
450+
exports.stopLocalBinary = (bsConfig, bs_local, args) => {
451+
return new Promise(async (resolve, reject) => {
452+
if(bsConfig['connection_settings'] && bsConfig['connection_settings']['local']){
453+
let localIdentifierRunning = await this.checkLocalIdentifierRunning(bsConfig,bsConfig["connection_settings"]["local_identifier"]);
454+
if(!localIdentifierRunning){
455+
let message = `Local Binary not running.`,
456+
errorCode = 'local_identifier_error';
457+
this.sendUsageReport(
458+
bsConfig,
459+
args,
460+
message,
461+
Constants.messageTypes.ERROR,
462+
errorCode
463+
);
464+
}
465+
}
466+
if (!this.isUndefined(bs_local) && bs_local.isRunning() && bsConfig['connection_settings'] && bsConfig['connection_settings']['local_mode'].toLowerCase() != "always-on") {
467+
let that = this;
468+
bs_local.stop(function (localStopError) {
469+
if (that.isUndefined(localStopError)) {
470+
resolve();
471+
} else {
472+
let message = `name: ${localStopError.name}, message: ${localStopError.message}, extra: ${localStopError.extra}`,
473+
errorCode = 'local_stop_error';
474+
that.sendUsageReport(
475+
bsConfig,
476+
args,
477+
message,
478+
Constants.messageTypes.ERROR,
479+
errorCode
480+
);
481+
resolve(Constants.userMessages.LOCAL_STOP_FAILED);
482+
}
483+
});
484+
} else {
485+
resolve();
486+
}
487+
});
488+
};
489+
490+
exports.getLocalBinary = () => {
491+
return new browserstack.Local();
492+
};
493+
494+
exports.setLocalArgs = (bsConfig, args) => {
495+
let local_args = {}
496+
local_args['key'] = bsConfig['auth']['access_key'];
497+
local_args['localIdentifier'] = bsConfig["connection_settings"]["local_identifier"];
498+
local_args['daemon'] = true;
499+
local_args['enable-logging-for-api'] = true
500+
local_args['source'] = `cypress:${usageReporting.cli_version_and_path(bsConfig).version}`;
501+
if(!this.isUndefined(bsConfig["connection_settings"]["local_config_file"])){
502+
local_args['config-file'] = path.resolve(bsConfig["connection_settings"]["local_config_file"]);
503+
}
504+
return local_args;
505+
};
506+
507+
exports.generateLocalIdentifier = (mode) => {
508+
let local_identifier = undefined;
509+
if(mode == "always-on"){
510+
local_identifier = getmac();
511+
} else {
512+
local_identifier = uuidv4();
513+
}
514+
return Buffer.from(local_identifier).toString("base64");
515+
};
516+
517+
exports.checkLocalIdentifierRunning = (bsConfig, localIdentifier) => {
518+
let options = {
519+
url: `${config.localTestingListUrl}?auth_token=${bsConfig.auth.access_key}&state=running`,
520+
auth: {
521+
user: bsConfig.auth.username,
522+
password: bsConfig.auth.access_key,
523+
},
524+
headers: {
525+
'User-Agent': this.getUserAgent(),
526+
},
527+
};
528+
let that = this;
529+
return new Promise ( function(resolve, reject) {
530+
request.get(options, function (err, resp, body) {
531+
if(err){
532+
reject(err);
533+
}
534+
let response = JSON.parse(body);
535+
let localInstances = [];
536+
if(!that.isUndefined(response['instances'])){
537+
localInstances = response['instances'];
538+
}
539+
let localIdentifiers = [];
540+
541+
localInstances.forEach(function(instance){
542+
localIdentifiers.push(instance['localIdentifier']);
543+
});
544+
545+
resolve(localIdentifiers.includes(localIdentifier));
546+
});
547+
});
548+
};
549+
550+
exports.setLocalConfigFile = (bsConfig, args) => {
551+
if(!this.isUndefined(args.localConfigFile)){
552+
bsConfig['connection_settings']['local_config_file'] = args.localConfigFile;
333553
}
334554
};
335555

@@ -402,6 +622,10 @@ exports.isJSONInvalid = (err, args) => {
402622
return false
403623
}
404624

625+
if( err === Constants.validationMessages.INVALID_CLI_LOCAL_IDENTIFIER || err === Constants.validationMessages.INVALID_LOCAL_MODE ){
626+
return false
627+
}
628+
405629
return invalid
406630
}
407631

‎bin/runner.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,22 @@ var argv = yargs
205205
default: false,
206206
describe: Constants.cliMessages.RUN.HEADED,
207207
type: "boolean"
208+
},
209+
'local': {
210+
describe: Constants.cliMessages.RUN.LOCAL,
211+
type: "boolean"
212+
},
213+
'local-identifier': {
214+
describe: Constants.cliMessages.RUN.LOCAL_IDENTIFIER,
215+
type: "string"
216+
},
217+
'local-mode': {
218+
describe: Constants.cliMessages.RUN.LOCAL_MODE,
219+
type: "string"
220+
},
221+
'local-config-file': {
222+
describe: Constants.cliMessages.RUN.LOCAL_CONFIG_FILE,
223+
type: "string"
208224
}
209225
})
210226
.help('help')

‎bin/templates/configTemplate.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ module.exports = function () {
6565
},
6666
"connection_settings": {
6767
"local": false,
68-
"local_identifier": null
68+
"local_identifier": null,
69+
"local_mode": null,
70+
"local_config_file": null
6971
},
7072
"disable_usage_reporting": false
7173
}

‎package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@
1414
"dependencies": {
1515
"archiver": "^5.2.0",
1616
"async": "^3.2.0",
17+
"browserstack-local": "^1.4.8",
1718
"chalk": "^4.1.0",
1819
"fs-extra": "^8.1.0",
20+
"getmac": "^5.17.0",
1921
"glob": "^7.1.6",
2022
"mkdirp": "^1.0.3",
2123
"request": "^2.88.0",
2224
"requestretry": "^4.1.0",
2325
"table": "^5.4.6",
26+
"uuid": "^8.3.2",
2427
"winston": "^2.3.1",
25-
"yargs": "^14.2.2"
28+
"yargs": "^14.2.3"
2629
},
2730
"repository": {
2831
"type": "git",

‎test/unit/bin/commands/runs.js

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const chai = require("chai"),
55
const Constants = require("../../../../bin/helpers/constants"),
66
logger = require("../../../../bin/helpers/logger").winstonLogger,
77
testObjects = require("../../support/fixtures/testObjects");
8-
const { setHeaded } = require("../../../../bin/helpers/utils");
8+
const { setHeaded, setupLocalTesting, stopLocalBinary, setUserSpecs, setLocalConfigFile } = require("../../../../bin/helpers/utils");
99

1010
const proxyquire = require("proxyquire").noCallThru();
1111

@@ -91,6 +91,7 @@ describe("runs", () => {
9191
setUserSpecsStub = sandbox.stub();
9292
setTestEnvsStub = sandbox.stub();
9393
getConfigPathStub = sandbox.stub();
94+
setupLocalTestingStub = sandbox.stub();
9495
setUsageReportingFlagStub = sandbox.stub().returns(undefined);
9596
sendUsageReportStub = sandbox.stub().callsFake(function () {
9697
return "end";
@@ -102,6 +103,8 @@ describe("runs", () => {
102103
setHeadedStub = sandbox.stub();
103104
deleteResultsStub = sandbox.stub();
104105
setDefaultsStub = sandbox.stub();
106+
setLocalModeStub = sandbox.stub();
107+
setLocalConfigFileStub = sandbox.stub();
105108
});
106109

107110
afterEach(() => {
@@ -132,14 +135,18 @@ describe("runs", () => {
132135
setHeaded: setHeadedStub,
133136
deleteResults: deleteResultsStub,
134137
setDefaults: setDefaultsStub,
135-
isJSONInvalid: isJSONInvalidStub
138+
setupLocalTesting: setupLocalTestingStub,
139+
isJSONInvalid: isJSONInvalidStub,
140+
setLocalMode: setLocalModeStub,
141+
setLocalConfigFile: setLocalConfigFileStub
136142
},
137143
'../helpers/capabilityHelper': {
138-
validate: capabilityValidatorStub,
144+
validate: capabilityValidatorStub
139145
},
140146
});
141147

142148
validateBstackJsonStub.returns(Promise.resolve(bsConfig));
149+
setupLocalTestingStub.returns(Promise.resolve("return nothing"));
143150
capabilityValidatorStub.returns(Promise.reject("random-error"));
144151

145152
return runs(args)
@@ -149,15 +156,23 @@ describe("runs", () => {
149156
.catch((error) => {
150157
sinon.assert.calledOnce(getConfigPathStub);
151158
sinon.assert.calledOnce(getConfigPathStub);
159+
sinon.assert.calledOnce(deleteResultsStub);
152160
sinon.assert.calledOnce(validateBstackJsonStub);
161+
sinon.assert.calledOnce(setDefaultsStub);
162+
sinon.assert.calledOnce(setUsernameStub);
163+
sinon.assert.calledOnce(setAccessKeyStub);
164+
sinon.assert.calledOnce(setBuildNameStub);
165+
sinon.assert.calledOnce(setCypressConfigFilenameStub);
166+
sinon.assert.calledOnce(setUserSpecsStub);
167+
sinon.assert.calledOnce(setTestEnvsStub);
168+
sinon.assert.calledOnce(setLocalStub);
169+
sinon.assert.calledOnce(setLocalModeStub);
170+
sinon.assert.calledOnce(setLocalConfigFileStub);
171+
sinon.assert.calledOnce(setHeadedStub);
153172
sinon.assert.calledOnce(capabilityValidatorStub);
154-
sinon.assert.calledOnce(setUsageReportingFlagStub);
155173
sinon.assert.calledOnce(getErrorCodeFromMsgStub);
156-
sinon.assert.calledOnce(setLocalStub);
157174
sinon.assert.calledOnce(setLocalIdentifierStub);
158-
sinon.assert.calledOnce(setHeadedStub);
159-
sinon.assert.calledOnce(deleteResultsStub);
160-
sinon.assert.calledOnce(setDefaultsStub);
175+
sinon.assert.calledOnce(setUsageReportingFlagStub);
161176
sinon.assert.calledOnceWithExactly(
162177
sendUsageReportStub,
163178
bsConfig,
@@ -192,11 +207,14 @@ describe("runs", () => {
192207
archiverStub = sandbox.stub();
193208
deleteZipStub = sandbox.stub();
194209
setLocalStub = sandbox.stub();
210+
setLocalModeStub = sandbox.stub();
211+
setupLocalTestingStub = sandbox.stub();
195212
setLocalIdentifierStub = sandbox.stub();
196213
setHeadedStub = sandbox.stub();
197214
deleteResultsStub = sandbox.stub();
198215
getNumberOfSpecFilesStub = sandbox.stub().returns([]);
199216
setDefaultsStub = sandbox.stub();
217+
setLocalConfigFileStub = sandbox.stub();
200218
});
201219

202220
afterEach(() => {
@@ -223,11 +241,14 @@ describe("runs", () => {
223241
setUsageReportingFlag: setUsageReportingFlagStub,
224242
getConfigPath: getConfigPathStub,
225243
setLocal: setLocalStub,
244+
setLocalMode: setLocalModeStub,
245+
setupLocalTesting: setupLocalTestingStub,
226246
setLocalIdentifier: setLocalIdentifierStub,
227247
setHeaded: setHeadedStub,
228248
deleteResults: deleteResultsStub,
229249
setDefaults: setDefaultsStub,
230-
getNumberOfSpecFiles: getNumberOfSpecFilesStub
250+
getNumberOfSpecFiles: getNumberOfSpecFilesStub,
251+
setLocalConfigFile: setLocalConfigFileStub
231252
},
232253
'../helpers/capabilityHelper': {
233254
validate: capabilityValidatorStub,
@@ -241,6 +262,7 @@ describe("runs", () => {
241262
});
242263

243264
validateBstackJsonStub.returns(Promise.resolve(bsConfig));
265+
setupLocalTestingStub.returns(Promise.resolve("nothing"))
244266
capabilityValidatorStub.returns(Promise.resolve(Constants.validationMessages.VALIDATED));
245267
archiverStub.returns(Promise.reject("random-error"));
246268

@@ -251,6 +273,12 @@ describe("runs", () => {
251273
.catch((error) => {
252274
sinon.assert.calledOnce(getConfigPathStub);
253275
sinon.assert.calledOnce(getConfigPathStub);
276+
sinon.assert.calledOnce(setLocalModeStub);
277+
sinon.assert.calledOnce(setUsernameStub);
278+
sinon.assert.calledOnce(setAccessKeyStub);
279+
sinon.assert.calledOnce(setBuildNameStub);
280+
sinon.assert.calledOnce(setLocalConfigFileStub);
281+
sinon.assert.calledOnce(setCypressConfigFilenameStub);
254282
sinon.assert.calledOnce(getNumberOfSpecFilesStub);
255283
sinon.assert.calledOnce(setParallelsStub);
256284
sinon.assert.calledOnce(setLocalStub);
@@ -298,11 +326,14 @@ describe("runs", () => {
298326
zipUploadStub = sandbox.stub();
299327
deleteZipStub = sandbox.stub();
300328
setLocalStub = sandbox.stub();
329+
setLocalModeStub = sandbox.stub();
330+
setupLocalTestingStub = sandbox.stub();
301331
setLocalIdentifierStub = sandbox.stub();
302332
setHeadedStub = sandbox.stub();
303333
deleteResultsStub = sandbox.stub();
304334
getNumberOfSpecFilesStub = sandbox.stub().returns([]);
305335
setDefaultsStub = sandbox.stub();
336+
setLocalConfigFileStub = sandbox.stub();
306337
});
307338

308339
afterEach(() => {
@@ -329,11 +360,14 @@ describe("runs", () => {
329360
setUsageReportingFlag: setUsageReportingFlagStub,
330361
getConfigPath: getConfigPathStub,
331362
setLocal: setLocalStub,
363+
setLocalMode: setLocalModeStub,
364+
setupLocalTesting: setupLocalTestingStub,
332365
setLocalIdentifier: setLocalIdentifierStub,
333366
setHeaded: setHeadedStub,
334367
deleteResults: deleteResultsStub,
335368
getNumberOfSpecFiles: getNumberOfSpecFilesStub,
336-
setDefaults: setDefaultsStub
369+
setDefaults: setDefaultsStub,
370+
setLocalConfigFile: setLocalConfigFileStub
337371
},
338372
'../helpers/capabilityHelper': {
339373
validate: capabilityValidatorStub,
@@ -351,6 +385,7 @@ describe("runs", () => {
351385

352386
validateBstackJsonStub.returns(Promise.resolve(bsConfig));
353387
capabilityValidatorStub.returns(Promise.resolve(Constants.validationMessages.VALIDATED));
388+
setupLocalTestingStub.returns(Promise.resolve("nothing"));
354389
archiverStub.returns(Promise.resolve("Zipping completed"));
355390
zipUploadStub.returns(Promise.reject("random-error"));
356391

@@ -361,6 +396,8 @@ describe("runs", () => {
361396
.catch((error) => {
362397
sinon.assert.calledOnce(getConfigPathStub);
363398
sinon.assert.calledOnce(getConfigPathStub);
399+
sinon.assert.calledOnce(setLocalModeStub);
400+
sinon.assert.calledOnce(setLocalConfigFileStub);
364401
sinon.assert.calledOnce(getNumberOfSpecFilesStub);
365402
sinon.assert.calledOnce(setParallelsStub);
366403
sinon.assert.calledOnce(setLocalStub);
@@ -412,11 +449,15 @@ describe("runs", () => {
412449
createBuildStub = sandbox.stub();
413450
deleteZipStub = sandbox.stub();
414451
setLocalStub = sandbox.stub();
452+
setLocalModeStub = sandbox.stub();
453+
setupLocalTestingStub = sandbox.stub();
415454
setLocalIdentifierStub = sandbox.stub();
416455
setHeadedStub = sandbox.stub();
417456
deleteResultsStub = sandbox.stub();
418457
getNumberOfSpecFilesStub = sandbox.stub().returns([]);
419458
setDefaultsStub = sandbox.stub();
459+
stopLocalBinaryStub = sandbox.stub();
460+
setLocalConfigFileStub = sandbox.stub();
420461
});
421462

422463
afterEach(() => {
@@ -443,11 +484,15 @@ describe("runs", () => {
443484
setUsageReportingFlag: setUsageReportingFlagStub,
444485
getConfigPath: getConfigPathStub,
445486
setLocal: setLocalStub,
487+
setLocalMode: setLocalModeStub,
488+
setupLocalTesting: setupLocalTestingStub,
446489
setLocalIdentifier: setLocalIdentifierStub,
447490
setHeaded: setHeadedStub,
448491
deleteResults: deleteResultsStub,
449492
getNumberOfSpecFiles: getNumberOfSpecFilesStub,
450-
setDefaults: setDefaultsStub
493+
setDefaults: setDefaultsStub,
494+
stopLocalBinary: stopLocalBinaryStub,
495+
setLocalConfigFile: setLocalConfigFileStub
451496
},
452497
'../helpers/capabilityHelper': {
453498
validate: capabilityValidatorStub,
@@ -467,11 +512,13 @@ describe("runs", () => {
467512
});
468513

469514
validateBstackJsonStub.returns(Promise.resolve(bsConfig));
515+
setupLocalTestingStub.returns(Promise.resolve("nothing"));
470516
capabilityValidatorStub.returns(
471517
Promise.resolve(Constants.validationMessages.VALIDATED)
472518
);
473519
archiverStub.returns(Promise.resolve("Zipping completed"));
474520
zipUploadStub.returns(Promise.resolve("zip uploaded"));
521+
stopLocalBinaryStub.returns(Promise.resolve("nothing"));
475522
createBuildStub.returns(Promise.reject("random-error"));
476523

477524
return runs(args)
@@ -481,6 +528,9 @@ describe("runs", () => {
481528
.catch((error) => {
482529
sinon.assert.calledOnce(getConfigPathStub);
483530
sinon.assert.calledOnce(getConfigPathStub);
531+
sinon.assert.calledOnce(setLocalConfigFileStub);
532+
sinon.assert.calledOnce(setLocalModeStub);
533+
sinon.assert.calledOnce(setupLocalTestingStub);
484534
sinon.assert.calledOnce(validateBstackJsonStub);
485535
sinon.assert.calledOnce(capabilityValidatorStub);
486536
sinon.assert.calledOnce(getNumberOfSpecFilesStub);
@@ -539,9 +589,12 @@ describe("runs", () => {
539589
setDefaultsStub = sandbox.stub();
540590
isUndefinedStub = sandbox.stub();
541591
setLocalStub = sandbox.stub();
592+
setLocalModeStub = sandbox.stub();
593+
setupLocalTestingStub = sandbox.stub();
542594
setLocalIdentifierStub = sandbox.stub();
543595
setHeadedStub = sandbox.stub();
544596
getNumberOfSpecFilesStub = sandbox.stub().returns([]);
597+
setLocalConfigFileStub = sandbox.stub();
545598
});
546599

547600
afterEach(() => {
@@ -569,13 +622,16 @@ describe("runs", () => {
569622
setParallels: setParallelsStub,
570623
getConfigPath: getConfigPathStub,
571624
setLocal: setLocalStub,
625+
setLocalMode: setLocalModeStub,
626+
setupLocalTesting: setupLocalTestingStub,
572627
setLocalIdentifier: setLocalIdentifierStub,
573628
setHeaded: setHeadedStub,
574629
exportResults: exportResultsStub,
575630
deleteResults: deleteResultsStub,
576631
setDefaults: setDefaultsStub,
577632
isUndefined: isUndefinedStub,
578-
getNumberOfSpecFiles: getNumberOfSpecFilesStub
633+
getNumberOfSpecFiles: getNumberOfSpecFilesStub,
634+
setLocalConfigFile: setLocalConfigFileStub
579635
},
580636
'../helpers/capabilityHelper': {
581637
validate: capabilityValidatorStub,
@@ -598,6 +654,7 @@ describe("runs", () => {
598654
});
599655

600656
validateBstackJsonStub.returns(Promise.resolve(bsConfig));
657+
setupLocalTestingStub.returns(Promise.resolve("nothing"));
601658
capabilityValidatorStub.returns(
602659
Promise.resolve(Constants.validationMessages.VALIDATED)
603660
);
@@ -613,10 +670,13 @@ describe("runs", () => {
613670
sinon.assert.calledOnce(getConfigPathStub);
614671
sinon.assert.calledOnce(getConfigPathStub);
615672
sinon.assert.calledOnce(validateBstackJsonStub);
673+
sinon.assert.calledOnce(setLocalConfigFileStub);
616674
sinon.assert.calledOnce(capabilityValidatorStub);
617675
sinon.assert.calledOnce(getNumberOfSpecFilesStub);
618676
sinon.assert.calledOnce(setParallelsStub);
619677
sinon.assert.calledOnce(setLocalStub);
678+
sinon.assert.calledOnce(setLocalModeStub);
679+
sinon.assert.calledOnce(setupLocalTestingStub);
620680
sinon.assert.calledOnce(setLocalIdentifierStub);
621681
sinon.assert.calledOnce(setHeadedStub);
622682
sinon.assert.calledOnce(archiverStub);

‎test/unit/bin/helpers/utils.js

Lines changed: 541 additions & 18 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.