Skip to content

initial changes #707

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use common nodeRequest method + remove debugging logs

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