diff --git a/bin/commands/runs.js b/bin/commands/runs.js index 74604378..55c4125d 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); + const { packagesInstalled } = await packageInstaller.packageSetupAndInstaller(bsConfig, config.packageDirName, {markBlockStart, markBlockEnd}); // set build tag caps utils.setBuildTags(bsConfig, args); @@ -129,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); @@ -140,6 +142,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'); @@ -153,7 +158,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/capabilityHelper.js b/bin/helpers/capabilityHelper.js index 09fe2546..06c9ebee 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,30 @@ 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); + logger.debug(`Validating ${bsConfig.run_settings.cypress_config_filename}`); + try { + 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 } - // 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); + // TODO: add validations for cypress_config_filename + } else { + let cypressJsonContent = fs.readFileSync(cypressConfigFilePath); + cypressConfigFile = JSON.parse(cypressJsonContent); } - } catch(error){ - reject(Constants.validationMessages.INVALID_CYPRESS_JSON) + + // 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 @@ -289,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(Constants.userMessages.CYPRESS_PORT_WARNING.replace("", cypressConfigFile.port)); + } resolve(cypressConfigFile); }); } diff --git a/bin/helpers/config.js b/bin/helpers/config.js index 967f1617..bd790d05 100644 --- a/bin/helpers/config.js +++ b/bin/helpers/config.js @@ -24,5 +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/constants.js b/bin/helpers/constants.js index 28ecf056..eb032384 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: @@ -111,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 = { @@ -367,6 +371,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; @@ -419,6 +424,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, @@ -432,6 +439,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, @@ -448,5 +456,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..35850237 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,34 @@ 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, 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); + } + 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 +188,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..56066c8c --- /dev/null +++ b/bin/helpers/readCypressConfigUtil.js @@ -0,0 +1,98 @@ +"use strict"; +const path = require("path"); +const fs = require("fs"); +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) => { + 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) + 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 = cp.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` + 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) + } + return cypress_config +} + +exports.readCypressConfigFile = (bsConfig) => { + const cypress_config_filepath = path.resolve(bsConfig.run_settings.cypressConfigFilePath) + try { + const cypress_config_filename = bsConfig.run_settings.cypress_config_filename + const bstack_node_modules_path = `${path.resolve(config.packageDirName)}/node_modules` + const conf_lang = this.detectLanguage(cypress_config_filename) + + logger.debug(`cypress config path: ${cypress_config_filepath}`); + + if (conf_lang == 'js' || conf_lang == 'cjs') { + 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) { + 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/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 c9247ae7..5b593d29 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,11 +986,79 @@ exports.getFilesToIgnore = (runSettings, excludeFiles, logging = true) => { } exports.getNumberOfSpecFiles = (bsConfig, args, cypressConfig) => { - let testFolderPath = cypressConfig.integrationFolder || Constants.DEFAULT_CYPRESS_SPEC_PATH; - 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}`); + let defaultSpecFolder + 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 + 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 { + globCypressConfigSpecPatterns = [`${testFolderPath}/**/*.+(${Constants.specFileTypes.join("|")})`] + } + } else { + // 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 + 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}/${cypressConfig.testFiles}`] + } + } else { + globCypressConfigSpecPatterns = [`${testFolderPath}/**/*.+(${Constants.specFileTypes.join("|")})`] + } + } + + let fileMatchedWithConfigSpecPattern = [] + globCypressConfigSpecPatterns.forEach(specPattern => { + fileMatchedWithConfigSpecPattern.push( + ...glob.sync(specPattern, { + cwd: bsConfig.run_settings.cypressProjectDir, matchBase: true, ignore: ignoreFiles + }) + ); + }); + 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 + }); + fileMatchedWithBstackSpecPattern = fileMatchedWithBstackSpecPattern.map((file) => path.resolve(bsConfig.run_settings.cypressProjectDir, file)) + + files = fileMatchedWithBstackSpecPattern.filter(file => fileMatchedWithConfigSpecPattern.includes(file)) + } else { + files = fileMatchedWithConfigSpecPattern; + } + + 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) { diff --git a/test/unit/bin/commands/runs.js b/test/unit/bin/commands/runs.js index 0f093a7f..4f717f4d 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( diff --git a/test/unit/bin/helpers/capabilityHelper.js b/test/unit/bin/helpers/capabilityHelper.js index 59beed8b..b8f7a8b7 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, Constants.userMessages.CYPRESS_PORT_WARNING.replace('', 23455)); + fs.existsSync.restore(); + fs.readFileSync.restore(); + }); + context("cypress config file set to false", () => { beforeEach(function() { readFileSpy = sinon.stub(fs, 'readFileSync'); diff --git a/test/unit/bin/helpers/packageInstaller.js b/test/unit/bin/helpers/packageInstaller.js index 943c0c9b..e45a8f46 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'); @@ -446,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}); }) @@ -456,11 +515,9 @@ 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 + packageArchiver: setupPackageArchiverErrorStub }); let packageWrapperrewire = packageInstaller.__get__('packageWrapper'); let bsConfig = { @@ -473,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' }); }) @@ -481,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"); + }); + }); }); }); diff --git a/test/unit/bin/helpers/readCypressConfigUtil.js b/test/unit/bin/helpers/readCypressConfigUtil.js new file mode 100644 index 00000000..2a8d8283 --- /dev/null +++ b/test/unit/bin/helpers/readCypressConfigUtil.js @@ -0,0 +1,160 @@ +'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 utils = require("../../../../bin/helpers/utils"); +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: {} }); + }); + + 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) + }); + }); +}); diff --git a/test/unit/bin/helpers/utils.js b/test/unit/bin/helpers/utils.js index 4ee15936..edfd9c30 100644 --- a/test/unit/bin/helpers/utils.js +++ b/test/unit/bin/helpers/utils.js @@ -2256,71 +2256,329 @@ describe('utils', () => { }); describe('getNumberOfSpecFiles', () => { - it('glob search pattern should be equal to bsConfig.run_settings.specs', () => { - let getNumberOfSpecFilesStub = sinon.stub(glob, 'sync'); + 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'); + 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, {}, {}); + 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(); + }); - sinon.assert.calledOnceWithExactly( - getNumberOfSpecFilesStub, - `cypress/integration/**/*.+(${constant.specFileTypes.join('|')})`, - { + 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', + }, + }; + + 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 >= 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', }, }; - utils.getNumberOfSpecFiles(bsConfig, {}, { integrationFolder: 'specs' }); + const result = utils.getNumberOfSpecFiles(bsConfig, {}, { e2e: {}}); + 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('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', + }, + }; - sinon.assert.calledOnceWithExactly( - getNumberOfSpecFilesStub, - `specs/**/*.+(${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']); + 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); + 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 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([ + '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); + 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(); + }); + + 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', () => { @@ -3295,8 +3553,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", () => { @@ -3304,8 +3561,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", () => { @@ -3313,8 +3569,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,