Skip to content

Improved CLI experience #174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions bin/commands/runs.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ const archiver = require("../helpers/archiver"),
checkUploaded = require("../helpers/checkUploaded"),
reportGenerator = require('../helpers/reporterHTML').reportGenerator,
{initTimeComponents, instrumentEventTime, markBlockStart, markBlockEnd, getTimeComponents} = require('../helpers/timeComponents'),
downloadBuildArtifacts = require('../helpers/buildArtifacts').downloadBuildArtifacts;
downloadBuildArtifacts = require('../helpers/buildArtifacts').downloadBuildArtifacts,
updateNotifier = require('update-notifier'),
pkg = require('../../package.json');

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

//set config (--config)
utils.setConfig(bsConfig, args);

// set sync/async mode (--async/--sync)
utils.setCLIMode(bsConfig, args);

// set other cypress configs e.g. reporter and reporter-options
utils.setOtherConfigs(bsConfig, args);
markBlockEnd('setConfig');
Expand Down Expand Up @@ -118,19 +124,19 @@ module.exports = function run(args) {
return build.createBuild(bsConfig, zip).then(function (data) {
markBlockEnd('createBuild');
markBlockEnd('total');
utils.setProcessHooks(data.build_id, bsConfig, bs_local, args);
let message = `${data.message}! ${Constants.userMessages.BUILD_CREATED} with build id: ${data.build_id}`;
let dashboardLink = `${Constants.userMessages.VISIT_DASHBOARD} ${data.dashboard_url}`;
utils.exportResults(data.build_id, `${config.dashboardUrl}${data.build_id}`);
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)) {
logger.warn(Constants.userMessages.NO_PARALLELS);
}

if (bsConfig.run_settings.cypress_version && bsConfig.run_settings.cypress_version !== data.cypress_version) {
if (bsConfig.run_settings.cypress_version.toString().match(Constants.LATEST_VERSION_SYNTAX_REGEX)) {
let versionMessage = utils.latestSyntaxToActualVersionMessage(bsConfig.run_settings.cypress_version, data.cypress_version);
let versionMessage = utils.latestSyntaxToActualVersionMessage(bsConfig.run_settings.cypress_version, data.cypress_version, data.framework_upgrade_message);
logger.info(versionMessage);
} else {
let versionMessage = utils.versionChangedMessage(bsConfig.run_settings.cypress_version, data.cypress_version);
let versionMessage = utils.versionChangedMessage(bsConfig.run_settings.cypress_version, data.cypress_version, data.framework_upgrade_message);
logger.warn(versionMessage);
}
}
Expand Down Expand Up @@ -241,5 +247,10 @@ module.exports = function run(args) {
utils.setUsageReportingFlag(null, args.disableUsageReporting);
utils.sendUsageReport(null, args, err.message, Constants.messageTypes.ERROR, utils.getErrorCodeFromErr(err));
process.exitCode = Constants.ERROR_EXIT_CODE;
}).finally(function(){
updateNotifier({
pkg,
updateCheckInterval: 1000 * 60 * 60 * 24 * 7,
}).notify({isGlobal: true});
});
}
65 changes: 2 additions & 63 deletions bin/commands/stop.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const config = require("../helpers/config"),
module.exports = function stop(args) {
let bsConfigPath = utils.getConfigPath(args.cf);

return utils.validateBstackJson(bsConfigPath).then(function (bsConfig) {
return utils.validateBstackJson(bsConfigPath).then(async function (bsConfig) {
utils.setDefaults(bsConfig, args);

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

let buildId = args._[1];

let options = {
url: config.buildStopUrl + buildId,
auth: {
user: bsConfig.auth.username,
password: bsConfig.auth.access_key,
},
headers: {
'User-Agent': utils.getUserAgent(),
},
};
await utils.stopBrowserStackBuild(bsConfig, args, buildId);

request.post(options, function (err, resp, body) {
let message = null;
let messageType = null;
let errorCode = null;

if (err) {
message = Constants.userMessages.BUILD_STOP_FAILED;
messageType = Constants.messageTypes.ERROR;
errorCode = 'api_failed_build_stop';

logger.info(message);
} else {
let build = null;
try {
build = JSON.parse(body);
} catch (error) {
build = null;
}

if (resp.statusCode == 299) {
messageType = Constants.messageTypes.INFO;
errorCode = 'api_deprecated';

if (build) {
message = build.message;
logger.info(message);
} else {
message = Constants.userMessages.API_DEPRECATED;
logger.info(message);
}
} else if (resp.statusCode != 200) {
messageType = Constants.messageTypes.ERROR;
errorCode = 'api_failed_build_stop';

if (build) {
message = `${
Constants.userMessages.BUILD_STOP_FAILED
} with error: \n${JSON.stringify(build, null, 2)}`;
logger.error(message);
if (build.message === 'Unauthorized') errorCode = 'api_auth_failed';
} else {
message = Constants.userMessages.BUILD_STOP_FAILED;
logger.error(message);
}
} else {
messageType = Constants.messageTypes.SUCCESS;
message = `${JSON.stringify(build, null, 2)}`;
logger.info(message);
}
}
utils.sendUsageReport(bsConfig, args, message, messageType, errorCode);
});
}).catch(function (err) {
logger.error(err);
utils.setUsageReportingFlag(null, args.disableUsageReporting);
Expand Down
2 changes: 2 additions & 0 deletions bin/helpers/capabilityHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ const validate = (bsConfig, args) => {

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

if( Utils.searchForOption('--async') && ( !Utils.isUndefined(args.async) && bsConfig["connection_settings"]["local"])) reject(Constants.validationMessages.INVALID_LOCAL_ASYNC_ARGS);

// validate if config file provided exists or not when cypress_config_file provided
// validate the cypressProjectDir key otherwise.
let cypressConfigFilePath = bsConfig.run_settings.cypressConfigFilePath;
Expand Down
14 changes: 9 additions & 5 deletions bin/helpers/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const syncCLI = {
},
INITIAL_DELAY_MULTIPLIER: 10,
DEFAULT_LINE_SEP: "\n--------------------------------------------------------------------------------",
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..."
};

const userMessages = {
Expand Down Expand Up @@ -41,15 +42,16 @@ const userMessages = {
FATAL_NETWORK_ERROR: `fatal: unable to access '${config.buildUrl}': Could not resolve host: ${config.rails_host}`,
RETRY_LIMIT_EXCEEDED: `Max retries exceeded trying to connect to the host (retries: ${config.retries})`,
CHECK_DASHBOARD_AT: "Please check the build status at: ",
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",
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",
LOCAL_START_FAILED: "Local Testing setup failed.",
LOCAL_STOP_FAILED: "Local Binary stop failed.",
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",
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.",
DOWNLOAD_BUILD_ARTIFACTS_FAILED: "Downloading build artifacts for the build <build-id> failed for <machine-count> machines.",
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>'",
DOWNLOAD_BUILD_ARTIFACTS_SUCCESS: "Your build artifact(s) have been successfully downloaded in '<user-path>/build_artifacts/<build-id>' directory",
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"
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",
PROCESS_KILL_MESSAGE: "Stopping the CLI and the execution of the build on BrowserStack",
};

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

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

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

const filesToIgnoreWhileUploading = [
'**/node_modules/**',
Expand Down
2 changes: 2 additions & 0 deletions bin/helpers/sync/syncSpecsLogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const request = require("request"),
config = require("../config"),
utils = require("../utils"),
logger = require("../logger").syncCliLogger,
winstonLogger = require("../logger").winstonLogger,
async = require('async'),
Constants = require("../constants"),
tableStream = require('table').createStream,
Expand Down Expand Up @@ -164,6 +165,7 @@ let showSpecsStatus = (data) => {
}

let printInitialLog = () => {
winstonLogger.info(Constants.syncCLI.STARTUP_MESSAGE);
logger.info(`\n${Constants.syncCLI.LOGS.INIT_LOG}`)
logger.info(lineSeparator);
n = Constants.syncCLI.INITIAL_DELAY_MULTIPLIER
Expand Down
106 changes: 104 additions & 2 deletions bin/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const usageReporting = require("./usageReporting"),
config = require("../helpers/config");

const request = require('request');
const axios = require("axios");

exports.validateBstackJson = (bsConfigPath) => {
return new Promise(function (resolve, reject) {
Expand Down Expand Up @@ -80,6 +81,9 @@ exports.getErrorCodeFromMsg = (errMsg) => {
case Constants.validationMessages.INVALID_CYPRESS_CONFIG_FILE:
errorCode = 'invalid_cypress_config_file';
break;
case Constants.validationMessages.INVALID_LOCAL_ASYNC_ARGS:
errorCode = 'invalid_local_async_args';
break;
}
if (
errMsg.includes("Please use --config-file <path to browserstack.json>.")
Expand Down Expand Up @@ -733,15 +737,18 @@ exports.getNetworkErrorMessage = (dashboard_url) => {
return chalk.red(message)
}

exports.versionChangedMessage = (preferredVersion, actualVersion) => {
exports.versionChangedMessage = (preferredVersion, actualVersion, frameworkUpgradeMessage = '') => {
let message = Constants.userMessages.CYPRESS_VERSION_CHANGED.replace("<preferredVersion>", preferredVersion);
message = message.replace("<actualVersion>", actualVersion);
frameworkUpgradeMessage = frameworkUpgradeMessage.replace('.latest', "");
message = message.replace('<frameworkUpgradeMessage>', frameworkUpgradeMessage);
return message
}

exports.latestSyntaxToActualVersionMessage = (latestSyntaxVersion, actualVersion) => {
exports.latestSyntaxToActualVersionMessage = (latestSyntaxVersion, actualVersion, frameworkUpgradeMessage = '') => {
let message = Constants.userMessages.LATEST_SYNTAX_TO_ACTUAL_VERSION_MESSAGE.replace("<latestSyntaxVersion>", latestSyntaxVersion);
message = message.replace("<actualVersion>", actualVersion);
message = message.replace('<frameworkUpgradeMessage>', frameworkUpgradeMessage)
return message
}

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

if( err === Constants.validationMessages.INVALID_LOCAL_ASYNC_ARGS && !this.isUndefined(args.async)) {
return false
}

return invalid
}

Expand Down Expand Up @@ -820,3 +831,94 @@ exports.getCypressJSON = (bsConfig) => {
}
return cypressJSON;
}

exports.setCLIMode = (bsConfig, args) => {
args.sync = true;
if(!this.isUndefined(args.async) && args.async){
args.sync = false;
}
}

exports.stopBrowserStackBuild = async (bsConfig, args, buildId ) => {
let url = config.buildStopUrl + buildId;
let options = {
url: url,
auth: {
username: bsConfig["auth"]["username"],
password: bsConfig["auth"]["access_key"],
},
headers: {
'User-Agent': this.getUserAgent(),
},
};

let message = null;
let messageType = null;
let errorCode = null;
try{
let resp = await axios.post(url, {} , options);
let build = null;
if(resp.data){
build = resp.data;
}

if (resp.status == 299) {
messageType = Constants.messageTypes.INFO;
errorCode = 'api_deprecated';

if (build) {
message = build.message;
logger.info(message);
} else {
message = Constants.userMessages.API_DEPRECATED;
logger.info(message);
}
} else if (resp.status != 200) {
messageType = Constants.messageTypes.ERROR;
errorCode = 'api_failed_build_stop';

if (build) {
message = `${
Constants.userMessages.BUILD_STOP_FAILED
} with error: \n${JSON.stringify(build, null, 2)}`;
logger.error(message);
if (build.message === 'Unauthorized') errorCode = 'api_auth_failed';
} else {
message = Constants.userMessages.BUILD_STOP_FAILED;
logger.error(message);
}
} else {
messageType = Constants.messageTypes.SUCCESS;
message = `${JSON.stringify(build, null, 2)}`;
logger.info(message);
}
} catch(err){
console.log(err);
message = Constants.userMessages.BUILD_STOP_FAILED;
messageType = Constants.messageTypes.ERROR;
errorCode = 'api_failed_build_stop';
logger.info(message);
} finally {
this.sendUsageReport(bsConfig, args, message, messageType, errorCode);
}
}

exports.setProcessHooks = (buildId, bsConfig, bsLocal, args) => {
let bindData = {
buildId: buildId,
bsConfig: bsConfig,
bsLocalInstance: bsLocal,
args: args
}
process.on('SIGINT', processExitHandler.bind(this, bindData));
process.on('SIGTERM', processExitHandler.bind(this, bindData));
process.on('SIGBREAK', processExitHandler.bind(this, bindData));
process.on('uncaughtException', processExitHandler.bind(this, bindData));
}

async function processExitHandler(exitData){
logger.warn(Constants.userMessages.PROCESS_KILL_MESSAGE);
await this.stopBrowserStackBuild(exitData.bsConfig, exitData.args, exitData.buildId);
await this.stopLocalBinary(exitData.bsConfig, exitData.bsLocalInstance, exitData.args);
process.exit(0);
}
7 changes: 6 additions & 1 deletion bin/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,15 @@ var argv = yargs
type: "boolean"
},
'sync': {
default: false,
default: true,
describe: Constants.cliMessages.RUN.SYNC_DESCRIPTION,
type: "boolean"
},
'async': {
default: false,
describe: Constants.cliMessages.RUN.ASYNC_DESCRIPTION,
type: "boolean"
},
'force-upload': {
default: false,
describe: Constants.cliMessages.COMMON.FORCE_UPLOAD,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"winston": "^2.3.1",
"yargs": "^14.2.3",
"axios": "^0.21.1",
"unzipper": "^0.10.11"
"unzipper": "^0.10.11",
"update-notifier": "^5.1.0"
},
"repository": {
"type": "git",
Expand Down
Loading