Skip to content

Commit d0219ff

Browse files
Added async mode and refactored build stop flow.
1 parent 2392a74 commit d0219ff

File tree

7 files changed

+134
-75
lines changed

7 files changed

+134
-75
lines changed

bin/commands/runs.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ module.exports = function run(args) {
7777

7878
//set config (--config)
7979
utils.setConfig(bsConfig, args);
80+
81+
// set sync/async mode (--async/--sync)
82+
utils.setCLIMode(bsConfig, args);
83+
8084
// set other cypress configs e.g. reporter and reporter-options
8185
utils.setOtherConfigs(bsConfig, args);
8286
markBlockEnd('setConfig');
@@ -120,19 +124,19 @@ module.exports = function run(args) {
120124
return build.createBuild(bsConfig, zip).then(function (data) {
121125
markBlockEnd('createBuild');
122126
markBlockEnd('total');
127+
utils.setProcessHooks(data.build_id, bsConfig, bs_local, args);
123128
let message = `${data.message}! ${Constants.userMessages.BUILD_CREATED} with build id: ${data.build_id}`;
124129
let dashboardLink = `${Constants.userMessages.VISIT_DASHBOARD} ${data.dashboard_url}`;
125130
utils.exportResults(data.build_id, `${config.dashboardUrl}${data.build_id}`);
126131
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)) {
127132
logger.warn(Constants.userMessages.NO_PARALLELS);
128133
}
129-
130134
if (bsConfig.run_settings.cypress_version && bsConfig.run_settings.cypress_version !== data.cypress_version) {
131135
if (bsConfig.run_settings.cypress_version.toString().match(Constants.LATEST_VERSION_SYNTAX_REGEX)) {
132-
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);
133137
logger.info(versionMessage);
134138
} else {
135-
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);
136140
logger.warn(versionMessage);
137141
}
138142
}
@@ -241,6 +245,6 @@ module.exports = function run(args) {
241245
updateNotifier({
242246
pkg,
243247
updateCheckInterval: 1000 * 60 * 60 * 24 * 7,
244-
}).notify();
248+
}).notify({isGlobal: true});
245249
});
246250
}

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: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,16 @@ const userMessages = {
4242
FATAL_NETWORK_ERROR: `fatal: unable to access '${config.buildUrl}': Could not resolve host: ${config.rails_host}`,
4343
RETRY_LIMIT_EXCEEDED: `Max retries exceeded trying to connect to the host (retries: ${config.retries})`,
4444
CHECK_DASHBOARD_AT: "Please check the build status at: ",
45-
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",
4646
LOCAL_START_FAILED: "Local Testing setup failed.",
4747
LOCAL_STOP_FAILED: "Local Binary stop failed.",
4848
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",
4949
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.",
5050
DOWNLOAD_BUILD_ARTIFACTS_FAILED: "Downloading build artifacts for the build <build-id> failed for <machine-count> machines.",
5151
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>'",
5252
DOWNLOAD_BUILD_ARTIFACTS_SUCCESS: "Your build artifact(s) have been successfully downloaded in '<user-path>/build_artifacts/<build-id>' directory",
53-
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",
5455
};
5556

5657
const validationMessages = {
@@ -75,7 +76,8 @@ const validationMessages = {
7576
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",
7677
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",
7778
INVALID_LOCAL_IDENTIFIER: "Invalid value specified for local_identifier. For more info, check out https://www.browserstack.com/docs/automate/cypress/cli-reference",
78-
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",
7981
};
8082

8183
const cliMessages = {
@@ -97,7 +99,7 @@ const cliMessages = {
9799
},
98100
RUN: {
99101
PARALLEL_DESC: "The maximum number of parallels to use to run your test suite",
100-
INFO: "Run your tests on BrowserStack.",
102+
INFO: "Run your tests on BrowserStack. For more help: `browserstack-cypress run --help`.",
101103
CYPRESS_DESC: "Path to Cypress config file",
102104
CYPRESS_CONFIG_DEMAND: "Cypress config file is required",
103105
BUILD_NAME: "The build name you want to use to name your test runs",
@@ -106,6 +108,7 @@ const cliMessages = {
106108
SPECS_DESCRIPTION: "Specify the spec files to run",
107109
ENV_DESCRIPTION: "Specify the environment variables for your spec files",
108110
SYNC_DESCRIPTION: "Makes the run command in sync",
111+
ASYNC_DESCRIPTION: "Makes the run command in async",
109112
BUILD_REPORT_MESSAGE: "See the entire build report here",
110113
HEADED: "Run your tests in a headed browser instead of a headless browser",
111114
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",
@@ -144,7 +147,7 @@ const messageTypes = {
144147
NULL: null
145148
}
146149

147-
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'];
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'];
148151

149152
const filesToIgnoreWhileUploading = [
150153
'**/node_modules/**',

bin/helpers/utils.js

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

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,

test/unit/bin/helpers/utils.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ describe('utils', () => {
9898
constant.validationMessages.INVALID_LOCAL_CONFIG_FILE
9999
)
100100
).to.eq('invalid_local_config_file');
101+
expect(
102+
utils.getErrorCodeFromMsg(
103+
constant.validationMessages.INVALID_LOCAL_ASYNC_ARGS
104+
)
105+
).to.eq('invalid_local_async_args');
101106
expect(
102107
utils.getErrorCodeFromMsg('Invalid browserstack.json file.')
103108
).to.eq('bstack_json_invalid');

0 commit comments

Comments
 (0)