Skip to content

Commit c7082ee

Browse files
committed
Merge remote-tracking branch 'origin/master' into sanitize-glob-pattern-update
2 parents 7e1ffac + ba41d26 commit c7082ee

File tree

7 files changed

+197
-32
lines changed

7 files changed

+197
-32
lines changed

bin/accessibility-automation/helper.js

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ exports.createAccessibilityTestRun = async (user_config, framework) => {
107107
logger.debug(`BrowserStack Accessibility Automation Test Run ID: ${response.data.data.id}`);
108108

109109
this.setAccessibilityCypressCapabilities(user_config, response.data);
110-
setAccessibilityEventListeners();
111110
helper.setBrowserstackCypressCliDependency(user_config);
112111

113112
} catch (error) {
@@ -175,41 +174,52 @@ const nodeRequest = (type, url, data, config) => {
175174
}
176175

177176
exports.supportFileCleanup = () => {
178-
logger.debug("Cleaning up support file changes added for accessibility. ")
177+
logger.debug("Cleaning up support file changes added for accessibility.")
179178
Object.keys(supportFileContentMap).forEach(file => {
180179
try {
181-
fs.writeFileSync(file, supportFileContentMap[file], {encoding: 'utf-8'});
180+
if(typeof supportFileContentMap[file] === 'object') {
181+
let fileOrDirpath = file;
182+
if(supportFileContentMap[file].deleteSupportDir) {
183+
fileOrDirpath = path.join(process.cwd(), 'cypress', 'support');
184+
}
185+
helper.deleteSupportFileOrDir(fileOrDirpath);
186+
} else {
187+
fs.writeFileSync(file, supportFileContentMap[file], {encoding: 'utf-8'});
188+
}
182189
} catch(e) {
183190
logger.debug(`Error while replacing file content for ${file} with it's original content with error : ${e}`, true, e);
184191
}
185192
});
186193
}
187194

188-
const getAccessibilityCypressCommandEventListener = () => {
189-
return (
195+
const getAccessibilityCypressCommandEventListener = (extName) => {
196+
return extName == 'js' ? (
190197
`require('browserstack-cypress-cli/bin/accessibility-automation/cypress');`
191-
);
198+
) : (
199+
`import 'browserstack-cypress-cli/bin/accessibility-automation/cypress'`
200+
)
192201
}
193202

194-
const setAccessibilityEventListeners = () => {
203+
exports.setAccessibilityEventListeners = (bsConfig) => {
195204
try {
196-
const cypressCommandEventListener = getAccessibilityCypressCommandEventListener();
197-
198205
// Searching form command.js recursively
199-
glob(process.cwd() + '/**/cypress/support/*.js', {}, (err, files) => {
206+
const supportFilesData = helper.getSupportFiles(bsConfig, true);
207+
if(!supportFilesData.supportFile) return;
208+
glob(process.cwd() + supportFilesData.supportFile, {}, (err, files) => {
200209
if(err) return logger.debug('EXCEPTION IN BUILD START EVENT : Unable to parse cypress support files');
201210
files.forEach(file => {
202211
try {
203-
if(!file.includes('commands.js')) {
212+
if(!file.includes('commands.js') && !file.includes('commands.ts')) {
204213
const defaultFileContent = fs.readFileSync(file, {encoding: 'utf-8'});
205214

215+
let cypressCommandEventListener = getAccessibilityCypressCommandEventListener(path.extname(file));
206216
if(!defaultFileContent.includes(cypressCommandEventListener)) {
207217
let newFileContent = defaultFileContent +
208218
'\n' +
209219
cypressCommandEventListener +
210220
'\n'
211221
fs.writeFileSync(file, newFileContent, {encoding: 'utf-8'});
212-
supportFileContentMap[file] = defaultFileContent;
222+
supportFileContentMap[file] = supportFilesData.cleanupParams ? supportFilesData.cleanupParams : defaultFileContent;
213223
}
214224
}
215225
} catch(e) {

bin/commands/runs.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const { getStackTraceUrl } = require('../helpers/sync/syncSpecsLogs');
2525

2626
const {
2727
launchTestSession,
28+
setEventListeners,
2829
setTestObservabilityFlags,
2930
runCypressTestsLocally,
3031
printBuildLink
@@ -33,6 +34,7 @@ const {
3334

3435
const {
3536
createAccessibilityTestRun,
37+
setAccessibilityEventListeners,
3638
checkAccessibilityPlatform,
3739
supportFileCleanup
3840
} = require('../accessibility-automation/helper');
@@ -146,7 +148,7 @@ module.exports = function run(args, rawArgs) {
146148

147149
// add cypress dependency if missing
148150
utils.setCypressNpmDependency(bsConfig);
149-
151+
150152
if (isAccessibilitySession && isBrowserstackInfra) {
151153
await createAccessibilityTestRun(bsConfig);
152154
}
@@ -205,6 +207,12 @@ module.exports = function run(args, rawArgs) {
205207
markBlockStart('validateConfig');
206208
logger.debug("Started configs validation");
207209
return capabilityHelper.validate(bsConfig, args).then(function (cypressConfigFile) {
210+
if(process.env.BROWSERSTACK_TEST_ACCESSIBILITY) {
211+
setAccessibilityEventListeners(bsConfig);
212+
}
213+
if(process.env.BS_TESTOPS_BUILD_COMPLETED) {
214+
// setEventListeners(bsConfig);
215+
}
208216
markBlockEnd('validateConfig');
209217
logger.debug("Completed configs validation");
210218
markBlockStart('preArchiveSteps');
@@ -255,7 +263,15 @@ module.exports = function run(args, rawArgs) {
255263

256264
let test_zip_size = utils.fetchZipSize(path.join(process.cwd(), config.fileName));
257265
let npm_zip_size = utils.fetchZipSize(path.join(process.cwd(), config.packageFileName));
258-
let node_modules_size = await utils.fetchFolderSize(path.join(process.cwd(), "node_modules"))
266+
let node_modules_size = await utils.fetchFolderSize(path.join(process.cwd(), "node_modules"));
267+
268+
if (Constants.turboScaleObj.enabled) {
269+
// Note: Calculating md5 here for turboscale force-upload so that we don't need to re-calculate at hub
270+
let zip_md5sum = await checkUploaded.checkSpecsMd5(bsConfig.run_settings, args, {markBlockStart, markBlockEnd});
271+
let npm_package_md5sum = await checkUploaded.checkPackageMd5(bsConfig.run_settings);
272+
Object.assign(md5data, { npm_package_md5sum });
273+
Object.assign(md5data, { zip_md5sum });
274+
}
259275

260276
//Package diff
261277
let isPackageDiff = false;

bin/helpers/checkUploaded.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ const crypto = require('crypto'),
1313

1414
const checkSpecsMd5 = (runSettings, args, instrumentBlocks) => {
1515
return new Promise(function (resolve, reject) {
16-
if (args["force-upload"]) {
17-
return resolve("force-upload");
18-
}
1916
let cypressFolderPath = undefined;
2017
if (runSettings.home_directory) {
2118
cypressFolderPath = runSettings.home_directory;
@@ -166,4 +163,8 @@ const checkUploadedMd5 = (bsConfig, args, instrumentBlocks) => {
166163
});
167164
};
168165

169-
exports.checkUploadedMd5 = checkUploadedMd5;
166+
module.exports = {
167+
checkSpecsMd5,
168+
checkPackageMd5,
169+
checkUploadedMd5
170+
};

bin/helpers/helper.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const gitconfig = require('gitconfiglocal');
1616
const { spawn, execSync } = require('child_process');
1717
const glob = require('glob');
1818
const pGitconfig = promisify(gitconfig);
19+
const { readCypressConfigFile } = require('./readCypressConfigUtil');
1920
const CrashReporter = require('../testObservability/crashReporter');
2021

2122
exports.debug = (text, shouldReport = false, throwable = null) => {
@@ -313,3 +314,76 @@ exports.setBrowserstackCypressCliDependency = (bsConfig) => {
313314
}
314315
}
315316
}
317+
318+
exports.deleteSupportFileOrDir = (fileOrDirPath) => {
319+
try {
320+
// Sanitize the input to remove any characters that could be used for directory traversal
321+
const sanitizedPath = fileOrDirPath.replace(/(\.\.\/|\.\/|\/\/)/g, '');
322+
const resolvedPath = path.resolve(sanitizedPath);
323+
if (fs.existsSync(resolvedPath)) {
324+
if (fs.lstatSync(resolvedPath).isDirectory()) {
325+
fs.readdirSync(resolvedPath).forEach((file) => {
326+
const sanitizedFile = file.replace(/(\.\.\/|\.\/|\/\/)/g, '');
327+
const currentPath = path.join(resolvedPath, sanitizedFile);
328+
fs.unlinkSync(currentPath);
329+
});
330+
fs.rmdirSync(resolvedPath);
331+
} else {
332+
fs.unlinkSync(resolvedPath);
333+
}
334+
}
335+
} catch(err) {}
336+
}
337+
338+
exports.getSupportFiles = (bsConfig, isA11y) => {
339+
let extension = null;
340+
try {
341+
extension = bsConfig.run_settings.cypress_config_file.split('.').pop();
342+
} catch (err) {}
343+
let supportFile = '/**/cypress/support/**/*.{js,ts}';
344+
let cleanupParams = {};
345+
let userSupportFile = null;
346+
try {
347+
const completeCypressConfigFile = readCypressConfigFile(bsConfig)
348+
let cypressConfigFile = {};
349+
if (!utils.isUndefined(completeCypressConfigFile)) {
350+
cypressConfigFile = !utils.isUndefined(completeCypressConfigFile.default) ? completeCypressConfigFile.default : completeCypressConfigFile
351+
}
352+
userSupportFile = cypressConfigFile.e2e?.supportFile !== null ? cypressConfigFile.e2e?.supportFile : cypressConfigFile.component?.supportFile !== null ? cypressConfigFile.component?.supportFile : cypressConfigFile.supportFile;
353+
if(userSupportFile == false && extension) {
354+
const supportFolderPath = path.join(process.cwd(), 'cypress', 'support');
355+
if (!fs.existsSync(supportFolderPath)) {
356+
fs.mkdirSync(supportFolderPath);
357+
cleanupParams.deleteSupportDir = true;
358+
}
359+
const sanitizedExtension = extension.replace(/(\.\.\/|\.\/|\/\/)/g, '');
360+
const supportFilePath = path.join(supportFolderPath, `tmpBstackSupportFile.${sanitizedExtension}`);
361+
fs.writeFileSync(supportFilePath, "");
362+
supportFile = `/cypress/support/tmpBstackSupportFile.${sanitizedExtension}`;
363+
const currEnvVars = bsConfig.run_settings.system_env_vars;
364+
const supportFileEnv = `CYPRESS_SUPPORT_FILE=${supportFile.substring(1)}`;
365+
if(!currEnvVars) {
366+
bsConfig.run_settings.system_env_vars = [supportFileEnv];
367+
} else {
368+
bsConfig.run_settings.system_env_vars = [...currEnvVars, supportFileEnv];
369+
}
370+
cleanupParams.deleteSupportFile = true;
371+
} else if(typeof userSupportFile == 'string') {
372+
if (userSupportFile.startsWith('${') && userSupportFile.endsWith('}')) {
373+
/* Template strings to reference environment variables */
374+
const envVar = userSupportFile.substring(2, userSupportFile.length - 1);
375+
supportFile = process.env[envVar];
376+
} else {
377+
/* Single file / glob pattern */
378+
supportFile = userSupportFile;
379+
}
380+
} else if(Array.isArray(userSupportFile)) {
381+
supportFile = userSupportFile[0];
382+
}
383+
} catch (err) {}
384+
if(supportFile && supportFile[0] != '/') supportFile = '/' + supportFile;
385+
return {
386+
supportFile,
387+
cleanupParams: Object.keys(cleanupParams).length ? cleanupParams : null
388+
};
389+
}

bin/helpers/usageReporting.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const config = require('./config'),
1010
utils = require('./utils');
1111

1212
const { AUTH_REGEX, REDACTED_AUTH, REDACTED, CLI_ARGS_REGEX, RAW_ARGS_REGEX } = require("./constants");
13+
const { isTurboScaleSession } = require('../helpers/atsHelper');
1314

1415
function get_version(package_name) {
1516
try {
@@ -197,10 +198,58 @@ function redactKeys(str, regex, redact) {
197198
return str.replace(regex, redact);
198199
}
199200

201+
function sendTurboscaleErrorLogs(args) {
202+
let bsConfig = JSON.parse(JSON.stringify(args.bstack_config));
203+
let data = utils.isUndefined(args.data) ? {} : args.data;
204+
const turboscaleErrorPayload = {
205+
kind: 'hst-cypress-cli-error',
206+
data: data,
207+
error: args.message
208+
}
209+
210+
const options = {
211+
headers: {
212+
'User-Agent': utils.getUserAgent()
213+
},
214+
method: "POST",
215+
auth: {
216+
username: bsConfig.auth.username,
217+
password: bsConfig.auth.access_key,
218+
},
219+
url: `${config.turboScaleAPIUrl}/send-instrumentation`,
220+
body: turboscaleErrorPayload,
221+
json: true,
222+
maxAttempts: 10, // (default) try 3 times
223+
retryDelay: 2000, // (default) wait for 2s before trying again
224+
retrySrategy: request.RetryStrategies.HTTPOrNetworkError, // (default) retry on 5xx or network errors
225+
};
226+
227+
fileLogger.info(`Sending ${JSON.stringify(turboscaleErrorPayload)} to ${config.turboScaleAPIUrl}/send-instrumentation`);
228+
request(options, function (error, res, body) {
229+
if (error) {
230+
//write err response to file
231+
fileLogger.error(JSON.stringify(error));
232+
return;
233+
}
234+
// write response file
235+
let response = {
236+
attempts: res.attempts,
237+
statusCode: res.statusCode,
238+
body: body
239+
};
240+
fileLogger.info(`${JSON.stringify(response)}`);
241+
});
242+
}
243+
200244
function send(args) {
245+
let bsConfig = JSON.parse(JSON.stringify(args.bstack_config));
246+
247+
if (isTurboScaleSession(bsConfig) && args.message_type === 'error') {
248+
sendTurboscaleErrorLogs(args);
249+
}
250+
201251
if (isUsageReportingEnabled() === "true") return;
202252

203-
let bsConfig = JSON.parse(JSON.stringify(args.bstack_config));
204253
let runSettings = "";
205254
let sanitizedbsConfig = "";
206255
let cli_details = cli_version_and_path(bsConfig);
@@ -258,6 +307,10 @@ function send(args) {
258307
},
259308
};
260309

310+
if (isTurboScaleSession(bsConfig)) {
311+
payload.event_type = 'hst_cypress_cli_stats';
312+
}
313+
261314
const options = {
262315
headers: {
263316
"Content-Type": "text/json",

bin/testObservability/helper/helper.js

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,15 @@ const httpsScreenshotsKeepAliveAgent = new https.Agent({
6666
const supportFileCleanup = () => {
6767
Object.keys(supportFileContentMap).forEach(file => {
6868
try {
69-
fs.writeFileSync(file, supportFileContentMap[file], {encoding: 'utf-8'});
69+
if(typeof supportFileContentMap[file] === 'object') {
70+
let fileOrDirpath = file;
71+
if(supportFileContentMap[file].deleteSupportDir) {
72+
fileOrDirpath = path.join(process.cwd(), 'cypress', 'support');
73+
}
74+
helper.deleteSupportFileOrDir(fileOrDirpath);
75+
} else {
76+
fs.writeFileSync(file, supportFileContentMap[file], {encoding: 'utf-8'});
77+
}
7078
} catch(e) {
7179
exports.debug(`Error while replacing file content for ${file} with it's original content with error : ${e}`, true, e);
7280
}
@@ -241,29 +249,33 @@ const setEnvironmentVariablesForRemoteReporter = (BS_TESTOPS_JWT, BS_TESTOPS_BUI
241249
process.env.OBSERVABILITY_LAUNCH_SDK_VERSION = OBSERVABILITY_LAUNCH_SDK_VERSION;
242250
}
243251

244-
const getCypressCommandEventListener = () => {
245-
return (
252+
const getCypressCommandEventListener = (isJS) => {
253+
return isJS ? (
246254
`require('browserstack-cypress-cli/bin/testObservability/cypress');`
247-
);
255+
) : (
256+
`import 'browserstack-cypress-cli/bin/testObservability/cypress'`
257+
)
248258
}
249259

250-
const setEventListeners = () => {
260+
exports.setEventListeners = (bsConfig) => {
251261
try {
252-
const cypressCommandEventListener = getCypressCommandEventListener();
253-
glob(process.cwd() + '/cypress/support/*.js', {}, (err, files) => {
262+
const supportFilesData = helper.getSupportFiles(bsConfig, false);
263+
if(!supportFilesData.supportFile) return;
264+
glob(process.cwd() + supportFilesData.supportFile, {}, (err, files) => {
254265
if(err) return exports.debug('EXCEPTION IN BUILD START EVENT : Unable to parse cypress support files');
255266
files.forEach(file => {
256267
try {
257268
if(!file.includes('commands.js')) {
258269
const defaultFileContent = fs.readFileSync(file, {encoding: 'utf-8'});
259270

271+
let cypressCommandEventListener = getCypressCommandEventListener(file.includes('js'));
260272
if(!defaultFileContent.includes(cypressCommandEventListener)) {
261273
let newFileContent = defaultFileContent +
262274
'\n' +
263275
cypressCommandEventListener +
264276
'\n'
265277
fs.writeFileSync(file, newFileContent, {encoding: 'utf-8'});
266-
supportFileContentMap[file] = defaultFileContent;
278+
supportFileContentMap[file] = supportFilesData.cleanupParams ? supportFilesData.cleanupParams : defaultFileContent;
267279
}
268280
}
269281
} catch(e) {
@@ -379,7 +391,6 @@ exports.launchTestSession = async (user_config, bsConfigPath) => {
379391
exports.debug('Build creation successfull!');
380392
process.env.BS_TESTOPS_BUILD_COMPLETED = true;
381393
setEnvironmentVariablesForRemoteReporter(response.data.jwt, response.data.build_hashed_id, response.data.allow_screenshots, data.observability_version.sdkVersion);
382-
// setEventListeners();
383394
if(this.isBrowserstackInfra()) helper.setBrowserstackCypressCliDependency(user_config);
384395
} catch(error) {
385396
if(!error.errorType) {
@@ -804,6 +815,7 @@ exports.resolveModule = (module) => {
804815
};
805816

806817
const getReRunSpecs = (rawArgs) => {
818+
let finalArgs = rawArgs;
807819
if (this.isTestObservabilitySession() && this.shouldReRunObservabilityTests()) {
808820
let startIdx = -1, numEle = 0;
809821
for(let idx=0; idx<rawArgs.length; idx++) {
@@ -816,10 +828,9 @@ const getReRunSpecs = (rawArgs) => {
816828
}
817829
}
818830
if(startIdx != -1) rawArgs.splice(startIdx, numEle + 1);
819-
return [...rawArgs, '--spec', process.env.BROWSERSTACK_RERUN_TESTS];
820-
} else {
821-
return rawArgs;
831+
finalArgs = [...rawArgs, '--spec', process.env.BROWSERSTACK_RERUN_TESTS];
822832
}
833+
return finalArgs.filter(item => item !== '--disable-test-observability' && item !== '--disable-browserstack-automation');
823834
}
824835

825836
const getLocalSessionReporter = () => {

0 commit comments

Comments
 (0)