Skip to content

Commit b884205

Browse files
Merge pull request #707 from rev-doshi/cypress-accessibility
initial changes
2 parents e75e182 + 3441bb6 commit b884205

File tree

9 files changed

+764
-231
lines changed

9 files changed

+764
-231
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
exports.API_URL = 'https://accessibility.browserstack.com/api';
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/* Event listeners + custom commands for Cypress */
2+
3+
Cypress.on('test:before:run', () => {
4+
try {
5+
if (Cypress.env("IS_ACCESSIBILITY_EXTENSION_LOADED") !== "true") return
6+
const extensionPath = Cypress.env("ACCESSIBILITY_EXTENSION_PATH")
7+
8+
if (extensionPath !== undefined) {
9+
new Promise((resolve, reject) => {
10+
window.parent.addEventListener('A11Y_TAP_STARTED', () => {
11+
resolve("A11Y_TAP_STARTED");
12+
});
13+
const e = new CustomEvent('A11Y_FORCE_START');
14+
window.parent.dispatchEvent(e);
15+
})
16+
}
17+
} catch {}
18+
19+
});
20+
21+
Cypress.on('test:after:run', (attributes, runnable) => {
22+
try {
23+
if (Cypress.env("IS_ACCESSIBILITY_EXTENSION_LOADED") !== "true") return
24+
const extensionPath = Cypress.env("ACCESSIBILITY_EXTENSION_PATH")
25+
const isHeaded = Cypress.browser.isHeaded;
26+
if (isHeaded && extensionPath !== undefined) {
27+
28+
let shouldScanTestForAccessibility = true;
29+
if (Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY") || Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY")) {
30+
31+
try {
32+
let includeTagArray = [];
33+
let excludeTagArray = [];
34+
if (Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY")) {
35+
includeTagArray = Cypress.env("INCLUDE_TAGS_FOR_ACCESSIBILITY").split(";")
36+
}
37+
if (Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY")) {
38+
excludeTagArray = Cypress.env("EXCLUDE_TAGS_FOR_ACCESSIBILITY").split(";")
39+
}
40+
41+
const fullTestName = attributes.title;
42+
const excluded = excludeTagArray.some((exclude) => fullTestName.includes(exclude));
43+
const included = includeTagArray.length === 0 || includeTags.some((include) => fullTestName.includes(include));
44+
shouldScanTestForAccessibility = !excluded && included;
45+
} catch (error) {
46+
console.log("Error while validating test case for accessibility before scanning. Error : ", error);
47+
}
48+
}
49+
let os_data;
50+
if (Cypress.env("OS")) {
51+
os_data = Cypress.env("OS");
52+
} else {
53+
os_data = Cypress.platform === 'linux' ? 'mac' : "win"
54+
}
55+
let filePath = '';
56+
if (attributes.invocationDetails !== undefined && attributes.invocationDetails.relativeFile !== undefined) {
57+
filePath = attributes.invocationDetails.relativeFile;
58+
}
59+
const dataForExtension = {
60+
"saveResults": shouldScanTestForAccessibility,
61+
"testDetails": {
62+
"name": attributes.title,
63+
"testRunId": '5058', // variable not consumed, shouldn't matter what we send
64+
"filePath": filePath,
65+
"scopeList": [
66+
filePath,
67+
attributes.title
68+
]
69+
},
70+
"platform": {
71+
"os_name": os_data,
72+
"os_version": Cypress.env("OS_VERSION"),
73+
"browser_name": Cypress.browser.name,
74+
"browser_version": Cypress.browser.version
75+
}
76+
};
77+
return new Promise((resolve, reject) => {
78+
if (dataForExtension.saveResults) {
79+
window.parent.addEventListener('A11Y_TAP_TRANSPORTER', (event) => {
80+
resolve(event.detail);
81+
});
82+
}
83+
const e = new CustomEvent('A11Y_TEST_END', {detail: dataForExtension});
84+
window.parent.dispatchEvent(e);
85+
if (dataForExtension.saveResults !== true )
86+
resolve();
87+
});
88+
}
89+
90+
} catch {}
91+
});
92+
93+
Cypress.Commands.add('getAccessibilityResultsSummary', () => {
94+
try {
95+
if (Cypress.env("IS_ACCESSIBILITY_EXTENSION_LOADED") !== "true") {
96+
console.log(`Not a Accessibility Automation session, cannot retrieve Accessibility results.`);
97+
return
98+
}
99+
return new Promise(function (resolve, reject) {
100+
try{
101+
const e = new CustomEvent('A11Y_TAP_GET_RESULTS_SUMMARY');
102+
const fn = function (event) {
103+
window.parent.removeEventListener('A11Y_RESULTS_SUMMARY_RESPONSE', fn);
104+
resolve(event.detail.summary);
105+
};
106+
window.parent.addEventListener('A11Y_RESULTS_SUMMARY_RESPONSE', fn);
107+
window.parent.dispatchEvent(e);
108+
} catch (err) {
109+
console.log("No accessibility results summary was found.");
110+
reject(err);
111+
}
112+
});
113+
} catch {}
114+
115+
});
116+
117+
Cypress.Commands.add('getAccessibilityResults', () => {
118+
try {
119+
if (Cypress.env("IS_ACCESSIBILITY_EXTENSION_LOADED") !== "true") {
120+
console.log(`Not a Accessibility Automation session, cannot retrieve Accessibility results.`);
121+
return
122+
}
123+
return new Promise(function (resolve, reject) {
124+
try{
125+
const e = new CustomEvent('A11Y_TAP_GET_RESULTS');
126+
const fn = function (event) {
127+
window.parent.removeEventListener('A11Y_RESULTS_RESPONSE', fn);
128+
resolve(event.detail.summary);
129+
};
130+
window.parent.addEventListener('A11Y_RESULTS_RESPONSE', fn);
131+
window.parent.dispatchEvent(e);
132+
} catch (err) {
133+
console.log("No accessibility results were found.");
134+
reject(err);
135+
}
136+
});
137+
} catch {}
138+
139+
});
140+
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
const logger = require("../helpers/logger").winstonLogger;
2+
const { API_URL } = require('./constants');
3+
const utils = require('../helpers/utils');
4+
const fs = require('fs');
5+
const path = require('path');
6+
const request = require('request');
7+
const os = require('os');
8+
const glob = require('glob');
9+
const helper = require('../helpers/helper');
10+
const { CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS } = require('../helpers/constants');
11+
const supportFileContentMap = {}
12+
13+
exports.checkAccessibilityPlatform = (user_config) => {
14+
let accessibility = false;
15+
try {
16+
user_config.browsers.forEach(browser => {
17+
if (browser.accessibility) {
18+
accessibility = true;
19+
}
20+
})
21+
} catch {}
22+
23+
return accessibility;
24+
}
25+
26+
exports.setAccessibilityCypressCapabilities = async (user_config, accessibilityResponse) => {
27+
if (utils.isUndefined(user_config.run_settings.accessibilityOptions)) {
28+
user_config.run_settings.accessibilityOptions = {}
29+
}
30+
user_config.run_settings.accessibilityOptions["authToken"] = accessibilityResponse.data.accessibilityToken;
31+
user_config.run_settings.accessibilityOptions["auth"] = accessibilityResponse.data.accessibilityToken;
32+
user_config.run_settings.accessibilityOptions["scannerVersion"] = accessibilityResponse.data.scannerVersion;
33+
user_config.run_settings.system_env_vars.push(`ACCESSIBILITY_AUTH=${accessibilityResponse.data.accessibilityToken}`)
34+
user_config.run_settings.system_env_vars.push(`ACCESSIBILITY_SCANNERVERSION=${accessibilityResponse.data.scannerVersion}`)
35+
}
36+
37+
exports.isAccessibilitySupportedCypressVersion = (cypress_config_filename) => {
38+
const extension = cypress_config_filename.split('.').pop();
39+
return CYPRESS_V10_AND_ABOVE_CONFIG_FILE_EXTENSIONS.includes(extension);
40+
}
41+
42+
exports.createAccessibilityTestRun = async (user_config, framework) => {
43+
44+
try {
45+
if (!this.isAccessibilitySupportedCypressVersion(user_config.run_settings.cypress_config_file) ){
46+
logger.warn(`Accessibility Testing is not supported on Cypress version 9 and below.`)
47+
process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'false';
48+
user_config.run_settings.accessibility = false;
49+
return;
50+
}
51+
const userName = user_config["auth"]["username"];
52+
const accessKey = user_config["auth"]["access_key"];
53+
let settings = utils.isUndefined(user_config.run_settings.accessibilityOptions) ? {} : user_config.run_settings.accessibilityOptions
54+
55+
const {
56+
buildName,
57+
projectName,
58+
buildDescription
59+
} = helper.getBuildDetails(user_config);
60+
61+
const data = {
62+
'projectName': projectName,
63+
'buildName': buildName,
64+
'startTime': (new Date()).toISOString(),
65+
'description': buildDescription,
66+
'source': {
67+
frameworkName: "Cypress",
68+
frameworkVersion: helper.getPackageVersion('cypress', user_config),
69+
sdkVersion: helper.getAgentVersion()
70+
},
71+
'settings': settings,
72+
'versionControl': await helper.getGitMetaData(),
73+
'ciInfo': helper.getCiInfo(),
74+
'hostInfo': {
75+
hostname: os.hostname(),
76+
platform: os.platform(),
77+
type: os.type(),
78+
version: os.version(),
79+
arch: os.arch()
80+
},
81+
'browserstackAutomation': process.env.BROWSERSTACK_AUTOMATION === 'true'
82+
};
83+
84+
const config = {
85+
auth: {
86+
user: userName,
87+
pass: accessKey
88+
},
89+
headers: {
90+
'Content-Type': 'application/json'
91+
}
92+
};
93+
94+
const response = await nodeRequest(
95+
'POST', 'test_runs', data, config, API_URL
96+
);
97+
if(!utils.isUndefined(response.data)) {
98+
process.env.BS_A11Y_JWT = response.data.data.accessibilityToken;
99+
process.env.BS_A11Y_TEST_RUN_ID = response.data.data.id;
100+
}
101+
if (process.env.BS_A11Y_JWT) {
102+
process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'true';
103+
}
104+
logger.debug(`BrowserStack Accessibility Automation Test Run ID: ${response.data.data.id}`);
105+
106+
this.setAccessibilityCypressCapabilities(user_config, response.data);
107+
setAccessibilityEventListeners();
108+
helper.setBrowserstackCypressCliDependency(user_config);
109+
110+
} catch (error) {
111+
if (error.response) {
112+
logger.error(
113+
`Exception while creating test run for BrowserStack Accessibility Automation: ${
114+
error.response.status
115+
} ${error.response.statusText} ${JSON.stringify(error.response.data)}`
116+
);
117+
} else {
118+
if(error.message === 'Invalid configuration passed.') {
119+
logger.error(
120+
`Exception while creating test run for BrowserStack Accessibility Automation: ${
121+
error.message || error.stack
122+
}`
123+
);
124+
for(const errorkey of error.errors){
125+
logger.error(errorkey.message);
126+
}
127+
128+
} else {
129+
logger.error(
130+
`Exception while creating test run for BrowserStack Accessibility Automation: ${
131+
error.message || error.stack
132+
}`
133+
);
134+
}
135+
// since create accessibility session failed
136+
process.env.BROWSERSTACK_TEST_ACCESSIBILITY = 'false';
137+
user_config.run_settings.accessibility = false;
138+
}
139+
}
140+
}
141+
142+
const nodeRequest = (type, url, data, config) => {
143+
return new Promise(async (resolve, reject) => {
144+
const options = {...config,...{
145+
method: type,
146+
url: `${API_URL}/${url}`,
147+
body: data,
148+
json: config.headers['Content-Type'] === 'application/json',
149+
}};
150+
151+
request(options, function callback(error, response, body) {
152+
if(error) {
153+
logger.info("error in nodeRequest", error);
154+
reject(error);
155+
} else if(!(response.statusCode == 201 || response.statusCode == 200)) {
156+
logger.info("response.statusCode in nodeRequest", response.statusCode);
157+
reject(response && response.body ? response.body : `Received response from BrowserStack Server with status : ${response.statusCode}`);
158+
} else {
159+
try {
160+
if(typeof(body) !== 'object') body = JSON.parse(body);
161+
} catch(e) {
162+
if(!url.includes('/stop')) {
163+
reject('Not a JSON response from BrowserStack Server');
164+
}
165+
}
166+
resolve({
167+
data: body
168+
});
169+
}
170+
});
171+
});
172+
}
173+
174+
exports.supportFileCleanup = () => {
175+
logger.debug("Cleaning up support file changes added for accessibility. ")
176+
Object.keys(supportFileContentMap).forEach(file => {
177+
try {
178+
fs.writeFileSync(file, supportFileContentMap[file], {encoding: 'utf-8'});
179+
} catch(e) {
180+
logger.debug(`Error while replacing file content for ${file} with it's original content with error : ${e}`, true, e);
181+
}
182+
});
183+
}
184+
185+
const getAccessibilityCypressCommandEventListener = () => {
186+
return (
187+
`require('browserstack-cypress-cli/bin/accessibility-automation/cypress');`
188+
);
189+
}
190+
191+
const setAccessibilityEventListeners = () => {
192+
try {
193+
const cypressCommandEventListener = getAccessibilityCypressCommandEventListener();
194+
glob(process.cwd() + '/cypress/support/*.js', {}, (err, files) => {
195+
if(err) return logger.debug('EXCEPTION IN BUILD START EVENT : Unable to parse cypress support files');
196+
files.forEach(file => {
197+
try {
198+
if(!file.includes('commands.js')) {
199+
const defaultFileContent = fs.readFileSync(file, {encoding: 'utf-8'});
200+
201+
if(!defaultFileContent.includes(cypressCommandEventListener)) {
202+
let newFileContent = defaultFileContent +
203+
'\n' +
204+
cypressCommandEventListener +
205+
'\n'
206+
fs.writeFileSync(file, newFileContent, {encoding: 'utf-8'});
207+
supportFileContentMap[file] = defaultFileContent;
208+
}
209+
}
210+
} catch(e) {
211+
logger.debug(`Unable to modify file contents for ${file} to set event listeners with error ${e}`, true, e);
212+
}
213+
});
214+
});
215+
} catch(e) {
216+
logger.debug(`Unable to parse support files to set event listeners with error ${e}`, true, e);
217+
}
218+
}

0 commit comments

Comments
 (0)