Skip to content
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

initial changes #707

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) => {
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
asambstack marked this conversation as resolved.
Show resolved Hide resolved
let os_data;
if (Cypress.env("OS")) {
os_data = Cypress.env("OS");
} else {
os_data = Cypress.platform === 'linux' ? 'mac' : "win"
asambstack marked this conversation as resolved.
Show resolved Hide resolved
}
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', () => {
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
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', () => {
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
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) => {
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
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 = () => {
karanshah-browserstack marked this conversation as resolved.
Show resolved Hide resolved
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