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 10 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';
113 changes: 113 additions & 0 deletions bin/accessibility-automation/cypress/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/* Event listeners + custom commands for Cypress */

Cypress.on('test:before:run', () => {
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);
})
}
})

Cypress.on('test:after:run', (attributes, runnable) => {
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
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 = Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY").split(";")
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
let excludeTagArray = Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY").split(";")
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved

const fullTestName = attributes.title;
const excluded = excludeTagArray.some((exclude) => fullTestName.includes(exclude));
const included = includeTagArray.length === 0 || includeTags.some((include) => fullTestName.includes(include));
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
shouldScanTestForAccessibility = !excluded && included;
} catch (error){
console.log("Error while validating test case for accessibility before scanning. Error : ", error);
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
}
}
const dataForExtension = {
"saveResults": shouldScanTestForAccessibility,
"testDetails": {
"name": attributes.title,
"testRunId": '5058', // variable not consumed, shouldn't matter what we send
"filePath": attributes.invocationDetails.relativeFile,
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
"scopeList": [
attributes.invocationDetails.relativeFile,
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
attributes.title
]
},
"platform": {
"os_name": Cypress.platform === "darwin" ? "mac" : "windows",
"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();
});
}

});

Cypress.Commands.add('getAccessibilityResultsSummary', () => {
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
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);
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
}
});
});

Cypress.Commands.add('getAccessibilityResults', () => {
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
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.");
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
reject(err);
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
}
});
});

219 changes: 219 additions & 0 deletions bin/accessibility-automation/helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
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');

exports.checkAccessibilityPlatform = (user_config) => {
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
let accessibility = false;
user_config.browsers.forEach(browser => {
if (browser.accessibility) {
accessibility = true;
return true;
}
})
return accessibility;
}

exports.setAccessibilityCypressCapabilities = async (user_config, accessibilityResponse) => {
if (user_config.run_settings.accessibilityOptions) {
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved

} else {
user_config.run_settings.accessibilityOptions = {}
}
user_config.run_settings.accessibilityOptions.authToken = accessibilityResponse.data.accessibilityToken;
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
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.createAccessibilityTestRun = async (user_config, framework) => {

try {
const userName = user_config["auth"]["username"];
const accessKey = user_config["auth"]["access_key"];
let settings = user_config.run_settings.accessibilityOptions;
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved

const {
buildName,
projectName,
buildDescription
} = 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
};

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

const response = await nodeRequest(
'POST', 'test_runs', data, config
);
logger.info("response in createAccessibilityTestRun", response);
process.env.BS_A11Y_JWT = response?.data?.data?.accessibilityToken;
asambstack marked this conversation as resolved.
Show resolved Hide resolved
process.env.BS_A11Y_TEST_RUN_ID = response?.data?.data?.id;
asambstack marked this conversation as resolved.
Show resolved Hide resolved

if (process.env.BS_A11Y_JWT) {
process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'true';
}

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

} 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

logger.info("API URL IN noderequest", API_URL);
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
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
});
}
});
});
}

const getBuildDetails = (bsConfig) => {
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
let buildName = '',
projectName = '',
buildDescription = '',
buildTags = [];

/* Pick from environment variables */
buildName = process.env.BROWSERSTACK_BUILD_NAME || buildName;
projectName = process.env.BROWSERSTACK_PROJECT_NAME || projectName;

/* Pick from run settings */
buildName = buildName || bsConfig["run_settings"]["build_name"];
projectName = projectName || bsConfig["run_settings"]["project_name"];
if(!utils.isUndefined(bsConfig["run_settings"]["build_tag"])) buildTags = [...buildTags, bsConfig["run_settings"]["build_tag"]];

buildName = buildName || path.basename(path.resolve(process.cwd()));

return {
buildName,
projectName,
buildDescription,
buildTags
};
}

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

const setAccessibilityEventListeners = () => {
karanshah-browserstack marked this conversation as resolved.
Show resolved Hide resolved
logger.info("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);
}
}
47 changes: 47 additions & 0 deletions bin/accessibility-automation/plugin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@

const path = require("node:path");

const browserstackAccessibility = (on, config) => {
let browser_validation = true;
on('before:browser:launch', (browser = {}, launchOptions) => {
try {

if (browser.name !== 'chrome') {
console.log(`Accessibility Automation will run only on Chrome browsers.`);
browser_validation = false;
}
if (browser.majorVersion <= 94) {
console.log(`Accessibility Automation will run only on Chrome browser version greater than 94.`);
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved
browser_validation = false;
}
if (browser.isHeadless === true) {
console.log(`Accessibility Automation will not run on legacy headless mode. Switch to new headless mode or avoid using headless mode.`);
browser_validation = false;
}

if (process.env.ACCESSIBILITY_EXTENSION_PATH === undefined) {
browser_validation = false
return
}

if (browser_validation) {
const ally_path = path.dirname(process.env.ACCESSIBILITY_EXTENSION_PATH)
launchOptions.extensions.push(ally_path);
return launchOptions
}
} catch {}
rev-doshi marked this conversation as resolved.
Show resolved Hide resolved

})
config.env.ACCESSIBILITY_EXTENSION_PATH = process.env.ACCESSIBILITY_EXTENSION_PATH
config.env.OS_VERSION = process.env.OS_VERSION
config.env.OS = process.env.SESSION_OS

config.env.IS_ACCESSIBILITY_EXTENSION_LOADED = browser_validation.toString()

config.env.INCLUDE_TAGS_FOR_ACCESSIBILITY = process.env.ACCESSIBILITY_INCLUDETAGSINTESTINGSCOPE
config.env.EXCLUDE_TAGS_FOR_ACCESSIBILITY = process.env.ACCESSIBILITY_EXCLUDETAGSINTESTINGSCOPE

return config;
}

module.exports = browserstackAccessibility;
Loading