Skip to content

Commit 59f1a3a

Browse files
committed
Merge branch 'master' of github.com:roshan04/browserstack-cypress-cli
2 parents e04d4c3 + 3179953 commit 59f1a3a

17 files changed

+913
-104
lines changed

bin/commands/runs.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ module.exports = function run(args, rawArgs) {
6969
// set cypress geo location
7070
utils.setGeolocation(bsConfig, args);
7171

72-
// set spec timeout
72+
// set spec timeout
7373
utils.setSpecTimeout(bsConfig, args);
7474

7575
// accept the specs list from command line if provided
@@ -84,6 +84,9 @@ module.exports = function run(args, rawArgs) {
8484
//accept the local from env variable if provided
8585
utils.setLocal(bsConfig, args);
8686

87+
//set network logs
88+
utils.setNetworkLogs(bsConfig);
89+
8790
// set Local Mode (on-demand/ always-on)
8891
utils.setLocalMode(bsConfig, args);
8992

@@ -99,8 +102,7 @@ module.exports = function run(args, rawArgs) {
99102
// set the no-wrap
100103
utils.setNoWrap(bsConfig, args);
101104

102-
// set record feature caps
103-
utils.setRecordCaps(bsConfig, args);
105+
const { packagesInstalled } = await packageInstaller.packageSetupAndInstaller(bsConfig, config.packageDirName, {markBlockStart, markBlockEnd});
104106

105107
// set build tag caps
106108
utils.setBuildTags(bsConfig, args);
@@ -129,8 +131,11 @@ module.exports = function run(args, rawArgs) {
129131
logger.debug("Completed configs validation");
130132
markBlockStart('preArchiveSteps');
131133
logger.debug("Started pre-archive steps");
134+
132135
//get the number of spec files
136+
markBlockStart('getNumberOfSpecFiles');
133137
let specFiles = utils.getNumberOfSpecFiles(bsConfig, args, cypressConfigFile);
138+
markBlockEnd('getNumberOfSpecFiles');
134139

135140
bsConfig['run_settings']['video_config'] = utils.getVideoConfig(cypressConfigFile);
136141

@@ -140,6 +145,9 @@ module.exports = function run(args, rawArgs) {
140145
// accept the number of parallels
141146
utils.setParallels(bsConfig, args, specFiles.length);
142147

148+
// set record feature caps
149+
utils.setRecordCaps(bsConfig, args, cypressConfigFile);
150+
143151
// warn if specFiles cross our limit
144152
utils.warnSpecLimit(bsConfig, args, specFiles, rawArgs, buildReportData);
145153
markBlockEnd('preArchiveSteps');
@@ -153,7 +161,7 @@ module.exports = function run(args, rawArgs) {
153161

154162
logger.debug("Started caching npm dependencies.");
155163
markBlockStart('zip.packageInstaller');
156-
return packageInstaller.packageWrapper(bsConfig, config.packageDirName, config.packageFileName, md5data, {markBlockStart, markBlockEnd}).then(function (packageData) {
164+
return packageInstaller.packageWrapper(bsConfig, config.packageDirName, config.packageFileName, md5data, {markBlockStart, markBlockEnd}, packagesInstalled).then(function (packageData) {
157165
logger.debug("Completed caching npm dependencies.")
158166
markBlockEnd('zip.packageInstaller');
159167

@@ -409,8 +417,8 @@ module.exports = function run(args, rawArgs) {
409417
updateCheckInterval: 1000 * 60 * 60 * 24 * 7,
410418
});
411419

412-
// Checks for update on first run.
413-
// Set lastUpdateCheck to 0 to spawn the check update process as notifier sets this to Date.now() for preventing
420+
// Checks for update on first run.
421+
// Set lastUpdateCheck to 0 to spawn the check update process as notifier sets this to Date.now() for preventing
414422
// the check untill one interval period. It runs once.
415423
if (!notifier.disabled && Date.now() - notifier.config.get('lastUpdateCheck') < 50) {
416424
notifier.config.set('lastUpdateCheck', 0);

bin/helpers/capabilityHelper.js

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const fs = require('fs'),
22
path = require('path');
3+
const { readCypressConfigFile } = require('./readCypressConfigUtil');
34

45
const logger = require("./logger").winstonLogger,
56
Constants = require("./constants"),
@@ -194,34 +195,30 @@ const validate = (bsConfig, args) => {
194195
logger.debug(`Checking for cypress config file at ${cypressConfigFilePath}`);
195196
if (!fs.existsSync(cypressConfigFilePath) && bsConfig.run_settings.cypress_config_filename !== 'false') reject(Constants.validationMessages.INVALID_CYPRESS_CONFIG_FILE);
196197

197-
if (bsConfig.run_settings.cypressTestSuiteType === Constants.CYPRESS_V10_AND_ABOVE_TYPE) {
198-
logger.debug(`Validating ${bsConfig.run_settings.cypress_config_filename}`);
199-
// TODO: add validations for cypress_config_filename
200-
} else {
201-
logger.debug("Validating cypress.json");
202-
try {
203-
if (bsConfig.run_settings.cypress_config_filename !== 'false') {
204-
205-
if (bsConfig.run_settings.cypressTestSuiteType === Constants.CYPRESS_V10_AND_ABOVE_TYPE) {
206-
if (cypressConfigFilePath.endsWith("cypress.config.js")) {
207-
cypressConfigFile = require(cypressConfigFilePath);
208-
} else {
209-
cypressConfigFile = {};
210-
}
211-
} else {
212-
let cypressJsonContent = fs.readFileSync(cypressConfigFilePath);
213-
cypressConfigFile = JSON.parse(cypressJsonContent);
198+
logger.debug(`Validating ${bsConfig.run_settings.cypress_config_filename}`);
199+
try {
200+
if (bsConfig.run_settings.cypress_config_filename !== 'false') {
201+
if (bsConfig.run_settings.cypressTestSuiteType === Constants.CYPRESS_V10_AND_ABOVE_TYPE) {
202+
const completeCypressConfigFile = readCypressConfigFile(bsConfig)
203+
if (!Utils.isUndefined(completeCypressConfigFile)) {
204+
// check if cypress config was exported using export default
205+
cypressConfigFile = !Utils.isUndefined(completeCypressConfigFile.default) ? completeCypressConfigFile.default : completeCypressConfigFile
214206
}
215207

216-
// Cypress Json Base Url & Local true check
217-
if (!Utils.isUndefined(cypressConfigFile.baseUrl) && cypressConfigFile.baseUrl.includes("localhost") && !Utils.getLocalFlag(bsConfig.connection_settings)) reject(Constants.validationMessages.LOCAL_NOT_SET.replace("<baseUrlValue>", cypressConfigFile.baseUrl));
218-
219-
// Detect if the user is not using the right directory structure, and throw an error
220-
if (!Utils.isUndefined(cypressConfigFile.integrationFolder) && !Utils.isCypressProjDirValid(bsConfig.run_settings.cypressProjectDir,cypressConfigFile.integrationFolder)) reject(Constants.validationMessages.INCORRECT_DIRECTORY_STRUCTURE);
208+
// TODO: add validations for cypress_config_filename
209+
} else {
210+
let cypressJsonContent = fs.readFileSync(cypressConfigFilePath);
211+
cypressConfigFile = JSON.parse(cypressJsonContent);
221212
}
222-
} catch(error){
223-
reject(Constants.validationMessages.INVALID_CYPRESS_JSON)
213+
214+
// Cypress Json Base Url & Local true check
215+
if (!Utils.isUndefined(cypressConfigFile.baseUrl) && cypressConfigFile.baseUrl.includes("localhost") && !Utils.getLocalFlag(bsConfig.connection_settings)) reject(Constants.validationMessages.LOCAL_NOT_SET.replace("<baseUrlValue>", cypressConfigFile.baseUrl));
216+
217+
// Detect if the user is not using the right directory structure, and throw an error
218+
if (!Utils.isUndefined(cypressConfigFile.integrationFolder) && !Utils.isCypressProjDirValid(bsConfig.run_settings.cypressProjectDir,cypressConfigFile.integrationFolder)) reject(Constants.validationMessages.INCORRECT_DIRECTORY_STRUCTURE);
224219
}
220+
} catch(error){
221+
reject(Constants.validationMessages.INVALID_CYPRESS_JSON)
225222
}
226223

227224
//check if home_directory is present or not in user run_settings
@@ -289,6 +286,9 @@ const validate = (bsConfig, args) => {
289286
if (!Utils.isUndefined(bsConfig.run_settings.nodeVersion) && typeof(bsConfig.run_settings.nodeVersion) === 'string' && !bsConfig.run_settings.nodeVersion.match(/^(\d+\.)?(\d+\.)?(\*|\d+)$/))
290287
logger.warn(Constants.validationMessages.NODE_VERSION_PARSING_ERROR);
291288

289+
if(!Utils.isUndefined(cypressConfigFile.port)) {
290+
logger.warn(Constants.userMessages.CYPRESS_PORT_WARNING.replace("<x>", cypressConfigFile.port));
291+
}
292292
resolve(cypressConfigFile);
293293
});
294294
}

bin/helpers/config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,7 @@ config.packageFileName = "bstackPackages.tar.gz";
2424
config.packageDirName = "tmpBstackPackages";
2525
config.retries = 5;
2626
config.networkErrorExitCode = 2;
27+
config.compiledConfigJsDirName = 'tmpBstackCompiledJs'
28+
config.configJsonFileName = 'tmpCypressConfig.json'
2729

2830
module.exports = config;

bin/helpers/constants.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ const userMessages = {
4343
"There was some issue while checking if zip is already uploaded.",
4444
ZIP_DELETE_FAILED: "Could not delete tests.zip successfully.",
4545
ZIP_DELETED: "Deleted tests.zip successfully.",
46-
NPM_INSTALL_AND_UPLOAD:
47-
"Installing required dependencies and building the package to upload to BrowserStack",
46+
NPM_INSTALL:
47+
"Installing required dependencies",
48+
NPM_UPLOAD:
49+
"Building the package to upload to BrowserStack",
4850
NPM_DELETE_FAILED: "Could not delete the dependency packages.",
4951
NPM_DELETED: "Deleted dependency packages successfully.",
5052
API_DEPRECATED:
@@ -111,7 +113,9 @@ const userMessages = {
111113
SPEC_LIMIT_SUCCESS_MESSAGE:
112114
"Spec timeout specified as <x> minutes. If any of your specs exceed the specified time limit, it would be forcibly killed by BrowserStack",
113115
NO_CONNECTION_WHILE_UPDATING_UPLOAD_PROGRESS_BAR:
114-
"Unable to determine zip upload progress due to undefined/null connection request"
116+
"Unable to determine zip upload progress due to undefined/null connection request",
117+
CYPRESS_PORT_WARNING:
118+
"The requested port number <x> is ignored. The default BrowserStack port will be used for this execution"
115119
};
116120

117121
const validationMessages = {
@@ -313,6 +317,7 @@ const allowedFileTypes = [
313317
"tsx",
314318
"pfx",
315319
"cfr",
320+
"ico"
316321
];
317322

318323
const filesToIgnoreWhileUploading = [
@@ -367,6 +372,7 @@ const packageInstallerOptions = {
367372
const specFileTypes = ["js", "ts", "feature", "jsx", "coffee", "cjsx"];
368373

369374
const DEFAULT_CYPRESS_SPEC_PATH = "cypress/integration";
375+
const DEFAULT_CYPRESS_10_SPEC_PATH = "cypress/e2e";
370376

371377
const SPEC_TOTAL_CHAR_LIMIT = 32243;
372378
const METADATA_CHAR_BUFFER_PER_SPEC = 175;
@@ -419,6 +425,8 @@ const CYPRESS_CONFIG_FILE_MAPPING = {
419425

420426
const CYPRESS_CONFIG_FILE_NAMES = Object.keys(CYPRESS_CONFIG_FILE_MAPPING);
421427

428+
const CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS = ['js', 'ts', 'cjs', 'mjs']
429+
422430
module.exports = Object.freeze({
423431
syncCLI,
424432
userMessages,
@@ -432,6 +440,7 @@ module.exports = Object.freeze({
432440
packageInstallerOptions,
433441
specFileTypes,
434442
DEFAULT_CYPRESS_SPEC_PATH,
443+
DEFAULT_CYPRESS_10_SPEC_PATH,
435444
SPEC_TOTAL_CHAR_LIMIT,
436445
METADATA_CHAR_BUFFER_PER_SPEC,
437446
usageReportingConstants,
@@ -448,5 +457,6 @@ module.exports = Object.freeze({
448457
CYPRESS_V9_AND_OLDER_TYPE,
449458
CYPRESS_V10_AND_ABOVE_TYPE,
450459
CYPRESS_CONFIG_FILE_MAPPING,
451-
CYPRESS_CONFIG_FILE_NAMES
460+
CYPRESS_CONFIG_FILE_NAMES,
461+
CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS
452462
});

bin/helpers/getInitialDetails.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ exports.getInitialDetails = (bsConfig, args, rawArgs) => {
3333
utils.sendUsageReport(bsConfig, args, responseData["error"], Constants.messageTypes.ERROR, 'get_initial_details_failed', null, rawArgs);
3434
resolve({});
3535
} else {
36+
if (!utils.isUndefined(responseData.grr) && responseData.grr.enabled && !utils.isUndefined(responseData.grr.urls)) {
37+
config.uploadUrl = responseData.grr.urls.upload_url;
38+
}
3639
resolve(responseData);
3740
}
3841
}

bin/helpers/packageInstaller.js

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,13 @@ const packageArchiver = (packageDir, packageFile) => {
128128
})
129129
}
130130

131-
const packageWrapper = (bsConfig, packageDir, packageFile, md5data, instrumentBlocks) => {
131+
const packageSetupAndInstaller = (bsConfig, packageDir, instrumentBlocks) => {
132132
return new Promise(function (resolve) {
133133
let obj = {
134-
packageArchieveCreated: false
134+
packagesInstalled: false
135135
};
136-
if (md5data.packageUrlPresent || !utils.isTrueString(bsConfig.run_settings.cache_dependencies)) {
137-
logger.debug("Skipping the caching of npm packages since BrowserStack has already cached your npm dependencies that have not changed since the last run.")
138-
return resolve(obj);
139-
}
140-
logger.info(Constants.userMessages.NPM_INSTALL_AND_UPLOAD);
136+
137+
logger.info(Constants.userMessages.NPM_INSTALL);
141138
instrumentBlocks.markBlockStart("packageInstaller.folderSetup");
142139
logger.debug("Started setting up package folder");
143140
return setupPackageFolder(bsConfig.run_settings, packageDir).then((_result) => {
@@ -150,10 +147,34 @@ const packageWrapper = (bsConfig, packageDir, packageFile, md5data, instrumentBl
150147
}).then((_result) => {
151148
logger.debug("Completed installing dependencies");
152149
instrumentBlocks.markBlockEnd("packageInstaller.packageInstall");
153-
instrumentBlocks.markBlockStart("packageInstaller.packageArchive");
154-
logger.debug("Started archiving node_modules")
155-
return packageArchiver(packageDir, packageFile);
156-
}).then((_result) => {
150+
Object.assign(obj, { packagesInstalled: true });
151+
return resolve(obj);
152+
}).catch((err) => {
153+
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.`);
154+
obj.error = err.stack ? err.stack.toString().substring(0,100) : err.toString().substring(0,100);
155+
return resolve(obj);
156+
})
157+
})
158+
}
159+
160+
const packageWrapper = (bsConfig, packageDir, packageFile, md5data, instrumentBlocks, packagesInstalled) => {
161+
return new Promise(function (resolve) {
162+
let obj = {
163+
packageArchieveCreated: false
164+
};
165+
if (!packagesInstalled) {
166+
logger.debug("Skipping the caching of npm packages since package installed failed")
167+
return resolve(obj);
168+
}
169+
if (md5data.packageUrlPresent || !utils.isTrueString(bsConfig.run_settings.cache_dependencies)) {
170+
logger.debug("Skipping the caching of npm packages since BrowserStack has already cached your npm dependencies that have not changed since the last run.")
171+
return resolve(obj);
172+
}
173+
logger.info(Constants.userMessages.NPM_UPLOAD);
174+
instrumentBlocks.markBlockStart("packageInstaller.packageArchive");
175+
logger.debug("Started archiving node_modules")
176+
return packageArchiver(packageDir, packageFile)
177+
.then((_result) => {
157178
logger.debug("Archiving of node_modules completed");
158179
instrumentBlocks.markBlockEnd("packageInstaller.packageArchive");
159180
Object.assign(obj, { packageArchieveCreated: true });
@@ -167,3 +188,4 @@ const packageWrapper = (bsConfig, packageDir, packageFile, md5data, instrumentBl
167188
}
168189

169190
exports.packageWrapper = packageWrapper;
191+
exports.packageSetupAndInstaller = packageSetupAndInstaller;

bin/helpers/readCypressConfigUtil.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"use strict";
2+
const path = require("path");
3+
const fs = require("fs");
4+
const cp = require('child_process');
5+
6+
const config = require('./config');
7+
const constants = require("./constants");
8+
const utils = require("./utils");
9+
const logger = require('./logger').winstonLogger;
10+
11+
exports.detectLanguage = (cypress_config_filename) => {
12+
const extension = cypress_config_filename.split('.').pop()
13+
return constants.CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS.includes(extension) ? extension : 'js'
14+
}
15+
16+
exports.convertTsConfig = (bsConfig, cypress_config_filepath, bstack_node_modules_path) => {
17+
const cypress_config_filename = bsConfig.run_settings.cypress_config_filename
18+
const working_dir = path.dirname(cypress_config_filepath);
19+
const complied_js_dir = path.join(working_dir, config.compiledConfigJsDirName)
20+
cp.execSync(`rm -rf ${config.compiledConfigJsDirName}`, { cwd: working_dir })
21+
cp.execSync(`mkdir ${config.compiledConfigJsDirName}`, { cwd: working_dir })
22+
23+
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}`
24+
let tsc_output
25+
try {
26+
logger.debug(`Running: ${tsc_command}`)
27+
tsc_output = cp.execSync(tsc_command, { cwd: working_dir })
28+
} catch (err) {
29+
// error while compiling ts files
30+
logger.debug(err.message);
31+
logger.debug(err.output.toString());
32+
tsc_output = err.output // if there is an error, tsc adds output of complilation to err.output key
33+
} finally {
34+
logger.debug(`Saved compiled js output at: ${complied_js_dir}`);
35+
logger.debug(`Finding compiled cypress config file in: ${complied_js_dir}`);
36+
37+
const lines = tsc_output.toString().split('\n');
38+
let foundLine = null;
39+
for (let i = 0; i < lines.length; i++) {
40+
if (lines[i].indexOf(`${path.parse(cypress_config_filename).name}.js`) > -1) {
41+
foundLine = lines[i]
42+
break;
43+
}
44+
}
45+
if (foundLine === null) {
46+
logger.error(`No compiled cypress config found. There might some error running ${tsc_command} command`)
47+
return null
48+
} else {
49+
const compiled_cypress_config_filepath = foundLine.split('TSFILE: ').pop()
50+
logger.debug(`Found compiled cypress config file: ${compiled_cypress_config_filepath}`);
51+
return compiled_cypress_config_filepath
52+
}
53+
}
54+
}
55+
56+
exports.loadJsFile = (cypress_config_filepath, bstack_node_modules_path) => {
57+
const require_module_helper_path = `${__dirname}/requireModule.js`
58+
cp.execSync(`NODE_PATH=${bstack_node_modules_path} node ${require_module_helper_path} ${cypress_config_filepath}`)
59+
const cypress_config = JSON.parse(fs.readFileSync(config.configJsonFileName).toString())
60+
if (fs.existsSync(config.configJsonFileName)) {
61+
fs.unlinkSync(config.configJsonFileName)
62+
}
63+
return cypress_config
64+
}
65+
66+
exports.readCypressConfigFile = (bsConfig) => {
67+
const cypress_config_filepath = path.resolve(bsConfig.run_settings.cypressConfigFilePath)
68+
try {
69+
const cypress_config_filename = bsConfig.run_settings.cypress_config_filename
70+
const bstack_node_modules_path = `${path.resolve(config.packageDirName)}/node_modules`
71+
const conf_lang = this.detectLanguage(cypress_config_filename)
72+
73+
logger.debug(`cypress config path: ${cypress_config_filepath}`);
74+
75+
if (conf_lang == 'js' || conf_lang == 'cjs') {
76+
return this.loadJsFile(cypress_config_filepath, bstack_node_modules_path)
77+
} else if (conf_lang === 'ts') {
78+
const compiled_cypress_config_filepath = this.convertTsConfig(bsConfig, cypress_config_filepath, bstack_node_modules_path)
79+
return this.loadJsFile(compiled_cypress_config_filepath, bstack_node_modules_path)
80+
}
81+
} catch (error) {
82+
const errorMessage = `Error while reading cypress config: ${error.message}`
83+
const errorCode = 'cypress_config_file_read_failed'
84+
logger.error(errorMessage)
85+
utils.sendUsageReport(
86+
bsConfig,
87+
null,
88+
errorMessage,
89+
constants.messageTypes.WARNING,
90+
errorCode,
91+
null,
92+
null
93+
)
94+
} finally {
95+
const working_dir = path.dirname(cypress_config_filepath);
96+
cp.execSync(`rm -rf ${config.compiledConfigJsDirName}`, { cwd: working_dir })
97+
}
98+
}

0 commit comments

Comments
 (0)