Skip to content

Commit 3e5da4b

Browse files
authored
Merge pull request #786 from browserstack/SDK-382
A11y Cypress TS Support
2 parents 7a8ef57 + d282b9d commit 3e5da4b

File tree

4 files changed

+125
-22
lines changed

4 files changed

+125
-22
lines changed

bin/accessibility-automation/helper.js

+22-12
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

+9-1
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');

bin/helpers/helper.js

+74
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/testObservability/helper/helper.js

+20-9
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) {

0 commit comments

Comments
 (0)