From 2dd074888b49d49fabbf6f1d39a20180ebd03719 Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Fri, 25 Sep 2020 15:28:05 +0530 Subject: [PATCH 01/34] Starting off the syn cli changes --- bin/commands/runs.js | 11 ++++++++++- bin/helpers/constants.js | 4 +++- bin/runner.js | 5 +++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/bin/commands/runs.js b/bin/commands/runs.js index 8e849c4e..c1e42908 100644 --- a/bin/commands/runs.js +++ b/bin/commands/runs.js @@ -7,7 +7,8 @@ const archiver = require("../helpers/archiver"), capabilityHelper = require("../helpers/capabilityHelper"), Constants = require("../helpers/constants"), utils = require("../helpers/utils"), - fileHelpers = require("../helpers/fileHelpers"); + fileHelpers = require("../helpers/fileHelpers"), + syncRunner = require("../helpers/syncRunner"); module.exports = function run(args) { let bsConfigPath = utils.getConfigPath(args.cf); @@ -65,6 +66,14 @@ module.exports = function run(args) { if (!args.disableNpmWarning && bsConfig.run_settings.npm_dependencies && Object.keys(bsConfig.run_settings.npm_dependencies).length <= 0) logger.warn(Constants.userMessages.NO_NPM_DEPENDENCIES); + if (args.sync) { + syncRunner.pollBuildStatus(bsConfig, data.build_id).then((exitCode) => { + utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null); + + process.exit(exitCode); + }); + } + logger.info(message); logger.info(dashboardLink); utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null); diff --git a/bin/helpers/constants.js b/bin/helpers/constants.js index 0f89948a..4f5ed3ae 100644 --- a/bin/helpers/constants.js +++ b/bin/helpers/constants.js @@ -3,6 +3,7 @@ const userMessages = { BUILD_CREATED: "Build created", BUILD_INFO_FAILED: "Failed to get build info.", BUILD_STOP_FAILED: "Failed to stop build.", + BUILD_REPORT_MESDSAGE: "See the entire build report here:", ZIP_UPLOADER_NOT_REACHABLE: "Could not reach to zip uploader.", ZIP_UPLOAD_FAILED: "Zip Upload failed.", CONFIG_FILE_CREATED: "BrowserStack Config File created, you can now run browserstack-cypress --config-file run", @@ -68,7 +69,8 @@ const cliMessages = { EXCLUDE: "Exclude files matching a pattern from zipping and uploading", DEFAULT_PARALLEL_MESSAGE: "Here goes the number of parallels you want to run", SPECS_DESCRIPTION: 'Specify the spec files to run', - ENV_DESCRIPTION: "Specify the environment variables for your spec files" + ENV_DESCRIPTION: "Specify the environment variables for your spec files", + SYNC_DESCRIPTION: "Makes the run command in sync" }, COMMON: { DISABLE_USAGE_REPORTING: "Disable usage reporting", diff --git a/bin/runner.js b/bin/runner.js index 597eb558..c9610b80 100755 --- a/bin/runner.js +++ b/bin/runner.js @@ -195,6 +195,11 @@ var argv = yargs default: false, description: Constants.cliMessages.COMMON.NO_NPM_WARNING, type: "boolean" + }, + 'sync': { + default: false, + describe: Constants.cliMessages.SYNC_DESCRIPTION, + type: "boolean" } }) .help('help') From ba1bac8fcf553647d73d32e51e50203b23be7cd8 Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Fri, 25 Sep 2020 15:28:36 +0530 Subject: [PATCH 02/34] Adding syncRunner implementation --- bin/helpers/syncRunner.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 bin/helpers/syncRunner.js diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js new file mode 100644 index 00000000..db84fd10 --- /dev/null +++ b/bin/helpers/syncRunner.js @@ -0,0 +1,13 @@ +'use strict'; +const config = require("./config"), + logger = require("./logger").winstonLogger, + Constants = require("./constants"), + utils = require("./utils"), + request = require('request'); + +exports.pollBuildStatus = (bsConfig, buildId) => { + + logger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); + logger.info(`${config.dashboardUrl}${buildId}`); + return 0; +}; From b367ec394fbdb23ae3a95d1e48ac3b712fa5717b Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Fri, 25 Sep 2020 15:40:25 +0530 Subject: [PATCH 03/34] Addin table package --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 9fe64ff4..4924d0c0 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "mkdirp": "^1.0.3", "request": "^2.88.0", "requestretry": "^4.1.0", + "table": "^5.4.6", "winston": "^2.3.1", "yargs": "^14.2.2" }, From 3bb6a6b219329abbcfc2ee68d1720bd2fe3932cb Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Fri, 25 Sep 2020 15:51:24 +0530 Subject: [PATCH 04/34] Added sync messages in parts --- bin/helpers/syncRunner.js | 40 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index db84fd10..38982138 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -6,8 +6,42 @@ const config = require("./config"), request = require('request'); exports.pollBuildStatus = (bsConfig, buildId) => { + logBuildDetails().then((data) => { + printSpecsStatus(); + }).then((data) => { + printSpecsRunSummary(); + }).then((data) => { + printFailedSpecsDetails(); + }).then((data) => { + printBuildDashboardLink(buildId); + }).then((data) => { + // success case! + return 0; // exit code 0 + }).catch((err) => { + // failed case! + return 1; // exit code 1 + }); +}; + +let logBuildDetails = () => { + +}; + +let printSpecsStatus = () => { + +}; + +let printSpecsRunSummary = () => { + +}; + +let printFailedSpecsDetails = () => { + +}; - logger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); - logger.info(`${config.dashboardUrl}${buildId}`); - return 0; +let printBuildDashboardLink = (buildId) => { + new Promise((resolve, reject) => { + logger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); + logger.info(`${config.dashboardUrl}${buildId}`); + }); }; From bf84d4683d1098e2add7709c85c30a0c395f4a88 Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Fri, 25 Sep 2020 17:29:03 +0530 Subject: [PATCH 05/34] Adding winston logger for sync cli --- bin/helpers/logger.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bin/helpers/logger.js b/bin/helpers/logger.js index 5188f358..f6fafa41 100644 --- a/bin/helpers/logger.js +++ b/bin/helpers/logger.js @@ -21,6 +21,16 @@ const winstonLoggerParams = { ], }; +const winstonSyncCliLoggerParams = { + transports: [ + new (winston.transports.Console)({ + formatter: (options) => { + return (options.message ? options.message : ''); + } + }), + ] +} + const winstonFileLoggerParams = { transports: [ new winston.transports.File({ @@ -31,3 +41,4 @@ const winstonFileLoggerParams = { exports.winstonLogger = new winston.Logger(winstonLoggerParams); exports.fileLogger = new winston.Logger(winstonFileLoggerParams); +exports.syncCliLogger = new winston.Logger(winstonSyncCliLoggerParams); From ccd2b86c62443e2016f439e3d1b05e30b4ea155f Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Fri, 25 Sep 2020 17:30:25 +0530 Subject: [PATCH 06/34] Updating the logger --- bin/helpers/syncRunner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index 38982138..c9325b17 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -1,6 +1,6 @@ 'use strict'; const config = require("./config"), - logger = require("./logger").winstonLogger, + logger = require("./logger").syncCliLogger, Constants = require("./constants"), utils = require("./utils"), request = require('request'); From 719ac27a70da679f0d8308ce831bcef0cc91ee03 Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Fri, 25 Sep 2020 20:37:35 +0530 Subject: [PATCH 07/34] Added spec failure/skipped message with table. - Print an ASCII table with Failed / Skipped specs in it. - Details include spec name, status, combiantion, and session id in the order. - The status and header are colour printed for better readability. - Handle the resolve and reject which bubble up the exit code to main run. - process will now exit as zero or non zero code. - Fixed typo in constant. --- bin/helpers/constants.js | 7 ++- bin/helpers/syncRunner.js | 90 +++++++++++++++++++++++++++++++-------- package.json | 1 + 3 files changed, 80 insertions(+), 18 deletions(-) diff --git a/bin/helpers/constants.js b/bin/helpers/constants.js index 4f5ed3ae..49c9b3d4 100644 --- a/bin/helpers/constants.js +++ b/bin/helpers/constants.js @@ -1,9 +1,13 @@ +const syncCLI = { + FAILED_SPEC_DETAILS_COL_HEADER: ['Spec', 'Status', 'Browser', 'BrowserStack Session ID'] +}; + const userMessages = { BUILD_FAILED: "Build creation failed.", BUILD_CREATED: "Build created", BUILD_INFO_FAILED: "Failed to get build info.", BUILD_STOP_FAILED: "Failed to stop build.", - BUILD_REPORT_MESDSAGE: "See the entire build report here:", + BUILD_REPORT_MESSAGE: "See the entire build report here:", ZIP_UPLOADER_NOT_REACHABLE: "Could not reach to zip uploader.", ZIP_UPLOAD_FAILED: "Zip Upload failed.", CONFIG_FILE_CREATED: "BrowserStack Config File created, you can now run browserstack-cypress --config-file run", @@ -94,6 +98,7 @@ const allowedFileTypes = ['js', 'json', 'txt', 'ts', 'feature', 'features', 'pdf const filesToIgnoreWhileUploading = ['node_modules/**', 'package-lock.json', 'package.json', 'browserstack-package.json', 'tests.zip', 'cypress.json'] module.exports = Object.freeze({ + syncCLI, userMessages, cliMessages, validationMessages, diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index c9325b17..210e932f 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -1,9 +1,11 @@ 'use strict'; -const config = require("./config"), +const Config = require("./config"), logger = require("./logger").syncCliLogger, Constants = require("./constants"), utils = require("./utils"), - request = require('request'); + request = require('request'), + { table, getBorderCharacters } = require('table'), + chalk = require('chalk'); exports.pollBuildStatus = (bsConfig, buildId) => { logBuildDetails().then((data) => { @@ -11,15 +13,14 @@ exports.pollBuildStatus = (bsConfig, buildId) => { }).then((data) => { printSpecsRunSummary(); }).then((data) => { - printFailedSpecsDetails(); - }).then((data) => { - printBuildDashboardLink(buildId); - }).then((data) => { - // success case! - return 0; // exit code 0 - }).catch((err) => { - // failed case! - return 1; // exit code 1 + printFailedSpecsDetails(data); + }).then((successExitCode) => { + return resolveExitCode(successExitCode); // exit code 0 + }).catch((nonZeroExitCode) => { + return resolveExitCode(nonZeroExitCode); // exit code 1 + }).finally(() => { + logger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); + logger.info(`${Config.dashboardUrl}${buildId}`); }); }; @@ -35,13 +36,68 @@ let printSpecsRunSummary = () => { }; -let printFailedSpecsDetails = () => { +/** + * + * @param {Array.<{specName: string, status: string, combination: string, sessionId: string}>} data + * @returns {Promise.resolve || Promise.reject} + */ +// Example: +// [ +// {specName: 'spec1.failed.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, +// {specName: 'spec2.name.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, +// {specName: 'spec3.network.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, +// {specName: 'spec6.utils.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, +// {specName: 'spec8.alias.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'} +// ] +let printFailedSpecsDetails = (data) => { + return new Promise((resolve, reject) => { + if (data.length === 0) resolve(0); // return if no failed/skipped tests. -}; + let failedSpecs = false; + let specResultHeader = Constants.syncCLI.FAILED_SPEC_DETAILS_COL_HEADER.map((col) => { + return chalk.blueBright(col); + }); -let printBuildDashboardLink = (buildId) => { - new Promise((resolve, reject) => { - logger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); - logger.info(`${config.dashboardUrl}${buildId}`); + let specData = [specResultHeader]; // 2-D array + + data.forEach((spec) => { + if (spec.status && spec.status.toLowerCase() === 'failed' && !failedSpecs) + failedSpecs = true; + + let specStatus = (spec.status && spec.status.toLowerCase() === 'failed') ? + chalk.red(spec.status) : chalk.yellow(spec.status); + specData.push([spec.specName, specStatus, spec.combination, spec.sessionId]); + }); + + let config = { + border: getBorderCharacters('ramac'), + columns: { + 0: { alignment: 'center' }, + 1: { alignment: 'center' }, + 2: { alignment: 'center' }, + 3: { alignment: 'center' }, + }, + /** + * @typedef {function} drawHorizontalLine + * @param {number} index + * @param {number} size + * @return {boolean} + */ + drawHorizontalLine: (index, size) => { + return (index === 0 || index === 1 || index === size); + } + } + + let result = table(specData, config); + + logger.info('Failed / skipped test report'); + logger.info(result); + + if (failedSpecs) reject(1); // specs failed, send exitCode as 1 + resolve(0); // No Specs failed, maybe skipped, but not failed, send exitCode as 0 }); }; + +let resolveExitCode = (exitCode) => { + return new Promise((resolve, _reject) => { resolve(exitCode) }); +}; diff --git a/package.json b/package.json index 4924d0c0..3c71b384 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "archiver": "^3.1.1", + "chalk": "^4.1.0", "fs-extra": "^8.1.0", "mkdirp": "^1.0.3", "request": "^2.88.0", From 165985ed69099a2c4e7b738b712332fe9d0ce6f5 Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Fri, 25 Sep 2020 20:45:04 +0530 Subject: [PATCH 08/34] Left align the content of table as center is messing the structure --- bin/helpers/syncRunner.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index 210e932f..03d0c74a 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -72,10 +72,10 @@ let printFailedSpecsDetails = (data) => { let config = { border: getBorderCharacters('ramac'), columns: { - 0: { alignment: 'center' }, - 1: { alignment: 'center' }, - 2: { alignment: 'center' }, - 3: { alignment: 'center' }, + 0: { alignment: 'left' }, + 1: { alignment: 'left' }, + 2: { alignment: 'left' }, + 3: { alignment: 'left' }, }, /** * @typedef {function} drawHorizontalLine From 6d8c43d3e79d52f3724c32440327c4b6711330e3 Mon Sep 17 00:00:00 2001 From: nagpalkaran95 Date: Mon, 28 Sep 2020 16:26:40 +0530 Subject: [PATCH 09/34] make changes for --sync --- bin/commands/runs.js | 17 +++--- bin/helpers/constants.js | 115 +++++++++++++++++++------------------- bin/helpers/syncRunner.js | 69 ++++++++++++++--------- bin/runner.js | 2 +- 4 files changed, 113 insertions(+), 90 deletions(-) diff --git a/bin/commands/runs.js b/bin/commands/runs.js index c1e42908..486e2cad 100644 --- a/bin/commands/runs.js +++ b/bin/commands/runs.js @@ -58,7 +58,7 @@ module.exports = function run(args) { // Create build return build.createBuild(bsConfig, zip).then(function (data) { let message = `${data.message}! ${Constants.userMessages.BUILD_CREATED} with build id: ${data.build_id}`; - let dashboardLink = `${Constants.userMessages.VISIT_DASHBOARD} ${config.dashboardUrl}${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); @@ -67,17 +67,20 @@ module.exports = function run(args) { if (!args.disableNpmWarning && bsConfig.run_settings.npm_dependencies && Object.keys(bsConfig.run_settings.npm_dependencies).length <= 0) logger.warn(Constants.userMessages.NO_NPM_DEPENDENCIES); if (args.sync) { - syncRunner.pollBuildStatus(bsConfig, data.build_id).then((exitCode) => { + syncRunner.pollBuildStatus(bsConfig, bsConfigPath, data).then((exitCode) => { utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null); process.exit(exitCode); + }).catch((err) => { + logger.error(err); + process.exit(1); }); + } else { + logger.info(message); + logger.info(dashboardLink); + utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null); + process.exit(2); } - - logger.info(message); - logger.info(dashboardLink); - utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null); - return; }).catch(function (err) { // Build creation failed logger.error(err); diff --git a/bin/helpers/constants.js b/bin/helpers/constants.js index 4f5ed3ae..6d72853f 100644 --- a/bin/helpers/constants.js +++ b/bin/helpers/constants.js @@ -1,22 +1,22 @@ const userMessages = { - BUILD_FAILED: "Build creation failed.", - BUILD_CREATED: "Build created", - BUILD_INFO_FAILED: "Failed to get build info.", - BUILD_STOP_FAILED: "Failed to stop build.", - BUILD_REPORT_MESDSAGE: "See the entire build report here:", - ZIP_UPLOADER_NOT_REACHABLE: "Could not reach to zip uploader.", - ZIP_UPLOAD_FAILED: "Zip Upload failed.", - CONFIG_FILE_CREATED: "BrowserStack Config File created, you can now run browserstack-cypress --config-file run", - CONFIG_FILE_EXISTS: "File already exists, delete the browserstack.json file manually. skipping...", - DIR_NOT_FOUND: "Given path does not exist. Failed to create browserstack.json in %s", - ZIP_DELETE_FAILED: "Could not delete local file.", - ZIP_DELETED: "Zip file deleted successfully.", - API_DEPRECATED: "This version of API is deprecated, please use latest version of API.", - FAILED_TO_ZIP: "Failed to zip files.", - VISIT_DASHBOARD: "Visit the Automate dashboard for test reporting:", - CONFLICTING_INIT_ARGUMENTS: "Conflicting arguments given. You can use --path only with a file name, and not with a file path.", - NO_PARALLELS: "Your tests will run sequentially. Read more about running your tests in parallel here: https://www.browserstack.com/docs/automate/cypress/run-tests-in-parallel", - NO_NPM_DEPENDENCIES: "No npm dependencies specified. Read more here: https://www.browserstack.com/docs/automate/cypress/npm-packages. You can suppress this warning by using --disable-npm-warning flag." + BUILD_FAILED: "Build creation failed.", + BUILD_CREATED: "Build created", + BUILD_INFO_FAILED: "Failed to get build info.", + BUILD_STOP_FAILED: "Failed to stop build.", + BUILD_REPORT_MESDSAGE: "See the entire build report here:", + ZIP_UPLOADER_NOT_REACHABLE: "Could not reach to zip uploader.", + ZIP_UPLOAD_FAILED: "Zip Upload failed.", + CONFIG_FILE_CREATED: "BrowserStack Config File created, you can now run browserstack-cypress --config-file run", + CONFIG_FILE_EXISTS: "File already exists, delete the browserstack.json file manually. skipping...", + DIR_NOT_FOUND: "Given path does not exist. Failed to create browserstack.json in %s", + ZIP_DELETE_FAILED: "Could not delete local file.", + ZIP_DELETED: "Zip file deleted successfully.", + API_DEPRECATED: "This version of API is deprecated, please use latest version of API.", + FAILED_TO_ZIP: "Failed to zip files.", + VISIT_DASHBOARD: "Visit the Automate dashboard for test reporting:", + CONFLICTING_INIT_ARGUMENTS: "Conflicting arguments given. You can use --path only with a file name, and not with a file path.", + NO_PARALLELS: "Your tests will run sequentially. Read more about running your tests in parallel here: https://www.browserstack.com/docs/automate/cypress/run-tests-in-parallel", + NO_NPM_DEPENDENCIES: "No npm dependencies specified. Read more here: https://www.browserstack.com/docs/automate/cypress/npm-packages. You can suppress this warning by using --disable-npm-warning flag." }; const validationMessages = { @@ -40,45 +40,46 @@ const validationMessages = { }; const cliMessages = { - VERSION: { - INFO: "shows version information", - HELP: "Specify --help for available options", - DEMAND: "Requires init, run or poll argument" - }, - INIT: { - INFO: "create a browserstack.json file in the folder specified with the default configuration options.", - DESC: "Init in a specified folder" - }, - BUILD: { - INFO: "Check status of your build.", - STOP: "Stop your build.", - DEMAND: "Requires a build id.", - DESC: "Path to BrowserStack config", - CONFIG_DEMAND: "config file is required", - INFO_MESSAGE: "Getting information for buildId ", - STOP_MESSAGE: "Stopping build with given buildId " - }, - RUN: { - PARALLEL_DESC: "The maximum number of parallels to use to run your test suite", - INFO: "Run your tests on BrowserStack.", - DESC: "Path to BrowserStack config", - CYPRESS_DESC: "Path to Cypress config file", - CONFIG_DEMAND: "config file is required", - CYPRESS_CONFIG_DEMAND: "Cypress config file is required", - BUILD_NAME: "The build name you want to use to name your test runs", - EXCLUDE: "Exclude files matching a pattern from zipping and uploading", - DEFAULT_PARALLEL_MESSAGE: "Here goes the number of parallels you want to run", - 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" - }, - COMMON: { - DISABLE_USAGE_REPORTING: "Disable usage reporting", - USERNAME: "Your BrowserStack username", - ACCESS_KEY: "Your BrowserStack access key", - NO_NPM_WARNING: "No NPM warning if npm_dependencies is empty" - } -} + VERSION: { + INFO: "shows version information", + HELP: "Specify --help for available options", + DEMAND: "Requires init, run or poll argument", + }, + INIT: { + INFO: "create a browserstack.json file in the folder specified with the default configuration options.", + DESC: "Init in a specified folder", + }, + BUILD: { + INFO: "Check status of your build.", + STOP: "Stop your build.", + DEMAND: "Requires a build id.", + DESC: "Path to BrowserStack config", + CONFIG_DEMAND: "config file is required", + INFO_MESSAGE: "Getting information for buildId ", + STOP_MESSAGE: "Stopping build with given buildId ", + }, + RUN: { + PARALLEL_DESC: "The maximum number of parallels to use to run your test suite", + INFO: "Run your tests on BrowserStack.", + DESC: "Path to BrowserStack config", + CYPRESS_DESC: "Path to Cypress config file", + CONFIG_DEMAND: "config file is required", + CYPRESS_CONFIG_DEMAND: "Cypress config file is required", + BUILD_NAME: "The build name you want to use to name your test runs", + EXCLUDE: "Exclude files matching a pattern from zipping and uploading", + DEFAULT_PARALLEL_MESSAGE: "Here goes the number of parallels you want to run", + 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", + BUILD_REPORT_MESSAGE: "See the entire build report here", + }, + COMMON: { + DISABLE_USAGE_REPORTING: "Disable usage reporting", + USERNAME: "Your BrowserStack username", + ACCESS_KEY: "Your BrowserStack access key", + NO_NPM_WARNING: "No NPM warning if npm_dependencies is empty", + }, +}; const messageTypes = { SUCCESS: "success", diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index 38982138..3a2696c8 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -5,43 +5,62 @@ const config = require("./config"), utils = require("./utils"), request = require('request'); -exports.pollBuildStatus = (bsConfig, buildId) => { - logBuildDetails().then((data) => { - printSpecsStatus(); - }).then((data) => { - printSpecsRunSummary(); - }).then((data) => { - printFailedSpecsDetails(); - }).then((data) => { - printBuildDashboardLink(buildId); - }).then((data) => { - // success case! - return 0; // exit code 0 - }).catch((err) => { - // failed case! - return 1; // exit code 1 - }); +exports.pollBuildStatus = (bsConfig, bsConfigPath, buildDetails) => { + return new Promise(function (resolve, reject) { + logBuildDetails(bsConfig, bsConfigPath, buildDetails); + printSpecsStatus() + .then((data) => { + printSpecsRunSummary(); + }) + .then((data) => { + printFailedSpecsDetails(); + }) + .then((data) => { + printBuildDashboardLink(buildDetails.dashboard_url); + // success case! + resolve(0); // exit code 0 + }) + .catch((err) => { + // failed case! + reject(err); // exit code 1 + }); + }); }; -let logBuildDetails = () => { +let logBuildDetails = (bsConfig, bsConfigPath, buildDetails) => { + let parallels_enabled = false; + if (bsConfig.run_settings.parallels) { + parallels_enabled = true; + } + let parallelMessage = `Run in parallel: ${parallels_enabled ? 'enabled' : 'disabled'}`; + if (parallels_enabled) parallelMessage = parallelMessage + `(attempting to run on ${buildDetails.machines} machines)`; + // logger.info(`Configuration file: ${bsConfigPath}`); + // logger.info(`Cypress config file: ${bsConfig.run_settings.cypress_config_file}`); + // logger.info(`Local connection: ${bsConfig.connection_settings.local ? 'enabled' : 'disabled'}`); + logger.info(`Browser Combinations: ${buildDetails.combinations}`); + logger.info(parallelMessage); + logger.info(`BrowserStack Dashboard: ${buildDetails.dashboard_url}`); }; let printSpecsStatus = () => { - + return new Promise(function (resolve, reject) { + resolve(); + }); }; let printSpecsRunSummary = () => { - + return new Promise(function (resolve, reject) { + resolve(); + }); }; let printFailedSpecsDetails = () => { - + return new Promise(function (resolve, reject) { + resolve(); + }); }; -let printBuildDashboardLink = (buildId) => { - new Promise((resolve, reject) => { - logger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); - logger.info(`${config.dashboardUrl}${buildId}`); - }); +let printBuildDashboardLink = (dashboardUrl) => { + logger.info(`${Constants.cliMessages.RUN.BUILD_REPORT_MESSAGE}: ${dashboardUrl}`); }; diff --git a/bin/runner.js b/bin/runner.js index c9610b80..bdc7f5b8 100755 --- a/bin/runner.js +++ b/bin/runner.js @@ -198,7 +198,7 @@ var argv = yargs }, 'sync': { default: false, - describe: Constants.cliMessages.SYNC_DESCRIPTION, + describe: Constants.cliMessages.RUN.SYNC_DESCRIPTION, type: "boolean" } }) From 7e1421f9fcaa02b76b0f8eda85d6eccd74372d85 Mon Sep 17 00:00:00 2001 From: nagpalkaran95 Date: Mon, 5 Oct 2020 16:01:23 +0530 Subject: [PATCH 10/34] add unit test cases --- bin/helpers/syncRunner.js | 2 +- test/unit/support/fixtures/testObjects.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index 3a2696c8..1919ef92 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -33,7 +33,7 @@ let logBuildDetails = (bsConfig, bsConfigPath, buildDetails) => { parallels_enabled = true; } let parallelMessage = `Run in parallel: ${parallels_enabled ? 'enabled' : 'disabled'}`; - if (parallels_enabled) parallelMessage = parallelMessage + `(attempting to run on ${buildDetails.machines} machines)`; + if (parallels_enabled) parallelMessage = parallelMessage + ` (attempting to run on ${buildDetails.machines} machines)`; // logger.info(`Configuration file: ${bsConfigPath}`); // logger.info(`Cypress config file: ${bsConfig.run_settings.cypress_config_file}`); diff --git a/test/unit/support/fixtures/testObjects.js b/test/unit/support/fixtures/testObjects.js index 432fe96e..5aa00ee5 100644 --- a/test/unit/support/fixtures/testObjects.js +++ b/test/unit/support/fixtures/testObjects.js @@ -10,6 +10,19 @@ const sampleBsConfig = { } }; +const sampleBsConfigWithParallels = { + auth: { + username: "random-username", + access_key: "random-access-key", + }, + run_settings: { + cypress_proj_dir: "random path", + cypressConfigFilePath: "random path", + cypressProjectDir: "random path", + parallels: 10, + }, +}; + const initSampleArgs = { _: ["init"], p: false, @@ -114,6 +127,7 @@ const runSampleArgs = { module.exports = Object.freeze({ sampleBsConfig, + sampleBsConfigWithParallels, initSampleArgs, buildInfoSampleArgs, buildInfoSampleBody, From 6dfb6ee55ff0c5fe374c1fa11b2b7d6738655d8d Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Tue, 6 Oct 2020 21:21:34 +0530 Subject: [PATCH 11/34] Adding basic tests for failedSpecDetails --- bin/helpers/sync/failedSpecsDetails.js | 64 ++++++++++++++++++ bin/helpers/syncRunner.js | 65 +------------------ .../bin/helpers/sync/failedSpecDetails.js | 31 +++++++++ test/unit/bin/helpers/syncRunner.js | 0 4 files changed, 97 insertions(+), 63 deletions(-) create mode 100644 bin/helpers/sync/failedSpecsDetails.js create mode 100644 test/unit/bin/helpers/sync/failedSpecDetails.js create mode 100644 test/unit/bin/helpers/syncRunner.js diff --git a/bin/helpers/sync/failedSpecsDetails.js b/bin/helpers/sync/failedSpecsDetails.js new file mode 100644 index 00000000..8f04e31c --- /dev/null +++ b/bin/helpers/sync/failedSpecsDetails.js @@ -0,0 +1,64 @@ +/** + * + * @param {Array.<{specName: string, status: string, combination: string, sessionId: string}>} data + * @returns {Promise.resolve || Promise.reject} + */ +// Example: +// [ +// {specName: 'spec1.failed.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, +// {specName: 'spec2.name.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, +// {specName: 'spec3.network.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, +// {specName: 'spec6.utils.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, +// {specName: 'spec8.alias.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'} +// ] +// +let failedSpecsDetails = (data) => { + return new Promise((resolve, reject) => { + if (data.length === 0) resolve(0); // return if no failed/skipped tests. + + let failedSpecs = false; + let specResultHeader = Constants.syncCLI.FAILED_SPEC_DETAILS_COL_HEADER.map((col) => { + return chalk.blueBright(col); + }); + + let specData = [specResultHeader]; // 2-D array + + data.forEach((spec) => { + if (spec.status && spec.status.toLowerCase() === 'failed' && !failedSpecs) + failedSpecs = true; + + let specStatus = (spec.status && spec.status.toLowerCase() === 'failed') ? + chalk.red(spec.status) : chalk.yellow(spec.status); + specData.push([spec.specName, specStatus, spec.combination, spec.sessionId]); + }); + + let config = { + border: getBorderCharacters('ramac'), + columns: { + 0: { alignment: 'left' }, + 1: { alignment: 'left' }, + 2: { alignment: 'left' }, + 3: { alignment: 'left' }, + }, + /** + * @typedef {function} drawHorizontalLine + * @param {number} index + * @param {number} size + * @return {boolean} + */ + drawHorizontalLine: (index, size) => { + return (index === 0 || index === 1 || index === size); + } + } + + let result = table(specData, config); + + logger.info('Failed / skipped test report'); + logger.info(result); + + if (failedSpecs) reject(1); // specs failed, send exitCode as 1 + resolve(0); // No Specs failed, maybe skipped, but not failed, send exitCode as 0 + }); +} + +exports.failedSpecsDetails = failedSpecsDetails; diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index 03d0c74a..1f7fb8af 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -4,6 +4,7 @@ const Config = require("./config"), Constants = require("./constants"), utils = require("./utils"), request = require('request'), + specDetails = require('./sync/failedSpecsDetails'), { table, getBorderCharacters } = require('table'), chalk = require('chalk'); @@ -13,7 +14,7 @@ exports.pollBuildStatus = (bsConfig, buildId) => { }).then((data) => { printSpecsRunSummary(); }).then((data) => { - printFailedSpecsDetails(data); + return specDetails.failedSpecsDetails(data); }).then((successExitCode) => { return resolveExitCode(successExitCode); // exit code 0 }).catch((nonZeroExitCode) => { @@ -36,68 +37,6 @@ let printSpecsRunSummary = () => { }; -/** - * - * @param {Array.<{specName: string, status: string, combination: string, sessionId: string}>} data - * @returns {Promise.resolve || Promise.reject} - */ -// Example: -// [ -// {specName: 'spec1.failed.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, -// {specName: 'spec2.name.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, -// {specName: 'spec3.network.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, -// {specName: 'spec6.utils.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, -// {specName: 'spec8.alias.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'} -// ] -let printFailedSpecsDetails = (data) => { - return new Promise((resolve, reject) => { - if (data.length === 0) resolve(0); // return if no failed/skipped tests. - - let failedSpecs = false; - let specResultHeader = Constants.syncCLI.FAILED_SPEC_DETAILS_COL_HEADER.map((col) => { - return chalk.blueBright(col); - }); - - let specData = [specResultHeader]; // 2-D array - - data.forEach((spec) => { - if (spec.status && spec.status.toLowerCase() === 'failed' && !failedSpecs) - failedSpecs = true; - - let specStatus = (spec.status && spec.status.toLowerCase() === 'failed') ? - chalk.red(spec.status) : chalk.yellow(spec.status); - specData.push([spec.specName, specStatus, spec.combination, spec.sessionId]); - }); - - let config = { - border: getBorderCharacters('ramac'), - columns: { - 0: { alignment: 'left' }, - 1: { alignment: 'left' }, - 2: { alignment: 'left' }, - 3: { alignment: 'left' }, - }, - /** - * @typedef {function} drawHorizontalLine - * @param {number} index - * @param {number} size - * @return {boolean} - */ - drawHorizontalLine: (index, size) => { - return (index === 0 || index === 1 || index === size); - } - } - - let result = table(specData, config); - - logger.info('Failed / skipped test report'); - logger.info(result); - - if (failedSpecs) reject(1); // specs failed, send exitCode as 1 - resolve(0); // No Specs failed, maybe skipped, but not failed, send exitCode as 0 - }); -}; - let resolveExitCode = (exitCode) => { return new Promise((resolve, _reject) => { resolve(exitCode) }); }; diff --git a/test/unit/bin/helpers/sync/failedSpecDetails.js b/test/unit/bin/helpers/sync/failedSpecDetails.js new file mode 100644 index 00000000..bb3cb92f --- /dev/null +++ b/test/unit/bin/helpers/sync/failedSpecDetails.js @@ -0,0 +1,31 @@ +'use strict'; +const chai = require("chai"), + expect = chai.expect, + sinon = require('sinon'), + chaiAsPromised = require("chai-as-promised"), + fs = require('fs'); + +const specDetails = require('../../../../../bin/helpers/sync/failedSpecsDetails'); + +describe("failedSpecsDetails", () => { + context("data is empty", () => { + let data = []; + it('returns 0 exit code', () => { + specDetails.failedSpecsDetails(data).then((status) => { + chai.assert.equal(data, 0); + }); + }); + }); + + context("data does not have failed specs", () => { + let data = [ + {specName: 'spec2.name.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'} + ]; + + it("returns 0 exit code", () => { + specDetails.failedSpecsDetails(data).then((status) => { + chai.assert.equal(data, 0); + }); + }); + }); +}); diff --git a/test/unit/bin/helpers/syncRunner.js b/test/unit/bin/helpers/syncRunner.js new file mode 100644 index 00000000..e69de29b From f54e33d1f10c492af88126cfc18f40d32de54a36 Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Tue, 6 Oct 2020 21:23:17 +0530 Subject: [PATCH 12/34] Adding missing dependencies --- bin/helpers/sync/failedSpecsDetails.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/helpers/sync/failedSpecsDetails.js b/bin/helpers/sync/failedSpecsDetails.js index 8f04e31c..bdedbe68 100644 --- a/bin/helpers/sync/failedSpecsDetails.js +++ b/bin/helpers/sync/failedSpecsDetails.js @@ -1,3 +1,6 @@ +const { table, getBorderCharacters } = require('table'), + chalk = require('chalk'); + /** * * @param {Array.<{specName: string, status: string, combination: string, sessionId: string}>} data From 14e97bf86f7ecec3719b38bf7e1bf403b561ef64 Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Wed, 7 Oct 2020 21:10:43 +0530 Subject: [PATCH 13/34] Added more tests for specific cases and exit code status --- bin/helpers/sync/failedSpecsDetails.js | 10 ++++--- .../bin/helpers/sync/failedSpecDetails.js | 29 ++++++++++++++----- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/bin/helpers/sync/failedSpecsDetails.js b/bin/helpers/sync/failedSpecsDetails.js index bdedbe68..4fe09508 100644 --- a/bin/helpers/sync/failedSpecsDetails.js +++ b/bin/helpers/sync/failedSpecsDetails.js @@ -1,5 +1,7 @@ -const { table, getBorderCharacters } = require('table'), - chalk = require('chalk'); +const tablePrinter = require('table'), // { table, getBorderCharacters } + chalk = require('chalk'), + Constants = require("../constants"), + logger = require("../logger").syncCliLogger; /** * @@ -36,7 +38,7 @@ let failedSpecsDetails = (data) => { }); let config = { - border: getBorderCharacters('ramac'), + border: tablePrinter.getBorderCharacters('ramac'), columns: { 0: { alignment: 'left' }, 1: { alignment: 'left' }, @@ -54,7 +56,7 @@ let failedSpecsDetails = (data) => { } } - let result = table(specData, config); + let result = tablePrinter.table(specData, config); logger.info('Failed / skipped test report'); logger.info(result); diff --git a/test/unit/bin/helpers/sync/failedSpecDetails.js b/test/unit/bin/helpers/sync/failedSpecDetails.js index bb3cb92f..ada31143 100644 --- a/test/unit/bin/helpers/sync/failedSpecDetails.js +++ b/test/unit/bin/helpers/sync/failedSpecDetails.js @@ -1,18 +1,17 @@ 'use strict'; const chai = require("chai"), expect = chai.expect, - sinon = require('sinon'), - chaiAsPromised = require("chai-as-promised"), - fs = require('fs'); + chaiAsPromised = require("chai-as-promised"); +chai.use(chaiAsPromised); const specDetails = require('../../../../../bin/helpers/sync/failedSpecsDetails'); describe("failedSpecsDetails", () => { context("data is empty", () => { let data = []; it('returns 0 exit code', () => { - specDetails.failedSpecsDetails(data).then((status) => { - chai.assert.equal(data, 0); + return specDetails.failedSpecsDetails(data).then((status) => { + expect(status).to.equal(0); }); }); }); @@ -23,8 +22,24 @@ describe("failedSpecsDetails", () => { ]; it("returns 0 exit code", () => { - specDetails.failedSpecsDetails(data).then((status) => { - chai.assert.equal(data, 0); + return specDetails.failedSpecsDetails(data).then((status) => { + expect(status).to.equal(0); + }); + }); + }); + + context("data has failed specs", () => { + let data = [ + {specName: 'spec2.name.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'} + ]; + + it("returns 1 exit code", () => { + return specDetails.failedSpecsDetails(data) + .then((status) => { + chai.assert.equal(status, 1); + expect(status).to.equal(1); + }).catch((status) => { + expect(status).to.equal(1); }); }); }); From 545f7d873859180e1483968ac7f2682ed8c4fbb5 Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Fri, 9 Oct 2020 17:54:35 +0530 Subject: [PATCH 14/34] Sync CLI Part 4 - Added sync CLI total, passed, failed, skipped, statistics. - Added showing the completed in time with num of machines. --- bin/helpers/sync/specsSummary.js | 54 +++++++++++++++++++++++ bin/helpers/syncRunner.js | 3 +- test/unit/bin/helpers/sync/specSummary.js | 41 +++++++++++++++++ 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 bin/helpers/sync/specsSummary.js create mode 100644 test/unit/bin/helpers/sync/specSummary.js diff --git a/bin/helpers/sync/specsSummary.js b/bin/helpers/sync/specsSummary.js new file mode 100644 index 00000000..5ccd1b76 --- /dev/null +++ b/bin/helpers/sync/specsSummary.js @@ -0,0 +1,54 @@ +const logger = require("../logger").syncCliLogger; + +/** + * + * @param {Array.<{specName: string, status: string, combination: string, sessionId: string}>} data + * @param {String} time + * @param {Number} machines + * @returns {Promise.resolve || Promise.reject} + */ +// Example: +// [ +// {specName: 'spec1.failed.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, +// {specName: 'spec2.name.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, +// {specName: 'spec3.network.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, +// {specName: 'spec6.utils.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, +// {specName: 'spec8.alias.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'} +// ] +// +let printSpecsRunSummary = (data, time, machines) => { + let summary = { + total: 0, + failed: 0, + passed: 0, + skipped: 0 + }; + + data.forEach((spec) => { + specSummaryCount(summary, spec.status.toLowerCase()); + }); + + logger.info(`Total tests: ${summary.total}, passed: ${summary.passed}, failed: ${summary.failed}, skipped: ${summary.skipped}`); + logger.info(`Done in ${time} using ${machines} machines\n`); + + return new Promise((resolve, _reject) => { + resolve(data); + }) +}; + +let specSummaryCount = (summary, status) => { + switch (status) { + case 'failed': + summary.failed++; + break; + case 'skipped': + summary.skipped++; + break; + case 'passed': + summary.passed++; + break; + } + summary.total++; +}; + +exports.printSpecsRunSummary = printSpecsRunSummary; diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index 1f7fb8af..b451e8bb 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -5,6 +5,7 @@ const Config = require("./config"), utils = require("./utils"), request = require('request'), specDetails = require('./sync/failedSpecsDetails'), + specsSummary = require('./sync/specsSummary'), { table, getBorderCharacters } = require('table'), chalk = require('chalk'); @@ -12,7 +13,7 @@ exports.pollBuildStatus = (bsConfig, buildId) => { logBuildDetails().then((data) => { printSpecsStatus(); }).then((data) => { - printSpecsRunSummary(); + return specsSummary.printSpecsRunSummary(data.specs, data.time, data.machines); }).then((data) => { return specDetails.failedSpecsDetails(data); }).then((successExitCode) => { diff --git a/test/unit/bin/helpers/sync/specSummary.js b/test/unit/bin/helpers/sync/specSummary.js new file mode 100644 index 00000000..6629d129 --- /dev/null +++ b/test/unit/bin/helpers/sync/specSummary.js @@ -0,0 +1,41 @@ +'use strict'; +const chai = require("chai"), + sinon = require("sinon"), + expect = chai.expect, + chaiAsPromised = require("chai-as-promised"); + +chai.use(chaiAsPromised); +var logger = require("../../../../../bin/helpers/logger").syncCliLogger; +var specSummary = require('../../../../../bin/helpers/sync/specsSummary'); + +describe("printSpecsRunSummary", () => { + context("data is empty", () => { + let data = [], time = '2 minutes', machines = 2; + it('returns passed specs data', () => { + return specSummary.printSpecsRunSummary(data, time, machines).then((specsData) => { + expect(data).to.equal(specsData); + }); + }); + }); + + context("with data", () => { + let time = '2 minutes', + machines = 2, + data = [ + {specName: 'spec2.name.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, + {specName: 'spec2.name.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, + {specName: 'spec2.name.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, + {specName: 'spec2.name.js', status: 'Passed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'} + ]; + + it('returns passed specs data', () => { + var loggerInfoSpy = sinon.spy(logger, 'info'); + + specSummary.printSpecsRunSummary(data, time, machines); + sinon.assert.calledWith(loggerInfoSpy, 'Total tests: 4, passed: 1, failed: 2, skipped: 1'); + sinon.assert.calledWith(loggerInfoSpy, `Done in ${time} using ${machines} machines\n`); + + loggerInfoSpy.restore(); + }); + }); +}); From d3e58a6823870ab0b2123ff2a14e18f3d071b69f Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Fri, 9 Oct 2020 18:43:52 +0530 Subject: [PATCH 15/34] Moving the method in the promise --- bin/helpers/sync/specsSummary.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/bin/helpers/sync/specsSummary.js b/bin/helpers/sync/specsSummary.js index 5ccd1b76..a25aa4b9 100644 --- a/bin/helpers/sync/specsSummary.js +++ b/bin/helpers/sync/specsSummary.js @@ -17,21 +17,21 @@ const logger = require("../logger").syncCliLogger; // ] // let printSpecsRunSummary = (data, time, machines) => { - let summary = { - total: 0, - failed: 0, - passed: 0, - skipped: 0 - }; + return new Promise((resolve, _reject) => { + let summary = { + total: 0, + failed: 0, + passed: 0, + skipped: 0 + }; - data.forEach((spec) => { - specSummaryCount(summary, spec.status.toLowerCase()); - }); + data.forEach((spec) => { + specSummaryCount(summary, spec.status.toLowerCase()); + }); - logger.info(`Total tests: ${summary.total}, passed: ${summary.passed}, failed: ${summary.failed}, skipped: ${summary.skipped}`); - logger.info(`Done in ${time} using ${machines} machines\n`); + logger.info(`Total tests: ${summary.total}, passed: ${summary.passed}, failed: ${summary.failed}, skipped: ${summary.skipped}`); + logger.info(`Done in ${time} using ${machines} machines\n`); - return new Promise((resolve, _reject) => { resolve(data); }) }; From eda59e43e7ae96d5ff533bc0afde0950797f4787 Mon Sep 17 00:00:00 2001 From: nagpalkaran95 Date: Mon, 12 Oct 2020 12:41:05 +0530 Subject: [PATCH 16/34] remove bsconf path from param --- bin/commands/runs.js | 2 +- bin/helpers/syncRunner.js | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/bin/commands/runs.js b/bin/commands/runs.js index 486e2cad..2410a219 100644 --- a/bin/commands/runs.js +++ b/bin/commands/runs.js @@ -67,7 +67,7 @@ module.exports = function run(args) { if (!args.disableNpmWarning && bsConfig.run_settings.npm_dependencies && Object.keys(bsConfig.run_settings.npm_dependencies).length <= 0) logger.warn(Constants.userMessages.NO_NPM_DEPENDENCIES); if (args.sync) { - syncRunner.pollBuildStatus(bsConfig, bsConfigPath, data).then((exitCode) => { + syncRunner.pollBuildStatus(bsConfig, data).then((exitCode) => { utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null); process.exit(exitCode); diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index 1919ef92..83689809 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -5,9 +5,9 @@ const config = require("./config"), utils = require("./utils"), request = require('request'); -exports.pollBuildStatus = (bsConfig, bsConfigPath, buildDetails) => { +exports.pollBuildStatus = (bsConfig, buildDetails) => { return new Promise(function (resolve, reject) { - logBuildDetails(bsConfig, bsConfigPath, buildDetails); + logBuildDetails(bsConfig, buildDetails); printSpecsStatus() .then((data) => { printSpecsRunSummary(); @@ -27,7 +27,7 @@ exports.pollBuildStatus = (bsConfig, bsConfigPath, buildDetails) => { }); }; -let logBuildDetails = (bsConfig, bsConfigPath, buildDetails) => { +let logBuildDetails = (bsConfig, buildDetails) => { let parallels_enabled = false; if (bsConfig.run_settings.parallels) { parallels_enabled = true; @@ -35,9 +35,6 @@ let logBuildDetails = (bsConfig, bsConfigPath, buildDetails) => { let parallelMessage = `Run in parallel: ${parallels_enabled ? 'enabled' : 'disabled'}`; if (parallels_enabled) parallelMessage = parallelMessage + ` (attempting to run on ${buildDetails.machines} machines)`; - // logger.info(`Configuration file: ${bsConfigPath}`); - // logger.info(`Cypress config file: ${bsConfig.run_settings.cypress_config_file}`); - // logger.info(`Local connection: ${bsConfig.connection_settings.local ? 'enabled' : 'disabled'}`); logger.info(`Browser Combinations: ${buildDetails.combinations}`); logger.info(parallelMessage); logger.info(`BrowserStack Dashboard: ${buildDetails.dashboard_url}`); From f165b56c18aa018e72a280ee406c5cd300fa8471 Mon Sep 17 00:00:00 2001 From: nagpalkaran95 Date: Mon, 12 Oct 2020 12:42:44 +0530 Subject: [PATCH 17/34] add test cases for sync Runner --- test/unit/bin/helpers/syncRunner.js | 53 +++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 test/unit/bin/helpers/syncRunner.js diff --git a/test/unit/bin/helpers/syncRunner.js b/test/unit/bin/helpers/syncRunner.js new file mode 100644 index 00000000..0f5cc58a --- /dev/null +++ b/test/unit/bin/helpers/syncRunner.js @@ -0,0 +1,53 @@ +const chai = require("chai"), + sinon = require("sinon"), + chaiAsPromised = require("chai-as-promised"), + rewire = require("rewire"); + +const logger = require("../../../../bin/helpers/logger").winstonLogger, + testObjects = require("../../support/fixtures/testObjects"); + +const syncRunner = rewire("../../../../bin/helpers/syncRunner"); + +chai.use(chaiAsPromised); +logger.transports["console.info"].silent = true; + +logBuildDetails = syncRunner.__get__("logBuildDetails"); + +describe("syncRunner", () => { + var sandbox, winstonLoggerStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + winstonLoggerStub = sinon.stub(logger, "log").callsFake(() => {}); + }); + + afterEach(() => { + sandbox.restore(); + sinon.restore(); + }); + + describe("logBuildDetails", () => { + let buildDetails = { + machines: 10, + combinations: 5, + dashboard_url: "random_url", + }; + + it("parallels defined", () => { + logBuildDetails(testObjects.sampleBsConfigWithParallels, buildDetails); + + sinon.assert.calledThrice(winstonLoggerStub); + sinon.assert.calledWithMatch(winstonLoggerStub, 'info', `Browser Combinations: ${buildDetails.combinations}`); + sinon.assert.calledWithMatch(winstonLoggerStub, 'info', `Run in parallel: enabled (attempting to run on ${buildDetails.machines} machines)`); + sinon.assert.calledWithMatch(winstonLoggerStub, 'info', `BrowserStack Dashboard: ${buildDetails.dashboard_url}`); + }); + it("parallels not defined", () => { + logBuildDetails(testObjects.sampleBsConfig, buildDetails); + + sinon.assert.calledThrice(winstonLoggerStub); + sinon.assert.calledWithMatch(winstonLoggerStub, 'info', `Browser Combinations: ${buildDetails.combinations}`); + sinon.assert.calledWithMatch(winstonLoggerStub, 'info', `Run in parallel: disabled`); + sinon.assert.calledWithMatch(winstonLoggerStub, 'info', `BrowserStack Dashboard: ${buildDetails.dashboard_url}`); + }); + }); +}); From b3e359096c08b6b91d908c9c40139369f1484628 Mon Sep 17 00:00:00 2001 From: nagpalkaran95 Date: Mon, 12 Oct 2020 14:45:07 +0530 Subject: [PATCH 18/34] change backs runs.js changes for promise handling --- bin/commands/runs.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/bin/commands/runs.js b/bin/commands/runs.js index 2410a219..0d434731 100644 --- a/bin/commands/runs.js +++ b/bin/commands/runs.js @@ -69,18 +69,14 @@ module.exports = function run(args) { if (args.sync) { syncRunner.pollBuildStatus(bsConfig, data).then((exitCode) => { utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null); - process.exit(exitCode); - }).catch((err) => { - logger.error(err); - process.exit(1); }); - } else { - logger.info(message); - logger.info(dashboardLink); - utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null); - process.exit(2); } + + logger.info(message); + logger.info(dashboardLink); + utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null); + return; }).catch(function (err) { // Build creation failed logger.error(err); From fb15912a8985ffd926a0955452ae43957fd77ceb Mon Sep 17 00:00:00 2001 From: nagpalkaran95 Date: Mon, 12 Oct 2020 14:49:47 +0530 Subject: [PATCH 19/34] change winstonLogger to syncCliLogger in test cases --- test/unit/bin/helpers/syncRunner.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/test/unit/bin/helpers/syncRunner.js b/test/unit/bin/helpers/syncRunner.js index 0f5cc58a..777479dc 100644 --- a/test/unit/bin/helpers/syncRunner.js +++ b/test/unit/bin/helpers/syncRunner.js @@ -3,22 +3,21 @@ const chai = require("chai"), chaiAsPromised = require("chai-as-promised"), rewire = require("rewire"); -const logger = require("../../../../bin/helpers/logger").winstonLogger, +const logger = require("../../../../bin/helpers/logger").syncCliLogger, testObjects = require("../../support/fixtures/testObjects"); const syncRunner = rewire("../../../../bin/helpers/syncRunner"); chai.use(chaiAsPromised); -logger.transports["console.info"].silent = true; logBuildDetails = syncRunner.__get__("logBuildDetails"); describe("syncRunner", () => { - var sandbox, winstonLoggerStub; + var sandbox, loggerStub; beforeEach(() => { sandbox = sinon.createSandbox(); - winstonLoggerStub = sinon.stub(logger, "log").callsFake(() => {}); + loggerStub = sinon.stub(logger, "log").callsFake(() => {}); }); afterEach(() => { @@ -36,18 +35,18 @@ describe("syncRunner", () => { it("parallels defined", () => { logBuildDetails(testObjects.sampleBsConfigWithParallels, buildDetails); - sinon.assert.calledThrice(winstonLoggerStub); - sinon.assert.calledWithMatch(winstonLoggerStub, 'info', `Browser Combinations: ${buildDetails.combinations}`); - sinon.assert.calledWithMatch(winstonLoggerStub, 'info', `Run in parallel: enabled (attempting to run on ${buildDetails.machines} machines)`); - sinon.assert.calledWithMatch(winstonLoggerStub, 'info', `BrowserStack Dashboard: ${buildDetails.dashboard_url}`); + sinon.assert.calledThrice(loggerStub); + sinon.assert.calledWithMatch(loggerStub, 'info', `Browser Combinations: ${buildDetails.combinations}`); + sinon.assert.calledWithMatch(loggerStub, 'info', `Run in parallel: enabled (attempting to run on ${buildDetails.machines} machines)`); + sinon.assert.calledWithMatch(loggerStub, 'info', `BrowserStack Dashboard: ${buildDetails.dashboard_url}`); }); it("parallels not defined", () => { logBuildDetails(testObjects.sampleBsConfig, buildDetails); - sinon.assert.calledThrice(winstonLoggerStub); - sinon.assert.calledWithMatch(winstonLoggerStub, 'info', `Browser Combinations: ${buildDetails.combinations}`); - sinon.assert.calledWithMatch(winstonLoggerStub, 'info', `Run in parallel: disabled`); - sinon.assert.calledWithMatch(winstonLoggerStub, 'info', `BrowserStack Dashboard: ${buildDetails.dashboard_url}`); + sinon.assert.calledThrice(loggerStub); + sinon.assert.calledWithMatch(loggerStub, 'info', `Browser Combinations: ${buildDetails.combinations}`); + sinon.assert.calledWithMatch(loggerStub, 'info', `Run in parallel: disabled`); + sinon.assert.calledWithMatch(loggerStub, 'info', `BrowserStack Dashboard: ${buildDetails.dashboard_url}`); }); }); }); From 9ed42adf2a97c48db94bc467d1bbac87a7979526 Mon Sep 17 00:00:00 2001 From: Surya Tripathi Date: Thu, 15 Oct 2020 10:36:36 +0530 Subject: [PATCH 20/34] Adding basic structure for sync specs logging --- bin/helpers/sync/syncSpecsLogs.js | 24 ++++++++++++++++++++++++ bin/helpers/syncRunner.js | 3 ++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 bin/helpers/sync/syncSpecsLogs.js diff --git a/bin/helpers/sync/syncSpecsLogs.js b/bin/helpers/sync/syncSpecsLogs.js new file mode 100644 index 00000000..bf2d6c7d --- /dev/null +++ b/bin/helpers/sync/syncSpecsLogs.js @@ -0,0 +1,24 @@ +'use strict'; +const request = require('request'), + config = require('../config'), + utils = require('../utils'), + logger = require("../logger").syncCliLogger; + +let printSpecsStatus = (bsConfig, buildId) => { + new Promise((resolve, reject) => { + let backOffFactor = 3; // 3 seconds + let options = { + url: `${config.buildUrl}${buildId}`, + auth: { + user: bsConfig.auth.username, + password: bsConfig.auth.access_key + }, + headers: { + 'Content-Type': 'application/json', + "User-Agent": utils.getUserAgent(), + } + } + }); +} + +exports.printSpecsStatus = printSpecsStatus; diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index 1f7fb8af..17360f52 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -4,13 +4,14 @@ const Config = require("./config"), Constants = require("./constants"), utils = require("./utils"), request = require('request'), + syncSpecsLogs = require('./sync/syncSpecsLogs'), specDetails = require('./sync/failedSpecsDetails'), { table, getBorderCharacters } = require('table'), chalk = require('chalk'); exports.pollBuildStatus = (bsConfig, buildId) => { logBuildDetails().then((data) => { - printSpecsStatus(); + return syncSpecsLogs.printSpecsStatus(bsConfig, buildId); }).then((data) => { printSpecsRunSummary(); }).then((data) => { From 037e31e20e7af8d6ec2b995521ffb555bc9112d8 Mon Sep 17 00:00:00 2001 From: Sagar Ganiga Date: Mon, 19 Oct 2020 12:38:09 +0530 Subject: [PATCH 21/34] partial code for polling --- bin/helpers/sync/syncSpecsLogs.js | 60 +++++++++++++++++++++++++------ bin/helpers/syncRunner.js | 2 +- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/bin/helpers/sync/syncSpecsLogs.js b/bin/helpers/sync/syncSpecsLogs.js index bf2d6c7d..a7acaa85 100644 --- a/bin/helpers/sync/syncSpecsLogs.js +++ b/bin/helpers/sync/syncSpecsLogs.js @@ -6,17 +6,57 @@ const request = require('request'), let printSpecsStatus = (bsConfig, buildId) => { new Promise((resolve, reject) => { - let backOffFactor = 3; // 3 seconds - let options = { - url: `${config.buildUrl}${buildId}`, - auth: { - user: bsConfig.auth.username, - password: bsConfig.auth.access_key - }, - headers: { - 'Content-Type': 'application/json', - "User-Agent": utils.getUserAgent(), + poll(data, 2000, 10000).then(resolve(data)); + }); +} + +let poll = (interval, timeout) => { + data = [] + + return pollRecursive() + .timeout(timeout) + .catch(Promise.TimeoutError, function () { + return false; + }); +} + +let pollRecursive = () => { + return signal() ? Promise.resolve(true) : Promise.delay(interval).then(pollRecursive); +} + +let makeReqest = () => { + let backOffFactor = 3; // 3 seconds + let options = { + url: `${config.buildUrl}${buildId}`, + auth: { + user: bsConfig.auth.username, + password: bsConfig.auth.access_key + }, + headers: { + 'Content-Type': 'application/json', + "User-Agent": utils.getUserAgent(), + } + } + request.post(options, function (err, resp, body) { + if (err) { + reject(err); + } else { + try { + data = JSON.parse(body); + } catch (error) { + data = null; } + if (resp.statusCode != 202) { + if (data) { + reject(`${Constants.userMessages.BUILD_FAILED} Error: ${build.message}`); + } else { + reject(Constants.userMessages.BUILD_FAILED); + } + } else { + resolve(build); + } + resolve(build); + } }); } diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index 2d8ebf56..7a174511 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -12,7 +12,7 @@ const Config = require("./config"), exports.pollBuildStatus = (bsConfig, buildDetails) => { logBuildDetails(bsConfig, buildDetails); - printSpecsStatus().then((data) => { + syncSpecsLogs.printSpecsStatus(bsConfig, buildDetails).then((data) => { return specsSummary.printSpecsRunSummary(data.specs, data.time, data.machines); }).then((data) => { return specDetails.failedSpecsDetails(data); From 7867925aa1f6853423ff1e1299fac581acb29b30 Mon Sep 17 00:00:00 2001 From: Sagar Ganiga Date: Thu, 22 Oct 2020 13:36:09 +0530 Subject: [PATCH 22/34] finalized the result and summary printing --- bin/helpers/sync/specsSummary.js | 2 +- bin/helpers/sync/syncSpecsLogs.js | 198 +++++++++++++++++++++++------- bin/helpers/syncRunner.js | 39 ++++-- 3 files changed, 179 insertions(+), 60 deletions(-) diff --git a/bin/helpers/sync/specsSummary.js b/bin/helpers/sync/specsSummary.js index a25aa4b9..38bef884 100644 --- a/bin/helpers/sync/specsSummary.js +++ b/bin/helpers/sync/specsSummary.js @@ -30,7 +30,7 @@ let printSpecsRunSummary = (data, time, machines) => { }); logger.info(`Total tests: ${summary.total}, passed: ${summary.passed}, failed: ${summary.failed}, skipped: ${summary.skipped}`); - logger.info(`Done in ${time} using ${machines} machines\n`); + logger.info(`Done in ${time/1000} seconds using ${machines} machines\n`); resolve(data); }) diff --git a/bin/helpers/sync/syncSpecsLogs.js b/bin/helpers/sync/syncSpecsLogs.js index a7acaa85..15203431 100644 --- a/bin/helpers/sync/syncSpecsLogs.js +++ b/bin/helpers/sync/syncSpecsLogs.js @@ -1,64 +1,168 @@ -'use strict'; -const request = require('request'), - config = require('../config'), - utils = require('../utils'), - logger = require("../logger").syncCliLogger; - -let printSpecsStatus = (bsConfig, buildId) => { - new Promise((resolve, reject) => { - poll(data, 2000, 10000).then(resolve(data)); - }); -} - -let poll = (interval, timeout) => { - data = [] +"use strict"; +const request = require("request"), + config = require("../config"), + utils = require("../utils"), + logger = require("../logger").syncCliLogger, + async = require('async'), + tableStream = require('table').createStream, + chalk = require('chalk'); - return pollRecursive() - .timeout(timeout) - .catch(Promise.TimeoutError, function () { - return false; - }); +let whileLoop = true, whileTries = 40, options, timeout = 3000, n = 10, tableConfig, stream, startTime, endTime; +let specSummary = { + "specs": [], + "duration": null } -let pollRecursive = () => { - return signal() ? Promise.resolve(true) : Promise.delay(interval).then(pollRecursive); -} -let makeReqest = () => { - let backOffFactor = 3; // 3 seconds - let options = { - url: `${config.buildUrl}${buildId}`, +let getOptions = (auth, build_id) => { + return { + url: `${config.buildUrl}${build_id}`, auth: { - user: bsConfig.auth.username, - password: bsConfig.auth.access_key + user: auth.username, + password: auth.access_key }, headers: { - 'Content-Type': 'application/json', - "User-Agent": utils.getUserAgent(), + "Content-Type": "application/json", + "User-Agent": utils.getUserAgent() } - } - request.post(options, function (err, resp, body) { - if (err) { - reject(err); - } else { - try { - data = JSON.parse(body); - } catch (error) { - data = null; - } - if (resp.statusCode != 202) { - if (data) { - reject(`${Constants.userMessages.BUILD_FAILED} Error: ${build.message}`); + }; +} + +let getTableConfig = () => { + return { + border: { + topBody: `-`, + topJoin: ``, + topLeft: ``, + topRight: ``, + + bottomBody: `-`, + bottomJoin: ``, + bottomLeft: ``, + bottomRight: ``, + + bodyLeft: ``, + bodyRight: ``, + bodyJoin: ``, + + joinBody: ``, + joinLeft: ``, + joinRight: ``, + joinJoin: `` + }, + singleLine: true, + columns: { + 0: { alignment: 'right' } + }, + columnDefault: { + width: 50 + }, + columnCount: 2 + }; +} + +let printSpecsStatus = (bsConfig, buildDetails) => { + return new Promise((resolve, reject) => { + options = getOptions(bsConfig.auth, buildDetails.build_id) + tableConfig = getTableConfig(); + stream = tableStream(tableConfig); + + async.whilst( + function() { + return whileLoop; + }, + function(callback) { + whileProcess(callback) + }, + function(err, result) { + if (err) { + reject(err) } else { - reject(Constants.userMessages.BUILD_FAILED); + specSummary.duration = endTime - startTime + logger.info(); + resolve(specSummary) } - } else { - resolve(build); } - resolve(build); + ); + }); +}; + +function whileProcess(whilstCallback) { + request.post(options, function(error, response, body) { + if (error) { + return whilstCallback(error); + } + switch (response.statusCode) { + case 202: // get data here and print it + n = 2 + showSpecsStatus(body); + return setTimeout(whilstCallback, timeout * n, null); + case 204: // No data available, wait for some time and ask again + n = 1 + return setTimeout(whilstCallback, timeout * n, null); + case 200: // Build is completed. + whileLoop = false; + endTime = Date.now(); + showSpecsStatus(body); + return whilstCallback(null, body); + default: + whileLoop = false; + whileTries -= 1; + if (whileTries === 0) { + return whilstCallback({ status: 504, message: "Tries limit reached" }); //Gateway Timeout + } + return whilstCallback({ status: response.statusCode, message: body }); + } + }); +} +let showSpecsStatus = (data) => { + let specData = JSON.parse(data); + specData.forEach(specDetails => { + if (specDetails == "created") { + startTime = Date.now(); + logger.info("Running Tests: ...") + n = 10 + } else { + try { + specDetails = JSON.parse(specDetails) + printSpecData(specDetails) + } catch (error) { + } } }); } +let printSpecData = (data) => { + let combination = getCombinationName(data["spec"]); + let specName = data["path"] + let status = getStatus(data["spec"]["status"]); + + stream.write([combination + ":", `${specName} ${status}`]); + + // for part 3 + // Format: {specName: 'spec1.failed.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, + specSummary["specs"].push({ + "specName": specName, + "status": data["spec"]["status"], + "combination": combination, + "sessionId": data["session_id"] + }) +} + +let getCombinationName = (spec) => { + return `${spec["os"]} ${spec["osVersion"]} / ${spec["browser"]} ${spec["browserVersion"]}` +} + +let getStatus = (status) => { + switch(status) { + case "passed": + return chalk.green("✔"); + case "failed": + return chalk.red("✘"); + default: + return chalk.blue(`[${status}]`); + } +} + exports.printSpecsStatus = printSpecsStatus; diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index 7a174511..12e390fd 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -11,19 +11,34 @@ const Config = require("./config"), chalk = require('chalk'); exports.pollBuildStatus = (bsConfig, buildDetails) => { - logBuildDetails(bsConfig, buildDetails); - syncSpecsLogs.printSpecsStatus(bsConfig, buildDetails).then((data) => { - return specsSummary.printSpecsRunSummary(data.specs, data.time, data.machines); - }).then((data) => { - return specDetails.failedSpecsDetails(data); - }).then((successExitCode) => { - return resolveExitCode(successExitCode); // exit code 0 - }).catch((nonZeroExitCode) => { - return resolveExitCode(nonZeroExitCode); // exit code 1 - }).finally(() => { - logger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); - logger.info(`${Config.dashboardUrl}${buildDetails.dashboard_url}`); + return new Promise((resolve, reject) => { + logBuildDetails(bsConfig, buildDetails); + syncSpecsLogs.printSpecsStatus(bsConfig, buildDetails).then((data) => { + return specsSummary.printSpecsRunSummary(data.specs, data.duration, buildDetails.machines); + }).then((data) => { + return specDetails.failedSpecsDetails(data); + }).then((successExitCode) => { + resolve(successExitCode); // exit code 0 + }).catch((nonZeroExitCode) => { + resolve(nonZeroExitCode); // exit code 1 + }).finally(() => { + logger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); + logger.info(`${Config.dashboardUrl}${buildDetails.dashboard_url}`); + }); }); + // logBuildDetails(bsConfig, buildDetails); + // syncSpecsLogs.printSpecsStatus(bsConfig, buildDetails).then((data) => { + // return specsSummary.printSpecsRunSummary(data.specs, data.duration, buildDetails.machines); + // }).then((data) => { + // return specDetails.failedSpecsDetails(data); + // }).then((successExitCode) => { + // return resolveExitCode(successExitCode); // exit code 0 + // }).catch((nonZeroExitCode) => { + // return resolveExitCode(nonZeroExitCode); // exit code 1 + // }).finally(() => { + // logger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); + // logger.info(`${Config.dashboardUrl}${buildDetails.dashboard_url}`); + // }); }; let logBuildDetails = (bsConfig, buildDetails) => { From deff51dfd24e6e9e6619f1b66f849e17228c116f Mon Sep 17 00:00:00 2001 From: Sagar Ganiga Date: Thu, 22 Oct 2020 14:35:49 +0530 Subject: [PATCH 23/34] clean up some promises --- bin/commands/runs.js | 2 ++ bin/helpers/syncRunner.js | 34 +--------------------------------- 2 files changed, 3 insertions(+), 33 deletions(-) diff --git a/bin/commands/runs.js b/bin/commands/runs.js index 0d434731..4ac89da3 100644 --- a/bin/commands/runs.js +++ b/bin/commands/runs.js @@ -69,6 +69,8 @@ module.exports = function run(args) { if (args.sync) { syncRunner.pollBuildStatus(bsConfig, data).then((exitCode) => { utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null); + logger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); + logger.info(data.dashboard_url) process.exit(exitCode); }); } diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index 12e390fd..894df63e 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -21,24 +21,8 @@ exports.pollBuildStatus = (bsConfig, buildDetails) => { resolve(successExitCode); // exit code 0 }).catch((nonZeroExitCode) => { resolve(nonZeroExitCode); // exit code 1 - }).finally(() => { - logger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); - logger.info(`${Config.dashboardUrl}${buildDetails.dashboard_url}`); - }); + }) }); - // logBuildDetails(bsConfig, buildDetails); - // syncSpecsLogs.printSpecsStatus(bsConfig, buildDetails).then((data) => { - // return specsSummary.printSpecsRunSummary(data.specs, data.duration, buildDetails.machines); - // }).then((data) => { - // return specDetails.failedSpecsDetails(data); - // }).then((successExitCode) => { - // return resolveExitCode(successExitCode); // exit code 0 - // }).catch((nonZeroExitCode) => { - // return resolveExitCode(nonZeroExitCode); // exit code 1 - // }).finally(() => { - // logger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); - // logger.info(`${Config.dashboardUrl}${buildDetails.dashboard_url}`); - // }); }; let logBuildDetails = (bsConfig, buildDetails) => { @@ -53,19 +37,3 @@ let logBuildDetails = (bsConfig, buildDetails) => { logger.info(parallelMessage); logger.info(`BrowserStack Dashboard: ${buildDetails.dashboard_url}`); }; - -let printSpecsStatus = () => { - return new Promise(function (resolve, reject) { - resolve(); - }); -}; - -let printSpecsRunSummary = () => { - return new Promise(function (resolve, reject) { - resolve(); - }); -}; - -let resolveExitCode = (exitCode) => { - return new Promise((resolve, _reject) => { resolve(exitCode) }); -}; From 1ec290895985f5a0da525c766caad06eacc7a041 Mon Sep 17 00:00:00 2001 From: Sagar Ganiga Date: Fri, 23 Oct 2020 15:40:13 +0530 Subject: [PATCH 24/34] Add test cases for part 2 --- bin/helpers/constants.js | 6 +- bin/helpers/sync/failedSpecsDetails.js | 3 + bin/helpers/sync/syncSpecsLogs.js | 94 ++--- test/unit/bin/helpers/sync/specSummary.js | 6 +- test/unit/bin/helpers/sync/syncSpecsLogs.js | 375 ++++++++++++++++++++ 5 files changed, 435 insertions(+), 49 deletions(-) create mode 100644 test/unit/bin/helpers/sync/syncSpecsLogs.js diff --git a/bin/helpers/constants.js b/bin/helpers/constants.js index edb60cf6..8248fb5c 100644 --- a/bin/helpers/constants.js +++ b/bin/helpers/constants.js @@ -1,5 +1,9 @@ const syncCLI = { - FAILED_SPEC_DETAILS_COL_HEADER: ['Spec', 'Status', 'Browser', 'BrowserStack Session ID'] + FAILED_SPEC_DETAILS_COL_HEADER: ['Spec', 'Status', 'Browser', 'BrowserStack Session ID'], + LOGS: { + INIT_LOG: "Running Tests: ..." + }, + INITIAL_DELAY_MULTIPLIER: 10 }; const userMessages = { diff --git a/bin/helpers/sync/failedSpecsDetails.js b/bin/helpers/sync/failedSpecsDetails.js index 4fe09508..b60ba6c9 100644 --- a/bin/helpers/sync/failedSpecsDetails.js +++ b/bin/helpers/sync/failedSpecsDetails.js @@ -29,6 +29,9 @@ let failedSpecsDetails = (data) => { let specData = [specResultHeader]; // 2-D array data.forEach((spec) => { + if (spec.status.toLowerCase() === 'passed') { + return; + } if (spec.status && spec.status.toLowerCase() === 'failed' && !failedSpecs) failedSpecs = true; diff --git a/bin/helpers/sync/syncSpecsLogs.js b/bin/helpers/sync/syncSpecsLogs.js index 15203431..950671e8 100644 --- a/bin/helpers/sync/syncSpecsLogs.js +++ b/bin/helpers/sync/syncSpecsLogs.js @@ -4,6 +4,7 @@ const request = require("request"), utils = require("../utils"), logger = require("../logger").syncCliLogger, async = require('async'), + Constants = require("../constants"), tableStream = require('table').createStream, chalk = require('chalk'); @@ -30,26 +31,7 @@ let getOptions = (auth, build_id) => { let getTableConfig = () => { return { - border: { - topBody: `-`, - topJoin: ``, - topLeft: ``, - topRight: ``, - - bottomBody: `-`, - bottomJoin: ``, - bottomLeft: ``, - bottomRight: ``, - - bodyLeft: ``, - bodyRight: ``, - bodyJoin: ``, - - joinBody: ``, - joinLeft: ``, - joinRight: ``, - joinJoin: `` - }, + border: getBorderConfig(), singleLine: true, columns: { 0: { alignment: 'right' } @@ -61,6 +43,29 @@ let getTableConfig = () => { }; } +let getBorderConfig = () => { + return { + topBody: `-`, + topJoin: ``, + topLeft: ``, + topRight: ``, + + bottomBody: `-`, + bottomJoin: ``, + bottomLeft: ``, + bottomRight: ``, + + bodyLeft: ``, + bodyRight: ``, + bodyJoin: ``, + + joinBody: ``, + joinLeft: ``, + joinRight: ``, + joinJoin: `` + } +} + let printSpecsStatus = (bsConfig, buildDetails) => { return new Promise((resolve, reject) => { options = getOptions(bsConfig.auth, buildDetails.build_id) @@ -68,26 +73,22 @@ let printSpecsStatus = (bsConfig, buildDetails) => { stream = tableStream(tableConfig); async.whilst( - function() { + function() { // condition for loop return whileLoop; }, - function(callback) { + function(callback) { // actual loop whileProcess(callback) }, - function(err, result) { - if (err) { - reject(err) - } else { - specSummary.duration = endTime - startTime - logger.info(); - resolve(specSummary) - } + function(err, result) { // when loop ends + specSummary.duration = endTime - startTime + logger.info(); + resolve(specSummary) } ); }); }; -function whileProcess(whilstCallback) { +let whileProcess = (whilstCallback) => { request.post(options, function(error, response, body) { if (error) { return whilstCallback(error); @@ -107,10 +108,6 @@ function whileProcess(whilstCallback) { return whilstCallback(null, body); default: whileLoop = false; - whileTries -= 1; - if (whileTries === 0) { - return whilstCallback({ status: 504, message: "Tries limit reached" }); //Gateway Timeout - } return whilstCallback({ status: response.statusCode, message: body }); } }); @@ -120,33 +117,40 @@ let showSpecsStatus = (data) => { let specData = JSON.parse(data); specData.forEach(specDetails => { if (specDetails == "created") { - startTime = Date.now(); - logger.info("Running Tests: ...") - n = 10 + printInitialLog(); } else { try { - specDetails = JSON.parse(specDetails) - printSpecData(specDetails) + printSpecData(JSON.parse(specDetails)) } catch (error) { } } }); } +let printInitialLog = () => { + startTime = Date.now(); + logger.info(Constants.syncCLI.LOGS.INIT_LOG) + n = Constants.syncCLI.INITIAL_DELAY_MULTIPLIER +} + let printSpecData = (data) => { let combination = getCombinationName(data["spec"]); - let specName = data["path"] let status = getStatus(data["spec"]["status"]); + writeToTable(combination, data["path"], status) + addSpecToSummary(data["path"], data["spec"]["status"], combination, data["session_id"]) +} +let writeToTable = (combination, specName, status) => { stream.write([combination + ":", `${specName} ${status}`]); +} - // for part 3 - // Format: {specName: 'spec1.failed.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, +let addSpecToSummary = (specName, status, combination, session_id) => { + // Format for part 3: {specName: 'spec1.failed.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, specSummary["specs"].push({ "specName": specName, - "status": data["spec"]["status"], + "status": status, "combination": combination, - "sessionId": data["session_id"] + "sessionId": session_id }) } diff --git a/test/unit/bin/helpers/sync/specSummary.js b/test/unit/bin/helpers/sync/specSummary.js index 6629d129..66c4b6c4 100644 --- a/test/unit/bin/helpers/sync/specSummary.js +++ b/test/unit/bin/helpers/sync/specSummary.js @@ -10,7 +10,7 @@ var specSummary = require('../../../../../bin/helpers/sync/specsSummary'); describe("printSpecsRunSummary", () => { context("data is empty", () => { - let data = [], time = '2 minutes', machines = 2; + let data = [], time = 6000, machines = 2; it('returns passed specs data', () => { return specSummary.printSpecsRunSummary(data, time, machines).then((specsData) => { expect(data).to.equal(specsData); @@ -19,7 +19,7 @@ describe("printSpecsRunSummary", () => { }); context("with data", () => { - let time = '2 minutes', + let time = 6000, machines = 2, data = [ {specName: 'spec2.name.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, @@ -33,7 +33,7 @@ describe("printSpecsRunSummary", () => { specSummary.printSpecsRunSummary(data, time, machines); sinon.assert.calledWith(loggerInfoSpy, 'Total tests: 4, passed: 1, failed: 2, skipped: 1'); - sinon.assert.calledWith(loggerInfoSpy, `Done in ${time} using ${machines} machines\n`); + sinon.assert.calledWith(loggerInfoSpy, `Done in ${time / 1000} seconds using ${machines} machines\n`); loggerInfoSpy.restore(); }); diff --git a/test/unit/bin/helpers/sync/syncSpecsLogs.js b/test/unit/bin/helpers/sync/syncSpecsLogs.js new file mode 100644 index 00000000..70346274 --- /dev/null +++ b/test/unit/bin/helpers/sync/syncSpecsLogs.js @@ -0,0 +1,375 @@ +"use strict"; +const chai = require("chai"), + expect = chai.expect, + rewire = require("rewire"), + chaiAsPromised = require("chai-as-promised"), + chalk = require('chalk'), + request = require("request"); + +const sinon = require("sinon"); +// var sandbox = require('sinon').createSandbox(); + +chai.use(chaiAsPromised); + +var syncSpecsLogs = rewire("../../../../../bin/helpers/sync/syncSpecsLogs.js"); +var logger = require("../../../../../bin/helpers/logger").syncCliLogger; +var Constants = require("../../../../../bin/helpers/constants.js"); +var config = require("../../../../../bin/helpers/config.js"); + +describe("syncSpecsLogs", () => { + var sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + context("getCombinationName", () => { + const get_path = syncSpecsLogs.__get__("getCombinationName");; + let spec = { + "os": "Windows 10", + "osVersion": "10", + "browser": "chrome", + "browserVersion": "86" + } + it("returns combination name", () => { + let expectedCombination = `${spec["os"]} ${spec["osVersion"]} / ${spec["browser"]} ${spec["browserVersion"]}`; + expect(get_path(spec)).to.equal(expectedCombination); + }); + }); + + context("getStatus", () => { + const getStatus = syncSpecsLogs.__get__("getStatus");; + + it("returns return ✔ in green when status is passes", () => { + expect(getStatus("passed")).to.equal(chalk.green("✔")); + }); + + it("returns return ✘ in red when status is failed", () => { + expect(getStatus("failed")).to.equal(chalk.red("✘")); + }); + + it("returns return [status] in yellow when status is skipped or ignored (anything else from pass/fail)", () => { + expect(getStatus("skipped")).to.equal(chalk.blue("[skipped]")); + expect(getStatus("ignored")).to.equal(chalk.blue("[ignored]")); + }); + }); + + context("printInitialLog", () => { + const printInitialLog = syncSpecsLogs.__get__("printInitialLog"); + + it("should print inital logs for specs in sync", () => { + var loggerInfoStub = sinon.stub(logger, 'info'); + + printInitialLog() + + expect(syncSpecsLogs.__get__("n")).to.equal(Constants.syncCLI.INITIAL_DELAY_MULTIPLIER); + expect(syncSpecsLogs.__get__("startTime")).to.not.be.null; + sinon.assert.calledWith(loggerInfoStub, Constants.syncCLI.LOGS.INIT_LOG); + + loggerInfoStub.restore(); + }); + }); + + context("getOptions", () => { + const getOptions = syncSpecsLogs.__get__("getOptions"); + let auth = {username: "cypress", access_key: "abcd"} + let build_id = "build1" + + it('should return proper request option for polling', () => { + let options = getOptions(auth, build_id); + expect(options.url).to.equal(`${config.buildUrl}${build_id}`); + expect(options.auth.user).to.equal(auth.username); + expect(options.auth.password).to.equal(auth.access_key); + expect(options.headers["Content-Type"]).to.equal("application/json"); + }); + }); + + context("getTableConfig", () => { + const getTableConfig = syncSpecsLogs.__get__("getTableConfig"); + + it('should return proper table config option for spec table', () => { + var getBorderConfigStub = sandbox.stub(); + syncSpecsLogs.__set__('getBorderConfig', getBorderConfigStub); + + let options = getTableConfig(); + expect(options.singleLine).to.be.true; + expect(options.columnDefault.width).to.equal(50); + expect(options.columns[0].alignment).to.equal('right'); + expect(options.columnCount).to.equal(2); + expect(getBorderConfigStub.calledOnce).to.be.true; + }); + }); + + context("getBorderConfig", () => { + const getBorderConfig = syncSpecsLogs.__get__("getBorderConfig"); + + it('should return proper border option for spec table', () => { + let options = getBorderConfig(); + expect(options.topBody).to.equal("-"); + expect(options.bottomBody).to.equal("-"); + }); + }); + + context("writeToTable", () => { + const writeToTable = syncSpecsLogs.__get__("writeToTable"); + + it('should print spec details to the table', () => { + const stream = sandbox.stub(); + stream.write = sandbox.stub(); + syncSpecsLogs.__set__('stream', stream); + let combination = "Windows 10", path = "path", status = "passed"; + writeToTable(combination, path, status); + sinon.assert.calledOnceWithExactly(stream.write, [combination + ":", `${path} ${status}`]); + }); + }); + + context("addSpecToSummary", () => { + const addSpecToSummary = syncSpecsLogs.__get__("addSpecToSummary"); + + it('should add spec details to specSummary', () => { + let specSummary = { specs: [] } + syncSpecsLogs.__set__('specSummary', specSummary); + let specName = "spec", status = "status", combination = "combo", session_id = "id"; + addSpecToSummary(specName, status, combination, session_id); + expect(specSummary.specs).deep.to.equal([{"specName": specName, "status": status, "combination": combination, "sessionId": session_id}]) + }); + }); + + context("printSpecData", () => { + const printSpecData = syncSpecsLogs.__get__("printSpecData"); + + it('Should print combination and status to the spec table and add spec details to spec array', () => { + let data = { spec: { status: "passed" }, path: "path", session_id: "id" } + var getCombinationName = sandbox.stub(); + syncSpecsLogs.__set__('getCombinationName', getCombinationName); + var getStatus = sandbox.stub(); + syncSpecsLogs.__set__('getStatus', getStatus); + var writeToTable = sandbox.stub(); + syncSpecsLogs.__set__('writeToTable', writeToTable); + var addSpecToSummary = sandbox.stub(); + syncSpecsLogs.__set__('addSpecToSummary', addSpecToSummary); + + + printSpecData(data); + sinon.assert.calledOnceWithExactly(getCombinationName, data["spec"]); + sinon.assert.calledOnceWithExactly(getStatus, data["spec"]["status"]); + sinon.assert.calledOnce(writeToTable); + sinon.assert.calledOnce(addSpecToSummary); + }); + }); + + + context("showSpecsStatus", () => { + const showSpecsStatus = syncSpecsLogs.__get__("showSpecsStatus"); + + it('should print initial log for running specs when it is the 1st polling response', () => { + let data = JSON.stringify(["created"]) + var printInitialLog = sandbox.stub(); + syncSpecsLogs.__set__('printInitialLog', printInitialLog); + + showSpecsStatus(data); + + expect(printInitialLog.calledOnce).to.be.true; + }); + + it('should print spec details when spec related data is sent in polling response', () => { + let specResult = JSON.stringify({"path": "path"}) + let data = JSON.stringify([specResult]) + var printSpecData = sandbox.stub(); + syncSpecsLogs.__set__('printSpecData', printSpecData); + showSpecsStatus(data); + expect(printSpecData.calledOnce).to.be.true; + }); + + it('should print initial and spec details when spec related data is sent in polling response', () => { + let specResult = JSON.stringify({"path": "path"}) + let data = JSON.stringify(["created", specResult]) + var printSpecData = sandbox.stub(); + syncSpecsLogs.__set__('printSpecData', printSpecData); + var printInitialLog = sandbox.stub(); + syncSpecsLogs.__set__('printInitialLog', printInitialLog); + showSpecsStatus(data); + expect(printSpecData.calledOnce).to.be.true; + expect(printInitialLog.calledOnce).to.be.true; + }); + }); + + context("printSpecsStatus", () => { + const printSpecsStatus = syncSpecsLogs.__get__("printSpecsStatus"); + let startTime = Date.now(), endTime = Date.now() + 10, counter = 0; + let specSummary = { specs: [] }, getOptions, getTableConfig, tableStream, whileProcess, loggerInfoStub; + + beforeEach(() => { + counter = 0; + + getOptions = sandbox.stub(); + syncSpecsLogs.__set__('getOptions', getOptions); + + getTableConfig = sandbox.stub(); + syncSpecsLogs.__set__('getTableConfig', getTableConfig); + + tableStream = sandbox.stub(); + syncSpecsLogs.__set__('tableStream', tableStream); + + loggerInfoStub = sandbox.stub(logger, 'info'); + + whileProcess = sandbox.stub().callsFake(function (whilstCallback) { + counter++ + if(counter >= 3) { + syncSpecsLogs.__set__('whileLoop', false); + whilstCallback(new Error("ggg"), {}); + } else {whileProcess(whilstCallback, 10, null)} + }); + + syncSpecsLogs.__set__('whileProcess', whileProcess); + }); + + it('Should not loop when whileLoop is false and set duration correctly', () => { + syncSpecsLogs.__set__('whileLoop', false); + syncSpecsLogs.__set__('startTime', startTime); + syncSpecsLogs.__set__('endTime', endTime); + syncSpecsLogs.__set__('specSummary', specSummary); + + return printSpecsStatus({}, {}).then((specSummary) => { + expect(getOptions.calledOnce).to.be.true; + expect(getTableConfig.calledOnce).to.be.true; + expect(tableStream.calledOnce).to.be.true; + expect(loggerInfoStub.calledOnce).to.be.true; + expect(whileProcess.calledOnce).to.be.false; + expect(specSummary.specs).deep.to.equal([]) + expect(specSummary.duration).to.eql(endTime - startTime); + }); + }); + + it('Should loop when whileLoop is true until it becomes false', () => { + syncSpecsLogs.__set__('whileLoop', true); + syncSpecsLogs.__set__('startTime', startTime); + syncSpecsLogs.__set__('endTime', endTime); + syncSpecsLogs.__set__('specSummary', specSummary); + + return printSpecsStatus({}, {}).then((specSummary) => { + expect(getOptions.calledOnce).to.be.true; + expect(getTableConfig.calledOnce).to.be.true; + expect(tableStream.calledOnce).to.be.true; + expect(whileProcess.callCount).to.eql(3); + expect(specSummary.duration).to.eql(endTime - startTime); + }); + }); + }); + + context("whileProcess", () => { + const whileProcess = syncSpecsLogs.__get__("whileProcess"); + + it('Should break the loop if request has error', () => { + let error = new Error("error"); + let requestStub = sandbox.stub(); + let postStub = sandbox + .stub(request, "post") + .yields(error, { statusCode: 200 }, JSON.stringify({})); + requestStub.post = postStub; + + syncSpecsLogs.__set__('request', requestStub); + + let whilstCallback = sandbox.stub(); + whileProcess(whilstCallback); + + sinon.assert.calledWith(whilstCallback, error); + }); + + it('Should print spec details when data is returned from server', () => { + let error = null, body={}, status = 202, n = 1, delayed_n = 2, timeout = 3000; + let requestStub = sandbox.stub(); + let postStub = sandbox + .stub(request, "post") + .yields(error, { statusCode: status }, JSON.stringify(body)); + requestStub.post = postStub; + syncSpecsLogs.__set__('request', requestStub); + + let showSpecsStatus = sandbox.stub(); + syncSpecsLogs.__set__('showSpecsStatus', showSpecsStatus); + + let setTimeout = sandbox.stub(); + syncSpecsLogs.__set__('setTimeout', setTimeout); + syncSpecsLogs.__set__('n', n); + syncSpecsLogs.__set__('timeout', timeout); + + let whilstCallback = sandbox.stub(); + whileProcess(whilstCallback); + + expect(syncSpecsLogs.__get__("n")).to.equal(delayed_n); + sinon.assert.calledWith(setTimeout, whilstCallback, timeout * delayed_n, null); + sinon.assert.calledWith(showSpecsStatus, JSON.stringify(body)); + }); + + it('Should poll for data when server responds with no data available', () => { + let error = null, body={}, status = 204, n = 1, delayed_n = 1, timeout = 3000; + let requestStub = sandbox.stub(); + let postStub = sandbox + .stub(request, "post") + .yields(error, { statusCode: status }, JSON.stringify(body)); + requestStub.post = postStub; + syncSpecsLogs.__set__('request', requestStub); + + let showSpecsStatus = sandbox.stub(); + syncSpecsLogs.__set__('showSpecsStatus', showSpecsStatus); + + let setTimeout = sandbox.stub(); + syncSpecsLogs.__set__('setTimeout', setTimeout); + syncSpecsLogs.__set__('n', n); + syncSpecsLogs.__set__('timeout', timeout); + + let whilstCallback = sandbox.stub(); + whileProcess(whilstCallback); + + expect(syncSpecsLogs.__get__("n")).to.equal(delayed_n); + sinon.assert.calledWith(setTimeout, whilstCallback, timeout * delayed_n, null); + }); + + it('Should stop polling for data when server responds build is completed', () => { + let error = null, body={}, status = 200, n = 1, timeout = 3000; + let requestStub = sandbox.stub(); + let postStub = sandbox.stub(request, "post").yields(error, { statusCode: status }, JSON.stringify(body)); + requestStub.post = postStub; + syncSpecsLogs.__set__('request', requestStub); + + let showSpecsStatus = sandbox.stub(); + syncSpecsLogs.__set__('showSpecsStatus', showSpecsStatus); + + syncSpecsLogs.__set__('whileLoop', true); + syncSpecsLogs.__set__('n', n); + syncSpecsLogs.__set__('timeout', timeout); + + let whilstCallback = sandbox.stub(); + whileProcess(whilstCallback); + + expect(syncSpecsLogs.__get__("whileLoop")).to.be.false; + sinon.assert.calledWith(whilstCallback, null, JSON.stringify(body)); + sinon.assert.calledWith(showSpecsStatus, JSON.stringify(body)); + }); + + it('Should stop polling for data when server responds with error ', () => { + let error = null, body={}, status = 404, n = 1, timeout = 3000; + let requestStub = sandbox.stub(); + let postStub = sandbox.stub(request, "post").yields(error, { statusCode: status }, JSON.stringify(body)); + requestStub.post = postStub; + syncSpecsLogs.__set__('request', requestStub); + + let showSpecsStatus = sandbox.stub(); + syncSpecsLogs.__set__('showSpecsStatus', showSpecsStatus); + + syncSpecsLogs.__set__('whileLoop', true); + syncSpecsLogs.__set__('n', n); + syncSpecsLogs.__set__('timeout', timeout); + + let whilstCallback = sandbox.stub(); + whileProcess(whilstCallback); + + expect(syncSpecsLogs.__get__("whileLoop")).to.be.false; + sinon.assert.calledWith(whilstCallback, {message: JSON.stringify(body), status: status}); + }); + }); +}); From 37d7936b50dfd568866e84846599e7cf5bc983bd Mon Sep 17 00:00:00 2001 From: Sagar Ganiga Date: Fri, 23 Oct 2020 17:49:23 +0530 Subject: [PATCH 25/34] fix test cases and handle some edge cases --- bin/helpers/sync/syncSpecsLogs.js | 5 +---- test/unit/bin/commands/runs.js | 4 ++-- test/unit/bin/helpers/sync/failedSpecDetails.js | 16 +++++++++++++++- test/unit/bin/helpers/sync/syncSpecsLogs.js | 1 - test/unit/bin/helpers/utils.js | 8 ++++++-- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/bin/helpers/sync/syncSpecsLogs.js b/bin/helpers/sync/syncSpecsLogs.js index 950671e8..39c6db3a 100644 --- a/bin/helpers/sync/syncSpecsLogs.js +++ b/bin/helpers/sync/syncSpecsLogs.js @@ -119,10 +119,7 @@ let showSpecsStatus = (data) => { if (specDetails == "created") { printInitialLog(); } else { - try { - printSpecData(JSON.parse(specDetails)) - } catch (error) { - } + printSpecData(JSON.parse(specDetails)) } }); } diff --git a/test/unit/bin/commands/runs.js b/test/unit/bin/commands/runs.js index dd45e9c4..133c855d 100644 --- a/test/unit/bin/commands/runs.js +++ b/test/unit/bin/commands/runs.js @@ -517,7 +517,7 @@ describe("runs", () => { let messageType = Constants.messageTypes.SUCCESS; let errorCode = null; let message = `Success! ${Constants.userMessages.BUILD_CREATED} with build id: random_build_id`; - let dashboardLink = `${Constants.userMessages.VISIT_DASHBOARD} ${dashboardUrl}random_build_id`; + let dashboardLink = `${Constants.userMessages.VISIT_DASHBOARD} ${dashboardUrl}`; const runs = proxyquire("../../../../bin/commands/runs", { "../helpers/utils": { @@ -564,7 +564,7 @@ describe("runs", () => { ); archiverStub.returns(Promise.resolve("Zipping completed")); zipUploadStub.returns(Promise.resolve("zip uploaded")); - createBuildStub.returns(Promise.resolve({ message: 'Success', build_id: 'random_build_id' })); + createBuildStub.returns(Promise.resolve({ message: 'Success', build_id: 'random_build_id', dashboard_url: dashboardUrl })); return runs(args) .then(function (_bsConfig) { diff --git a/test/unit/bin/helpers/sync/failedSpecDetails.js b/test/unit/bin/helpers/sync/failedSpecDetails.js index ada31143..595c8301 100644 --- a/test/unit/bin/helpers/sync/failedSpecDetails.js +++ b/test/unit/bin/helpers/sync/failedSpecDetails.js @@ -3,10 +3,23 @@ const chai = require("chai"), expect = chai.expect, chaiAsPromised = require("chai-as-promised"); +const sinon = require("sinon"); chai.use(chaiAsPromised); const specDetails = require('../../../../../bin/helpers/sync/failedSpecsDetails'); +var logger = require("../../../../../bin/helpers/logger").syncCliLogger; describe("failedSpecsDetails", () => { + var sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(logger, 'info'); + }); + + afterEach(() => { + sandbox.restore(); + }); + context("data is empty", () => { let data = []; it('returns 0 exit code', () => { @@ -30,7 +43,8 @@ describe("failedSpecsDetails", () => { context("data has failed specs", () => { let data = [ - {specName: 'spec2.name.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'} + {specName: 'spec2.name.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, + {specName: 'spec2.name.js', status: 'Passed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'} ]; it("returns 1 exit code", () => { diff --git a/test/unit/bin/helpers/sync/syncSpecsLogs.js b/test/unit/bin/helpers/sync/syncSpecsLogs.js index 70346274..daae2169 100644 --- a/test/unit/bin/helpers/sync/syncSpecsLogs.js +++ b/test/unit/bin/helpers/sync/syncSpecsLogs.js @@ -7,7 +7,6 @@ const chai = require("chai"), request = require("request"); const sinon = require("sinon"); -// var sandbox = require('sinon').createSandbox(); chai.use(chaiAsPromised); diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index 4413aabd..a1f1c3d6 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -140,7 +140,9 @@ describe("utils", () => { describe("validateBstackJson", () => { it("should reject with SyntaxError for empty file", () => { let bsConfigPath = path.join(process.cwd(), 'test', 'test_files', 'dummy_bstack.json'); - expect(utils.validateBstackJson(bsConfigPath)).to.be.rejectedWith(SyntaxError); + return utils.validateBstackJson(bsConfigPath).catch((error)=>{ + sinon.match(error, "Invalid browserstack.json file") + }); }); it("should resolve with data for valid json", () => { let bsConfigPath = path.join(process.cwd(), 'test', 'test_files', 'dummy_bstack_2.json'); @@ -148,7 +150,9 @@ describe("utils", () => { }); it("should reject with SyntaxError for invalid json file", () => { let bsConfigPath = path.join(process.cwd(), 'test', 'test_files', 'dummy_bstack_3.json'); - expect(utils.validateBstackJson(bsConfigPath)).to.be.rejectedWith(SyntaxError); + return utils.validateBstackJson(bsConfigPath).catch((error) => { + sinon.match(error, "Invalid browserstack.json file") + }); }); }); From fa323b1bcc83c58134d2f97cc04aee860664b657 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Thu, 29 Oct 2020 21:22:52 +0530 Subject: [PATCH 26/34] Sync Cli Log Changes --- bin/commands/runs.js | 20 +++++++++++--------- bin/helpers/archiver.js | 2 ++ bin/helpers/capabilityHelper.js | 6 +++--- bin/helpers/constants.js | 18 ++++++++++++------ bin/helpers/sync/failedSpecsDetails.js | 24 ++++++++++++++++-------- bin/helpers/sync/specsSummary.js | 8 ++++---- bin/helpers/sync/syncSpecsLogs.js | 2 +- bin/helpers/syncRunner.js | 8 +++++--- bin/helpers/zipUpload.js | 7 +++++-- 9 files changed, 59 insertions(+), 36 deletions(-) diff --git a/bin/commands/runs.js b/bin/commands/runs.js index 4ac89da3..1c513793 100644 --- a/bin/commands/runs.js +++ b/bin/commands/runs.js @@ -8,7 +8,8 @@ const archiver = require("../helpers/archiver"), Constants = require("../helpers/constants"), utils = require("../helpers/utils"), fileHelpers = require("../helpers/fileHelpers"), - syncRunner = require("../helpers/syncRunner"); + syncRunner = require("../helpers/syncRunner"), + syncCliLogger = require("../helpers/logger").syncCliLogger; module.exports = function run(args) { let bsConfigPath = utils.getConfigPath(args.cf); @@ -44,7 +45,7 @@ module.exports = function run(args) { // Validate browserstack.json values and parallels specified via arguments return capabilityHelper.validate(bsConfig, args).then(function (validated) { - logger.info(validated); + // logger.info(validated); // accept the number of parallels utils.setParallels(bsConfig, args); @@ -54,7 +55,6 @@ module.exports = function run(args) { // Uploaded zip file return zipUploader.zipUpload(bsConfig, config.fileName).then(function (zip) { - // Create build return build.createBuild(bsConfig, zip).then(function (data) { let message = `${data.message}! ${Constants.userMessages.BUILD_CREATED} with build id: ${data.build_id}`; @@ -64,19 +64,22 @@ module.exports = function run(args) { logger.warn(Constants.userMessages.NO_PARALLELS); } - if (!args.disableNpmWarning && bsConfig.run_settings.npm_dependencies && Object.keys(bsConfig.run_settings.npm_dependencies).length <= 0) logger.warn(Constants.userMessages.NO_NPM_DEPENDENCIES); - + if (!args.disableNpmWarning && bsConfig.run_settings.npm_dependencies && Object.keys(bsConfig.run_settings.npm_dependencies).length <= 0) { + logger.warn(Constants.userMessages.NO_NPM_DEPENDENCIES); + logger.warn(Constants.userMessages.NO_NPM_DEPENDENCIES_READ_MORE); + } if (args.sync) { syncRunner.pollBuildStatus(bsConfig, data).then((exitCode) => { utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null); - logger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); - logger.info(data.dashboard_url) + syncCliLogger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); + syncCliLogger.info(data.dashboard_url); process.exit(exitCode); }); } logger.info(message); logger.info(dashboardLink); + if(!args.sync) logger.info(Constants.userMessages.EXIT_SYNC_CLI_MESSAGE.replace("",data.build_id)); utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null); return; }).catch(function (err) { @@ -88,9 +91,8 @@ module.exports = function run(args) { // Zip Upload failed logger.error(err); logger.error(Constants.userMessages.ZIP_UPLOAD_FAILED); - utils.sendUsageReport(bsConfig, args, `${err}\n${Constants.userMessages.ZIP_UPLOAD_FAILED}`, Constants.messageTypes.ERROR, 'zip_upload_failed'); - }).finally(function () { fileHelpers.deleteZip(); + utils.sendUsageReport(bsConfig, args, `${err}\n${Constants.userMessages.ZIP_UPLOAD_FAILED}`, Constants.messageTypes.ERROR, 'zip_upload_failed'); }); }).catch(function (err) { // Zipping failed diff --git a/bin/helpers/archiver.js b/bin/helpers/archiver.js index 4ffcccd1..61364963 100644 --- a/bin/helpers/archiver.js +++ b/bin/helpers/archiver.js @@ -13,6 +13,8 @@ const archiveSpecs = (runSettings, filePath, excludeFiles) => { var cypressFolderPath = path.dirname(runSettings.cypressConfigFilePath); + logger.info(`Creating tests.zip with files in ${cypressFolderPath}`); + var archive = archiver('zip', { zlib: { level: 9 } // Sets the compression level. }); diff --git a/bin/helpers/capabilityHelper.js b/bin/helpers/capabilityHelper.js index bb7a3268..5915429f 100644 --- a/bin/helpers/capabilityHelper.js +++ b/bin/helpers/capabilityHelper.js @@ -30,7 +30,7 @@ const caps = (bsConfig, zip) => { } obj.devices = osBrowserArray; if (obj.devices.length == 0) reject(Constants.validationMessages.EMPTY_BROWSER_LIST); - logger.info(`Browser list: ${osBrowserArray.toString()}`); + logger.info(`Browsers list: ${osBrowserArray.toString()}`); // Test suite if (zip.zip_url && zip.zip_url.split("://")[1].length !== 0) { @@ -38,12 +38,11 @@ const caps = (bsConfig, zip) => { } else { reject("Test suite is empty"); } - logger.info(`Test suite: bs://${obj.test_suite}`); // Local obj.local = false; if (bsConfig.connection_settings && bsConfig.connection_settings.local === true) obj.local = true; - logger.info(`Local is set to: ${obj.local}`); + logger.info(`Local is set to: ${obj.local} (${obj.local ? Constants.userMessages.LOCAL_TRUE : Constants.userMessages.LOCAL_FALSE})`); // Local Identifier obj.localIdentifier = null; @@ -100,6 +99,7 @@ const caps = (bsConfig, zip) => { const validate = (bsConfig, args) => { return new Promise(function (resolve, reject) { + logger.info(Constants.userMessages.VALIDATING_CONFIG); if (!bsConfig) reject(Constants.validationMessages.EMPTY_BROWSERSTACK_JSON); if (!bsConfig.auth) reject(Constants.validationMessages.INCORRECT_AUTH_PARAMS); diff --git a/bin/helpers/constants.js b/bin/helpers/constants.js index 8248fb5c..bed4e798 100644 --- a/bin/helpers/constants.js +++ b/bin/helpers/constants.js @@ -1,7 +1,7 @@ const syncCLI = { FAILED_SPEC_DETAILS_COL_HEADER: ['Spec', 'Status', 'Browser', 'BrowserStack Session ID'], LOGS: { - INIT_LOG: "Running Tests: ..." + INIT_LOG: "All tests:" }, INITIAL_DELAY_MULTIPLIER: 10 }; @@ -17,14 +17,20 @@ const userMessages = { CONFIG_FILE_CREATED: "BrowserStack Config File created, you can now run browserstack-cypress --config-file run", CONFIG_FILE_EXISTS: "File already exists, delete the browserstack.json file manually. skipping...", DIR_NOT_FOUND: "Given path does not exist. Failed to create browserstack.json in %s", - ZIP_DELETE_FAILED: "Could not delete local file.", - ZIP_DELETED: "Zip file deleted successfully.", + ZIP_DELETE_FAILED: "Could not delete tests.zip successfully.", + ZIP_DELETED: "Deleted tests.zip successfully.", API_DEPRECATED: "This version of API is deprecated, please use latest version of API.", FAILED_TO_ZIP: "Failed to zip files.", - VISIT_DASHBOARD: "Visit the Automate dashboard for test reporting:", + VISIT_DASHBOARD: "Visit the Automate dashboard for real-time test reporting:", CONFLICTING_INIT_ARGUMENTS: "Conflicting arguments given. You can use --path only with a file name, and not with a file path.", - NO_PARALLELS: "Your tests will run sequentially. Read more about running your tests in parallel here: https://www.browserstack.com/docs/automate/cypress/run-tests-in-parallel", - NO_NPM_DEPENDENCIES: "No npm dependencies specified. Read more here: https://www.browserstack.com/docs/automate/cypress/npm-packages. You can suppress this warning by using --disable-npm-warning flag." + NO_PARALLELS: "Your specs will run sequentially on a single machine. Read more about running your specs in parallel here: https://www.browserstack.com/docs/automate/cypress/run-tests-in-parallel", + NO_NPM_DEPENDENCIES: "No npm dependencies specified - your specs might fail if they need any packages to be installed before running.", + NO_NPM_DEPENDENCIES_READ_MORE: "Read more about npm dependencies here: https://www.browserstack.com/docs/automate/cypress/npm-packages. You can suppress this warning by using --disable-npm-warning flag.", + VALIDATING_CONFIG: "Validating the config", + UPLOADING_TESTS: "Uploading the tests to BrowserStack", + LOCAL_TRUE: "you will now be able to test localhost / private URLs", + LOCAL_FALSE: "you won't be able to test localhost / private URLs", + EXIT_SYNC_CLI_MESSAGE: "Exiting the CLI, but your build is still running. You can use the --sync option to keep getting test updates. You can also use the build-info command now." }; const validationMessages = { diff --git a/bin/helpers/sync/failedSpecsDetails.js b/bin/helpers/sync/failedSpecsDetails.js index b60ba6c9..b44b438f 100644 --- a/bin/helpers/sync/failedSpecsDetails.js +++ b/bin/helpers/sync/failedSpecsDetails.js @@ -19,7 +19,8 @@ const tablePrinter = require('table'), // { table, getBorderCharacters } // let failedSpecsDetails = (data) => { return new Promise((resolve, reject) => { - if (data.length === 0) resolve(0); // return if no failed/skipped tests. + data.exitCode = 0; + if (data.specs.length === 0) resolve(data); // return if no failed/skipped tests. let failedSpecs = false; let specResultHeader = Constants.syncCLI.FAILED_SPEC_DETAILS_COL_HEADER.map((col) => { @@ -28,16 +29,23 @@ let failedSpecsDetails = (data) => { let specData = [specResultHeader]; // 2-D array - data.forEach((spec) => { + data.specs.forEach((spec) => { if (spec.status.toLowerCase() === 'passed') { return; } if (spec.status && spec.status.toLowerCase() === 'failed' && !failedSpecs) failedSpecs = true; - let specStatus = (spec.status && spec.status.toLowerCase() === 'failed') ? - chalk.red(spec.status) : chalk.yellow(spec.status); - specData.push([spec.specName, specStatus, spec.combination, spec.sessionId]); + let specStatus = + spec.status && spec.status.toLowerCase() === 'failed' + ? chalk.red(spec.status) + : chalk.yellow(spec.status); + specData.push([ + spec.specName, + specStatus, + spec.combination, + spec.sessionId, + ]); }); let config = { @@ -61,11 +69,11 @@ let failedSpecsDetails = (data) => { let result = tablePrinter.table(specData, config); - logger.info('Failed / skipped test report'); + logger.info('\nFailed / skipped test report:'); logger.info(result); - if (failedSpecs) reject(1); // specs failed, send exitCode as 1 - resolve(0); // No Specs failed, maybe skipped, but not failed, send exitCode as 0 + if (failedSpecs) data.exitCode = 1 ; // specs failed, send exitCode as 1 + resolve(data); // No Specs failed, maybe skipped, but not failed, send exitCode as 0 }); } diff --git a/bin/helpers/sync/specsSummary.js b/bin/helpers/sync/specsSummary.js index 38bef884..bdab4fa5 100644 --- a/bin/helpers/sync/specsSummary.js +++ b/bin/helpers/sync/specsSummary.js @@ -16,7 +16,7 @@ const logger = require("../logger").syncCliLogger; // {specName: 'spec8.alias.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'} // ] // -let printSpecsRunSummary = (data, time, machines) => { +let printSpecsRunSummary = (data, machines) => { return new Promise((resolve, _reject) => { let summary = { total: 0, @@ -25,14 +25,14 @@ let printSpecsRunSummary = (data, time, machines) => { skipped: 0 }; - data.forEach((spec) => { + data.specs.forEach((spec) => { specSummaryCount(summary, spec.status.toLowerCase()); }); logger.info(`Total tests: ${summary.total}, passed: ${summary.passed}, failed: ${summary.failed}, skipped: ${summary.skipped}`); - logger.info(`Done in ${time/1000} seconds using ${machines} machines\n`); + logger.info(`Done in ${data.duration/1000} seconds using ${machines} machines\n`); - resolve(data); + resolve(data.exitCode); }) }; diff --git a/bin/helpers/sync/syncSpecsLogs.js b/bin/helpers/sync/syncSpecsLogs.js index 39c6db3a..31877c8b 100644 --- a/bin/helpers/sync/syncSpecsLogs.js +++ b/bin/helpers/sync/syncSpecsLogs.js @@ -126,7 +126,7 @@ let showSpecsStatus = (data) => { let printInitialLog = () => { startTime = Date.now(); - logger.info(Constants.syncCLI.LOGS.INIT_LOG) + logger.info(`\n${Constants.syncCLI.LOGS.INIT_LOG}`) n = Constants.syncCLI.INITIAL_DELAY_MULTIPLIER } diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index 894df63e..41dea952 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -12,11 +12,13 @@ const Config = require("./config"), exports.pollBuildStatus = (bsConfig, buildDetails) => { return new Promise((resolve, reject) => { - logBuildDetails(bsConfig, buildDetails); + //logBuildDetails(bsConfig, buildDetails); syncSpecsLogs.printSpecsStatus(bsConfig, buildDetails).then((data) => { - return specsSummary.printSpecsRunSummary(data.specs, data.duration, buildDetails.machines); - }).then((data) => { return specDetails.failedSpecsDetails(data); + // return specsSummary.printSpecsRunSummary(data.specs, data.duration, buildDetails.machines); + }).then((data) => { + return specsSummary.printSpecsRunSummary(data, buildDetails.machines); + // return specDetails.failedSpecsDetails(data); }).then((successExitCode) => { resolve(successExitCode); // exit code 0 }).catch((nonZeroExitCode) => { diff --git a/bin/helpers/zipUpload.js b/bin/helpers/zipUpload.js index 4720ae52..654dd481 100644 --- a/bin/helpers/zipUpload.js +++ b/bin/helpers/zipUpload.js @@ -4,10 +4,12 @@ const config = require("./config"), fs = require("fs"), logger = require("./logger").winstonLogger, Constants = require("./constants"), - utils = require("./utils"); + utils = require("./utils"), + fileHelpers = require("./fileHelpers"); const uploadCypressZip = (bsConfig, filePath) => { return new Promise(function (resolve, reject) { + logger.info(Constants.userMessages.UPLOADING_TESTS); let options = { url: config.uploadUrl, auth: { @@ -41,7 +43,8 @@ const uploadCypressZip = (bsConfig, filePath) => { reject(Constants.userMessages.ZIP_UPLOADER_NOT_REACHABLE); } } else { - logger.info(`Zip uploaded with url: ${responseData.zip_url}`); + logger.info(`Uploaded tests successfully (${responseData.zip_url})`); + fileHelpers.deleteZip(); resolve(responseData); } } From 7227e2c1288dacff03b49433f2c420210a523023 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Fri, 30 Oct 2020 18:01:43 +0530 Subject: [PATCH 27/34] Merge conflict resolve --- test/unit/bin/helpers/utils.js | 43 +++++++++++----------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index 1222ffde..c7a6ed91 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -181,37 +181,22 @@ describe('utils', () => { }); }); - describe('validateBstackJson', () => { - it('should reject with SyntaxError for empty file', () => { - let bsConfigPath = path.join( - process.cwd(), - 'test', - 'test_files', - 'dummy_bstack.json' - ); - expect(utils.validateBstackJson(bsConfigPath)).to.be.rejectedWith( - SyntaxError - ); - }); - it('should resolve with data for valid json', () => { - let bsConfigPath = path.join( - process.cwd(), - 'test', - 'test_files', - 'dummy_bstack_2.json' - ); + describe("validateBstackJson", () => { + it("should reject with SyntaxError for empty file", () => { + let bsConfigPath = path.join(process.cwd(), 'test', 'test_files', 'dummy_bstack.json'); + return utils.validateBstackJson(bsConfigPath).catch((error)=>{ + sinon.match(error, "Invalid browserstack.json file") + }); + }); + it("should resolve with data for valid json", () => { + let bsConfigPath = path.join(process.cwd(), 'test', 'test_files', 'dummy_bstack_2.json'); expect(utils.validateBstackJson(bsConfigPath)).to.be.eventually.eql({}); }); - it('should reject with SyntaxError for invalid json file', () => { - let bsConfigPath = path.join( - process.cwd(), - 'test', - 'test_files', - 'dummy_bstack_3.json' - ); - expect(utils.validateBstackJson(bsConfigPath)).to.be.rejectedWith( - SyntaxError - ); + it("should reject with SyntaxError for invalid json file", () => { + let bsConfigPath = path.join(process.cwd(), 'test', 'test_files', 'dummy_bstack_3.json'); + return utils.validateBstackJson(bsConfigPath).catch((error) => { + sinon.match(error, "Invalid browserstack.json file") + }); }); }); From d74d8fd762a2b2dfd04c126bb6510ca257506851 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Fri, 30 Oct 2020 18:16:02 +0530 Subject: [PATCH 28/34] Removing logBuilddetails --- bin/commands/runs.js | 1 - bin/helpers/syncRunner.js | 27 ++------------- test/unit/bin/helpers/syncRunner.js | 52 ----------------------------- 3 files changed, 2 insertions(+), 78 deletions(-) delete mode 100644 test/unit/bin/helpers/syncRunner.js diff --git a/bin/commands/runs.js b/bin/commands/runs.js index c3242b80..545facc7 100644 --- a/bin/commands/runs.js +++ b/bin/commands/runs.js @@ -48,7 +48,6 @@ module.exports = function run(args) { // Validate browserstack.json values and parallels specified via arguments return capabilityHelper.validate(bsConfig, args).then(function (validated) { - // logger.info(validated); // accept the number of parallels utils.setParallels(bsConfig, args); diff --git a/bin/helpers/syncRunner.js b/bin/helpers/syncRunner.js index 41dea952..b98eeca2 100644 --- a/bin/helpers/syncRunner.js +++ b/bin/helpers/syncRunner.js @@ -1,24 +1,14 @@ 'use strict'; -const Config = require("./config"), - logger = require("./logger").syncCliLogger, - Constants = require("./constants"), - utils = require("./utils"), - request = require('request'), - syncSpecsLogs = require('./sync/syncSpecsLogs'), +const syncSpecsLogs = require('./sync/syncSpecsLogs'), specDetails = require('./sync/failedSpecsDetails'), - specsSummary = require('./sync/specsSummary'), - { table, getBorderCharacters } = require('table'), - chalk = require('chalk'); + specsSummary = require('./sync/specsSummary'); exports.pollBuildStatus = (bsConfig, buildDetails) => { return new Promise((resolve, reject) => { - //logBuildDetails(bsConfig, buildDetails); syncSpecsLogs.printSpecsStatus(bsConfig, buildDetails).then((data) => { return specDetails.failedSpecsDetails(data); - // return specsSummary.printSpecsRunSummary(data.specs, data.duration, buildDetails.machines); }).then((data) => { return specsSummary.printSpecsRunSummary(data, buildDetails.machines); - // return specDetails.failedSpecsDetails(data); }).then((successExitCode) => { resolve(successExitCode); // exit code 0 }).catch((nonZeroExitCode) => { @@ -26,16 +16,3 @@ exports.pollBuildStatus = (bsConfig, buildDetails) => { }) }); }; - -let logBuildDetails = (bsConfig, buildDetails) => { - let parallels_enabled = false; - if (bsConfig.run_settings.parallels) { - parallels_enabled = true; - } - let parallelMessage = `Run in parallel: ${parallels_enabled ? 'enabled' : 'disabled'}`; - if (parallels_enabled) parallelMessage = parallelMessage + ` (attempting to run on ${buildDetails.machines} machines)`; - - logger.info(`Browser Combinations: ${buildDetails.combinations}`); - logger.info(parallelMessage); - logger.info(`BrowserStack Dashboard: ${buildDetails.dashboard_url}`); -}; diff --git a/test/unit/bin/helpers/syncRunner.js b/test/unit/bin/helpers/syncRunner.js deleted file mode 100644 index 777479dc..00000000 --- a/test/unit/bin/helpers/syncRunner.js +++ /dev/null @@ -1,52 +0,0 @@ -const chai = require("chai"), - sinon = require("sinon"), - chaiAsPromised = require("chai-as-promised"), - rewire = require("rewire"); - -const logger = require("../../../../bin/helpers/logger").syncCliLogger, - testObjects = require("../../support/fixtures/testObjects"); - -const syncRunner = rewire("../../../../bin/helpers/syncRunner"); - -chai.use(chaiAsPromised); - -logBuildDetails = syncRunner.__get__("logBuildDetails"); - -describe("syncRunner", () => { - var sandbox, loggerStub; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - loggerStub = sinon.stub(logger, "log").callsFake(() => {}); - }); - - afterEach(() => { - sandbox.restore(); - sinon.restore(); - }); - - describe("logBuildDetails", () => { - let buildDetails = { - machines: 10, - combinations: 5, - dashboard_url: "random_url", - }; - - it("parallels defined", () => { - logBuildDetails(testObjects.sampleBsConfigWithParallels, buildDetails); - - sinon.assert.calledThrice(loggerStub); - sinon.assert.calledWithMatch(loggerStub, 'info', `Browser Combinations: ${buildDetails.combinations}`); - sinon.assert.calledWithMatch(loggerStub, 'info', `Run in parallel: enabled (attempting to run on ${buildDetails.machines} machines)`); - sinon.assert.calledWithMatch(loggerStub, 'info', `BrowserStack Dashboard: ${buildDetails.dashboard_url}`); - }); - it("parallels not defined", () => { - logBuildDetails(testObjects.sampleBsConfig, buildDetails); - - sinon.assert.calledThrice(loggerStub); - sinon.assert.calledWithMatch(loggerStub, 'info', `Browser Combinations: ${buildDetails.combinations}`); - sinon.assert.calledWithMatch(loggerStub, 'info', `Run in parallel: disabled`); - sinon.assert.calledWithMatch(loggerStub, 'info', `BrowserStack Dashboard: ${buildDetails.dashboard_url}`); - }); - }); -}); From 0bcdf1caf541c0bd353095086b6386bc6c4fd6b8 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Fri, 30 Oct 2020 18:18:17 +0530 Subject: [PATCH 29/34] Browser List Log Format change --- bin/helpers/capabilityHelper.js | 5 ++++- bin/helpers/fileHelpers.js | 37 +++++++++++++++---------------- bin/helpers/sync/syncSpecsLogs.js | 19 ++++++++-------- bin/helpers/utils.js | 4 ++++ 4 files changed, 36 insertions(+), 29 deletions(-) diff --git a/bin/helpers/capabilityHelper.js b/bin/helpers/capabilityHelper.js index 5915429f..c99851da 100644 --- a/bin/helpers/capabilityHelper.js +++ b/bin/helpers/capabilityHelper.js @@ -20,17 +20,20 @@ const caps = (bsConfig, zip) => { // Browser list let osBrowserArray = []; + let browsersList = []; if (bsConfig.browsers) { bsConfig.browsers.forEach((element) => { osBrowser = element.os + "-" + element.browser; + osAndBrowser = element.os + " / " + Utils.capitalizeFirstLetter(element.browser); element.versions.forEach((version) => { osBrowserArray.push(osBrowser + version); + browsersList.push(`${osAndBrowser} (${version})`); }); }); } obj.devices = osBrowserArray; if (obj.devices.length == 0) reject(Constants.validationMessages.EMPTY_BROWSER_LIST); - logger.info(`Browsers list: ${osBrowserArray.toString()}`); + logger.info(`Browsers list: ${browsersList.join(", ")}`); // Test suite if (zip.zip_url && zip.zip_url.split("://")[1].length !== 0) { diff --git a/bin/helpers/fileHelpers.js b/bin/helpers/fileHelpers.js index cd01f3af..dd8f0383 100644 --- a/bin/helpers/fileHelpers.js +++ b/bin/helpers/fileHelpers.js @@ -2,17 +2,17 @@ const fs = require('fs-extra'), path = require('path'); -const logger = require("./logger").winstonLogger, - Constants = require("../helpers/constants"), - config = require("../helpers/config"); +const logger = require('./logger').winstonLogger, + Constants = require('../helpers/constants'), + config = require('../helpers/config'); -exports.write = function(f, message, args, cb) { +exports.write = function (f, message, args, cb) { message = message || 'Creating'; - fs.writeFile(f.path, f.file, function() { - logger.info(message + " file: " + f.path); - cb && cb(args) + fs.writeFile(f.path, f.file, function () { + logger.info(message + ' file: ' + f.path); + cb && cb(args); }); -} +}; exports.fileExists = function (filePath, cb) { fs.access(filePath, fs.F_OK, (err) => { @@ -25,16 +25,15 @@ exports.fileExists = function (filePath, cb) { }; exports.deleteZip = () => { - return fs.unlink(config.fileName, function (err) { - if (err) { - logger.info(Constants.userMessages.ZIP_DELETE_FAILED); - return 1; - } else { - logger.info(Constants.userMessages.ZIP_DELETED); - return 0; - } - }); -} + try { + fs.unlinkSync(config.fileName); + logger.info(Constants.userMessages.ZIP_DELETED); + return 0; + } catch (err) { + logger.info(Constants.userMessages.ZIP_DELETE_FAILED); + return 1; + } +}; exports.dirExists = function (filePath, cb) { let exists = false; @@ -42,4 +41,4 @@ exports.dirExists = function (filePath, cb) { exists = true; } cb && cb(exists); -} +}; diff --git a/bin/helpers/sync/syncSpecsLogs.js b/bin/helpers/sync/syncSpecsLogs.js index 31877c8b..1c79bea4 100644 --- a/bin/helpers/sync/syncSpecsLogs.js +++ b/bin/helpers/sync/syncSpecsLogs.js @@ -32,25 +32,25 @@ let getOptions = (auth, build_id) => { let getTableConfig = () => { return { border: getBorderConfig(), - singleLine: true, columns: { - 0: { alignment: 'right' } + 1: {alignment: 'center', width: 1}, + 2: {alignment: 'left', width: 30} }, columnDefault: { - width: 50 + width: 25, }, - columnCount: 2 + columnCount: 3, }; } let getBorderConfig = () => { return { - topBody: `-`, + topBody: ``, topJoin: ``, topLeft: ``, topRight: ``, - bottomBody: `-`, + bottomBody: ``, bottomJoin: ``, bottomLeft: ``, bottomRight: ``, @@ -81,7 +81,6 @@ let printSpecsStatus = (bsConfig, buildDetails) => { }, function(err, result) { // when loop ends specSummary.duration = endTime - startTime - logger.info(); resolve(specSummary) } ); @@ -105,6 +104,7 @@ let whileProcess = (whilstCallback) => { whileLoop = false; endTime = Date.now(); showSpecsStatus(body); + logger.info("\n--------------------------------------------------------------------------------") return whilstCallback(null, body); default: whileLoop = false; @@ -127,6 +127,7 @@ let showSpecsStatus = (data) => { let printInitialLog = () => { startTime = Date.now(); logger.info(`\n${Constants.syncCLI.LOGS.INIT_LOG}`) + logger.info("--------------------------------------------------------------------------------") n = Constants.syncCLI.INITIAL_DELAY_MULTIPLIER } @@ -138,7 +139,7 @@ let printSpecData = (data) => { } let writeToTable = (combination, specName, status) => { - stream.write([combination + ":", `${specName} ${status}`]); + stream.write([combination , ":", `${specName} ${status}`]); } let addSpecToSummary = (specName, status, combination, session_id) => { @@ -152,7 +153,7 @@ let addSpecToSummary = (specName, status, combination, session_id) => { } let getCombinationName = (spec) => { - return `${spec["os"]} ${spec["osVersion"]} / ${spec["browser"]} ${spec["browserVersion"]}` + return `${utils.capitalizeFirstLetter(spec['browser'])} ${spec['browserVersion']} (${spec['os']} ${spec['osVersion']})`; } let getStatus = (status) => { diff --git a/bin/helpers/utils.js b/bin/helpers/utils.js index 66df447c..6ca51502 100644 --- a/bin/helpers/utils.js +++ b/bin/helpers/utils.js @@ -314,3 +314,7 @@ exports.setLocalIdentifier = (bsConfig) => { ); } }; + +exports.capitalizeFirstLetter = (stringToCapitalize) => { + return stringToCapitalize && (stringToCapitalize[0].toUpperCase() + stringToCapitalize.slice(1)); +}; From 9dc85c4c6e0f3303bbc8ecd117de5cbd20d88f22 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Fri, 30 Oct 2020 20:51:48 +0530 Subject: [PATCH 30/34] Unit test cases fix for cli log changes --- test/unit/bin/commands/runs.js | 6 --- test/unit/bin/helpers/fileHelpers.js | 27 +++++-------- .../bin/helpers/sync/failedSpecDetails.js | 38 ++++++++++--------- test/unit/bin/helpers/sync/specSummary.js | 17 ++++++--- test/unit/bin/helpers/sync/syncSpecsLogs.js | 28 ++++++-------- test/unit/bin/helpers/utils.js | 12 ++++++ test/unit/bin/helpers/zipUpload.js | 11 ++++-- 7 files changed, 72 insertions(+), 67 deletions(-) diff --git a/test/unit/bin/commands/runs.js b/test/unit/bin/commands/runs.js index 608d8908..d5ab37fd 100644 --- a/test/unit/bin/commands/runs.js +++ b/test/unit/bin/commands/runs.js @@ -481,9 +481,6 @@ describe("runs", () => { messageType, errorCode ); - }) - .finally(function () { - sinon.assert.calledOnce(deleteZipStub); }); }); }); @@ -607,9 +604,6 @@ describe("runs", () => { messageType, errorCode ); - }) - .finally(function () { - sinon.assert.calledOnce(deleteZipStub); }); }); }); diff --git a/test/unit/bin/helpers/fileHelpers.js b/test/unit/bin/helpers/fileHelpers.js index e2ba0b64..17e5493c 100644 --- a/test/unit/bin/helpers/fileHelpers.js +++ b/test/unit/bin/helpers/fileHelpers.js @@ -1,8 +1,10 @@ -const chai = require("chai"), - sinon = require("sinon"), +const chai = require('chai'), + sinon = require('sinon'), expect = chai.expect, assert = chai.assert, - chaiAsPromised = require("chai-as-promised"); + chaiAsPromised = require('chai-as-promised'), + fs = require('fs-extra'), + fileHelpers = require('../../../../bin/helpers/fileHelpers'); const logger = require("../../../../bin/helpers/logger").winstonLogger, proxyquire = require("proxyquire").noCallThru(); @@ -82,30 +84,19 @@ describe("fileHelpers", () => { }); it("deleteZip returns 0 on success", () => { - let unlinkStub = sandbox.stub().yields(); - - const fileHelpers = proxyquire("../../../../bin/helpers/fileHelpers", { - "fs-extra": { - unlink: unlinkStub, - }, - }); + let unlinkStub = sinon.stub(fs, 'unlinkSync').returns(true); let result = fileHelpers.deleteZip(); sinon.assert.calledOnce(unlinkStub); assert.equal(result, 0); + fs.unlinkSync.restore(); }); it("deleteZip returns 1 on failure", () => { - let unlinkStub = sandbox.stub().yields(new Error("random-error")); - - const fileHelpers = proxyquire("../../../../bin/helpers/fileHelpers", { - "fs-extra": { - unlink: unlinkStub, - }, - }); - + let unlinkStub = sinon.stub(fs, 'unlinkSync').yields(new Error("random-error")); let result = fileHelpers.deleteZip(); sinon.assert.calledOnce(unlinkStub); assert.equal(result, 1); + fs.unlinkSync.restore(); }); }); diff --git a/test/unit/bin/helpers/sync/failedSpecDetails.js b/test/unit/bin/helpers/sync/failedSpecDetails.js index 595c8301..b4e87dda 100644 --- a/test/unit/bin/helpers/sync/failedSpecDetails.js +++ b/test/unit/bin/helpers/sync/failedSpecDetails.js @@ -21,39 +21,41 @@ describe("failedSpecsDetails", () => { }); context("data is empty", () => { - let data = []; + let data = { + specs: [], + exitCode: 0 + }; + it('returns 0 exit code', () => { - return specDetails.failedSpecsDetails(data).then((status) => { - expect(status).to.equal(0); + return specDetails.failedSpecsDetails(data).then((result) => { + expect(result).to.equal(data); }); }); }); context("data does not have failed specs", () => { - let data = [ - {specName: 'spec2.name.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'} - ]; + let data = { + specs: [{specName: 'spec2.name.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}], + exitCode: 0 + }; it("returns 0 exit code", () => { - return specDetails.failedSpecsDetails(data).then((status) => { - expect(status).to.equal(0); + return specDetails.failedSpecsDetails(data).then((result) => { + expect(result).to.equal(data); }); }); }); context("data has failed specs", () => { - let data = [ - {specName: 'spec2.name.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, - {specName: 'spec2.name.js', status: 'Passed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'} - ]; + let data = { + specs: [ {specName: 'spec2.name.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, + {specName: 'spec2.name.js', status: 'Passed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}], + exitCode: 1 + }; it("returns 1 exit code", () => { - return specDetails.failedSpecsDetails(data) - .then((status) => { - chai.assert.equal(status, 1); - expect(status).to.equal(1); - }).catch((status) => { - expect(status).to.equal(1); + return specDetails.failedSpecsDetails(data).then((result) => { + expect(result).to.equal(data); }); }); }); diff --git a/test/unit/bin/helpers/sync/specSummary.js b/test/unit/bin/helpers/sync/specSummary.js index 66c4b6c4..b1889dee 100644 --- a/test/unit/bin/helpers/sync/specSummary.js +++ b/test/unit/bin/helpers/sync/specSummary.js @@ -10,10 +10,10 @@ var specSummary = require('../../../../../bin/helpers/sync/specsSummary'); describe("printSpecsRunSummary", () => { context("data is empty", () => { - let data = [], time = 6000, machines = 2; + let data = { specs: [], duration: 6000, exitCode: 0}, machines = 2; it('returns passed specs data', () => { - return specSummary.printSpecsRunSummary(data, time, machines).then((specsData) => { - expect(data).to.equal(specsData); + return specSummary.printSpecsRunSummary(data, machines).then((specsData) => { + expect(data.exitCode).to.equal(specsData); }); }); }); @@ -21,17 +21,22 @@ describe("printSpecsRunSummary", () => { context("with data", () => { let time = 6000, machines = 2, - data = [ + specs = [ {specName: 'spec2.name.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, {specName: 'spec2.name.js', status: 'Skipped', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, {specName: 'spec2.name.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, {specName: 'spec2.name.js', status: 'Passed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'} - ]; + ], + data = { + specs: specs, + duration: time, + exitCode: 0 + }; it('returns passed specs data', () => { var loggerInfoSpy = sinon.spy(logger, 'info'); - specSummary.printSpecsRunSummary(data, time, machines); + specSummary.printSpecsRunSummary(data, machines); sinon.assert.calledWith(loggerInfoSpy, 'Total tests: 4, passed: 1, failed: 2, skipped: 1'); sinon.assert.calledWith(loggerInfoSpy, `Done in ${time / 1000} seconds using ${machines} machines\n`); diff --git a/test/unit/bin/helpers/sync/syncSpecsLogs.js b/test/unit/bin/helpers/sync/syncSpecsLogs.js index daae2169..bdcc5b48 100644 --- a/test/unit/bin/helpers/sync/syncSpecsLogs.js +++ b/test/unit/bin/helpers/sync/syncSpecsLogs.js @@ -29,13 +29,13 @@ describe("syncSpecsLogs", () => { context("getCombinationName", () => { const get_path = syncSpecsLogs.__get__("getCombinationName");; let spec = { - "os": "Windows 10", + "os": "Windows", "osVersion": "10", "browser": "chrome", "browserVersion": "86" } it("returns combination name", () => { - let expectedCombination = `${spec["os"]} ${spec["osVersion"]} / ${spec["browser"]} ${spec["browserVersion"]}`; + let expectedCombination = `Chrome 86 (Windows 10)`; expect(get_path(spec)).to.equal(expectedCombination); }); }); @@ -61,15 +61,12 @@ describe("syncSpecsLogs", () => { const printInitialLog = syncSpecsLogs.__get__("printInitialLog"); it("should print inital logs for specs in sync", () => { - var loggerInfoStub = sinon.stub(logger, 'info'); printInitialLog() expect(syncSpecsLogs.__get__("n")).to.equal(Constants.syncCLI.INITIAL_DELAY_MULTIPLIER); expect(syncSpecsLogs.__get__("startTime")).to.not.be.null; - sinon.assert.calledWith(loggerInfoStub, Constants.syncCLI.LOGS.INIT_LOG); - loggerInfoStub.restore(); }); }); @@ -95,10 +92,12 @@ describe("syncSpecsLogs", () => { syncSpecsLogs.__set__('getBorderConfig', getBorderConfigStub); let options = getTableConfig(); - expect(options.singleLine).to.be.true; - expect(options.columnDefault.width).to.equal(50); - expect(options.columns[0].alignment).to.equal('right'); - expect(options.columnCount).to.equal(2); + expect(options.columnDefault.width).to.equal(25); + expect(options.columns[1].alignment).to.equal('center'); + expect(options.columns[2].alignment).to.equal('left'); + expect(options.columns[1].width).to.equal(1); + expect(options.columns[2].width).to.equal(30); + expect(options.columnCount).to.equal(3); expect(getBorderConfigStub.calledOnce).to.be.true; }); }); @@ -108,8 +107,8 @@ describe("syncSpecsLogs", () => { it('should return proper border option for spec table', () => { let options = getBorderConfig(); - expect(options.topBody).to.equal("-"); - expect(options.bottomBody).to.equal("-"); + expect(options.topBody).to.equal(""); + expect(options.bottomBody).to.equal(""); }); }); @@ -122,7 +121,7 @@ describe("syncSpecsLogs", () => { syncSpecsLogs.__set__('stream', stream); let combination = "Windows 10", path = "path", status = "passed"; writeToTable(combination, path, status); - sinon.assert.calledOnceWithExactly(stream.write, [combination + ":", `${path} ${status}`]); + sinon.assert.calledOnceWithExactly(stream.write, [combination , ":", `${path} ${status}`]); }); }); @@ -200,7 +199,7 @@ describe("syncSpecsLogs", () => { context("printSpecsStatus", () => { const printSpecsStatus = syncSpecsLogs.__get__("printSpecsStatus"); let startTime = Date.now(), endTime = Date.now() + 10, counter = 0; - let specSummary = { specs: [] }, getOptions, getTableConfig, tableStream, whileProcess, loggerInfoStub; + let specSummary = { specs: [] }, getOptions, getTableConfig, tableStream, whileProcess; beforeEach(() => { counter = 0; @@ -214,8 +213,6 @@ describe("syncSpecsLogs", () => { tableStream = sandbox.stub(); syncSpecsLogs.__set__('tableStream', tableStream); - loggerInfoStub = sandbox.stub(logger, 'info'); - whileProcess = sandbox.stub().callsFake(function (whilstCallback) { counter++ if(counter >= 3) { @@ -237,7 +234,6 @@ describe("syncSpecsLogs", () => { expect(getOptions.calledOnce).to.be.true; expect(getTableConfig.calledOnce).to.be.true; expect(tableStream.calledOnce).to.be.true; - expect(loggerInfoStub.calledOnce).to.be.true; expect(whileProcess.calledOnce).to.be.false; expect(specSummary.specs).deep.to.equal([]) expect(specSummary.duration).to.eql(endTime - startTime); diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index c7a6ed91..37e61187 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -931,4 +931,16 @@ describe('utils', () => { expect(utils.isUndefined(bsConfig.auth)).to.be.true; }); }); + + describe('capitalizeFirstLetter', () => { + + it('should capitalize First Letter ', () => { + expect(utils.capitalizeFirstLetter("chrome")).to.eq("Chrome"); + }); + + it('should return null if value passed is null', () => { + expect(utils.capitalizeFirstLetter(null)).to.eq(null); + }); + + }); }); diff --git a/test/unit/bin/helpers/zipUpload.js b/test/unit/bin/helpers/zipUpload.js index c50cf940..a2b3e5b5 100644 --- a/test/unit/bin/helpers/zipUpload.js +++ b/test/unit/bin/helpers/zipUpload.js @@ -22,6 +22,7 @@ describe("zipUpload", () => { sandbox = sinon.createSandbox(); getUserAgentStub = sandbox.stub().returns("random user-agent"); createReadStreamStub = sandbox.stub(fs, "createReadStream"); + deleteZipStub = sandbox.stub().returns(true); }); afterEach(() => { @@ -119,11 +120,14 @@ describe("zipUpload", () => { .stub(request, "post") .yields(null, { statusCode: 200 }, JSON.stringify({ zip_url: zip_url })); - const zipUploader = proxyquire("../../../../bin/helpers/zipUpload", { - "./utils": { + const zipUploader = proxyquire('../../../../bin/helpers/zipUpload', { + './utils': { getUserAgent: getUserAgentStub, }, - request: { post: requestStub }, + request: {post: requestStub}, + './fileHelpers': { + deleteZip: deleteZipStub, + }, }); return zipUploader @@ -132,6 +136,7 @@ describe("zipUpload", () => { sinon.assert.calledOnce(requestStub); sinon.assert.calledOnce(getUserAgentStub); sinon.assert.calledOnce(createReadStreamStub); + sinon.assert.calledOnce(deleteZipStub); chai.assert.equal(data.zip_url, zip_url); }) .catch((error) => { From 390a491f8843b5e73d6786ec737c54380a623d65 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Wed, 4 Nov 2020 14:02:45 +0530 Subject: [PATCH 31/34] Modifying table config to set width ot 50 --- bin/helpers/sync/syncSpecsLogs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/helpers/sync/syncSpecsLogs.js b/bin/helpers/sync/syncSpecsLogs.js index 1c79bea4..0e5e488a 100644 --- a/bin/helpers/sync/syncSpecsLogs.js +++ b/bin/helpers/sync/syncSpecsLogs.js @@ -34,7 +34,7 @@ let getTableConfig = () => { border: getBorderConfig(), columns: { 1: {alignment: 'center', width: 1}, - 2: {alignment: 'left', width: 30} + 2: {alignment: 'left', width: 50} }, columnDefault: { width: 25, From 370db2dc6d5dc81053c012788c01d0980bf24c57 Mon Sep 17 00:00:00 2001 From: Sagar Ganiga Date: Tue, 10 Nov 2020 18:06:29 +0530 Subject: [PATCH 32/34] Changes for retrying in case of network issue --- bin/commands/runs.js | 7 +-- bin/helpers/config.js | 2 + bin/helpers/sync/failedSpecsDetails.js | 5 +- bin/helpers/sync/syncSpecsLogs.js | 24 +++++++-- bin/helpers/utils.js | 22 +++++++- test/unit/bin/helpers/sync/syncSpecsLogs.js | 57 ++++++++++++++++++++- 6 files changed, 103 insertions(+), 14 deletions(-) diff --git a/bin/commands/runs.js b/bin/commands/runs.js index 545facc7..b261ba0f 100644 --- a/bin/commands/runs.js +++ b/bin/commands/runs.js @@ -8,8 +8,7 @@ const archiver = require("../helpers/archiver"), Constants = require("../helpers/constants"), utils = require("../helpers/utils"), fileHelpers = require("../helpers/fileHelpers"), - syncRunner = require("../helpers/syncRunner"), - syncCliLogger = require("../helpers/logger").syncCliLogger; + syncRunner = require("../helpers/syncRunner"); module.exports = function run(args) { let bsConfigPath = utils.getConfigPath(args.cf); @@ -73,9 +72,7 @@ module.exports = function run(args) { if (args.sync) { syncRunner.pollBuildStatus(bsConfig, data).then((exitCode) => { utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null); - syncCliLogger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); - syncCliLogger.info(data.dashboard_url); - process.exit(exitCode); + utils.handleSyncExit(exitCode, data.dashboard_url) }); } diff --git a/bin/helpers/config.js b/bin/helpers/config.js index da88c3ab..3f6daeed 100644 --- a/bin/helpers/config.js +++ b/bin/helpers/config.js @@ -16,5 +16,7 @@ config.cypress_v1 = `${config.rails_host}/automate/cypress/v1`; config.buildUrl = `${config.cypress_v1}/builds/`; config.buildStopUrl = `${config.cypress_v1}/builds/stop/`; config.fileName = "tests.zip"; +config.retries = 5; +config.networkErrorExitCode = 2; module.exports = config; diff --git a/bin/helpers/sync/failedSpecsDetails.js b/bin/helpers/sync/failedSpecsDetails.js index b44b438f..9d5e449f 100644 --- a/bin/helpers/sync/failedSpecsDetails.js +++ b/bin/helpers/sync/failedSpecsDetails.js @@ -19,7 +19,8 @@ const tablePrinter = require('table'), // { table, getBorderCharacters } // let failedSpecsDetails = (data) => { return new Promise((resolve, reject) => { - data.exitCode = 0; + if (!data.exitCode) data.exitCode = 0; + if (data.specs.length === 0) resolve(data); // return if no failed/skipped tests. let failedSpecs = false; @@ -72,7 +73,7 @@ let failedSpecsDetails = (data) => { logger.info('\nFailed / skipped test report:'); logger.info(result); - if (failedSpecs) data.exitCode = 1 ; // specs failed, send exitCode as 1 + if (failedSpecs && data.exitCode !== 2) data.exitCode = 1 ; // specs failed, send exitCode as 1 resolve(data); // No Specs failed, maybe skipped, but not failed, send exitCode as 0 }); } diff --git a/bin/helpers/sync/syncSpecsLogs.js b/bin/helpers/sync/syncSpecsLogs.js index 0e5e488a..5dfbe248 100644 --- a/bin/helpers/sync/syncSpecsLogs.js +++ b/bin/helpers/sync/syncSpecsLogs.js @@ -8,7 +8,7 @@ const request = require("request"), tableStream = require('table').createStream, chalk = require('chalk'); -let whileLoop = true, whileTries = 40, options, timeout = 3000, n = 10, tableConfig, stream, startTime, endTime; +let whileLoop = true, whileTries = config.retries, options, timeout = 3000, n = 10, tableConfig, stream, endTime, startTime = Date.now(); let specSummary = { "specs": [], "duration": null @@ -22,6 +22,7 @@ let getOptions = (auth, build_id) => { user: auth.username, password: auth.access_key }, + timeout: 10000, headers: { "Content-Type": "application/json", "User-Agent": utils.getUserAgent() @@ -80,6 +81,7 @@ let printSpecsStatus = (bsConfig, buildDetails) => { whileProcess(callback) }, function(err, result) { // when loop ends + logger.info("\n--------------------------------------------------------------------------------") specSummary.duration = endTime - startTime resolve(specSummary) } @@ -90,8 +92,24 @@ let printSpecsStatus = (bsConfig, buildDetails) => { let whileProcess = (whilstCallback) => { request.post(options, function(error, response, body) { if (error) { - return whilstCallback(error); + if (error.code === "ETIMEDOUT") { + whileTries -= 1; + if (whileTries === 0) { + whileLoop = false; + endTime = Date.now(); + specSummary.exitCode = config.networkErrorExitCode; + return whilstCallback({ status: 504, message: "Tries limit reached" }); //Gateway Timeout + } else { + n = 2 + return setTimeout(whilstCallback, timeout * n, null); + } + } else { + return whilstCallback(error); + } } + + whileTries = config.retries; // reset to default after every successful request + switch (response.statusCode) { case 202: // get data here and print it n = 2 @@ -104,7 +122,6 @@ let whileProcess = (whilstCallback) => { whileLoop = false; endTime = Date.now(); showSpecsStatus(body); - logger.info("\n--------------------------------------------------------------------------------") return whilstCallback(null, body); default: whileLoop = false; @@ -125,7 +142,6 @@ let showSpecsStatus = (data) => { } let printInitialLog = () => { - startTime = Date.now(); logger.info(`\n${Constants.syncCLI.LOGS.INIT_LOG}`) logger.info("--------------------------------------------------------------------------------") n = Constants.syncCLI.INITIAL_DELAY_MULTIPLIER diff --git a/bin/helpers/utils.js b/bin/helpers/utils.js index 6ca51502..07ffbb1c 100644 --- a/bin/helpers/utils.js +++ b/bin/helpers/utils.js @@ -5,7 +5,10 @@ const fs = require("fs"); const usageReporting = require("./usageReporting"), logger = require("./logger").winstonLogger, - Constants = require("./constants"); + Constants = require("./constants"), + chalk = require('chalk'), + syncCliLogger = require("../helpers/logger").syncCliLogger, + config = require("../helpers/config"); exports.validateBstackJson = (bsConfigPath) => { return new Promise(function (resolve, reject) { @@ -318,3 +321,20 @@ exports.setLocalIdentifier = (bsConfig) => { exports.capitalizeFirstLetter = (stringToCapitalize) => { return stringToCapitalize && (stringToCapitalize[0].toUpperCase() + stringToCapitalize.slice(1)); }; + +exports.handleSyncExit = (exitCode, dashboard_url) => { + if (exitCode === config.networkErrorExitCode) { + syncCliLogger.info(this.getNetworkErrorMessage(dashboard_url)); + } else { + syncCliLogger.info(Constants.userMessages.BUILD_REPORT_MESSAGE); + syncCliLogger.info(dashboard_url); + } + process.exit(exitCode); +} + +exports.getNetworkErrorMessage = (dashboard_url) => { + let message = `fatal: unable to access '${config.buildUrl}': Could not resolve host: ${config.rails_host}` + '\n' + + `Max retries exceeded trying to connect to the host (retries: ${config.retries})` + '\n' + + `Please check the build status at: ${dashboard_url}` + return chalk.red(message) +} diff --git a/test/unit/bin/helpers/sync/syncSpecsLogs.js b/test/unit/bin/helpers/sync/syncSpecsLogs.js index bdcc5b48..ca94ae1b 100644 --- a/test/unit/bin/helpers/sync/syncSpecsLogs.js +++ b/test/unit/bin/helpers/sync/syncSpecsLogs.js @@ -96,7 +96,7 @@ describe("syncSpecsLogs", () => { expect(options.columns[1].alignment).to.equal('center'); expect(options.columns[2].alignment).to.equal('left'); expect(options.columns[1].width).to.equal(1); - expect(options.columns[2].width).to.equal(30); + expect(options.columns[2].width).to.equal(50); expect(options.columnCount).to.equal(3); expect(getBorderConfigStub.calledOnce).to.be.true; }); @@ -259,7 +259,60 @@ describe("syncSpecsLogs", () => { context("whileProcess", () => { const whileProcess = syncSpecsLogs.__get__("whileProcess"); - it('Should break the loop if request has error', () => { + context('network issue', () => { + it('Should retry when error is because of network issue', () => { + let delayed_n = 2, timeout = 3000, n = 1; + let error = new Error("error"); + error.code = "ETIMEDOUT"; + + let requestStub = sandbox.stub(); + + let postStub = sandbox + .stub(request, "post") + .yields(error, { statusCode: 502 }, JSON.stringify({})); + + requestStub.post = postStub; + + let setTimeout = sandbox.stub(); + syncSpecsLogs.__set__('setTimeout', setTimeout); + syncSpecsLogs.__set__('n', n); + syncSpecsLogs.__set__('timeout', timeout); + syncSpecsLogs.__set__('request', requestStub); + syncSpecsLogs.__set__('whileTries', 5); + + let whilstCallback = sandbox.stub(); + whileProcess(whilstCallback); + + sinon.assert.calledWith(setTimeout, whilstCallback, timeout * delayed_n, null); + expect(syncSpecsLogs.__get__("whileTries")).to.equal(4); + }); + + it('Should give-up on retry when error is b because of network issue after 5 retries and set proper exit code', () => { + let error = new Error("error"), requestStub = sandbox.stub(); + error.code = "ETIMEDOUT"; + + let postStub = sandbox + .stub(request, "post") + .yields(error, { statusCode: 502 }, JSON.stringify({})); + + requestStub.post = postStub; + + syncSpecsLogs.__set__('request', requestStub); + syncSpecsLogs.__set__('whileTries', 1); + syncSpecsLogs.__set__('specSummary', {}); + syncSpecsLogs.__set__('whileLoop', true); + + let whilstCallback = sandbox.stub(); + whileProcess(whilstCallback); + + sinon.assert.calledWith(whilstCallback, { status: 504, message: "Tries limit reached" }); + expect(syncSpecsLogs.__get__("whileTries")).to.equal(0); + expect(syncSpecsLogs.__get__("whileLoop")).to.equal(false); + expect(syncSpecsLogs.__get__("specSummary.exitCode")).to.equal(2); + }); + }); + + it('Should break the loop if request has error other than network issue', () => { let error = new Error("error"); let requestStub = sandbox.stub(); let postStub = sandbox From b4af193da9a38763d97041eb9ac4d0fde4bc62b8 Mon Sep 17 00:00:00 2001 From: Sagar Ganiga Date: Wed, 11 Nov 2020 15:45:57 +0530 Subject: [PATCH 33/34] hanndle no internet case --- bin/helpers/constants.js | 7 +- bin/helpers/sync/failedSpecsDetails.js | 9 ++- bin/helpers/sync/syncSpecsLogs.js | 20 ++--- bin/helpers/utils.js | 6 +- .../bin/helpers/sync/failedSpecDetails.js | 14 ++++ test/unit/bin/helpers/sync/specSummary.js | 9 +++ test/unit/bin/helpers/sync/syncSpecsLogs.js | 80 +++++++------------ test/unit/bin/helpers/utils.js | 42 +++++++++- 8 files changed, 116 insertions(+), 71 deletions(-) diff --git a/bin/helpers/constants.js b/bin/helpers/constants.js index bed4e798..35d085ea 100644 --- a/bin/helpers/constants.js +++ b/bin/helpers/constants.js @@ -1,3 +1,5 @@ +let config = require("./config"); + const syncCLI = { FAILED_SPEC_DETAILS_COL_HEADER: ['Spec', 'Status', 'Browser', 'BrowserStack Session ID'], LOGS: { @@ -30,7 +32,10 @@ const userMessages = { UPLOADING_TESTS: "Uploading the tests to BrowserStack", LOCAL_TRUE: "you will now be able to test localhost / private URLs", LOCAL_FALSE: "you won't be able to test localhost / private URLs", - EXIT_SYNC_CLI_MESSAGE: "Exiting the CLI, but your build is still running. You can use the --sync option to keep getting test updates. You can also use the build-info command now." + EXIT_SYNC_CLI_MESSAGE: "Exiting the CLI, but your build is still running. You can use the --sync option to keep getting test updates. You can also use the build-info command now.", + 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:" }; const validationMessages = { diff --git a/bin/helpers/sync/failedSpecsDetails.js b/bin/helpers/sync/failedSpecsDetails.js index 9d5e449f..4710eea8 100644 --- a/bin/helpers/sync/failedSpecsDetails.js +++ b/bin/helpers/sync/failedSpecsDetails.js @@ -1,7 +1,8 @@ const tablePrinter = require('table'), // { table, getBorderCharacters } chalk = require('chalk'), Constants = require("../constants"), - logger = require("../logger").syncCliLogger; + logger = require("../logger").syncCliLogger, + config = require("../config"); /** * @@ -49,7 +50,7 @@ let failedSpecsDetails = (data) => { ]); }); - let config = { + let tableConfig = { border: tablePrinter.getBorderCharacters('ramac'), columns: { 0: { alignment: 'left' }, @@ -68,12 +69,12 @@ let failedSpecsDetails = (data) => { } } - let result = tablePrinter.table(specData, config); + let result = tablePrinter.table(specData, tableConfig); logger.info('\nFailed / skipped test report:'); logger.info(result); - if (failedSpecs && data.exitCode !== 2) data.exitCode = 1 ; // specs failed, send exitCode as 1 + if (failedSpecs && data.exitCode !== config.networkErrorExitCode) data.exitCode = 1 ; // specs failed, send exitCode as 1 resolve(data); // No Specs failed, maybe skipped, but not failed, send exitCode as 0 }); } diff --git a/bin/helpers/sync/syncSpecsLogs.js b/bin/helpers/sync/syncSpecsLogs.js index 5dfbe248..558ab941 100644 --- a/bin/helpers/sync/syncSpecsLogs.js +++ b/bin/helpers/sync/syncSpecsLogs.js @@ -92,19 +92,15 @@ let printSpecsStatus = (bsConfig, buildDetails) => { let whileProcess = (whilstCallback) => { request.post(options, function(error, response, body) { if (error) { - if (error.code === "ETIMEDOUT") { - whileTries -= 1; - if (whileTries === 0) { - whileLoop = false; - endTime = Date.now(); - specSummary.exitCode = config.networkErrorExitCode; - return whilstCallback({ status: 504, message: "Tries limit reached" }); //Gateway Timeout - } else { - n = 2 - return setTimeout(whilstCallback, timeout * n, null); - } + whileTries -= 1; + if (whileTries === 0) { + whileLoop = false; + endTime = Date.now(); + specSummary.exitCode = config.networkErrorExitCode; + return whilstCallback({ status: 504, message: "Tries limit reached" }); //Gateway Timeout } else { - return whilstCallback(error); + n = 2 + return setTimeout(whilstCallback, timeout * n, null); } } diff --git a/bin/helpers/utils.js b/bin/helpers/utils.js index 07ffbb1c..95ffbcae 100644 --- a/bin/helpers/utils.js +++ b/bin/helpers/utils.js @@ -333,8 +333,8 @@ exports.handleSyncExit = (exitCode, dashboard_url) => { } exports.getNetworkErrorMessage = (dashboard_url) => { - let message = `fatal: unable to access '${config.buildUrl}': Could not resolve host: ${config.rails_host}` + '\n' - + `Max retries exceeded trying to connect to the host (retries: ${config.retries})` + '\n' - + `Please check the build status at: ${dashboard_url}` + let message = Constants.userMessages.FATAL_NETWORK_ERROR + '\n' + + Constants.userMessages.RETRY_LIMIT_EXCEEDED + '\n' + + Constants.userMessages.CHECK_DASHBOARD_AT + dashboard_url return chalk.red(message) } diff --git a/test/unit/bin/helpers/sync/failedSpecDetails.js b/test/unit/bin/helpers/sync/failedSpecDetails.js index b4e87dda..a66f6363 100644 --- a/test/unit/bin/helpers/sync/failedSpecDetails.js +++ b/test/unit/bin/helpers/sync/failedSpecDetails.js @@ -59,4 +59,18 @@ describe("failedSpecsDetails", () => { }); }); }); + + context("failed because of network issue", () => { + let data = { + specs: [ {specName: 'spec2.name.js', status: 'Failed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}, + {specName: 'spec2.name.js', status: 'Passed', combination: 'Win 10 / Chrome 78', sessionId: '3d3rdf3r...'}], + exitCode: 2 + }; + + it("returns 2 exit code", () => { + return specDetails.failedSpecsDetails(data).then((result) => { + expect(result).to.equal(data); + }); + }); + }); }); diff --git a/test/unit/bin/helpers/sync/specSummary.js b/test/unit/bin/helpers/sync/specSummary.js index b1889dee..4bf7d7a9 100644 --- a/test/unit/bin/helpers/sync/specSummary.js +++ b/test/unit/bin/helpers/sync/specSummary.js @@ -18,6 +18,15 @@ describe("printSpecsRunSummary", () => { }); }); + context("request failure", () => { + let data = { specs: [], duration: 6000, exitCode: 2}, machines = 2; + it('returns passed specs data with proper exit code', () => { + return specSummary.printSpecsRunSummary(data, machines).then((specsData) => { + expect(data.exitCode).to.equal(specsData); + }); + }); + }); + context("with data", () => { let time = 6000, machines = 2, diff --git a/test/unit/bin/helpers/sync/syncSpecsLogs.js b/test/unit/bin/helpers/sync/syncSpecsLogs.js index ca94ae1b..b0c2828f 100644 --- a/test/unit/bin/helpers/sync/syncSpecsLogs.js +++ b/test/unit/bin/helpers/sync/syncSpecsLogs.js @@ -259,73 +259,53 @@ describe("syncSpecsLogs", () => { context("whileProcess", () => { const whileProcess = syncSpecsLogs.__get__("whileProcess"); - context('network issue', () => { - it('Should retry when error is because of network issue', () => { - let delayed_n = 2, timeout = 3000, n = 1; - let error = new Error("error"); - error.code = "ETIMEDOUT"; - - let requestStub = sandbox.stub(); - - let postStub = sandbox - .stub(request, "post") - .yields(error, { statusCode: 502 }, JSON.stringify({})); - - requestStub.post = postStub; - - let setTimeout = sandbox.stub(); - syncSpecsLogs.__set__('setTimeout', setTimeout); - syncSpecsLogs.__set__('n', n); - syncSpecsLogs.__set__('timeout', timeout); - syncSpecsLogs.__set__('request', requestStub); - syncSpecsLogs.__set__('whileTries', 5); - - let whilstCallback = sandbox.stub(); - whileProcess(whilstCallback); - - sinon.assert.calledWith(setTimeout, whilstCallback, timeout * delayed_n, null); - expect(syncSpecsLogs.__get__("whileTries")).to.equal(4); - }); + it('Should retry when request fails with error', () => { + let delayed_n = 2, timeout = 3000, n = 1; + let error = new Error("error"); - it('Should give-up on retry when error is b because of network issue after 5 retries and set proper exit code', () => { - let error = new Error("error"), requestStub = sandbox.stub(); - error.code = "ETIMEDOUT"; + let requestStub = sandbox.stub(); - let postStub = sandbox - .stub(request, "post") - .yields(error, { statusCode: 502 }, JSON.stringify({})); + let postStub = sandbox + .stub(request, "post") + .yields(error, { statusCode: 502 }, JSON.stringify({})); - requestStub.post = postStub; + requestStub.post = postStub; - syncSpecsLogs.__set__('request', requestStub); - syncSpecsLogs.__set__('whileTries', 1); - syncSpecsLogs.__set__('specSummary', {}); - syncSpecsLogs.__set__('whileLoop', true); + let setTimeout = sandbox.stub(); + syncSpecsLogs.__set__('setTimeout', setTimeout); + syncSpecsLogs.__set__('n', n); + syncSpecsLogs.__set__('timeout', timeout); + syncSpecsLogs.__set__('request', requestStub); + syncSpecsLogs.__set__('whileTries', 5); - let whilstCallback = sandbox.stub(); - whileProcess(whilstCallback); + let whilstCallback = sandbox.stub(); + whileProcess(whilstCallback); - sinon.assert.calledWith(whilstCallback, { status: 504, message: "Tries limit reached" }); - expect(syncSpecsLogs.__get__("whileTries")).to.equal(0); - expect(syncSpecsLogs.__get__("whileLoop")).to.equal(false); - expect(syncSpecsLogs.__get__("specSummary.exitCode")).to.equal(2); - }); + sinon.assert.calledWith(setTimeout, whilstCallback, timeout * delayed_n, null); + expect(syncSpecsLogs.__get__("whileTries")).to.equal(4); }); - it('Should break the loop if request has error other than network issue', () => { - let error = new Error("error"); - let requestStub = sandbox.stub(); + it('Should exit after defined number of retries in case of error', () => { + let error = new Error("error"), requestStub = sandbox.stub(); + let postStub = sandbox .stub(request, "post") - .yields(error, { statusCode: 200 }, JSON.stringify({})); + .yields(error, { statusCode: 502 }, JSON.stringify({})); + requestStub.post = postStub; syncSpecsLogs.__set__('request', requestStub); + syncSpecsLogs.__set__('whileTries', 1); + syncSpecsLogs.__set__('specSummary', {}); + syncSpecsLogs.__set__('whileLoop', true); let whilstCallback = sandbox.stub(); whileProcess(whilstCallback); - sinon.assert.calledWith(whilstCallback, error); + sinon.assert.calledWith(whilstCallback, { status: 504, message: "Tries limit reached" }); + expect(syncSpecsLogs.__get__("whileTries")).to.equal(0); + expect(syncSpecsLogs.__get__("whileLoop")).to.equal(false); + expect(syncSpecsLogs.__get__("specSummary.exitCode")).to.equal(2); }); it('Should print spec details when data is returned from server', () => { diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index 37e61187..56d082d9 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -5,12 +5,14 @@ const chai = require('chai'), expect = chai.expect, sinon = require('sinon'), chaiAsPromised = require('chai-as-promised'), + chalk = require('chalk'), fs = require('fs'); const utils = require('../../../../bin/helpers/utils'), constant = require('../../../../bin/helpers/constants'), logger = require('../../../../bin/helpers/logger').winstonLogger, - testObjects = require('../../support/fixtures/testObjects'); + testObjects = require('../../support/fixtures/testObjects'), + syncLogger = require("../../../../bin/helpers/logger").syncCliLogger; chai.use(chaiAsPromised); logger.transports['console.info'].silent = true; @@ -943,4 +945,42 @@ describe('utils', () => { }); }); + + describe('#handleSyncExit', () => { + let processStub; + beforeEach(function () { + processStub = sinon.stub(process, 'exit'); + }); + + afterEach(function () { + processStub.restore(); + }); + it('should print network error message when exit code is set to network error code', () => { + let dashboard_url = "dashboard_url", exitCode = 2; + let getNetworkErrorMessageStub = sinon.stub(utils, 'getNetworkErrorMessage'); + utils.handleSyncExit(exitCode, dashboard_url); + sinon.assert.calledOnce(getNetworkErrorMessageStub); + sinon.assert.calledOnceWithExactly(processStub, exitCode); + getNetworkErrorMessageStub.restore(); + }); + + it('should print dashboard link when exit code is not network error code', () => { + let dashboard_url = "dashboard_url", exitCode = 1; + let syncCliLoggerStub = sinon.stub(syncLogger, 'info'); + utils.handleSyncExit(exitCode, dashboard_url); + sinon.assert.calledTwice(syncCliLoggerStub); + sinon.assert.calledOnceWithExactly(processStub, exitCode); + }); + }); + + describe('#getNetworkErrorMessage', () => { + it('should return the error message in red color', () => { + let dashboard_url = "dashboard_url"; + let message = constant.userMessages.FATAL_NETWORK_ERROR + '\n' + + constant.userMessages.RETRY_LIMIT_EXCEEDED + '\n' + + constant.userMessages.CHECK_DASHBOARD_AT + dashboard_url + utils.getNetworkErrorMessage(dashboard_url); + expect(utils.getNetworkErrorMessage(dashboard_url)).to.eq(chalk.red(message)) + }); + }); }); From 7b94fc39d7fc62f32df465fad8bcd9aa0f321991 Mon Sep 17 00:00:00 2001 From: Sagar Ganiga Date: Wed, 11 Nov 2020 16:37:37 +0530 Subject: [PATCH 34/34] change formatting for error message --- bin/helpers/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/helpers/constants.js b/bin/helpers/constants.js index 35d085ea..8b6b4c4c 100644 --- a/bin/helpers/constants.js +++ b/bin/helpers/constants.js @@ -35,7 +35,7 @@ const userMessages = { EXIT_SYNC_CLI_MESSAGE: "Exiting the CLI, but your build is still running. You can use the --sync option to keep getting test updates. You can also use the build-info command now.", 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:" + CHECK_DASHBOARD_AT: "Please check the build status at: " }; const validationMessages = {