Skip to content

Commit 78b4856

Browse files
Merge pull request #535 from browserstack/AFD-1923-fix-spec-pattern-and-lls
Afd 1923 fix spec pattern and lls
2 parents 18c8281 + cc68672 commit 78b4856

15 files changed

+827
-94
lines changed

bin/commands/runs.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,7 @@ module.exports = function run(args, rawArgs) {
9999
// set the no-wrap
100100
utils.setNoWrap(bsConfig, args);
101101

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

105104
// set build tag caps
106105
utils.setBuildTags(bsConfig, args);
@@ -129,8 +128,11 @@ module.exports = function run(args, rawArgs) {
129128
logger.debug("Completed configs validation");
130129
markBlockStart('preArchiveSteps');
131130
logger.debug("Started pre-archive steps");
131+
132132
//get the number of spec files
133+
markBlockStart('getNumberOfSpecFiles');
133134
let specFiles = utils.getNumberOfSpecFiles(bsConfig, args, cypressConfigFile);
135+
markBlockEnd('getNumberOfSpecFiles');
134136

135137
bsConfig['run_settings']['video_config'] = utils.getVideoConfig(cypressConfigFile);
136138

@@ -140,6 +142,9 @@ module.exports = function run(args, rawArgs) {
140142
// accept the number of parallels
141143
utils.setParallels(bsConfig, args, specFiles.length);
142144

145+
// set record feature caps
146+
utils.setRecordCaps(bsConfig, args, cypressConfigFile);
147+
143148
// warn if specFiles cross our limit
144149
utils.warnSpecLimit(bsConfig, args, specFiles, rawArgs, buildReportData);
145150
markBlockEnd('preArchiveSteps');
@@ -153,7 +158,7 @@ module.exports = function run(args, rawArgs) {
153158

154159
logger.debug("Started caching npm dependencies.");
155160
markBlockStart('zip.packageInstaller');
156-
return packageInstaller.packageWrapper(bsConfig, config.packageDirName, config.packageFileName, md5data, {markBlockStart, markBlockEnd}).then(function (packageData) {
161+
return packageInstaller.packageWrapper(bsConfig, config.packageDirName, config.packageFileName, md5data, {markBlockStart, markBlockEnd}, packagesInstalled).then(function (packageData) {
157162
logger.debug("Completed caching npm dependencies.")
158163
markBlockEnd('zip.packageInstaller');
159164

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: 13 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 = {
@@ -367,6 +371,7 @@ const packageInstallerOptions = {
367371
const specFileTypes = ["js", "ts", "feature", "jsx", "coffee", "cjsx"];
368372

369373
const DEFAULT_CYPRESS_SPEC_PATH = "cypress/integration";
374+
const DEFAULT_CYPRESS_10_SPEC_PATH = "cypress/e2e";
370375

371376
const SPEC_TOTAL_CHAR_LIMIT = 32243;
372377
const METADATA_CHAR_BUFFER_PER_SPEC = 175;
@@ -419,6 +424,8 @@ const CYPRESS_CONFIG_FILE_MAPPING = {
419424

420425
const CYPRESS_CONFIG_FILE_NAMES = Object.keys(CYPRESS_CONFIG_FILE_MAPPING);
421426

427+
const CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS = ['js', 'ts', 'cjs', 'mjs']
428+
422429
module.exports = Object.freeze({
423430
syncCLI,
424431
userMessages,
@@ -432,6 +439,7 @@ module.exports = Object.freeze({
432439
packageInstallerOptions,
433440
specFileTypes,
434441
DEFAULT_CYPRESS_SPEC_PATH,
442+
DEFAULT_CYPRESS_10_SPEC_PATH,
435443
SPEC_TOTAL_CHAR_LIMIT,
436444
METADATA_CHAR_BUFFER_PER_SPEC,
437445
usageReportingConstants,
@@ -448,5 +456,6 @@ module.exports = Object.freeze({
448456
CYPRESS_V9_AND_OLDER_TYPE,
449457
CYPRESS_V10_AND_ABOVE_TYPE,
450458
CYPRESS_CONFIG_FILE_MAPPING,
451-
CYPRESS_CONFIG_FILE_NAMES
459+
CYPRESS_CONFIG_FILE_NAMES,
460+
CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS
452461
});

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+
}

bin/helpers/requireModule.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// NOTE: DO NOT change the name or location of file, the execution of this file is invoked using fixed path
2+
// helper file to load and read js modules
3+
const fs = require('fs');
4+
const config = require('./config');
5+
const moduleName = process.argv[2];
6+
7+
const mod = require(moduleName)
8+
9+
if (fs.existsSync(config.configJsonFileName)) {
10+
fs.unlinkSync(config.configJsonFileName)
11+
}
12+
13+
// write module in temporary json file
14+
fs.writeFileSync(config.configJsonFileName, JSON.stringify(mod))

0 commit comments

Comments
 (0)