From 91f80b6c06067449aff05109982466246b70a2d1 Mon Sep 17 00:00:00 2001 From: Pranav Jain Date: Fri, 14 Oct 2022 20:28:49 +0530 Subject: [PATCH 01/16] add code to set correct default spec folder --- bin/helpers/constants.js | 2 ++ bin/helpers/utils.js | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bin/helpers/constants.js b/bin/helpers/constants.js index cb4358ed..bf2390a5 100644 --- a/bin/helpers/constants.js +++ b/bin/helpers/constants.js @@ -358,6 +358,7 @@ const packageInstallerOptions = { const specFileTypes = ["js", "ts", "feature", "jsx", "coffee", "cjsx"]; const DEFAULT_CYPRESS_SPEC_PATH = "cypress/integration"; +const DEFAULT_CYPRESS_10_SPEC_PATH = "cypress/e2e"; const SPEC_TOTAL_CHAR_LIMIT = 32243; const METADATA_CHAR_BUFFER_PER_SPEC = 175; @@ -423,6 +424,7 @@ module.exports = Object.freeze({ packageInstallerOptions, specFileTypes, DEFAULT_CYPRESS_SPEC_PATH, + DEFAULT_CYPRESS_10_SPEC_PATH, SPEC_TOTAL_CHAR_LIMIT, METADATA_CHAR_BUFFER_PER_SPEC, usageReportingConstants, diff --git a/bin/helpers/utils.js b/bin/helpers/utils.js index 6616816f..50f1e552 100644 --- a/bin/helpers/utils.js +++ b/bin/helpers/utils.js @@ -967,7 +967,11 @@ exports.getFilesToIgnore = (runSettings, excludeFiles, logging = true) => { } exports.getNumberOfSpecFiles = (bsConfig, args, cypressConfig) => { - let testFolderPath = cypressConfig.integrationFolder || Constants.DEFAULT_CYPRESS_SPEC_PATH; + let defaultSpecFolder = Constants.DEFAULT_CYPRESS_SPEC_PATH; + if (bsConfig.run_settings.cypressTestSuiteType === Constants.CYPRESS_V10_AND_ABOVE_TYPE) { + defaultSpecFolder = Constants.DEFAULT_CYPRESS_10_SPEC_PATH; + } + let testFolderPath = cypressConfig.integrationFolder || defaultSpecFolder; let globSearchPattern = this.sanitizeSpecsPattern(bsConfig.run_settings.specs) || `${testFolderPath}/**/*.+(${Constants.specFileTypes.join("|")})`; let ignoreFiles = args.exclude || bsConfig.run_settings.exclude; let files = glob.sync(globSearchPattern, {cwd: bsConfig.run_settings.cypressProjectDir, matchBase: true, ignore: ignoreFiles}); From 613812bf56413e09e8d8933207c12622624bb9e8 Mon Sep 17 00:00:00 2001 From: Pranav Jain Date: Thu, 17 Nov 2022 21:54:32 +0530 Subject: [PATCH 02/16] add specs --- test/unit/bin/helpers/utils.js | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index db1317aa..17f21daa 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -2313,6 +2313,54 @@ describe('utils', () => { ); glob.sync.restore(); }); + + it('glob search pattern should contain default cypress 10 folder when cypressTestSuiteType is CYPRESS_V10_AND_ABOVE_TYPE', () => { + let getNumberOfSpecFilesStub = sinon.stub(glob, 'sync'); + let bsConfig = { + run_settings: { + cypressProjectDir: 'cypressProjectDir', + exclude: 'exclude', + cypressTestSuiteType: constant.CYPRESS_V10_AND_ABOVE_TYPE + }, + }; + + utils.getNumberOfSpecFiles(bsConfig, {}, {}); + + sinon.assert.calledOnceWithExactly( + getNumberOfSpecFilesStub, + `cypress/e2e/**/*.+(${constant.specFileTypes.join('|')})`, + { + cwd: 'cypressProjectDir', + matchBase: true, + ignore: 'exclude', + } + ); + glob.sync.restore(); + }); + + it('glob search pattern should contain default cypress folder when cypressTestSuiteType is not CYPRESS_V10_AND_ABOVE_TYPE', () => { + let getNumberOfSpecFilesStub = sinon.stub(glob, 'sync'); + let bsConfig = { + run_settings: { + cypressProjectDir: 'cypressProjectDir', + exclude: 'exclude', + cypressTestSuiteType: constant.CYPRESS_V9_AND_OLDER_TYPE + }, + }; + + utils.getNumberOfSpecFiles(bsConfig, {}, {}); + + sinon.assert.calledOnceWithExactly( + getNumberOfSpecFilesStub, + `cypress/integration/**/*.+(${constant.specFileTypes.join('|')})`, + { + cwd: 'cypressProjectDir', + matchBase: true, + ignore: 'exclude', + } + ); + glob.sync.restore(); + }); }); describe('warnSpecLimit', () => { From 2959b3e2b4e36af65d54c7157219be3ee0b4dfd7 Mon Sep 17 00:00:00 2001 From: Pranav Jain Date: Fri, 18 Nov 2022 11:47:56 +0530 Subject: [PATCH 03/16] update specs --- test/unit/bin/helpers/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index 17f21daa..4bb11cc7 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -2328,7 +2328,7 @@ describe('utils', () => { sinon.assert.calledOnceWithExactly( getNumberOfSpecFilesStub, - `cypress/e2e/**/*.+(${constant.specFileTypes.join('|')})`, + `${constant.DEFAULT_CYPRESS_10_SPEC_PATH}/**/*.+(${constant.specFileTypes.join('|')})`, { cwd: 'cypressProjectDir', matchBase: true, @@ -2352,7 +2352,7 @@ describe('utils', () => { sinon.assert.calledOnceWithExactly( getNumberOfSpecFilesStub, - `cypress/integration/**/*.+(${constant.specFileTypes.join('|')})`, + `${constant.DEFAULT_CYPRESS_SPEC_PATH}/**/*.+(${constant.specFileTypes.join('|')})`, { cwd: 'cypressProjectDir', matchBase: true, From e8b4b68c366f7ac9005198db28a439ed23baa30b Mon Sep 17 00:00:00 2001 From: Prajwal Dhawarikar Date: Fri, 3 Mar 2023 21:14:03 +0530 Subject: [PATCH 04/16] js config implementation --- bin/commands/runs.js | 6 ++- bin/helpers/capabilityHelper.js | 47 ++++++++---------- bin/helpers/config.js | 1 + bin/helpers/constants.js | 11 +++-- bin/helpers/packageInstaller.js | 40 ++++++++++----- bin/helpers/readCypressConfigUtil.js | 37 ++++++++++++++ bin/helpers/requireModule.js | 14 ++++++ bin/helpers/utils.js | 73 ++++++++++++++++++++++++---- bin/helpers/zipUpload.js | 1 + 9 files changed, 178 insertions(+), 52 deletions(-) create mode 100644 bin/helpers/readCypressConfigUtil.js create mode 100644 bin/helpers/requireModule.js diff --git a/bin/commands/runs.js b/bin/commands/runs.js index 74604378..03f29b54 100644 --- a/bin/commands/runs.js +++ b/bin/commands/runs.js @@ -99,8 +99,7 @@ module.exports = function run(args, rawArgs) { // set the no-wrap utils.setNoWrap(bsConfig, args); - // set record feature caps - utils.setRecordCaps(bsConfig, args); + await packageInstaller.packageSetupAndInstaller(bsConfig, config.packageDirName, {markBlockStart, markBlockEnd}); // set build tag caps utils.setBuildTags(bsConfig, args); @@ -140,6 +139,9 @@ module.exports = function run(args, rawArgs) { // accept the number of parallels utils.setParallels(bsConfig, args, specFiles.length); + // set record feature caps + utils.setRecordCaps(bsConfig, args, cypressConfigFile); + // warn if specFiles cross our limit utils.warnSpecLimit(bsConfig, args, specFiles, rawArgs, buildReportData); markBlockEnd('preArchiveSteps'); diff --git a/bin/helpers/capabilityHelper.js b/bin/helpers/capabilityHelper.js index 09fe2546..2c5a25f1 100644 --- a/bin/helpers/capabilityHelper.js +++ b/bin/helpers/capabilityHelper.js @@ -1,5 +1,6 @@ const fs = require('fs'), path = require('path'); +const { readCypressConfigFile } = require('./readCypressConfigUtil'); const logger = require("./logger").winstonLogger, Constants = require("./constants"), @@ -194,34 +195,28 @@ const validate = (bsConfig, args) => { logger.debug(`Checking for cypress config file at ${cypressConfigFilePath}`); if (!fs.existsSync(cypressConfigFilePath) && bsConfig.run_settings.cypress_config_filename !== 'false') reject(Constants.validationMessages.INVALID_CYPRESS_CONFIG_FILE); - if (bsConfig.run_settings.cypressTestSuiteType === Constants.CYPRESS_V10_AND_ABOVE_TYPE) { - logger.debug(`Validating ${bsConfig.run_settings.cypress_config_filename}`); - // TODO: add validations for cypress_config_filename - } else { - logger.debug("Validating cypress.json"); - try { - if (bsConfig.run_settings.cypress_config_filename !== 'false') { - - if (bsConfig.run_settings.cypressTestSuiteType === Constants.CYPRESS_V10_AND_ABOVE_TYPE) { - if (cypressConfigFilePath.endsWith("cypress.config.js")) { - cypressConfigFile = require(cypressConfigFilePath); - } else { - cypressConfigFile = {}; - } - } else { - let cypressJsonContent = fs.readFileSync(cypressConfigFilePath); - cypressConfigFile = JSON.parse(cypressJsonContent); - } - - // Cypress Json Base Url & Local true check - if (!Utils.isUndefined(cypressConfigFile.baseUrl) && cypressConfigFile.baseUrl.includes("localhost") && !Utils.getLocalFlag(bsConfig.connection_settings)) reject(Constants.validationMessages.LOCAL_NOT_SET.replace("", cypressConfigFile.baseUrl)); - - // Detect if the user is not using the right directory structure, and throw an error - if (!Utils.isUndefined(cypressConfigFile.integrationFolder) && !Utils.isCypressProjDirValid(bsConfig.run_settings.cypressProjectDir,cypressConfigFile.integrationFolder)) reject(Constants.validationMessages.INCORRECT_DIRECTORY_STRUCTURE); + logger.debug(`Validating ${bsConfig.run_settings.cypress_config_filename}`); + try { + if (bsConfig.run_settings.cypressTestSuiteType === Constants.CYPRESS_V10_AND_ABOVE_TYPE) { + const completeCypressConfigFile = readCypressConfigFile(bsConfig) + if (!Utils.isUndefined(completeCypressConfigFile)) { + // check if cypress config was exported using export default + cypressConfigFile = !Utils.isUndefined(completeCypressConfigFile.default) ? completeCypressConfigFile.default : completeCypressConfigFile } - } catch(error){ - reject(Constants.validationMessages.INVALID_CYPRESS_JSON) + + // TODO: add validations for cypress_config_filename + } else { + let cypressJsonContent = fs.readFileSync(cypressConfigFilePath); + cypressConfigFile = JSON.parse(cypressJsonContent); } + + // Cypress Json Base Url & Local true check + if (!Utils.isUndefined(cypressConfigFile.baseUrl) && cypressConfigFile.baseUrl.includes("localhost") && !Utils.getLocalFlag(bsConfig.connection_settings)) reject(Constants.validationMessages.LOCAL_NOT_SET.replace("", cypressConfigFile.baseUrl)); + + // Detect if the user is not using the right directory structure, and throw an error + if (!Utils.isUndefined(cypressConfigFile.integrationFolder) && !Utils.isCypressProjDirValid(bsConfig.run_settings.cypressProjectDir,cypressConfigFile.integrationFolder)) reject(Constants.validationMessages.INCORRECT_DIRECTORY_STRUCTURE); + } catch(error){ + reject(Constants.validationMessages.INVALID_CYPRESS_JSON) } //check if home_directory is present or not in user run_settings diff --git a/bin/helpers/config.js b/bin/helpers/config.js index 967f1617..ed4e1a62 100644 --- a/bin/helpers/config.js +++ b/bin/helpers/config.js @@ -24,5 +24,6 @@ config.packageFileName = "bstackPackages.tar.gz"; config.packageDirName = "tmpBstackPackages"; config.retries = 5; config.networkErrorExitCode = 2; +config.configJsonFileName = 'tmpCypressConfig.json' module.exports = config; diff --git a/bin/helpers/constants.js b/bin/helpers/constants.js index 8194a112..cdb19579 100644 --- a/bin/helpers/constants.js +++ b/bin/helpers/constants.js @@ -43,8 +43,10 @@ const userMessages = { "There was some issue while checking if zip is already uploaded.", ZIP_DELETE_FAILED: "Could not delete tests.zip successfully.", ZIP_DELETED: "Deleted tests.zip successfully.", - NPM_INSTALL_AND_UPLOAD: - "Installing required dependencies and building the package to upload to BrowserStack", + NPM_INSTALL: + "Installing required dependencies", + NPM_UPLOAD: + "Building the package to upload to BrowserStack", NPM_DELETE_FAILED: "Could not delete the dependency packages.", NPM_DELETED: "Deleted dependency packages successfully.", API_DEPRECATED: @@ -420,6 +422,8 @@ const CYPRESS_CONFIG_FILE_MAPPING = { const CYPRESS_CONFIG_FILE_NAMES = Object.keys(CYPRESS_CONFIG_FILE_MAPPING); +const CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS = ['js', 'ts', 'cjs', 'mjs'] + module.exports = Object.freeze({ syncCLI, userMessages, @@ -450,5 +454,6 @@ module.exports = Object.freeze({ CYPRESS_V9_AND_OLDER_TYPE, CYPRESS_V10_AND_ABOVE_TYPE, CYPRESS_CONFIG_FILE_MAPPING, - CYPRESS_CONFIG_FILE_NAMES + CYPRESS_CONFIG_FILE_NAMES, + CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS }); diff --git a/bin/helpers/packageInstaller.js b/bin/helpers/packageInstaller.js index cf2444b3..b0896a88 100644 --- a/bin/helpers/packageInstaller.js +++ b/bin/helpers/packageInstaller.js @@ -128,16 +128,13 @@ const packageArchiver = (packageDir, packageFile) => { }) } -const packageWrapper = (bsConfig, packageDir, packageFile, md5data, instrumentBlocks) => { +const packageSetupAndInstaller = (bsConfig, packageDir, instrumentBlocks) => { return new Promise(function (resolve) { let obj = { - packageArchieveCreated: false + packagesInstalled: false }; - if (md5data.packageUrlPresent || !utils.isTrueString(bsConfig.run_settings.cache_dependencies)) { - logger.debug("Skipping the caching of npm packages since BrowserStack has already cached your npm dependencies that have not changed since the last run.") - return resolve(obj); - } - logger.info(Constants.userMessages.NPM_INSTALL_AND_UPLOAD); + + logger.info(Constants.userMessages.NPM_INSTALL); instrumentBlocks.markBlockStart("packageInstaller.folderSetup"); logger.debug("Started setting up package folder"); return setupPackageFolder(bsConfig.run_settings, packageDir).then((_result) => { @@ -150,10 +147,30 @@ const packageWrapper = (bsConfig, packageDir, packageFile, md5data, instrumentBl }).then((_result) => { logger.debug("Completed installing dependencies"); instrumentBlocks.markBlockEnd("packageInstaller.packageInstall"); - instrumentBlocks.markBlockStart("packageInstaller.packageArchive"); - logger.debug("Started archiving node_modules") - return packageArchiver(packageDir, packageFile); - }).then((_result) => { + Object.assign(obj, { packagesInstalled: true }); + return resolve(obj); + }).catch((err) => { + logger.warn(`Error occured while installing npm dependencies. Dependencies will be installed in runtime. This will have a negative impact on performance. Reach out to browserstack.com/contact, if you persistantly face this issue.`); + obj.error = err.stack ? err.stack.toString().substring(0,100) : err.toString().substring(0,100); + return resolve(obj); + }) + }) +} + +const packageWrapper = (bsConfig, packageDir, packageFile, md5data, instrumentBlocks) => { + return new Promise(function (resolve) { + let obj = { + packageArchieveCreated: false + }; + if (md5data.packageUrlPresent || !utils.isTrueString(bsConfig.run_settings.cache_dependencies)) { + logger.debug("Skipping the caching of npm packages since BrowserStack has already cached your npm dependencies that have not changed since the last run.") + return resolve(obj); + } + logger.info(Constants.userMessages.NPM_UPLOAD); + instrumentBlocks.markBlockStart("packageInstaller.packageArchive"); + logger.debug("Started archiving node_modules") + return packageArchiver(packageDir, packageFile) + .then((_result) => { logger.debug("Archiving of node_modules completed"); instrumentBlocks.markBlockEnd("packageInstaller.packageArchive"); Object.assign(obj, { packageArchieveCreated: true }); @@ -167,3 +184,4 @@ const packageWrapper = (bsConfig, packageDir, packageFile, md5data, instrumentBl } exports.packageWrapper = packageWrapper; +exports.packageSetupAndInstaller = packageSetupAndInstaller; diff --git a/bin/helpers/readCypressConfigUtil.js b/bin/helpers/readCypressConfigUtil.js new file mode 100644 index 00000000..781c992b --- /dev/null +++ b/bin/helpers/readCypressConfigUtil.js @@ -0,0 +1,37 @@ +"use strict"; +const path = require("path"); +const fs = require("fs"); +const { execSync } = require('child_process'); + +const config = require('./config'); +const constants = require("./constants"); +const logger = require('./logger').winstonLogger; + +const detectLanguage = (cypress_config_filename) => { + const extension = cypress_config_filename.split('.').pop() + return constants.CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS.includes(extension) ? extension : 'js' +} + +exports.readCypressConfigFile = (bsConfig) => { + try { + const cypress_config_filepath = path.resolve(bsConfig.run_settings.cypressConfigFilePath) + const cypress_config_filename = bsConfig.run_settings.cypress_config_filename + const bstack_node_modules_path = `${path.resolve(config.packageDirName)}/node_modules` + const conf_lang = detectLanguage(cypress_config_filename) + const require_module_helper_path = `${__dirname}/requireModule.js` + + logger.debug(`cypress config path: ${cypress_config_filepath}`); + + if (conf_lang == 'js' || conf_lang == 'cjs') { + execSync(`NODE_PATH=${bstack_node_modules_path} node ${require_module_helper_path} ${cypress_config_filepath}`) + const cypress_config = JSON.parse(fs.readFileSync(config.configJsonFileName).toString()) + if (fs.existsSync(config.configJsonFileName)) { + fs.unlinkSync(config.configJsonFileName) + } + return cypress_config + } + } catch (error) { + logger.error(`Error while reading cypress config: ${error.message}`) + // TODO: Add instrumention if error occurred + } +} diff --git a/bin/helpers/requireModule.js b/bin/helpers/requireModule.js new file mode 100644 index 00000000..90abd0e0 --- /dev/null +++ b/bin/helpers/requireModule.js @@ -0,0 +1,14 @@ +// NOTE: DO NOT change the name or location of file, the execution of this file is invoked using fixed path +// helper file to load and read js modules +const fs = require('fs'); +const config = require('./config'); +const moduleName = process.argv[2]; + +const mod = require(moduleName) + +if (fs.existsSync(config.configJsonFileName)) { + fs.unlinkSync(config.configJsonFileName) +} + +// write module in temporary json file +fs.writeFileSync(config.configJsonFileName, JSON.stringify(mod)) diff --git a/bin/helpers/utils.js b/bin/helpers/utils.js index 8e06fcbb..caee4363 100644 --- a/bin/helpers/utils.js +++ b/bin/helpers/utils.js @@ -405,7 +405,7 @@ exports.setRecordKeyFlag = (bsConfig, args) => { return bsConfig.run_settings["record-key"]; } -exports.setProjectId = (bsConfig, args) => { +exports.setProjectId = (bsConfig, args, cypressConfigFile) => { if(!this.isUndefined(args["projectId"])) { return args["projectId"]; } else if(!this.isUndefined(process.env.CYPRESS_PROJECT_ID)) { @@ -413,17 +413,16 @@ exports.setProjectId = (bsConfig, args) => { } else if(!this.isUndefined(bsConfig.run_settings["projectId"])) { return bsConfig.run_settings["projectId"]; } else { - let cypressConfigFile = this.getCypressConfigFile(bsConfig); if (!this.isUndefined(cypressConfigFile) && !this.isUndefined(cypressConfigFile["projectId"])) { return cypressConfigFile["projectId"]; } } } -exports.setRecordCaps = (bsConfig, args) => { +exports.setRecordCaps = (bsConfig, args, cypressConfigFile) => { bsConfig.run_settings["record"] = this.setRecordFlag(bsConfig, args); bsConfig.run_settings["record-key"] = this.setRecordKeyFlag(bsConfig, args); - bsConfig.run_settings["projectId"] = this.setProjectId(bsConfig, args); + bsConfig.run_settings["projectId"] = this.setProjectId(bsConfig, args, cypressConfigFile); } exports.verifyNodeVersionOption = () => { @@ -987,15 +986,69 @@ exports.getFilesToIgnore = (runSettings, excludeFiles, logging = true) => { } exports.getNumberOfSpecFiles = (bsConfig, args, cypressConfig) => { - let defaultSpecFolder = Constants.DEFAULT_CYPRESS_SPEC_PATH; + let defaultSpecFolder + let testFolderPath + let globCypressConfigSpecPatterns = [] + let globSearchPattern = this.sanitizeSpecsPattern(bsConfig.run_settings.specs); + if (bsConfig.run_settings.cypressTestSuiteType === Constants.CYPRESS_V10_AND_ABOVE_TYPE) { - defaultSpecFolder = Constants.DEFAULT_CYPRESS_10_SPEC_PATH; + defaultSpecFolder = Constants.DEFAULT_CYPRESS_10_SPEC_PATH + testFolderPath = defaultSpecFolder + if(!this.isUndefined(cypressConfig) && !this.isUndefined(cypressConfig.e2e)) { + if(!this.isUndefined(cypressConfig.e2e.specPattern)) { + globCypressConfigSpecPatterns = Array.isArray(cypressConfig.e2e.specPattern) ? + cypressConfig.e2e.specPattern : [cypressConfig.e2e.specPattern]; + } else { + console.log('herer', [`${testFolderPath}/**/*.+(${Constants.specFileTypes.join("|")})`]) + globCypressConfigSpecPatterns = [`${testFolderPath}/**/*.+(${Constants.specFileTypes.join("|")})`] + } + } else { + // if not able read cypress config, use bstack specs arg(existing logic, which is not correct) + globCypressConfigSpecPatterns = globSearchPattern ? [globSearchPattern] : [`${testFolderPath}/**/*.+(${Constants.specFileTypes.join("|")})`] + } + } else { + defaultSpecFolder = Constants.DEFAULT_CYPRESS_SPEC_PATH + // console.log('cypressConfig.integrationFolder', cypressConfig.integrationFolder) + let testFolderPath = cypressConfig.integrationFolder && cypressConfig.integrationFolder !== '.' ? + cypressConfig.integrationFolder : defaultSpecFolder; + if(!this.isUndefined(cypressConfig.testFiles)) { + if (Array.isArray(cypressConfig.testFiles)) { + cypressConfig.testFiles.forEach(specPattern => { + globCypressConfigSpecPatterns.push(`${testFolderPath}/${specPattern}`) + }); + } else { + globCypressConfigSpecPatterns = [`${testFolderPath}/${specPattern}`] + } + } else { + globCypressConfigSpecPatterns = [`${testFolderPath}/**/*.+(${Constants.specFileTypes.join("|")})`] + } } - let testFolderPath = cypressConfig.integrationFolder || defaultSpecFolder; - let globSearchPattern = this.sanitizeSpecsPattern(bsConfig.run_settings.specs) || `${testFolderPath}/**/*.+(${Constants.specFileTypes.join("|")})`; + let ignoreFiles = args.exclude || bsConfig.run_settings.exclude; - let files = glob.sync(globSearchPattern, {cwd: bsConfig.run_settings.cypressProjectDir, matchBase: true, ignore: ignoreFiles}); - logger.debug(`${files ? files.length : 0} spec files found at ${testFolderPath}`); + // TODO: remove console logs + let fileMatchedWithConfigSpecPattern = []; + globCypressConfigSpecPatterns.forEach(specPattern => { + fileMatchedWithConfigSpecPattern.push( + ...glob.sync(specPattern, { + cwd: bsConfig.run_settings.cypressProjectDir, matchBase: true, ignore: ignoreFiles + }) + ); + }); + + console.log('configSpecPattern', fileMatchedWithConfigSpecPattern) + let files + + if (globSearchPattern) { + let fileMatchedWithBstackSpecPattern = glob.sync(globSearchPattern, { + cwd: bsConfig.run_settings.cypressProjectDir, matchBase: true, ignore: ignoreFiles + }); + console.log('specArg', fileMatchedWithBstackSpecPattern); + files = fileMatchedWithBstackSpecPattern.filter(file => fileMatchedWithConfigSpecPattern.includes(file)) + } else { + files = fileMatchedWithConfigSpecPattern; + } + console.log('files', files) + logger.debug(`${files ? files.length : 0} spec files found`); return files; }; diff --git a/bin/helpers/zipUpload.js b/bin/helpers/zipUpload.js index 612bc840..441f9ec0 100644 --- a/bin/helpers/zipUpload.js +++ b/bin/helpers/zipUpload.js @@ -28,6 +28,7 @@ const uploadSuits = (bsConfig, filePath, opts, obj) => { obj.startTime = Date.now(); if (opts.urlPresent) { + opts.cleanupMethod(); return resolve({ [opts.md5ReturnKey]: opts.url }); } if (!opts.archivePresent) { From 1099cbeb862bfc53692f6a391e3b43e19685c373 Mon Sep 17 00:00:00 2001 From: Prajwal Dhawarikar Date: Mon, 6 Mar 2023 14:34:48 +0530 Subject: [PATCH 05/16] SOme UT fix --- bin/helpers/capabilityHelper.js | 32 ++++++++++++----------- test/unit/bin/helpers/packageInstaller.js | 4 +-- test/unit/bin/helpers/utils.js | 13 ++++----- test/unit/bin/helpers/zipUpload.js | 3 ++- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/bin/helpers/capabilityHelper.js b/bin/helpers/capabilityHelper.js index 2c5a25f1..4e6cb2bd 100644 --- a/bin/helpers/capabilityHelper.js +++ b/bin/helpers/capabilityHelper.js @@ -197,24 +197,26 @@ const validate = (bsConfig, args) => { logger.debug(`Validating ${bsConfig.run_settings.cypress_config_filename}`); try { - if (bsConfig.run_settings.cypressTestSuiteType === Constants.CYPRESS_V10_AND_ABOVE_TYPE) { - const completeCypressConfigFile = readCypressConfigFile(bsConfig) - if (!Utils.isUndefined(completeCypressConfigFile)) { - // check if cypress config was exported using export default - cypressConfigFile = !Utils.isUndefined(completeCypressConfigFile.default) ? completeCypressConfigFile.default : completeCypressConfigFile - } + if (bsConfig.run_settings.cypress_config_filename !== 'false') { + if (bsConfig.run_settings.cypressTestSuiteType === Constants.CYPRESS_V10_AND_ABOVE_TYPE) { + const completeCypressConfigFile = readCypressConfigFile(bsConfig) + if (!Utils.isUndefined(completeCypressConfigFile)) { + // check if cypress config was exported using export default + cypressConfigFile = !Utils.isUndefined(completeCypressConfigFile.default) ? completeCypressConfigFile.default : completeCypressConfigFile + } - // TODO: add validations for cypress_config_filename - } else { - let cypressJsonContent = fs.readFileSync(cypressConfigFilePath); - cypressConfigFile = JSON.parse(cypressJsonContent); - } + // TODO: add validations for cypress_config_filename + } else { + let cypressJsonContent = fs.readFileSync(cypressConfigFilePath); + cypressConfigFile = JSON.parse(cypressJsonContent); + } - // Cypress Json Base Url & Local true check - if (!Utils.isUndefined(cypressConfigFile.baseUrl) && cypressConfigFile.baseUrl.includes("localhost") && !Utils.getLocalFlag(bsConfig.connection_settings)) reject(Constants.validationMessages.LOCAL_NOT_SET.replace("", cypressConfigFile.baseUrl)); + // Cypress Json Base Url & Local true check + if (!Utils.isUndefined(cypressConfigFile.baseUrl) && cypressConfigFile.baseUrl.includes("localhost") && !Utils.getLocalFlag(bsConfig.connection_settings)) reject(Constants.validationMessages.LOCAL_NOT_SET.replace("", cypressConfigFile.baseUrl)); - // Detect if the user is not using the right directory structure, and throw an error - if (!Utils.isUndefined(cypressConfigFile.integrationFolder) && !Utils.isCypressProjDirValid(bsConfig.run_settings.cypressProjectDir,cypressConfigFile.integrationFolder)) reject(Constants.validationMessages.INCORRECT_DIRECTORY_STRUCTURE); + // Detect if the user is not using the right directory structure, and throw an error + if (!Utils.isUndefined(cypressConfigFile.integrationFolder) && !Utils.isCypressProjDirValid(bsConfig.run_settings.cypressProjectDir,cypressConfigFile.integrationFolder)) reject(Constants.validationMessages.INCORRECT_DIRECTORY_STRUCTURE); + } } catch(error){ reject(Constants.validationMessages.INVALID_CYPRESS_JSON) } diff --git a/test/unit/bin/helpers/packageInstaller.js b/test/unit/bin/helpers/packageInstaller.js index 943c0c9b..f1b4fd43 100644 --- a/test/unit/bin/helpers/packageInstaller.js +++ b/test/unit/bin/helpers/packageInstaller.js @@ -456,10 +456,8 @@ describe("packageInstaller", () => { }); it("should reject with error if issue in any step", () => { - setupPackageFolderErrorStub = sandbox.stub().returns(Promise.reject({message: "test error", stack: "test error stack"})); + setupPackageArchiverErrorStub = sandbox.stub().returns(Promise.reject({message: "test error", stack: "test error stack"})); packageInstaller.__set__({ - setupPackageFolder: setupPackageFolderErrorStub, - packageInstall:setupPackageInstallStub, packageArchiver: setupPackageArchiverStub }); let packageWrapperrewire = packageInstaller.__get__('packageWrapper'); diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index 9a153bae..fc08b63b 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -2257,7 +2257,7 @@ describe('utils', () => { describe('getNumberOfSpecFiles', () => { it('glob search pattern should be equal to bsConfig.run_settings.specs', () => { - let getNumberOfSpecFilesStub = sinon.stub(glob, 'sync'); + let getNumberOfSpecFilesStub = sinon.stub(glob, 'sync').returns([]); let bsConfig = { run_settings: { specs: 'specs', @@ -2277,7 +2277,7 @@ describe('utils', () => { }); it('glob search pattern should be equal to default', () => { - let getNumberOfSpecFilesStub = sinon.stub(glob, 'sync'); + let getNumberOfSpecFilesStub = sinon.stub(glob, 'sync').returns([]); let bsConfig = { run_settings: { cypressProjectDir: 'cypressProjectDir', @@ -3343,8 +3343,7 @@ describe('utils', () => { } } let args = {}; - getCypressConfigFileStub.returns({ projectId: "ghi" }) - expect(utils.setProjectId(bsConfig, args)).to.eq("abc") + expect(utils.setProjectId(bsConfig, args, { projectId: "ghi" })).to.eq("abc") }); it("prioritizes projectId passed in cypress json when no args, env var and bsConfig is passed", () => { @@ -3352,8 +3351,7 @@ describe('utils', () => { run_settings: {} } let args = {} - getCypressConfigFileStub.returns({ projectId: "ghi" }) - expect(utils.setProjectId(bsConfig, args)).to.eq("ghi") + expect(utils.setProjectId(bsConfig, args, { projectId: "ghi" })).to.eq("ghi") }); it("returns undefined when nothing is passed", () => { @@ -3361,8 +3359,7 @@ describe('utils', () => { run_settings: {} } let args = {} - getCypressConfigFileStub.returns({}) - expect(utils.setProjectId(bsConfig, args)).to.eq(undefined) + expect(utils.setProjectId(bsConfig, args, {})).to.eq(undefined) }); }); diff --git a/test/unit/bin/helpers/zipUpload.js b/test/unit/bin/helpers/zipUpload.js index 42543295..fa418b7d 100644 --- a/test/unit/bin/helpers/zipUpload.js +++ b/test/unit/bin/helpers/zipUpload.js @@ -94,7 +94,8 @@ describe("zipUpload", () => { let opts = { urlPresent: true, md5ReturnKey: 'returnKey', - url: 'bs://random_hash' + url: 'bs://random_hash', + cleanupMethod: sinon.stub().returns(null) } let obj = { bar1: null, From 89cc1eb65f694c9eccb562eb83561771ad6eeac6 Mon Sep 17 00:00:00 2001 From: Prajwal Dhawarikar Date: Tue, 7 Mar 2023 15:30:55 +0530 Subject: [PATCH 06/16] TS support and fixes --- bin/helpers/config.js | 1 + bin/helpers/readCypressConfigUtil.js | 72 ++++++++++++++++++++++++---- bin/helpers/utils.js | 19 ++++---- 3 files changed, 71 insertions(+), 21 deletions(-) diff --git a/bin/helpers/config.js b/bin/helpers/config.js index ed4e1a62..bd790d05 100644 --- a/bin/helpers/config.js +++ b/bin/helpers/config.js @@ -24,6 +24,7 @@ config.packageFileName = "bstackPackages.tar.gz"; config.packageDirName = "tmpBstackPackages"; config.retries = 5; config.networkErrorExitCode = 2; +config.compiledConfigJsDirName = 'tmpBstackCompiledJs' config.configJsonFileName = 'tmpCypressConfig.json' module.exports = config; diff --git a/bin/helpers/readCypressConfigUtil.js b/bin/helpers/readCypressConfigUtil.js index 781c992b..6665535d 100644 --- a/bin/helpers/readCypressConfigUtil.js +++ b/bin/helpers/readCypressConfigUtil.js @@ -7,31 +7,83 @@ const config = require('./config'); const constants = require("./constants"); const logger = require('./logger').winstonLogger; -const detectLanguage = (cypress_config_filename) => { +exports.detectLanguage = (cypress_config_filename) => { const extension = cypress_config_filename.split('.').pop() return constants.CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS.includes(extension) ? extension : 'js' } +exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_modules_path) => { + const cypress_config_filename = bsConfig.run_settings.cypress_config_filename + const working_dir = path.dirname(cypress_config_filepath); + const complied_js_dir = path.join(working_dir, config.compiledConfigJsDirName) + execSync(`rm -rf ${config.compiledConfigJsDirName}`, { cwd: working_dir }) + execSync(`mkdir ${config.compiledConfigJsDirName}`, { cwd: working_dir }) + + let tsc_command = `NODE_PATH=${bstack_node_modules_path} ${bstack_node_modules_path}/typescript/bin/tsc --outDir ${complied_js_dir} --listEmittedFiles true --allowSyntheticDefaultImports --module commonjs --declaration false ${cypress_config_filepath}` + let tsc_output + + try { + logger.debug(`Running: ${tsc_command}`) + tsc_output = execSync(tsc_command, { cwd: working_dir }) + } catch (err) { + // error while compiling ts files + logger.debug(err.message); + logger.debug(err.output.toString()); + tsc_output = err.output // if there is an error, tsc adds output of complilation to err.output key + } finally { + logger.debug(`Saved compiled js output at: ${complied_js_dir}`); + logger.debug(`Finding compiled cypress config file in: ${complied_js_dir}`); + + const lines = tsc_output.toString().split('\n'); + let foundLine = null; + for (let i = 0; i < lines.length; i++) { + if (lines[i].indexOf(`${path.parse(cypress_config_filename).name}.js`) > -1) { + foundLine = lines[i] + break; + } + } + if (foundLine === null) { + logger.error(`No compiled cypress config found. There might some error running ${tsc_command} command`) + return null + } else { + const compiled_cypress_config_filepath = foundLine.split('TSFILE: ').pop() + logger.debug(`Found compiled cypress config file: ${compiled_cypress_config_filepath}`); + return compiled_cypress_config_filepath + } + } +} + +exports.loadJsFile = (cypress_config_filepath, bstack_node_modules_path) => { + const require_module_helper_path = `${__dirname}/requireModule.js` + execSync(`NODE_PATH=${bstack_node_modules_path} node ${require_module_helper_path} ${cypress_config_filepath}`) + const cypress_config = JSON.parse(fs.readFileSync(config.configJsonFileName).toString()) + if (fs.existsSync(config.configJsonFileName)) { + fs.unlinkSync(config.configJsonFileName) + } + return cypress_config +} + exports.readCypressConfigFile = (bsConfig) => { + const cypress_config_filepath = path.resolve(bsConfig.run_settings.cypressConfigFilePath) try { - const cypress_config_filepath = path.resolve(bsConfig.run_settings.cypressConfigFilePath) const cypress_config_filename = bsConfig.run_settings.cypress_config_filename const bstack_node_modules_path = `${path.resolve(config.packageDirName)}/node_modules` - const conf_lang = detectLanguage(cypress_config_filename) - const require_module_helper_path = `${__dirname}/requireModule.js` + const conf_lang = this.detectLanguage(cypress_config_filename) logger.debug(`cypress config path: ${cypress_config_filepath}`); if (conf_lang == 'js' || conf_lang == 'cjs') { - execSync(`NODE_PATH=${bstack_node_modules_path} node ${require_module_helper_path} ${cypress_config_filepath}`) - const cypress_config = JSON.parse(fs.readFileSync(config.configJsonFileName).toString()) - if (fs.existsSync(config.configJsonFileName)) { - fs.unlinkSync(config.configJsonFileName) - } - return cypress_config + return this.loadJsFile(cypress_config_filepath, bstack_node_modules_path) + } else if (conf_lang === 'ts') { + const compiled_cypress_config_filepath = this.convertTsConfig(bsConfig, cypress_config_filepath, bstack_node_modules_path) + return this.loadJsFile(compiled_cypress_config_filepath, bstack_node_modules_path) } } catch (error) { logger.error(`Error while reading cypress config: ${error.message}`) + // TODO: Add instrumention if error occurred + } finally { + const working_dir = path.dirname(cypress_config_filepath); + execSync(`rm -rf ${config.compiledConfigJsDirName}`, { cwd: working_dir }) } } diff --git a/bin/helpers/utils.js b/bin/helpers/utils.js index caee4363..80a19be6 100644 --- a/bin/helpers/utils.js +++ b/bin/helpers/utils.js @@ -999,7 +999,6 @@ exports.getNumberOfSpecFiles = (bsConfig, args, cypressConfig) => { globCypressConfigSpecPatterns = Array.isArray(cypressConfig.e2e.specPattern) ? cypressConfig.e2e.specPattern : [cypressConfig.e2e.specPattern]; } else { - console.log('herer', [`${testFolderPath}/**/*.+(${Constants.specFileTypes.join("|")})`]) globCypressConfigSpecPatterns = [`${testFolderPath}/**/*.+(${Constants.specFileTypes.join("|")})`] } } else { @@ -1008,7 +1007,6 @@ exports.getNumberOfSpecFiles = (bsConfig, args, cypressConfig) => { } } else { defaultSpecFolder = Constants.DEFAULT_CYPRESS_SPEC_PATH - // console.log('cypressConfig.integrationFolder', cypressConfig.integrationFolder) let testFolderPath = cypressConfig.integrationFolder && cypressConfig.integrationFolder !== '.' ? cypressConfig.integrationFolder : defaultSpecFolder; if(!this.isUndefined(cypressConfig.testFiles)) { @@ -1017,16 +1015,15 @@ exports.getNumberOfSpecFiles = (bsConfig, args, cypressConfig) => { globCypressConfigSpecPatterns.push(`${testFolderPath}/${specPattern}`) }); } else { - globCypressConfigSpecPatterns = [`${testFolderPath}/${specPattern}`] + globCypressConfigSpecPatterns = [`${testFolderPath}/${cypressConfig.testFiles}`] } } else { globCypressConfigSpecPatterns = [`${testFolderPath}/**/*.+(${Constants.specFileTypes.join("|")})`] } } - let ignoreFiles = args.exclude || bsConfig.run_settings.exclude; - // TODO: remove console logs - let fileMatchedWithConfigSpecPattern = []; + let ignoreFiles = args.exclude || bsConfig.run_settings.exclude + let fileMatchedWithConfigSpecPattern = [] globCypressConfigSpecPatterns.forEach(specPattern => { fileMatchedWithConfigSpecPattern.push( ...glob.sync(specPattern, { @@ -1034,20 +1031,20 @@ exports.getNumberOfSpecFiles = (bsConfig, args, cypressConfig) => { }) ); }); - - console.log('configSpecPattern', fileMatchedWithConfigSpecPattern) - let files + fileMatchedWithConfigSpecPattern = fileMatchedWithConfigSpecPattern.map((file) => path.resolve(bsConfig.run_settings.cypressProjectDir, file)) + let files if (globSearchPattern) { let fileMatchedWithBstackSpecPattern = glob.sync(globSearchPattern, { cwd: bsConfig.run_settings.cypressProjectDir, matchBase: true, ignore: ignoreFiles }); - console.log('specArg', fileMatchedWithBstackSpecPattern); + fileMatchedWithBstackSpecPattern = fileMatchedWithBstackSpecPattern.map((file) => path.resolve(bsConfig.run_settings.cypressProjectDir, file)) + files = fileMatchedWithBstackSpecPattern.filter(file => fileMatchedWithConfigSpecPattern.includes(file)) } else { files = fileMatchedWithConfigSpecPattern; } - console.log('files', files) + logger.debug(`${files ? files.length : 0} spec files found`); return files; }; From bcf86c8ae280af85747f977755875eceea377390 Mon Sep 17 00:00:00 2001 From: Prajwal Dhawarikar Date: Tue, 7 Mar 2023 19:11:32 +0530 Subject: [PATCH 07/16] Add UTs --- test/unit/bin/commands/runs.js | 3 +- test/unit/bin/helpers/packageInstaller.js | 67 ++++- test/unit/bin/helpers/utils.js | 282 ++++++++++++++++++---- 3 files changed, 299 insertions(+), 53 deletions(-) diff --git a/test/unit/bin/commands/runs.js b/test/unit/bin/commands/runs.js index 0f093a7f..b89225ed 100644 --- a/test/unit/bin/commands/runs.js +++ b/test/unit/bin/commands/runs.js @@ -218,7 +218,6 @@ describe("runs", () => { sinon.assert.calledOnce(setGeolocationStub); sinon.assert.calledOnce(setSpecTimeoutStub); sinon.assert.calledOnce(getInitialDetailsStub); - sinon.assert.calledOnce(setRecordCapsStub); sinon.assert.calledOnce(setNodeVersionStub); sinon.assert.calledOnce(setBuildTagsStub); sinon.assert.calledOnceWithExactly( @@ -809,7 +808,7 @@ describe("runs", () => { }); - describe("handle createBuild success", () => { + describe.only("handle createBuild success", () => { var sandbox; beforeEach(() => { diff --git a/test/unit/bin/helpers/packageInstaller.js b/test/unit/bin/helpers/packageInstaller.js index f1b4fd43..70f03b68 100644 --- a/test/unit/bin/helpers/packageInstaller.js +++ b/test/unit/bin/helpers/packageInstaller.js @@ -397,8 +397,69 @@ describe("packageInstaller", () => { }); }); + context("packageSetupAndInstaller", () => { + let setupPackageFolderStub, setupPackageFolderStubErrorStub, setupPackageInstallStub; + let packageDir = "/random/path"; + + const packageInstaller = rewire("../../../../bin/helpers/packageInstaller"); + beforeEach(() => { + setupPackageFolderStub = sandbox.stub().returns(Promise.resolve("random")); + setupPackageInstallStub = sandbox.stub().returns(Promise.resolve("random")); + }); + + + it("should resolve with package exist if all step are successful", () => { + packageInstaller.__set__({ + setupPackageFolder: setupPackageFolderStub, + packageInstall: setupPackageInstallStub + }); + let packageSetupAndInstallerrewire = packageInstaller.__get__('packageSetupAndInstaller'); + let bsConfig = { + run_settings: { + cache_dependencies: true + } + }; + + let instrumentBlocks = { + markBlockStart: sinon.stub(), + markBlockEnd: sinon.stub() + } + return packageSetupAndInstallerrewire(bsConfig, packageDir, instrumentBlocks) + .then((data) => { + chai.assert.deepEqual(data, {packagesInstalled: true}); + }) + .catch((_error) => { + chai.assert.fail("Promise error"); + }); + }); + + it("should reject with error if issue in any step", () => { + setupPackageFolderStubErrorStub = sandbox.stub().returns(Promise.reject({message: "test error", stack: "test error stack"})); + packageInstaller.__set__({ + setupPackageFolder: setupPackageFolderStubErrorStub + }); + let packageSetupAndInstallerrewire = packageInstaller.__get__('packageSetupAndInstaller'); + let bsConfig = { + run_settings: { + cache_dependencies: true + } + }; + let instrumentBlocks = { + markBlockStart: sinon.stub(), + markBlockEnd: sinon.stub() + } + return packageSetupAndInstallerrewire(bsConfig, packageDir, instrumentBlocks) + .then((data) => { + chai.assert.deepEqual(data, { packagesInstalled: false, error: 'test error stack' }); + }) + .catch((_error) => { + chai.assert.fail("Promise error"); + }); + }); + }); + context("packageWrapper", () => { - let setupPackageFolderStub, setupPackageFolderErrorStub, setupPackageInstallStub, setupPackageArchiverStub; + let setupPackageFolderStub, setupPackageArchiverErrorStub, setupPackageInstallStub, setupPackageArchiverStub; let packageDir = "/random/path"; let packageFile = "/random/path/to/file"; const packageInstaller = rewire("../../../../bin/helpers/packageInstaller"); @@ -431,8 +492,6 @@ describe("packageInstaller", () => { it("should resolve with package exist if all step are successful", () => { packageInstaller.__set__({ - setupPackageFolder: setupPackageFolderStub, - packageInstall:setupPackageInstallStub, packageArchiver: setupPackageArchiverStub }); let packageWrapperrewire = packageInstaller.__get__('packageWrapper'); @@ -458,7 +517,7 @@ describe("packageInstaller", () => { it("should reject with error if issue in any step", () => { setupPackageArchiverErrorStub = sandbox.stub().returns(Promise.reject({message: "test error", stack: "test error stack"})); packageInstaller.__set__({ - packageArchiver: setupPackageArchiverStub + packageArchiver: setupPackageArchiverErrorStub }); let packageWrapperrewire = packageInstaller.__get__('packageWrapper'); let bsConfig = { diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index fc08b63b..273d2f63 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -2256,119 +2256,307 @@ describe('utils', () => { }); describe('getNumberOfSpecFiles', () => { - it('glob search pattern should be equal to bsConfig.run_settings.specs', () => { - let getNumberOfSpecFilesStub = sinon.stub(glob, 'sync').returns([]); + it('should return files matching with run_settings.specs and under default folder if cypress v <= 9 and no integration/testFiles patterm provided', () => { + let globStub = sinon.stub(glob, 'sync') + globStub.withArgs('cypress/integration/foo*.js') + .returns(['cypress/integration/foo_1.js']); + globStub.withArgs(`cypress/integration/**/*.+(${constant.specFileTypes.join('|')})`) + .returns([ + 'cypress/integration/foo_1.js', + 'cypress/integration/foo_2.js', + 'cypress/integration/bar_1.js' + ]); let bsConfig = { run_settings: { - specs: 'specs', + cypressTestSuiteType: 'CYPRESS_V9_AND_OLDER_TYPE', + specs: 'cypress/integration/foo*.js', cypressProjectDir: 'cypressProjectDir', exclude: 'exclude', }, }; - utils.getNumberOfSpecFiles(bsConfig, {}, {}); - sinon.assert.calledOnce(getNumberOfSpecFilesStub); - sinon.assert.calledOnceWithExactly(getNumberOfSpecFilesStub, 'specs', { - cwd: 'cypressProjectDir', - matchBase: true, - ignore: 'exclude', - }); + const result = utils.getNumberOfSpecFiles(bsConfig, {}, {}); + expect(result.length).to.eql(1); + expect(result[0].endsWith('cypress/integration/foo_1.js')).to.eql(true); + sinon.assert.calledTwice(globStub) + sinon.assert.callOrder( + globStub.withArgs(`cypress/integration/**/*.+(${constant.specFileTypes.join('|')})`, { + cwd: 'cypressProjectDir', + matchBase: true, + ignore: 'exclude', + }), + globStub.withArgs('cypress/integration/foo*.js', { + cwd: 'cypressProjectDir', + matchBase: true, + ignore: 'exclude', + }) + ); glob.sync.restore(); }); - it('glob search pattern should be equal to default', () => { - let getNumberOfSpecFilesStub = sinon.stub(glob, 'sync').returns([]); + it('should return files matching with run_settings.specs and under default folder if cypress v <= 9 and testFiles pattern string provided', () => { + let globStub = sinon.stub(glob, 'sync') + globStub.withArgs('cypress/integration/foo*.js') + .returns(['cypress/integration/foo_1.js']); + globStub.withArgs('cypress/integration/**.js') + .returns([ + 'cypress/integration/foo_1.js', + 'cypress/integration/foo_2.js', + 'cypress/integration/bar_1.js' + ]); let bsConfig = { run_settings: { + cypressTestSuiteType: 'CYPRESS_V9_AND_OLDER_TYPE', + specs: 'cypress/integration/foo*.js', cypressProjectDir: 'cypressProjectDir', exclude: 'exclude', }, }; - utils.getNumberOfSpecFiles(bsConfig, {}, {}); - - sinon.assert.calledOnceWithExactly( - getNumberOfSpecFilesStub, - `cypress/integration/**/*.+(${constant.specFileTypes.join('|')})`, - { + const result = utils.getNumberOfSpecFiles(bsConfig, {}, { + integrationFolder: 'cypress/integration', + testFiles: '**.js' + }); + expect(result.length).to.eql(1); + expect(result[0].endsWith('cypress/integration/foo_1.js')).to.eql(true); + sinon.assert.calledTwice(globStub) + sinon.assert.callOrder( + globStub.withArgs('cypress/integration/**.js', { cwd: 'cypressProjectDir', matchBase: true, ignore: 'exclude', - } + }), + globStub.withArgs('cypress/integration/foo*.js', { + cwd: 'cypressProjectDir', + matchBase: true, + ignore: 'exclude', + }) ); glob.sync.restore(); }); - it('glob search pattern should be equal to default with integrationFolder', () => { - let getNumberOfSpecFilesStub = sinon.stub(glob, 'sync'); + it('should return files matching with run_settings.specs and under default folder if cypress v <= 9 and testFiles pattern array provided', () => { + let globStub = sinon.stub(glob, 'sync') + globStub.withArgs('cypress/integration/foo*.js') + .returns(['cypress/integration/foo_1.js']); + globStub.withArgs('cypress/integration/**.js') + .returns([ + 'cypress/integration/foo_1.js', + 'cypress/integration/foo_2.js', + 'cypress/integration/bar_1.js' + ]); let bsConfig = { run_settings: { + cypressTestSuiteType: 'CYPRESS_V9_AND_OLDER_TYPE', + specs: 'cypress/integration/foo*.js', cypressProjectDir: 'cypressProjectDir', exclude: 'exclude', }, }; - utils.getNumberOfSpecFiles(bsConfig, {}, { integrationFolder: 'specs' }); - - sinon.assert.calledOnceWithExactly( - getNumberOfSpecFilesStub, - `specs/**/*.+(${constant.specFileTypes.join('|')})`, - { + const result = utils.getNumberOfSpecFiles(bsConfig, {}, { + integrationFolder: 'cypress/integration', + testFiles: ['**.js'] + }); + expect(result.length).to.eql(1); + expect(result[0].endsWith('cypress/integration/foo_1.js')).to.eql(true); + sinon.assert.calledTwice(globStub) + sinon.assert.callOrder( + globStub.withArgs('cypress/integration/**.js', { cwd: 'cypressProjectDir', matchBase: true, ignore: 'exclude', - } + }), + globStub.withArgs('cypress/integration/foo*.js', { + cwd: 'cypressProjectDir', + matchBase: true, + ignore: 'exclude', + }) ); glob.sync.restore(); }); - it('glob search pattern should contain default cypress 10 folder when cypressTestSuiteType is CYPRESS_V10_AND_ABOVE_TYPE', () => { - let getNumberOfSpecFilesStub = sinon.stub(glob, 'sync'); + it('should return files matching with run_settings.specs and under default folder if cypress v >= 10 and no specPattern provided', () => { + let globStub = sinon.stub(glob, 'sync') + globStub.withArgs('cypress/e2e/foo*.js') + .returns(['cypress/e2e/foo_1.js']); + globStub.withArgs(`cypress/e2e/**/*.+(${constant.specFileTypes.join('|')})`) + .returns([ + 'cypress/e2e/foo_1.js', + 'cypress/e2e/foo_2.js', + 'cypress/e2e/bar_1.js' + ]); let bsConfig = { run_settings: { + cypressTestSuiteType: 'CYPRESS_V10_AND_ABOVE_TYPE', + specs: 'cypress/e2e/foo*.js', cypressProjectDir: 'cypressProjectDir', exclude: 'exclude', - cypressTestSuiteType: constant.CYPRESS_V10_AND_ABOVE_TYPE }, }; - utils.getNumberOfSpecFiles(bsConfig, {}, {}); - - sinon.assert.calledOnceWithExactly( - getNumberOfSpecFilesStub, - `${constant.DEFAULT_CYPRESS_10_SPEC_PATH}/**/*.+(${constant.specFileTypes.join('|')})`, - { + const result = utils.getNumberOfSpecFiles(bsConfig, {}, { e2e: {}}); + console.log(result) + expect(result.length).to.eql(1); + expect(result[0].endsWith('cypress/e2e/foo_1.js')).to.eql(true); + sinon.assert.calledTwice(globStub) + sinon.assert.callOrder( + globStub.withArgs(`cypress/e2e/**/*.+(${constant.specFileTypes.join('|')})`, { cwd: 'cypressProjectDir', matchBase: true, ignore: 'exclude', - } + }), + globStub.withArgs('cypress/e2e/foo*.js', { + cwd: 'cypressProjectDir', + matchBase: true, + ignore: 'exclude', + }) ); glob.sync.restore(); }); - it('glob search pattern should contain default cypress folder when cypressTestSuiteType is not CYPRESS_V10_AND_ABOVE_TYPE', () => { - let getNumberOfSpecFilesStub = sinon.stub(glob, 'sync'); + it('should return files matching with run_settings.specs and under default folder if cypress v >= 10 and specPattern pattern string provided', () => { + let globStub = sinon.stub(glob, 'sync') + globStub.withArgs('cypress/e2e/foo*.js') + .returns(['cypress/e2e/foo_1.js']); + globStub.withArgs('cypress/e2e/**.js') + .returns([ + 'cypress/e2e/foo_1.js', + 'cypress/e2e/foo_2.js', + 'cypress/e2e/bar_1.js' + ]); let bsConfig = { run_settings: { + cypressTestSuiteType: 'CYPRESS_V10_AND_ABOVE_TYPE', + specs: 'cypress/e2e/foo*.js', cypressProjectDir: 'cypressProjectDir', exclude: 'exclude', - cypressTestSuiteType: constant.CYPRESS_V9_AND_OLDER_TYPE }, }; - utils.getNumberOfSpecFiles(bsConfig, {}, {}); - - sinon.assert.calledOnceWithExactly( - getNumberOfSpecFilesStub, - `${constant.DEFAULT_CYPRESS_SPEC_PATH}/**/*.+(${constant.specFileTypes.join('|')})`, - { + const result = utils.getNumberOfSpecFiles(bsConfig, {}, { + e2e: { + specPattern: 'cypress/e2e/**.js' + } + }); + expect(result.length).to.eql(1); + expect(result[0].endsWith('cypress/e2e/foo_1.js')).to.eql(true); + sinon.assert.calledTwice(globStub) + sinon.assert.callOrder( + globStub.withArgs('cypress/e2e/**.js', { cwd: 'cypressProjectDir', matchBase: true, ignore: 'exclude', + }), + globStub.withArgs('cypress/e2e/foo*.js', { + cwd: 'cypressProjectDir', + matchBase: true, + ignore: 'exclude', + }) + ); + glob.sync.restore(); + }); + + it('should return files matching with run_settings.specs and under default folder if cypress v >= 10 and specPattern pattern array provided', () => { + let globStub = sinon.stub(glob, 'sync') + globStub.withArgs('cypress/e2e/foo*.js') + .returns(['cypress/e2e/foo_1.js']); + globStub.withArgs('cypress/e2e/**.js') + .returns([ + 'cypress/e2e/foo_1.js', + 'cypress/e2e/foo_2.js', + 'cypress/e2e/bar_1.js' + ]); + let bsConfig = { + run_settings: { + cypressTestSuiteType: 'CYPRESS_V10_AND_ABOVE_TYPE', + specs: 'cypress/e2e/foo*.js', + cypressProjectDir: 'cypressProjectDir', + exclude: 'exclude', + }, + }; + + const result = utils.getNumberOfSpecFiles(bsConfig, {}, { + e2e: { + specPattern: ['cypress/e2e/**.js'] } + }); + expect(result.length).to.eql(1); + expect(result[0].endsWith('cypress/e2e/foo_1.js')).to.eql(true); + sinon.assert.calledTwice(globStub) + sinon.assert.callOrder( + globStub.withArgs('cypress/e2e/**.js', { + cwd: 'cypressProjectDir', + matchBase: true, + ignore: 'exclude', + }), + globStub.withArgs('cypress/e2e/foo*.js', { + cwd: 'cypressProjectDir', + matchBase: true, + ignore: 'exclude', + }) ); glob.sync.restore(); }); + + it('should return files matching with run_settings.specs if cypress v >= 10 and error while reading config file', () => { + let globStub = sinon.stub(glob, 'sync') + globStub.withArgs('cypress/e2e/foo*.js') + .returns(['cypress/e2e/foo_1.js']); + // globStub.withArgs(`cypress/e2e/**/*.+(${constant.specFileTypes.join('|')})`) + // .returns([ + // 'cypress/e2e/foo_1.js', + // 'cypress/e2e/foo_2.js', + // 'cypress/e2e/bar_1.js' + // ]); + let bsConfig = { + run_settings: { + cypressTestSuiteType: 'CYPRESS_V10_AND_ABOVE_TYPE', + specs: 'cypress/e2e/foo*.js', + cypressProjectDir: 'cypressProjectDir', + exclude: 'exclude', + }, + }; + + const result = utils.getNumberOfSpecFiles(bsConfig, {}, null); + console.log(result) + expect(result.length).to.eql(1); + expect(result[0].endsWith('cypress/e2e/foo_1.js')).to.eql(true); + sinon.assert.alwaysCalledWithExactly(globStub, 'cypress/e2e/foo*.js', { + cwd: 'cypressProjectDir', + matchBase: true, + ignore: 'exclude', + }) + glob.sync.restore(); + }); + + it('should return under default folder if cypress v >= 10 and error while reading config file', () => { + let globStub = sinon.stub(glob, 'sync') + globStub.withArgs(`cypress/e2e/**/*.+(${constant.specFileTypes.join('|')})`) + .returns([ + 'cypress/e2e/foo_1.js', + 'cypress/e2e/foo_2.js', + 'cypress/e2e/bar_1.js' + ]); + let bsConfig = { + run_settings: { + cypressTestSuiteType: 'CYPRESS_V10_AND_ABOVE_TYPE', + cypressProjectDir: 'cypressProjectDir', + exclude: 'exclude', + }, + }; + + const result = utils.getNumberOfSpecFiles(bsConfig, {}, null); + console.log(result) + expect(result.length).to.eql(3); + expect(result[0].endsWith('cypress/e2e/foo_1.js')).to.eql(true); + sinon.assert.calledWithExactly(globStub, `cypress/e2e/**/*.+(${constant.specFileTypes.join('|')})`, { + cwd: 'cypressProjectDir', + matchBase: true, + ignore: 'exclude', + }) + glob.sync.restore(); + }); }); describe('warnSpecLimit', () => { From 844c9a971844183838fa024a1d5677bb0a337ec0 Mon Sep 17 00:00:00 2001 From: Prajwal Dhawarikar Date: Wed, 8 Mar 2023 10:36:41 +0530 Subject: [PATCH 08/16] Add UTs --- bin/helpers/readCypressConfigUtil.js | 13 +- test/unit/bin/commands/runs.js | 2 +- .../unit/bin/helpers/readCypressConfigUtil.js | 138 ++++++++++++++++++ 3 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 test/unit/bin/helpers/readCypressConfigUtil.js diff --git a/bin/helpers/readCypressConfigUtil.js b/bin/helpers/readCypressConfigUtil.js index 6665535d..c30472ef 100644 --- a/bin/helpers/readCypressConfigUtil.js +++ b/bin/helpers/readCypressConfigUtil.js @@ -1,7 +1,7 @@ "use strict"; const path = require("path"); const fs = require("fs"); -const { execSync } = require('child_process'); +const cp = require('child_process'); const config = require('./config'); const constants = require("./constants"); @@ -16,15 +16,14 @@ exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_module const cypress_config_filename = bsConfig.run_settings.cypress_config_filename const working_dir = path.dirname(cypress_config_filepath); const complied_js_dir = path.join(working_dir, config.compiledConfigJsDirName) - execSync(`rm -rf ${config.compiledConfigJsDirName}`, { cwd: working_dir }) - execSync(`mkdir ${config.compiledConfigJsDirName}`, { cwd: working_dir }) + cp.execSync(`rm -rf ${config.compiledConfigJsDirName}`, { cwd: working_dir }) + cp.execSync(`mkdir ${config.compiledConfigJsDirName}`, { cwd: working_dir }) let tsc_command = `NODE_PATH=${bstack_node_modules_path} ${bstack_node_modules_path}/typescript/bin/tsc --outDir ${complied_js_dir} --listEmittedFiles true --allowSyntheticDefaultImports --module commonjs --declaration false ${cypress_config_filepath}` let tsc_output - try { logger.debug(`Running: ${tsc_command}`) - tsc_output = execSync(tsc_command, { cwd: working_dir }) + tsc_output = cp.execSync(tsc_command, { cwd: working_dir }) } catch (err) { // error while compiling ts files logger.debug(err.message); @@ -55,7 +54,7 @@ exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_module exports.loadJsFile = (cypress_config_filepath, bstack_node_modules_path) => { const require_module_helper_path = `${__dirname}/requireModule.js` - execSync(`NODE_PATH=${bstack_node_modules_path} node ${require_module_helper_path} ${cypress_config_filepath}`) + cp.execSync(`NODE_PATH=${bstack_node_modules_path} node ${require_module_helper_path} ${cypress_config_filepath}`) const cypress_config = JSON.parse(fs.readFileSync(config.configJsonFileName).toString()) if (fs.existsSync(config.configJsonFileName)) { fs.unlinkSync(config.configJsonFileName) @@ -84,6 +83,6 @@ exports.readCypressConfigFile = (bsConfig) => { // TODO: Add instrumention if error occurred } finally { const working_dir = path.dirname(cypress_config_filepath); - execSync(`rm -rf ${config.compiledConfigJsDirName}`, { cwd: working_dir }) + cp.execSync(`rm -rf ${config.compiledConfigJsDirName}`, { cwd: working_dir }) } } diff --git a/test/unit/bin/commands/runs.js b/test/unit/bin/commands/runs.js index b89225ed..4f717f4d 100644 --- a/test/unit/bin/commands/runs.js +++ b/test/unit/bin/commands/runs.js @@ -808,7 +808,7 @@ describe("runs", () => { }); - describe.only("handle createBuild success", () => { + describe("handle createBuild success", () => { var sandbox; beforeEach(() => { diff --git a/test/unit/bin/helpers/readCypressConfigUtil.js b/test/unit/bin/helpers/readCypressConfigUtil.js new file mode 100644 index 00000000..6ebd1831 --- /dev/null +++ b/test/unit/bin/helpers/readCypressConfigUtil.js @@ -0,0 +1,138 @@ +'use strict'; +const chai = require("chai"), + expect = chai.expect, + sinon = require("sinon"), + EventEmitter = require('events'); + +const logger = require("../../../../bin/helpers/logger").winstonLogger; + +const cp = require("child_process"); +const fs = require("fs"); +const rewire = require("rewire"); +const readCypressConfigUtil = require("../../../../bin/helpers/readCypressConfigUtil"); + +logger.transports["console.info"].silent = true; + + +describe("readCypressConfigUtil", () => { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + sinon.restore(); + }); + + describe('detectLanguage', () => { + it('should return file extension', () => { + const result = readCypressConfigUtil.detectLanguage('cypress.config.ts'); + expect(result).to.eql('ts'); + }); + + it('should return file js extension if not matched with defined ones', () => { + const result = readCypressConfigUtil.detectLanguage('cypress.config.mts'); + expect(result).to.eql('js'); + }); + }); + + describe('loadJsFile', () => { + it('should load js file', () => { + sandbox.stub(cp, "execSync").returns("random string"); + const readFileSyncStub = sandbox.stub(fs, 'readFileSync').returns('{"e2e": {}}'); + const existsSyncStub = sandbox.stub(fs, 'existsSync').returns(true); + const unlinkSyncSyncStub = sandbox.stub(fs, 'unlinkSync'); + + const result = readCypressConfigUtil.loadJsFile('path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); + + expect(result).to.eql({ e2e: {} }); + sinon.assert.calledOnce(readFileSyncStub); + sinon.assert.calledOnce(unlinkSyncSyncStub); + sinon.assert.calledOnce(existsSyncStub); + }); + }); + + describe('convertTsConfig', () => { + it('should compile cypress.config.ts to cypress.config.js', () => { + const bsConfig = { + run_settings: { + cypressConfigFilePath: 'path/to/cypress.config.ts', + cypress_config_filename: 'cypress.config.ts' + } + }; + sandbox.stub(cp, "execSync").returns("TSFILE: path/to/compiled/cypress.config.js"); + + const result = readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); + + expect(result).to.eql('path/to/compiled/cypress.config.js'); + }); + + it('should return null if compilation fails', () => { + const bsConfig = { + run_settings: { + cypressConfigFilePath: 'path/to/cypress.config.ts', + cypress_config_filename: 'cypress.config.ts' + } + }; + sandbox.stub(cp, "execSync").returns("Error: some error\n"); + + const result = readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); + + expect(result).to.eql(null); + }); + + it('should compile cypress.config.ts to cypress.config.js if unrelevant error', () => { + const bsConfig = { + run_settings: { + cypressConfigFilePath: 'path/to/folder/cypress.config.ts', + cypress_config_filename: 'cypress.config.ts' + } + }; + const execSyncStub = sandbox.stub(cp, "execSync") + execSyncStub + .withArgs(`NODE_PATH=path/to/tmpBstackPackages path/to/tmpBstackPackages/typescript/bin/tsc --outDir path/to/tmpBstackCompiledJs --listEmittedFiles true --allowSyntheticDefaultImports --module commonjs --declaration false path/to/cypress.config.ts`, { cwd: 'path/to' }) + .throws({ + output: Buffer.from("Error: Some Error \n TSFILE: path/to/compiled/cypress.config.js") + }); + + const result = readCypressConfigUtil.convertTsConfig(bsConfig, 'path/to/cypress.config.ts', 'path/to/tmpBstackPackages'); + + expect(result).to.eql('path/to/compiled/cypress.config.js'); + }); + }); + + describe('readCypressConfigFile', () => { + it('should read js file', () => { + const bsConfig = { + run_settings: { + cypressConfigFilePath: 'path/to/cypress.config.js', + cypress_config_filename: 'cypress.config.js' + } + }; + sandbox.stub(readCypressConfigUtil, 'loadJsFile').returns({e2e: {}}); + sandbox.stub(cp, 'execSync'); + + const result = readCypressConfigUtil.readCypressConfigFile(bsConfig); + + expect(result).to.eql({ e2e: {} }); + }); + + it('should read ts file', () => { + const bsConfig = { + run_settings: { + cypressConfigFilePath: 'path/to/cypress.config.ts', + cypress_config_filename: 'cypress.config.ts' + } + }; + sandbox.stub(readCypressConfigUtil, 'convertTsConfig').returns('path/to/compiled/cypress.config.js'); + sandbox.stub(readCypressConfigUtil, 'loadJsFile').returns({e2e: {}}); + sandbox.stub(cp, 'execSync'); + + const result = readCypressConfigUtil.readCypressConfigFile(bsConfig); + + expect(result).to.eql({ e2e: {} }); + }); + }); +}); From d9e8c022fe23f2a3b9f4949987054d52b72054c9 Mon Sep 17 00:00:00 2001 From: Prajwal Dhawarikar Date: Wed, 8 Mar 2023 11:12:00 +0530 Subject: [PATCH 09/16] Add instrumentation if cypress config read fails --- bin/helpers/readCypressConfigUtil.js | 16 ++++++++++--- .../unit/bin/helpers/readCypressConfigUtil.js | 24 ++++++++++++++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/bin/helpers/readCypressConfigUtil.js b/bin/helpers/readCypressConfigUtil.js index c30472ef..56066c8c 100644 --- a/bin/helpers/readCypressConfigUtil.js +++ b/bin/helpers/readCypressConfigUtil.js @@ -5,6 +5,7 @@ const cp = require('child_process'); const config = require('./config'); const constants = require("./constants"); +const utils = require("./utils"); const logger = require('./logger').winstonLogger; exports.detectLanguage = (cypress_config_filename) => { @@ -78,9 +79,18 @@ exports.readCypressConfigFile = (bsConfig) => { return this.loadJsFile(compiled_cypress_config_filepath, bstack_node_modules_path) } } catch (error) { - logger.error(`Error while reading cypress config: ${error.message}`) - - // TODO: Add instrumention if error occurred + const errorMessage = `Error while reading cypress config: ${error.message}` + const errorCode = 'cypress_config_file_read_failed' + logger.error(errorMessage) + utils.sendUsageReport( + bsConfig, + null, + errorMessage, + constants.messageTypes.WARNING, + errorCode, + null, + null + ) } finally { const working_dir = path.dirname(cypress_config_filepath); cp.execSync(`rm -rf ${config.compiledConfigJsDirName}`, { cwd: working_dir }) diff --git a/test/unit/bin/helpers/readCypressConfigUtil.js b/test/unit/bin/helpers/readCypressConfigUtil.js index 6ebd1831..2a8d8283 100644 --- a/test/unit/bin/helpers/readCypressConfigUtil.js +++ b/test/unit/bin/helpers/readCypressConfigUtil.js @@ -8,7 +8,7 @@ const logger = require("../../../../bin/helpers/logger").winstonLogger; const cp = require("child_process"); const fs = require("fs"); -const rewire = require("rewire"); +const utils = require("../../../../bin/helpers/utils"); const readCypressConfigUtil = require("../../../../bin/helpers/readCypressConfigUtil"); logger.transports["console.info"].silent = true; @@ -134,5 +134,27 @@ describe("readCypressConfigUtil", () => { expect(result).to.eql({ e2e: {} }); }); + + it('should handle error if any error occurred', () => { + const bsConfig = { + run_settings: { + cypressConfigFilePath: 'path/to/cypress.config.js', + cypress_config_filename: 'cypress.config.js' + } + }; + sandbox.stub(readCypressConfigUtil, 'loadJsFile').throws(new Error("Some error")); + const sendUsageReportStub = sandbox.stub(utils, 'sendUsageReport'); + sandbox.stub(cp, 'execSync'); + + const result = readCypressConfigUtil.readCypressConfigFile(bsConfig); + + expect(result).to.eql(undefined); + sinon.assert.calledWithExactly(sendUsageReportStub, { + run_settings: { + cypressConfigFilePath: 'path/to/cypress.config.js', + cypress_config_filename: 'cypress.config.js' + } + }, null, 'Error while reading cypress config: Some error', 'warning','cypress_config_file_read_failed', null, null) + }); }); }); From 0263f33a9b7e09dc19059635d78228526a91fcc5 Mon Sep 17 00:00:00 2001 From: Prajwal Dhawarikar Date: Wed, 8 Mar 2023 11:14:08 +0530 Subject: [PATCH 10/16] remove console logs --- test/unit/bin/helpers/utils.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index 273d2f63..ad8d175c 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -2396,7 +2396,6 @@ describe('utils', () => { }; const result = utils.getNumberOfSpecFiles(bsConfig, {}, { e2e: {}}); - console.log(result) expect(result.length).to.eql(1); expect(result[0].endsWith('cypress/e2e/foo_1.js')).to.eql(true); sinon.assert.calledTwice(globStub) @@ -2519,7 +2518,6 @@ describe('utils', () => { }; const result = utils.getNumberOfSpecFiles(bsConfig, {}, null); - console.log(result) expect(result.length).to.eql(1); expect(result[0].endsWith('cypress/e2e/foo_1.js')).to.eql(true); sinon.assert.alwaysCalledWithExactly(globStub, 'cypress/e2e/foo*.js', { @@ -2547,7 +2545,6 @@ describe('utils', () => { }; const result = utils.getNumberOfSpecFiles(bsConfig, {}, null); - console.log(result) expect(result.length).to.eql(3); expect(result[0].endsWith('cypress/e2e/foo_1.js')).to.eql(true); sinon.assert.calledWithExactly(globStub, `cypress/e2e/**/*.+(${constant.specFileTypes.join('|')})`, { From da2c8c174db2c6d80d21758d8e46f3f211490699 Mon Sep 17 00:00:00 2001 From: Prajwal Dhawarikar Date: Mon, 13 Mar 2023 17:46:28 +0530 Subject: [PATCH 11/16] Add warn message --- bin/helpers/capabilityHelper.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/helpers/capabilityHelper.js b/bin/helpers/capabilityHelper.js index 4e6cb2bd..eebe4317 100644 --- a/bin/helpers/capabilityHelper.js +++ b/bin/helpers/capabilityHelper.js @@ -286,6 +286,9 @@ const validate = (bsConfig, args) => { if (!Utils.isUndefined(bsConfig.run_settings.nodeVersion) && typeof(bsConfig.run_settings.nodeVersion) === 'string' && !bsConfig.run_settings.nodeVersion.match(/^(\d+\.)?(\d+\.)?(\*|\d+)$/)) logger.warn(Constants.validationMessages.NODE_VERSION_PARSING_ERROR); + if(!Utils.isUndefined(cypressConfigFile.port)) { + logger.warn(`The requested port number ${cypressConfigFile.port} is ignored. The default BrowserStack port will be used for this execution`); + } resolve(cypressConfigFile); }); } From 81028314560cd32fd316c2b690c1ec6025d9544d Mon Sep 17 00:00:00 2001 From: Prajwal Dhawarikar Date: Tue, 14 Mar 2023 14:04:20 +0530 Subject: [PATCH 12/16] Added UT --- test/unit/bin/helpers/capabilityHelper.js | 24 ++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/test/unit/bin/helpers/capabilityHelper.js b/test/unit/bin/helpers/capabilityHelper.js index 59beed8b..56ade625 100644 --- a/test/unit/bin/helpers/capabilityHelper.js +++ b/test/unit/bin/helpers/capabilityHelper.js @@ -962,11 +962,20 @@ describe("capabilityHelper.js", () => { run_settings: { cypress_proj_dir: "random path", cypressConfigFilePath: "random path", - cypressProjectDir: "random path" + cypressProjectDir: "random path", + cypress_config_filename: "cypress.json", + spec_timeout: 10, + cypressTestSuiteType: Constants.CYPRESS_V9_AND_OLDER_TYPE }, connection_settings: {local: false} }; + loggerWarningSpy = sinon.stub(logger, 'warn'); + }); + + afterEach(function() { + loggerWarningSpy.restore(); }); + it("validate cypress json is present", () => { //Stub for cypress json validation sinon.stub(fs, 'existsSync').returns(false); @@ -1046,6 +1055,19 @@ describe("capabilityHelper.js", () => { }); }); + it("should warn if port is passed in cypress config file", async () => { + //Stub for cypress json validation + sinon.stub(fs, 'existsSync').returns(true); + sinon.stub(fs, 'readFileSync').returns('{ "port": 23455}'); + + await capabilityHelper + .validate(bsConfig, { parallels: 2 }) + + sinon.assert.calledWith(loggerWarningSpy, `The requested port number 23455 is ignored. The default BrowserStack port will be used for this execution`); + fs.existsSync.restore(); + fs.readFileSync.restore(); + }); + context("cypress config file set to false", () => { beforeEach(function() { readFileSpy = sinon.stub(fs, 'readFileSync'); From 5e5cdd2c6cc1557ceb83d5528d3b3f130d04b7c4 Mon Sep 17 00:00:00 2001 From: Prajwal Dhawarikar Date: Tue, 14 Mar 2023 14:15:09 +0530 Subject: [PATCH 13/16] Add warn in constants --- bin/helpers/capabilityHelper.js | 2 +- bin/helpers/constants.js | 4 +++- test/unit/bin/helpers/capabilityHelper.js | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/helpers/capabilityHelper.js b/bin/helpers/capabilityHelper.js index eebe4317..06c9ebee 100644 --- a/bin/helpers/capabilityHelper.js +++ b/bin/helpers/capabilityHelper.js @@ -287,7 +287,7 @@ const validate = (bsConfig, args) => { logger.warn(Constants.validationMessages.NODE_VERSION_PARSING_ERROR); if(!Utils.isUndefined(cypressConfigFile.port)) { - logger.warn(`The requested port number ${cypressConfigFile.port} is ignored. The default BrowserStack port will be used for this execution`); + logger.warn(Constants.userMessages.CYPRESS_PORT_WARNING.replace("", cypressConfigFile.port)); } resolve(cypressConfigFile); }); diff --git a/bin/helpers/constants.js b/bin/helpers/constants.js index cdb19579..eb032384 100644 --- a/bin/helpers/constants.js +++ b/bin/helpers/constants.js @@ -113,7 +113,9 @@ const userMessages = { SPEC_LIMIT_SUCCESS_MESSAGE: "Spec timeout specified as minutes. If any of your specs exceed the specified time limit, it would be forcibly killed by BrowserStack", NO_CONNECTION_WHILE_UPDATING_UPLOAD_PROGRESS_BAR: - "Unable to determine zip upload progress due to undefined/null connection request" + "Unable to determine zip upload progress due to undefined/null connection request", + CYPRESS_PORT_WARNING: + "The requested port number is ignored. The default BrowserStack port will be used for this execution" }; const validationMessages = { diff --git a/test/unit/bin/helpers/capabilityHelper.js b/test/unit/bin/helpers/capabilityHelper.js index 56ade625..b8f7a8b7 100644 --- a/test/unit/bin/helpers/capabilityHelper.js +++ b/test/unit/bin/helpers/capabilityHelper.js @@ -1063,7 +1063,7 @@ describe("capabilityHelper.js", () => { await capabilityHelper .validate(bsConfig, { parallels: 2 }) - sinon.assert.calledWith(loggerWarningSpy, `The requested port number 23455 is ignored. The default BrowserStack port will be used for this execution`); + sinon.assert.calledWith(loggerWarningSpy, Constants.userMessages.CYPRESS_PORT_WARNING.replace('', 23455)); fs.existsSync.restore(); fs.readFileSync.restore(); }); From 57e01b669fd930df93535f2fae4cab8919f1dbb8 Mon Sep 17 00:00:00 2001 From: Prajwal Dhawarikar Date: Wed, 15 Mar 2023 18:15:44 +0530 Subject: [PATCH 14/16] Add fallback logic --- bin/helpers/utils.js | 17 +++++++++++++-- test/unit/bin/helpers/utils.js | 39 ++++++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/bin/helpers/utils.js b/bin/helpers/utils.js index 80a19be6..5b593d29 100644 --- a/bin/helpers/utils.js +++ b/bin/helpers/utils.js @@ -990,6 +990,7 @@ exports.getNumberOfSpecFiles = (bsConfig, args, cypressConfig) => { let testFolderPath let globCypressConfigSpecPatterns = [] let globSearchPattern = this.sanitizeSpecsPattern(bsConfig.run_settings.specs); + let ignoreFiles = args.exclude || bsConfig.run_settings.exclude if (bsConfig.run_settings.cypressTestSuiteType === Constants.CYPRESS_V10_AND_ABOVE_TYPE) { defaultSpecFolder = Constants.DEFAULT_CYPRESS_10_SPEC_PATH @@ -1002,8 +1003,21 @@ exports.getNumberOfSpecFiles = (bsConfig, args, cypressConfig) => { globCypressConfigSpecPatterns = [`${testFolderPath}/**/*.+(${Constants.specFileTypes.join("|")})`] } } else { - // if not able read cypress config, use bstack specs arg(existing logic, which is not correct) + // if not able read cypress config + // use bstack specs arg(existing logic, which is not correct) if bstack specs arg not provided check for cypress/e2e folder globCypressConfigSpecPatterns = globSearchPattern ? [globSearchPattern] : [`${testFolderPath}/**/*.+(${Constants.specFileTypes.join("|")})`] + const filesMatched = []; + globCypressConfigSpecPatterns.forEach(specPattern => { + filesMatched.push( + ...glob.sync(specPattern, { + cwd: bsConfig.run_settings.cypressProjectDir, matchBase: true, ignore: ignoreFiles + }) + ); + }); + if (!filesMatched.length) { + // if no files found under cypress/e2e check for cypress/integration + globCypressConfigSpecPatterns = [`${Constants.DEFAULT_CYPRESS_SPEC_PATH}/**/*.+(${Constants.specFileTypes.join("|")})`] + } } } else { defaultSpecFolder = Constants.DEFAULT_CYPRESS_SPEC_PATH @@ -1022,7 +1036,6 @@ exports.getNumberOfSpecFiles = (bsConfig, args, cypressConfig) => { } } - let ignoreFiles = args.exclude || bsConfig.run_settings.exclude let fileMatchedWithConfigSpecPattern = [] globCypressConfigSpecPatterns.forEach(specPattern => { fileMatchedWithConfigSpecPattern.push( diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index ad8d175c..edfd9c30 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -2502,12 +2502,6 @@ describe('utils', () => { let globStub = sinon.stub(glob, 'sync') globStub.withArgs('cypress/e2e/foo*.js') .returns(['cypress/e2e/foo_1.js']); - // globStub.withArgs(`cypress/e2e/**/*.+(${constant.specFileTypes.join('|')})`) - // .returns([ - // 'cypress/e2e/foo_1.js', - // 'cypress/e2e/foo_2.js', - // 'cypress/e2e/bar_1.js' - // ]); let bsConfig = { run_settings: { cypressTestSuiteType: 'CYPRESS_V10_AND_ABOVE_TYPE', @@ -2528,7 +2522,7 @@ describe('utils', () => { glob.sync.restore(); }); - it('should return under default folder if cypress v >= 10 and error while reading config file', () => { + it('should return files under default e2e folder if cypress v >= 10 and error while reading config file', () => { let globStub = sinon.stub(glob, 'sync') globStub.withArgs(`cypress/e2e/**/*.+(${constant.specFileTypes.join('|')})`) .returns([ @@ -2554,6 +2548,37 @@ describe('utils', () => { }) glob.sync.restore(); }); + + it('should return files under integration folder if cypress v >= 10, no spec arg in bstack.json and error while reading config file and no files under cypress/e2e', () => { + let globStub = sinon.stub(glob, 'sync') + globStub.withArgs(`cypress/e2e/**/*.+(${constant.specFileTypes.join('|')})`) + .returns([]); + globStub.withArgs(`cypress/integration/**/*.+(${constant.specFileTypes.join('|')})`) + .returns([ + 'cypress/integration/foo_1.js', + 'cypress/integration/foo_2.js', + 'cypress/integration/bar_1.js' + ]); + let bsConfig = { + run_settings: { + cypressTestSuiteType: 'CYPRESS_V10_AND_ABOVE_TYPE', + cypressProjectDir: 'cypressProjectDir', + exclude: 'exclude', + }, + }; + + const result = utils.getNumberOfSpecFiles(bsConfig, {}, null); + expect(result.length).to.eql(3); + expect(result[0].endsWith('cypress/integration/foo_1.js')).to.eql(true); + expect(result[1].endsWith('cypress/integration/foo_2.js')).to.eql(true); + expect(result[2].endsWith('cypress/integration/bar_1.js')).to.eql(true); + sinon.assert.calledWithExactly(globStub, `cypress/e2e/**/*.+(${constant.specFileTypes.join('|')})`, { + cwd: 'cypressProjectDir', + matchBase: true, + ignore: 'exclude', + }) + glob.sync.restore(); + }); }); describe('warnSpecLimit', () => { From 8d17b510433c88d604ecf07b496abd269a462326 Mon Sep 17 00:00:00 2001 From: Prajwal Dhawarikar Date: Thu, 16 Mar 2023 18:28:57 +0530 Subject: [PATCH 15/16] block for get number of specs --- bin/commands/runs.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/commands/runs.js b/bin/commands/runs.js index 03f29b54..6d2a7808 100644 --- a/bin/commands/runs.js +++ b/bin/commands/runs.js @@ -128,8 +128,11 @@ module.exports = function run(args, rawArgs) { logger.debug("Completed configs validation"); markBlockStart('preArchiveSteps'); logger.debug("Started pre-archive steps"); + //get the number of spec files + markBlockStart('getNumberOfSpecFiles'); let specFiles = utils.getNumberOfSpecFiles(bsConfig, args, cypressConfigFile); + markBlockEnd('getNumberOfSpecFiles'); bsConfig['run_settings']['video_config'] = utils.getVideoConfig(cypressConfigFile); From 649bf75424a186bc0962c66ca8bc021feca41e84 Mon Sep 17 00:00:00 2001 From: Prajwal Dhawarikar Date: Wed, 22 Mar 2023 13:17:25 +0530 Subject: [PATCH 16/16] fix AFD-2103 --- bin/commands/runs.js | 4 ++-- bin/helpers/packageInstaller.js | 6 +++++- test/unit/bin/helpers/packageInstaller.js | 25 +++++++++++++++++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/bin/commands/runs.js b/bin/commands/runs.js index 03f29b54..d1ad1bb8 100644 --- a/bin/commands/runs.js +++ b/bin/commands/runs.js @@ -99,7 +99,7 @@ module.exports = function run(args, rawArgs) { // set the no-wrap utils.setNoWrap(bsConfig, args); - await packageInstaller.packageSetupAndInstaller(bsConfig, config.packageDirName, {markBlockStart, markBlockEnd}); + const { packagesInstalled } = await packageInstaller.packageSetupAndInstaller(bsConfig, config.packageDirName, {markBlockStart, markBlockEnd}); // set build tag caps utils.setBuildTags(bsConfig, args); @@ -155,7 +155,7 @@ module.exports = function run(args, rawArgs) { logger.debug("Started caching npm dependencies."); markBlockStart('zip.packageInstaller'); - return packageInstaller.packageWrapper(bsConfig, config.packageDirName, config.packageFileName, md5data, {markBlockStart, markBlockEnd}).then(function (packageData) { + return packageInstaller.packageWrapper(bsConfig, config.packageDirName, config.packageFileName, md5data, {markBlockStart, markBlockEnd}, packagesInstalled).then(function (packageData) { logger.debug("Completed caching npm dependencies.") markBlockEnd('zip.packageInstaller'); diff --git a/bin/helpers/packageInstaller.js b/bin/helpers/packageInstaller.js index b0896a88..35850237 100644 --- a/bin/helpers/packageInstaller.js +++ b/bin/helpers/packageInstaller.js @@ -157,11 +157,15 @@ const packageSetupAndInstaller = (bsConfig, packageDir, instrumentBlocks) => { }) } -const packageWrapper = (bsConfig, packageDir, packageFile, md5data, instrumentBlocks) => { +const packageWrapper = (bsConfig, packageDir, packageFile, md5data, instrumentBlocks, packagesInstalled) => { return new Promise(function (resolve) { let obj = { packageArchieveCreated: false }; + if (!packagesInstalled) { + logger.debug("Skipping the caching of npm packages since package installed failed") + return resolve(obj); + } if (md5data.packageUrlPresent || !utils.isTrueString(bsConfig.run_settings.cache_dependencies)) { logger.debug("Skipping the caching of npm packages since BrowserStack has already cached your npm dependencies that have not changed since the last run.") return resolve(obj); diff --git a/test/unit/bin/helpers/packageInstaller.js b/test/unit/bin/helpers/packageInstaller.js index 70f03b68..e45a8f46 100644 --- a/test/unit/bin/helpers/packageInstaller.js +++ b/test/unit/bin/helpers/packageInstaller.js @@ -505,7 +505,7 @@ describe("packageInstaller", () => { markBlockStart: sinon.stub(), markBlockEnd: sinon.stub() } - return packageWrapperrewire(bsConfig, packageDir, packageFile, md5data, instrumentBlocks) + return packageWrapperrewire(bsConfig, packageDir, packageFile, md5data, instrumentBlocks, true) .then((data) => { chai.assert.deepEqual(data, {packageArchieveCreated: true}); }) @@ -530,7 +530,7 @@ describe("packageInstaller", () => { markBlockStart: sinon.stub(), markBlockEnd: sinon.stub() } - return packageWrapperrewire(bsConfig, packageDir, packageFile, md5data, instrumentBlocks) + return packageWrapperrewire(bsConfig, packageDir, packageFile, md5data, instrumentBlocks, true) .then((data) => { chai.assert.deepEqual(data, { packageArchieveCreated: false, error: 'test error stack' }); }) @@ -538,5 +538,26 @@ describe("packageInstaller", () => { chai.assert.fail("Promise error"); }); }); + + it("should reject with if issue in package install failed", () => { + let packageWrapperrewire = packageInstaller.__get__('packageWrapper'); + let bsConfig = { + run_settings: { + cache_dependencies: true + } + }; + let md5data = {}; + let instrumentBlocks = { + markBlockStart: sinon.stub(), + markBlockEnd: sinon.stub() + } + return packageWrapperrewire(bsConfig, packageDir, packageFile, md5data, instrumentBlocks, false) + .then((data) => { + chai.assert.deepEqual(data, { packageArchieveCreated: false }); + }) + .catch((_error) => { + chai.assert.fail("Promise error"); + }); + }); }); });