Skip to content

Commit c20373e

Browse files
Merge pull request #174 from browserstack/improved_cli
Improved CLI experience
2 parents 4a7acc7 + 312d006 commit c20373e

File tree

11 files changed

+350
-354
lines changed

11 files changed

+350
-354
lines changed

bin/commands/runs.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ const archiver = require("../helpers/archiver"),
1212
checkUploaded = require("../helpers/checkUploaded"),
1313
reportGenerator = require('../helpers/reporterHTML').reportGenerator,
1414
{initTimeComponents, instrumentEventTime, markBlockStart, markBlockEnd, getTimeComponents} = require('../helpers/timeComponents'),
15-
downloadBuildArtifacts = require('../helpers/buildArtifacts').downloadBuildArtifacts;
15+
downloadBuildArtifacts = require('../helpers/buildArtifacts').downloadBuildArtifacts,
16+
updateNotifier = require('update-notifier'),
17+
pkg = require('../../package.json');
1618

1719
module.exports = function run(args) {
1820
let bsConfigPath = utils.getConfigPath(args.cf);
@@ -75,6 +77,10 @@ module.exports = function run(args) {
7577

7678
//set config (--config)
7779
utils.setConfig(bsConfig, args);
80+
81+
// set sync/async mode (--async/--sync)
82+
utils.setCLIMode(bsConfig, args);
83+
7884
// set other cypress configs e.g. reporter and reporter-options
7985
utils.setOtherConfigs(bsConfig, args);
8086
markBlockEnd('setConfig');
@@ -118,19 +124,19 @@ module.exports = function run(args) {
118124
return build.createBuild(bsConfig, zip).then(function (data) {
119125
markBlockEnd('createBuild');
120126
markBlockEnd('total');
127+
utils.setProcessHooks(data.build_id, bsConfig, bs_local, args);
121128
let message = `${data.message}! ${Constants.userMessages.BUILD_CREATED} with build id: ${data.build_id}`;
122129
let dashboardLink = `${Constants.userMessages.VISIT_DASHBOARD} ${data.dashboard_url}`;
123130
utils.exportResults(data.build_id, `${config.dashboardUrl}${data.build_id}`);
124131
if ((utils.isUndefined(bsConfig.run_settings.parallels) && utils.isUndefined(args.parallels)) || (!utils.isUndefined(bsConfig.run_settings.parallels) && bsConfig.run_settings.parallels == Constants.cliMessages.RUN.DEFAULT_PARALLEL_MESSAGE)) {
125132
logger.warn(Constants.userMessages.NO_PARALLELS);
126133
}
127-
128134
if (bsConfig.run_settings.cypress_version && bsConfig.run_settings.cypress_version !== data.cypress_version) {
129135
if (bsConfig.run_settings.cypress_version.toString().match(Constants.LATEST_VERSION_SYNTAX_REGEX)) {
130-
let versionMessage = utils.latestSyntaxToActualVersionMessage(bsConfig.run_settings.cypress_version, data.cypress_version);
136+
let versionMessage = utils.latestSyntaxToActualVersionMessage(bsConfig.run_settings.cypress_version, data.cypress_version, data.framework_upgrade_message);
131137
logger.info(versionMessage);
132138
} else {
133-
let versionMessage = utils.versionChangedMessage(bsConfig.run_settings.cypress_version, data.cypress_version);
139+
let versionMessage = utils.versionChangedMessage(bsConfig.run_settings.cypress_version, data.cypress_version, data.framework_upgrade_message);
134140
logger.warn(versionMessage);
135141
}
136142
}
@@ -241,5 +247,10 @@ module.exports = function run(args) {
241247
utils.setUsageReportingFlag(null, args.disableUsageReporting);
242248
utils.sendUsageReport(null, args, err.message, Constants.messageTypes.ERROR, utils.getErrorCodeFromErr(err));
243249
process.exitCode = Constants.ERROR_EXIT_CODE;
250+
}).finally(function(){
251+
updateNotifier({
252+
pkg,
253+
updateCheckInterval: 1000 * 60 * 60 * 24 * 7,
254+
}).notify({isGlobal: true});
244255
});
245256
}

bin/commands/stop.js

Lines changed: 2 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const config = require("../helpers/config"),
99
module.exports = function stop(args) {
1010
let bsConfigPath = utils.getConfigPath(args.cf);
1111

12-
return utils.validateBstackJson(bsConfigPath).then(function (bsConfig) {
12+
return utils.validateBstackJson(bsConfigPath).then(async function (bsConfig) {
1313
utils.setDefaults(bsConfig, args);
1414

1515
// accept the username from command line if provided
@@ -25,69 +25,8 @@ module.exports = function stop(args) {
2525

2626
let buildId = args._[1];
2727

28-
let options = {
29-
url: config.buildStopUrl + buildId,
30-
auth: {
31-
user: bsConfig.auth.username,
32-
password: bsConfig.auth.access_key,
33-
},
34-
headers: {
35-
'User-Agent': utils.getUserAgent(),
36-
},
37-
};
28+
await utils.stopBrowserStackBuild(bsConfig, args, buildId);
3829

39-
request.post(options, function (err, resp, body) {
40-
let message = null;
41-
let messageType = null;
42-
let errorCode = null;
43-
44-
if (err) {
45-
message = Constants.userMessages.BUILD_STOP_FAILED;
46-
messageType = Constants.messageTypes.ERROR;
47-
errorCode = 'api_failed_build_stop';
48-
49-
logger.info(message);
50-
} else {
51-
let build = null;
52-
try {
53-
build = JSON.parse(body);
54-
} catch (error) {
55-
build = null;
56-
}
57-
58-
if (resp.statusCode == 299) {
59-
messageType = Constants.messageTypes.INFO;
60-
errorCode = 'api_deprecated';
61-
62-
if (build) {
63-
message = build.message;
64-
logger.info(message);
65-
} else {
66-
message = Constants.userMessages.API_DEPRECATED;
67-
logger.info(message);
68-
}
69-
} else if (resp.statusCode != 200) {
70-
messageType = Constants.messageTypes.ERROR;
71-
errorCode = 'api_failed_build_stop';
72-
73-
if (build) {
74-
message = `${
75-
Constants.userMessages.BUILD_STOP_FAILED
76-
} with error: \n${JSON.stringify(build, null, 2)}`;
77-
logger.error(message);
78-
if (build.message === 'Unauthorized') errorCode = 'api_auth_failed';
79-
} else {
80-
message = Constants.userMessages.BUILD_STOP_FAILED;
81-
logger.error(message);
82-
}
83-
} else {
84-
messageType = Constants.messageTypes.SUCCESS;
85-
message = `${JSON.stringify(build, null, 2)}`;
86-
logger.info(message);
87-
}
88-
}
89-
utils.sendUsageReport(bsConfig, args, message, messageType, errorCode);
90-
});
9130
}).catch(function (err) {
9231
logger.error(err);
9332
utils.setUsageReportingFlag(null, args.disableUsageReporting);

bin/helpers/capabilityHelper.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ const validate = (bsConfig, args) => {
156156

157157
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);
158158

159+
if( Utils.searchForOption('--async') && ( !Utils.isUndefined(args.async) && bsConfig["connection_settings"]["local"])) reject(Constants.validationMessages.INVALID_LOCAL_ASYNC_ARGS);
160+
159161
// validate if config file provided exists or not when cypress_config_file provided
160162
// validate the cypressProjectDir key otherwise.
161163
let cypressConfigFilePath = bsConfig.run_settings.cypressConfigFilePath;

bin/helpers/constants.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const syncCLI = {
77
},
88
INITIAL_DELAY_MULTIPLIER: 10,
99
DEFAULT_LINE_SEP: "\n--------------------------------------------------------------------------------",
10+
STARTUP_MESSAGE: "BrowserStack machines are now setting up Cypress with the specified npm dependencies for running your tests. It might take some time before your tests start runnning and showing up below..."
1011
};
1112

1213
const userMessages = {
@@ -41,15 +42,16 @@ const userMessages = {
4142
FATAL_NETWORK_ERROR: `fatal: unable to access '${config.buildUrl}': Could not resolve host: ${config.rails_host}`,
4243
RETRY_LIMIT_EXCEEDED: `Max retries exceeded trying to connect to the host (retries: ${config.retries})`,
4344
CHECK_DASHBOARD_AT: "Please check the build status at: ",
44-
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",
45+
CYPRESS_VERSION_CHANGED: "Your build will run using Cypress <actualVersion> instead of Cypress <preferredVersion>.<frameworkUpgradeMessage> Read more about supported versions here: http://browserstack.com/docs/automate/cypress/supported-versions",
4546
LOCAL_START_FAILED: "Local Testing setup failed.",
4647
LOCAL_STOP_FAILED: "Local Binary stop failed.",
4748
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",
4849
SPEC_LIMIT_WARNING: "You might not see all your results on the dashboard because of high spec count, please consider reducing the number of spec files in this folder.",
4950
DOWNLOAD_BUILD_ARTIFACTS_FAILED: "Downloading build artifacts for the build <build-id> failed for <machine-count> machines.",
5051
ASYNC_DOWNLOADS: "Test artifacts as specified under 'downloads' can be downloaded after the build has completed its run, using 'browserstack-cypress generate-downloads <build-id>'",
5152
DOWNLOAD_BUILD_ARTIFACTS_SUCCESS: "Your build artifact(s) have been successfully downloaded in '<user-path>/build_artifacts/<build-id>' directory",
52-
LATEST_SYNTAX_TO_ACTUAL_VERSION_MESSAGE: "Your build will run using Cypress <actualVersion> as you had specified <latestSyntaxVersion>. Read more about supported versions here: http://browserstack.com/docs/automate/cypress/supported-versions"
53+
LATEST_SYNTAX_TO_ACTUAL_VERSION_MESSAGE: "Your build will run using Cypress <actualVersion> as you had specified <latestSyntaxVersion>.<frameworkUpgradeMessage> Read more about supported versions here: http://browserstack.com/docs/automate/cypress/supported-versions",
54+
PROCESS_KILL_MESSAGE: "Stopping the CLI and the execution of the build on BrowserStack",
5355
};
5456

5557
const validationMessages = {
@@ -74,7 +76,8 @@ const validationMessages = {
7476
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",
7577
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",
7678
INVALID_LOCAL_IDENTIFIER: "Invalid value specified for local_identifier. For more info, check out https://www.browserstack.com/docs/automate/cypress/cli-reference",
77-
INVALID_BROWSER_ARGS: "Aborting as an unacceptable value was passed for --browser. Read more at https://www.browserstack.com/docs/automate/cypress/cli-reference"
79+
INVALID_BROWSER_ARGS: "Aborting as an unacceptable value was passed for --browser. Read more at https://www.browserstack.com/docs/automate/cypress/cli-reference",
80+
INVALID_LOCAL_ASYNC_ARGS: "Cannot run in --async mode when local is set to true. Please run the build after removing --async",
7881
};
7982

8083
const cliMessages = {
@@ -96,7 +99,7 @@ const cliMessages = {
9699
},
97100
RUN: {
98101
PARALLEL_DESC: "The maximum number of parallels to use to run your test suite",
99-
INFO: "Run your tests on BrowserStack.",
102+
INFO: "Run your tests on BrowserStack. For more help: `browserstack-cypress run --help`.",
100103
CYPRESS_DESC: "Path to Cypress config file",
101104
CYPRESS_CONFIG_DEMAND: "Cypress config file is required",
102105
BUILD_NAME: "The build name you want to use to name your test runs",
@@ -105,6 +108,7 @@ const cliMessages = {
105108
SPECS_DESCRIPTION: "Specify the spec files to run",
106109
ENV_DESCRIPTION: "Specify the environment variables for your spec files",
107110
SYNC_DESCRIPTION: "Makes the run command in sync",
111+
ASYNC_DESCRIPTION: "Makes the run command in async",
108112
BUILD_REPORT_MESSAGE: "See the entire build report here",
109113
HEADED: "Run your tests in a headed browser instead of a headless browser",
110114
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",
@@ -143,7 +147,7 @@ const messageTypes = {
143147
NULL: null
144148
}
145149

146-
const allowedFileTypes = ['js', 'json', 'txt', 'ts', 'feature', 'features', 'pdf', 'jpg', 'jpeg', 'png', 'zip', 'npmrc', 'xml', 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'jsx', 'coffee', 'cjsx', 'csv', 'tsv', 'yml', 'yaml', 'env'];
150+
const allowedFileTypes = ['js', 'json', 'txt', 'ts', 'feature', 'features', 'pdf', 'jpg', 'jpeg', 'png', 'zip', 'npmrc', 'xml', 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'jsx', 'coffee', 'cjsx', 'csv', 'tsv', 'yml', 'yaml', 'env', 'mov', 'mp4', 'mp3', 'wav'];
147151

148152
const filesToIgnoreWhileUploading = [
149153
'**/node_modules/**',

bin/helpers/sync/syncSpecsLogs.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const request = require("request"),
33
config = require("../config"),
44
utils = require("../utils"),
55
logger = require("../logger").syncCliLogger,
6+
winstonLogger = require("../logger").winstonLogger,
67
async = require('async'),
78
Constants = require("../constants"),
89
tableStream = require('table').createStream,
@@ -164,6 +165,7 @@ let showSpecsStatus = (data) => {
164165
}
165166

166167
let printInitialLog = () => {
168+
winstonLogger.info(Constants.syncCLI.STARTUP_MESSAGE);
167169
logger.info(`\n${Constants.syncCLI.LOGS.INIT_LOG}`)
168170
logger.info(lineSeparator);
169171
n = Constants.syncCLI.INITIAL_DELAY_MULTIPLIER

bin/helpers/utils.js

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const usageReporting = require("./usageReporting"),
1616
config = require("../helpers/config");
1717

1818
const request = require('request');
19+
const axios = require("axios");
1920

2021
exports.validateBstackJson = (bsConfigPath) => {
2122
return new Promise(function (resolve, reject) {
@@ -80,6 +81,9 @@ exports.getErrorCodeFromMsg = (errMsg) => {
8081
case Constants.validationMessages.INVALID_CYPRESS_CONFIG_FILE:
8182
errorCode = 'invalid_cypress_config_file';
8283
break;
84+
case Constants.validationMessages.INVALID_LOCAL_ASYNC_ARGS:
85+
errorCode = 'invalid_local_async_args';
86+
break;
8387
}
8488
if (
8589
errMsg.includes("Please use --config-file <path to browserstack.json>.")
@@ -733,15 +737,18 @@ exports.getNetworkErrorMessage = (dashboard_url) => {
733737
return chalk.red(message)
734738
}
735739

736-
exports.versionChangedMessage = (preferredVersion, actualVersion) => {
740+
exports.versionChangedMessage = (preferredVersion, actualVersion, frameworkUpgradeMessage = '') => {
737741
let message = Constants.userMessages.CYPRESS_VERSION_CHANGED.replace("<preferredVersion>", preferredVersion);
738742
message = message.replace("<actualVersion>", actualVersion);
743+
frameworkUpgradeMessage = frameworkUpgradeMessage.replace('.latest', "");
744+
message = message.replace('<frameworkUpgradeMessage>', frameworkUpgradeMessage);
739745
return message
740746
}
741747

742-
exports.latestSyntaxToActualVersionMessage = (latestSyntaxVersion, actualVersion) => {
748+
exports.latestSyntaxToActualVersionMessage = (latestSyntaxVersion, actualVersion, frameworkUpgradeMessage = '') => {
743749
let message = Constants.userMessages.LATEST_SYNTAX_TO_ACTUAL_VERSION_MESSAGE.replace("<latestSyntaxVersion>", latestSyntaxVersion);
744750
message = message.replace("<actualVersion>", actualVersion);
751+
message = message.replace('<frameworkUpgradeMessage>', frameworkUpgradeMessage)
745752
return message
746753
}
747754

@@ -760,6 +767,10 @@ exports.isJSONInvalid = (err, args) => {
760767
return false
761768
}
762769

770+
if( err === Constants.validationMessages.INVALID_LOCAL_ASYNC_ARGS && !this.isUndefined(args.async)) {
771+
return false
772+
}
773+
763774
return invalid
764775
}
765776

@@ -820,3 +831,94 @@ exports.getCypressJSON = (bsConfig) => {
820831
}
821832
return cypressJSON;
822833
}
834+
835+
exports.setCLIMode = (bsConfig, args) => {
836+
args.sync = true;
837+
if(!this.isUndefined(args.async) && args.async){
838+
args.sync = false;
839+
}
840+
}
841+
842+
exports.stopBrowserStackBuild = async (bsConfig, args, buildId ) => {
843+
let url = config.buildStopUrl + buildId;
844+
let options = {
845+
url: url,
846+
auth: {
847+
username: bsConfig["auth"]["username"],
848+
password: bsConfig["auth"]["access_key"],
849+
},
850+
headers: {
851+
'User-Agent': this.getUserAgent(),
852+
},
853+
};
854+
855+
let message = null;
856+
let messageType = null;
857+
let errorCode = null;
858+
try{
859+
let resp = await axios.post(url, {} , options);
860+
let build = null;
861+
if(resp.data){
862+
build = resp.data;
863+
}
864+
865+
if (resp.status == 299) {
866+
messageType = Constants.messageTypes.INFO;
867+
errorCode = 'api_deprecated';
868+
869+
if (build) {
870+
message = build.message;
871+
logger.info(message);
872+
} else {
873+
message = Constants.userMessages.API_DEPRECATED;
874+
logger.info(message);
875+
}
876+
} else if (resp.status != 200) {
877+
messageType = Constants.messageTypes.ERROR;
878+
errorCode = 'api_failed_build_stop';
879+
880+
if (build) {
881+
message = `${
882+
Constants.userMessages.BUILD_STOP_FAILED
883+
} with error: \n${JSON.stringify(build, null, 2)}`;
884+
logger.error(message);
885+
if (build.message === 'Unauthorized') errorCode = 'api_auth_failed';
886+
} else {
887+
message = Constants.userMessages.BUILD_STOP_FAILED;
888+
logger.error(message);
889+
}
890+
} else {
891+
messageType = Constants.messageTypes.SUCCESS;
892+
message = `${JSON.stringify(build, null, 2)}`;
893+
logger.info(message);
894+
}
895+
} catch(err){
896+
console.log(err);
897+
message = Constants.userMessages.BUILD_STOP_FAILED;
898+
messageType = Constants.messageTypes.ERROR;
899+
errorCode = 'api_failed_build_stop';
900+
logger.info(message);
901+
} finally {
902+
this.sendUsageReport(bsConfig, args, message, messageType, errorCode);
903+
}
904+
}
905+
906+
exports.setProcessHooks = (buildId, bsConfig, bsLocal, args) => {
907+
let bindData = {
908+
buildId: buildId,
909+
bsConfig: bsConfig,
910+
bsLocalInstance: bsLocal,
911+
args: args
912+
}
913+
process.on('SIGINT', processExitHandler.bind(this, bindData));
914+
process.on('SIGTERM', processExitHandler.bind(this, bindData));
915+
process.on('SIGBREAK', processExitHandler.bind(this, bindData));
916+
process.on('uncaughtException', processExitHandler.bind(this, bindData));
917+
}
918+
919+
async function processExitHandler(exitData){
920+
logger.warn(Constants.userMessages.PROCESS_KILL_MESSAGE);
921+
await this.stopBrowserStackBuild(exitData.bsConfig, exitData.args, exitData.buildId);
922+
await this.stopLocalBinary(exitData.bsConfig, exitData.bsLocalInstance, exitData.args);
923+
process.exit(0);
924+
}

bin/runner.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,15 @@ var argv = yargs
164164
type: "boolean"
165165
},
166166
'sync': {
167-
default: false,
167+
default: true,
168168
describe: Constants.cliMessages.RUN.SYNC_DESCRIPTION,
169169
type: "boolean"
170170
},
171+
'async': {
172+
default: false,
173+
describe: Constants.cliMessages.RUN.ASYNC_DESCRIPTION,
174+
type: "boolean"
175+
},
171176
'force-upload': {
172177
default: false,
173178
describe: Constants.cliMessages.COMMON.FORCE_UPLOAD,

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"winston": "^2.3.1",
2828
"yargs": "^14.2.3",
2929
"axios": "^0.21.1",
30-
"unzipper": "^0.10.11"
30+
"unzipper": "^0.10.11",
31+
"update-notifier": "^5.1.0"
3132
},
3233
"repository": {
3334
"type": "git",

0 commit comments

Comments
 (0)