Skip to content

Commit

Permalink
Merge pull request #707 from rev-doshi/cypress-accessibility
Browse files Browse the repository at this point in the history
initial changes
  • Loading branch information
agrawalsaurabhs committed Oct 17, 2023
2 parents e75e182 + 3441bb6 commit b884205
Show file tree
Hide file tree
Showing 9 changed files with 764 additions and 231 deletions.
1 change: 1 addition & 0 deletions bin/accessibility-automation/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports.API_URL = 'https://accessibility.browserstack.com/api';
140 changes: 140 additions & 0 deletions bin/accessibility-automation/cypress/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/* Event listeners + custom commands for Cypress */

Cypress.on('test:before:run', () => {
try {
if (Cypress.env("IS_ACCESSIBILITY_EXTENSION_LOADED") !== "true") return
const extensionPath = Cypress.env("ACCESSIBILITY_EXTENSION_PATH")

if (extensionPath !== undefined) {
new Promise((resolve, reject) => {
window.parent.addEventListener('A11Y_TAP_STARTED', () => {
resolve("A11Y_TAP_STARTED");
});
const e = new CustomEvent('A11Y_FORCE_START');
window.parent.dispatchEvent(e);
})
}
} catch {}

});

Cypress.on('test:after:run', (attributes, runnable) => {
try {
if (Cypress.env("IS_ACCESSIBILITY_EXTENSION_LOADED") !== "true") return
const extensionPath = Cypress.env("ACCESSIBILITY_EXTENSION_PATH")
const isHeaded = Cypress.browser.isHeaded;
if (isHeaded && extensionPath !== undefined) {

let shouldScanTestForAccessibility = true;
if (Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY") || Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY")) {

try {
let includeTagArray = [];
let excludeTagArray = [];
if (Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY")) {
includeTagArray = Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY").split(";")
}
if (Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY")) {
excludeTagArray = Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY").split(";")
}

const fullTestName = attributes.title;
const excluded = excludeTagArray.some((exclude) => fullTestName.includes(exclude));
const included = includeTagArray.length === 0 || includeTags.some((include) => fullTestName.includes(include));
shouldScanTestForAccessibility = !excluded && included;
} catch (error) {
console.log("Error while validating test case for accessibility before scanning. Error : ", error);
}
}
let os_data;
if (Cypress.env("OS")) {
os_data = Cypress.env("OS");
} else {
os_data = Cypress.platform === 'linux' ? 'mac' : "win"
}
let filePath = '';
if (attributes.invocationDetails !== undefined && attributes.invocationDetails.relativeFile !== undefined) {
filePath = attributes.invocationDetails.relativeFile;
}
const dataForExtension = {
"saveResults": shouldScanTestForAccessibility,
"testDetails": {
"name": attributes.title,
"testRunId": '5058', // variable not consumed, shouldn't matter what we send
"filePath": filePath,
"scopeList": [
filePath,
attributes.title
]
},
"platform": {
"os_name": os_data,
"os_version": Cypress.env("OS_VERSION"),
"browser_name": Cypress.browser.name,
"browser_version": Cypress.browser.version
}
};
return new Promise((resolve, reject) => {
if (dataForExtension.saveResults) {
window.parent.addEventListener('A11Y_TAP_TRANSPORTER', (event) => {
resolve(event.detail);
});
}
const e = new CustomEvent('A11Y_TEST_END', {detail: dataForExtension});
window.parent.dispatchEvent(e);
if (dataForExtension.saveResults !== true )
resolve();
});
}

} catch {}
});

Cypress.Commands.add('getAccessibilityResultsSummary', () => {
try {
if (Cypress.env("IS_ACCESSIBILITY_EXTENSION_LOADED") !== "true") {
console.log(`Not a Accessibility Automation session, cannot retrieve Accessibility results.`);
return
}
return new Promise(function (resolve, reject) {
try{
const e = new CustomEvent('A11Y_TAP_GET_RESULTS_SUMMARY');
const fn = function (event) {
window.parent.removeEventListener('A11Y_RESULTS_SUMMARY_RESPONSE', fn);
resolve(event.detail.summary);
};
window.parent.addEventListener('A11Y_RESULTS_SUMMARY_RESPONSE', fn);
window.parent.dispatchEvent(e);
} catch (err) {
console.log("No accessibility results summary was found.");
reject(err);
}
});
} catch {}

});

Cypress.Commands.add('getAccessibilityResults', () => {
try {
if (Cypress.env("IS_ACCESSIBILITY_EXTENSION_LOADED") !== "true") {
console.log(`Not a Accessibility Automation session, cannot retrieve Accessibility results.`);
return
}
return new Promise(function (resolve, reject) {
try{
const e = new CustomEvent('A11Y_TAP_GET_RESULTS');
const fn = function (event) {
window.parent.removeEventListener('A11Y_RESULTS_RESPONSE', fn);
resolve(event.detail.summary);
};
window.parent.addEventListener('A11Y_RESULTS_RESPONSE', fn);
window.parent.dispatchEvent(e);
} catch (err) {
console.log("No accessibility results were found.");
reject(err);
}
});
} catch {}

});

218 changes: 218 additions & 0 deletions bin/accessibility-automation/helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
const logger = require("../helpers/logger").winstonLogger;
const { API_URL } = require('./constants');
const utils = require('../helpers/utils');
const fs = require('fs');
const path = require('path');
const request = require('request');
const os = require('os');
const glob = require('glob');
const helper = require('../helpers/helper');
const { CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS } = require('../helpers/constants');
const supportFileContentMap = {}

exports.checkAccessibilityPlatform = (user_config) => {
let accessibility = false;
try {
user_config.browsers.forEach(browser => {
if (browser.accessibility) {
accessibility = true;
}
})
} catch {}

return accessibility;
}

exports.setAccessibilityCypressCapabilities = async (user_config, accessibilityResponse) => {
if (utils.isUndefined(user_config.run_settings.accessibilityOptions)) {
user_config.run_settings.accessibilityOptions = {}
}
user_config.run_settings.accessibilityOptions["authToken"] = accessibilityResponse.data.accessibilityToken;
user_config.run_settings.accessibilityOptions["auth"] = accessibilityResponse.data.accessibilityToken;
user_config.run_settings.accessibilityOptions["scannerVersion"] = accessibilityResponse.data.scannerVersion;
user_config.run_settings.system_env_vars.push(`ACCESSIBILITY_AUTH=${accessibilityResponse.data.accessibilityToken}`)
user_config.run_settings.system_env_vars.push(`ACCESSIBILITY_SCANNERVERSION=${accessibilityResponse.data.scannerVersion}`)
}

exports.isAccessibilitySupportedCypressVersion = (cypress_config_filename) => {
const extension = cypress_config_filename.split('.').pop();
return CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS.includes(extension);
}

exports.createAccessibilityTestRun = async (user_config, framework) => {

try {
if (!this.isAccessibilitySupportedCypressVersion(user_config.run_settings.cypress_config_file) ){
logger.warn(`Accessibility Testing is not supported on Cypress version 9 and below.`)
process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'false';
user_config.run_settings.accessibility = false;
return;
}
const userName = user_config["auth"]["username"];
const accessKey = user_config["auth"]["access_key"];
let settings = utils.isUndefined(user_config.run_settings.accessibilityOptions) ? {} : user_config.run_settings.accessibilityOptions

const {
buildName,
projectName,
buildDescription
} = helper.getBuildDetails(user_config);

const data = {
'projectName': projectName,
'buildName': buildName,
'startTime': (new Date()).toISOString(),
'description': buildDescription,
'source': {
frameworkName: "Cypress",
frameworkVersion: helper.getPackageVersion('cypress', user_config),
sdkVersion: helper.getAgentVersion()
},
'settings': settings,
'versionControl': await helper.getGitMetaData(),
'ciInfo': helper.getCiInfo(),
'hostInfo': {
hostname: os.hostname(),
platform: os.platform(),
type: os.type(),
version: os.version(),
arch: os.arch()
},
'browserstackAutomation': process.env.BROWSERSTACK_AUTOMATION === 'true'
};

const config = {
auth: {
user: userName,
pass: accessKey
},
headers: {
'Content-Type': 'application/json'
}
};

const response = await nodeRequest(
'POST', 'test_runs', data, config, API_URL
);
if(!utils.isUndefined(response.data)) {
process.env.BS_A11Y_JWT = response.data.data.accessibilityToken;
process.env.BS_A11Y_TEST_RUN_ID = response.data.data.id;
}
if (process.env.BS_A11Y_JWT) {
process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'true';
}
logger.debug(`BrowserStack Accessibility Automation Test Run ID: ${response.data.data.id}`);

this.setAccessibilityCypressCapabilities(user_config, response.data);
setAccessibilityEventListeners();
helper.setBrowserstackCypressCliDependency(user_config);

} catch (error) {
if (error.response) {
logger.error(
`Exception while creating test run for BrowserStack Accessibility Automation: ${
error.response.status
} ${error.response.statusText} ${JSON.stringify(error.response.data)}`
);
} else {
if(error.message === 'Invalid configuration passed.') {
logger.error(
`Exception while creating test run for BrowserStack Accessibility Automation: ${
error.message || error.stack
}`
);
for(const errorkey of error.errors){
logger.error(errorkey.message);
}

} else {
logger.error(
`Exception while creating test run for BrowserStack Accessibility Automation: ${
error.message || error.stack
}`
);
}
// since create accessibility session failed
process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'false';
user_config.run_settings.accessibility = false;
}
}
}

const nodeRequest = (type, url, data, config) => {
return new Promise(async (resolve, reject) => {
const options = {...config,...{
method: type,
url: `${API_URL}/${url}`,
body: data,
json: config.headers['Content-Type'] === 'application/json',
}};

request(options, function callback(error, response, body) {
if(error) {
logger.info("error in nodeRequest", error);
reject(error);
} else if(!(response.statusCode == 201 || response.statusCode == 200)) {
logger.info("response.statusCode in nodeRequest", response.statusCode);
reject(response && response.body ? response.body : `Received response from BrowserStack Server with status : ${response.statusCode}`);
} else {
try {
if(typeof(body) !== 'object') body = JSON.parse(body);
} catch(e) {
if(!url.includes('/stop')) {
reject('Not a JSON response from BrowserStack Server');
}
}
resolve({
data: body
});
}
});
});
}

exports.supportFileCleanup = () => {
logger.debug("Cleaning up support file changes added for accessibility. ")
Object.keys(supportFileContentMap).forEach(file => {
try {
fs.writeFileSync(file, supportFileContentMap[file], {encoding: 'utf-8'});
} catch(e) {
logger.debug(`Error while replacing file content for ${file} with it's original content with error : ${e}`, true, e);
}
});
}

const getAccessibilityCypressCommandEventListener = () => {
return (
`require('browserstack-cypress-cli/bin/accessibility-automation/cypress');`
);
}

const setAccessibilityEventListeners = () => {
try {
const cypressCommandEventListener = getAccessibilityCypressCommandEventListener();
glob(process.cwd() + '/cypress/support/*.js', {}, (err, files) => {
if(err) return logger.debug('EXCEPTION IN BUILD START EVENT : Unable to parse cypress support files');
files.forEach(file => {
try {
if(!file.includes('commands.js')) {
const defaultFileContent = fs.readFileSync(file, {encoding: 'utf-8'});

if(!defaultFileContent.includes(cypressCommandEventListener)) {
let newFileContent = defaultFileContent +
'\n' +
cypressCommandEventListener +
'\n'
fs.writeFileSync(file, newFileContent, {encoding: 'utf-8'});
supportFileContentMap[file] = defaultFileContent;
}
}
} catch(e) {
logger.debug(`Unable to modify file contents for ${file} to set event listeners with error ${e}`, true, e);
}
});
});
} catch(e) {
logger.debug(`Unable to parse support files to set event listeners with error ${e}`, true, e);
}
}
Loading

0 comments on commit b884205

Please sign in to comment.