Skip to content

cypress accessibility plugin #321

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 6 commits into from
Jul 26, 2024
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
58 changes: 58 additions & 0 deletions accessibility/plugin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const fs = require("fs");
const Accessibility = (on, config) => {

on('task', {
lambdatest_log(message) {
console.log(message)
return null
},
initializeFile(filePath) {
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, '[]');
}
return filePath;
}
})

let browser_validation = true;

on('before:browser:launch', (browser = {}, launchOptions) => {
try {
if (process.env.ACCESSIBILITY_EXTENSION_PATH !== undefined) {
if (browser.name !== 'chrome') {
console.log(`Accessibility Automation will run only on Chrome browsers.`);
browser_validation = false;
}
if (browser.name === 'chrome' && browser.majorVersion <= 94) {
console.log(`Accessibility Automation will run only on Chrome browser version greater than 94.`);
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 (browser_validation) {

const accessibility_ext_path = process.env.ACCESSIBILITY_EXTENSION_PATH

launchOptions.args.push(`--load-extension=` + accessibility_ext_path)
return launchOptions
}
}
} catch(err) {
console.log(`Error in loading Accessibility Automation extension: ${err.message}`);
}

})
config.env.WCAG_CRITERIA= process.env.WCAG_CRITERIA;
config.env.BEST_PRACTICE= process.env.BEST_PRACTICE;
config.env.NEEDS_REVIEW= process.env.NEEDS_REVIEW;
config.env.ACCESSIBILITY_REPORT_PATH = process.env.ACCESSIBILITY_REPORT_PATH;
console.log(`parameter for accessibility report WCAG_CRITERIA - ` + config.env.WCAG_CRITERIA)
console.log(`parameter for accessibility report BEST_PRACTICE -` + config.env.BEST_PRACTICE)
console.log(`parameter for accessibility report NEEDS_REVIEW -` + config.env.NEEDS_REVIEW)

return config;
}

module.exports = Accessibility;
179 changes: 179 additions & 0 deletions accessibility/scanner/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
const fs = require("fs")

const LambdatestLog = (message) => {
if (!Cypress.env('LAMBDATEST_LOGS')) return;
cy.task('lambdatest_log', message);
}

const commandsToWrap = ['visit', 'click', 'type', 'request', 'dblclick', 'rightclick', 'clear', 'check', 'uncheck', 'select', 'trigger', 'selectFile', 'scrollIntoView', 'scroll', 'scrollTo', 'blur', 'focus', 'go', 'reload', 'submit', 'viewport', 'origin'];

const setScanConfig = (win, payload) =>
new Promise(async (resolve, reject) => {
const isHttpOrHttps = /^(http|https):$/.test(win.location.protocol);
if (!isHttpOrHttps) {
resolve();
}

function startScan() {
console.log('log', "Accessibility setting scan config")
function onScanComplete(event) {
win.document.removeEventListener("automation-custom-event", onScanComplete);
console.log('log', "Recieved scan config data " + event.detail)
resolve(event.detail);
}

win.document.addEventListener("automation-custom-event", onScanComplete);
const e = new CustomEvent("accessibility-extension-custom-event", { detail: payload });
win.document.dispatchEvent(e);


setTimeout(() => {
reject(new Error('automation-custom-event not received within timeout'));
}, 9000);
}
startScan();

})

const getScanData = (win, payload) =>
new Promise( async (resolve,reject) => {
const isHttpOrHttps = /^(http|https):$/.test(window.location.protocol);
if (!isHttpOrHttps) {
resolve();
}


function getSummary() {
function onReceiveSummary(event) {

win.document.removeEventListener("automation-custom-event", onReceiveSummary);
resolve(event.detail);
}


win.document.addEventListener("automation-custom-event", onReceiveSummary);
const e = new CustomEvent("accessibility-extension-custom-event", { detail: payload });
win.document.dispatchEvent(e);

setTimeout(() => {
reject(new Error('automation-custom-event not received within timeout'));
}, 9000);

}


getSummary();

})

const shouldScanForAccessibility = (attributes) => {
if (Cypress.env("IS_ACCESSIBILITY_EXTENSION_LOADED") !== "true") return false;

const extensionPath = Cypress.env("ACCESSIBILITY_EXTENSION_PATH");
const isHeaded = Cypress.browser.isHeaded;

if (!isHeaded || (extensionPath === undefined)) return false;

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) {
LambdatestLog("Error while validating test case for accessibility before scanning. Error : ", error);
}
}

return shouldScanTestForAccessibility;
}

Cypress.on('command:start', async (command) => {
if(!command || !command.attributes) return;
if(command.attributes.name == 'window' || command.attributes.name == 'then' || command.attributes.name == 'wrap' || command.attributes.name == 'wait') {
return;
}

if (!commandsToWrap.includes(command.attributes.name)) return;

// const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable;

// let shouldScanTestForAccessibility = shouldScanForAccessibility(attributes);
// if (!shouldScanTestForAccessibility) return;
// console.log('log', "debugging scan form command " + command.attributes.name);
console.log('log', "debugging scan form command " + command.attributes.name);
cy.window().then((win) => {
// LambdatestLog('Performing scan form command ' + command.attributes.name);
let wcagCriteriaValue = Cypress.env("WCAG_CRITERIA") || "wcag21a";
let bestPracticeValue = Cypress.env("BEST_PRACTICE") || false;
let needsReviewValue = Cypress.env("NEEDS_REVIEW") || true;

const payloadToSend = {
message: 'SET_CONFIG',
wcagCriteria: wcagCriteriaValue,
bestPractice: bestPracticeValue,
needsReview: needsReviewValue
}
let testId = Cypress.env("TEST_ID") || ""
// const filePath = 'cypress/reports/accessibilityReport_' + testId + '.json';
const filePath = Cypress.env("ACCESSIBILITY_REPORT_PATH") || 'cypress/results/accessibilityReport_' + testId + '.json';

cy.wrap(setScanConfig(win, payloadToSend), {timeout: 30000}).then((res) => {
// LambdatestLog('log', "logging report **************")
console.log('logging config reponse', res);

const payload = {
message: 'GET_LATEST_SCAN_DATA',
}
// cy.wait(5000);
cy.wrap(getScanData(win, payload), {timeout: 30000}).then((res) => {
LambdatestLog('log', "logging report **************")


cy.task('initializeFile', filePath).then((filePath) => {
cy.readFile(filePath, { log: true, timeout: 30000 }).then((fileContent) => {
let resultsArray = [{}];
console.log('logging report', res);
// If the file is not empty, parse the existing content
if (fileContent) {
try {
resultsArray = JSON.parse(JSON.stringify(fileContent));
} catch (e) {
console.log("parsing error for content " , fileContent)
console.log('Error parsing JSON file:', e);
return;
}
}
console.log('debugging res', res.message);
if (res.message == "GET_LATEST_SCAN_DATA") {
// Append the new result
resultsArray.push(res);
console.log('resultsarray logging', resultsArray);
}

// Write the updated content back to the file
cy.writeFile(filePath, resultsArray, { log: true, timeout: 30000 });
});
});
});

});
})
})


Cypress.on('command:end', (command) => {

return;
})
10 changes: 10 additions & 0 deletions commands/utils/set_args.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,16 @@ function sync_args_from_cmd(args) {
lt_config["run_settings"]["useNode18"] = false;
}

if ("accessibility" in args) {
if (args["accessibility"] == "true") {
lt_config.run_settings.accessibility = true;
} else {
lt_config.run_settings.accessibility = false;
}
} else if (lt_config["run_settings"]["accessibility"] && !lt_config["run_settings"]["accessibility"]) {
lt_config["run_settings"]["accessibility"] = false;
}

if ("network_ws" in args) {
if (args["network_ws"] == "true") {
lt_config.run_settings.network_ws = true;
Expand Down
9 changes: 8 additions & 1 deletion commands/utils/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,14 @@ module.exports = validate_config = function (lt_config, validation_configs) {
}
}

//validate if network_http2 field contains expected value
//validate if accessibility field contains expected value
if ("accessibility" in lt_config["run_settings"]) {
if (![true, false].includes(lt_config["run_settings"]["accessibility"])) {
reject("Error!! boolean value is expected in accessibility key");
}
}

//validate if useNode18 field contains expected value
if ("useNode18" in lt_config["run_settings"]) {
if (![true, false].includes(lt_config["run_settings"]["useNode18"])) {
reject("Error!! boolean value is expected in useNode18 key");
Expand Down
5 changes: 5 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@ const argv = require("yargs")
alias: "network_sse",
describe: "Bypass sse events calls for Network logs",
type: "bool",
})
.option("cypress_accessibility", {
alias: "accessibility",
describe: "enable accessibility testing for cypress.",
type: "bool",
});
},
function (argv) {
Expand Down