-
Notifications
You must be signed in to change notification settings - Fork 40
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
SDK-1884: Cypress SDK not wrapping A11Y commands appropriately #949
Changes from 6 commits
234fcfd
02ea36a
da5fc16
e8d4bc9
ccde61e
f96066a
f04069d
deefe01
2148ba1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,32 +6,60 @@ const browserStackLog = (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']; | ||
// scroll is not a default function in cypress. | ||
const commandToOverwrite = ['visit', 'click', 'type', 'request', 'dblclick', 'rightclick', 'clear', 'check', 'uncheck', 'select', 'trigger', 'selectFile', 'scrollIntoView', 'scrollTo', 'blur', 'focus', 'go', 'reload', 'submit', 'viewport', 'origin']; | ||
|
||
/* | ||
Overrriding the cypress commands to perform Accessibility Scan before Each command | ||
- runCutomizedCommand is handling both the cases of subject available in cypress original command | ||
and chaning available from original cypress command. | ||
*/ | ||
const performModifiedScan = (originalFn, Subject, stateType, ...args) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add some comments explaining the below function for future ref |
||
let customChaining = cy.wrap(null).performScan(); | ||
const changeSub = (args, stateType, newSubject) => { | ||
if (stateType !== 'parent') { | ||
return [newSubject, ...args.slice(1)]; | ||
} | ||
return args; | ||
} | ||
const runCutomizedCommand = () => { | ||
if (!Subject) { | ||
let cypressCommandSubject = (cy.subject?.call(cy)) ?? null; | ||
customChaining.then(() => cypressCommandSubject).then(() => { originalFn(...args); }); | ||
} else { | ||
let setTimeout = args.find(arg => arg?.timeout)?.timeout ?? null; | ||
let cypressCommandChain = (cy.subjectChain?.call(cy)) ?? null; | ||
customChaining.performScanSubjectQuery(cypressCommandChain, setTimeout).then({ timeout: 10000 }, newSubject => originalFn(...changeSub(args, stateType, newSubject))); | ||
} | ||
} | ||
runCutomizedCommand(); | ||
} | ||
|
||
const performScan = (win, payloadToSend) => | ||
new Promise(async (resolve, reject) => { | ||
const isHttpOrHttps = /^(http|https):$/.test(win.location.protocol); | ||
if (!isHttpOrHttps) { | ||
resolve(); | ||
return resolve(); | ||
} | ||
|
||
function findAccessibilityAutomationElement() { | ||
return win.document.querySelector("#accessibility-automation-element"); | ||
} | ||
|
||
function waitForScannerReadiness(retryCount = 30, retryInterval = 100) { | ||
function waitForScannerReadiness(retryCount = 100, retryInterval = 100) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes a11y team told to retry for 10 sec other framework have same count. |
||
return new Promise(async (resolve, reject) => { | ||
let count = 0; | ||
const intervalID = setInterval(async () => { | ||
if (count > retryCount) { | ||
clearInterval(intervalID); | ||
reject( | ||
return reject( | ||
new Error( | ||
"Accessibility Automation Scanner is not ready on the page." | ||
) | ||
); | ||
} else if (findAccessibilityAutomationElement()) { | ||
clearInterval(intervalID); | ||
resolve("Scanner set"); | ||
return resolve("Scanner set"); | ||
} else { | ||
count += 1; | ||
} | ||
|
@@ -42,7 +70,7 @@ new Promise(async (resolve, reject) => { | |
function startScan() { | ||
function onScanComplete() { | ||
win.removeEventListener("A11Y_SCAN_FINISHED", onScanComplete); | ||
resolve(); | ||
return resolve(); | ||
} | ||
|
||
win.addEventListener("A11Y_SCAN_FINISHED", onScanComplete); | ||
|
@@ -56,16 +84,16 @@ new Promise(async (resolve, reject) => { | |
waitForScannerReadiness() | ||
.then(startScan) | ||
.catch(async (err) => { | ||
resolve("Scanner is not ready on the page after multiple retries. performscan"); | ||
}); | ||
return resolve("Scanner is not ready on the page after multiple retries. performscan"); | ||
}); | ||
} | ||
}) | ||
|
||
const getAccessibilityResultsSummary = (win) => | ||
new Promise((resolve) => { | ||
const isHttpOrHttps = /^(http|https):$/.test(window.location.protocol); | ||
if (!isHttpOrHttps) { | ||
resolve(); | ||
return resolve(); | ||
} | ||
|
||
function findAccessibilityAutomationElement() { | ||
|
@@ -78,14 +106,14 @@ new Promise((resolve) => { | |
const intervalID = setInterval(() => { | ||
if (count > retryCount) { | ||
clearInterval(intervalID); | ||
reject( | ||
return reject( | ||
new Error( | ||
"Accessibility Automation Scanner is not ready on the page." | ||
) | ||
); | ||
} else if (findAccessibilityAutomationElement()) { | ||
clearInterval(intervalID); | ||
resolve("Scanner set"); | ||
return resolve("Scanner set"); | ||
} else { | ||
count += 1; | ||
} | ||
|
@@ -96,7 +124,7 @@ new Promise((resolve) => { | |
function getSummary() { | ||
function onReceiveSummary(event) { | ||
win.removeEventListener("A11Y_RESULTS_SUMMARY", onReceiveSummary); | ||
resolve(event.detail); | ||
return resolve(event.detail); | ||
} | ||
|
||
win.addEventListener("A11Y_RESULTS_SUMMARY", onReceiveSummary); | ||
|
@@ -110,16 +138,16 @@ new Promise((resolve) => { | |
waitForScannerReadiness() | ||
.then(getSummary) | ||
.catch((err) => { | ||
resolve(); | ||
}); | ||
return resolve(); | ||
}); | ||
} | ||
}) | ||
|
||
const getAccessibilityResults = (win) => | ||
new Promise((resolve) => { | ||
const isHttpOrHttps = /^(http|https):$/.test(window.location.protocol); | ||
if (!isHttpOrHttps) { | ||
resolve(); | ||
return resolve(); | ||
} | ||
|
||
function findAccessibilityAutomationElement() { | ||
|
@@ -132,14 +160,14 @@ new Promise((resolve) => { | |
const intervalID = setInterval(() => { | ||
if (count > retryCount) { | ||
clearInterval(intervalID); | ||
reject( | ||
return reject( | ||
new Error( | ||
"Accessibility Automation Scanner is not ready on the page." | ||
) | ||
); | ||
} else if (findAccessibilityAutomationElement()) { | ||
clearInterval(intervalID); | ||
resolve("Scanner set"); | ||
return resolve("Scanner set"); | ||
} else { | ||
count += 1; | ||
} | ||
|
@@ -150,7 +178,7 @@ new Promise((resolve) => { | |
function getResults() { | ||
function onReceivedResult(event) { | ||
win.removeEventListener("A11Y_RESULTS_RESPONSE", onReceivedResult); | ||
resolve(event.detail); | ||
return resolve(event.detail); | ||
} | ||
|
||
win.addEventListener("A11Y_RESULTS_RESPONSE", onReceivedResult); | ||
|
@@ -164,8 +192,8 @@ new Promise((resolve) => { | |
waitForScannerReadiness() | ||
.then(getResults) | ||
.catch((err) => { | ||
resolve(); | ||
}); | ||
return resolve(); | ||
}); | ||
} | ||
}); | ||
|
||
|
@@ -175,6 +203,7 @@ new Promise( (resolve, reject) => { | |
const isHttpOrHttps = /^(http|https):$/.test(win.location.protocol); | ||
if (!isHttpOrHttps) { | ||
resolve("Unable to save accessibility results, Invalid URL."); | ||
return; | ||
} | ||
|
||
function findAccessibilityAutomationElement() { | ||
|
@@ -187,14 +216,14 @@ new Promise( (resolve, reject) => { | |
const intervalID = setInterval(async () => { | ||
if (count > retryCount) { | ||
clearInterval(intervalID); | ||
reject( | ||
return reject( | ||
new Error( | ||
"Accessibility Automation Scanner is not ready on the page." | ||
) | ||
); | ||
} else if (findAccessibilityAutomationElement()) { | ||
clearInterval(intervalID); | ||
resolve("Scanner set"); | ||
return resolve("Scanner set"); | ||
} else { | ||
count += 1; | ||
} | ||
|
@@ -204,7 +233,7 @@ new Promise( (resolve, reject) => { | |
|
||
function saveResults() { | ||
function onResultsSaved(event) { | ||
resolve(); | ||
return resolve(); | ||
} | ||
win.addEventListener("A11Y_RESULTS_SAVED", onResultsSaved); | ||
const e = new CustomEvent("A11Y_SAVE_RESULTS", { | ||
|
@@ -219,11 +248,12 @@ new Promise( (resolve, reject) => { | |
waitForScannerReadiness() | ||
.then(saveResults) | ||
.catch(async (err) => { | ||
resolve("Scanner is not ready on the page after multiple retries. after run"); | ||
return resolve("Scanner is not ready on the page after multiple retries. after run"); | ||
}); | ||
} | ||
} catch(er) { | ||
resolve() | ||
} catch(error) { | ||
browserStackLog(`Error in saving results with error: ${error.message}`); | ||
return resolve(); | ||
} | ||
|
||
}) | ||
|
@@ -254,39 +284,40 @@ const shouldScanForAccessibility = (attributes) => { | |
const included = includeTagArray.length === 0 || includeTags.some((include) => fullTestName.includes(include)); | ||
shouldScanTestForAccessibility = !excluded && included; | ||
} catch (error) { | ||
browserStackLog("Error while validating test case for accessibility before scanning. Error : ", error); | ||
browserStackLog(`Error while validating test case for accessibility before scanning. Error : ${error.message}`); | ||
} | ||
} | ||
|
||
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') { | ||
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; | ||
|
||
cy.window().then((win) => { | ||
browserStackLog('Performing scan form command ' + command.attributes.name); | ||
cy.wrap(performScan(win, {method: command.attributes.name}), {timeout: 30000}); | ||
}) | ||
}) | ||
commandToOverwrite.forEach((command) => { | ||
Cypress.Commands.overwrite(command, (originalFn, ...args) => { | ||
const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest || Cypress.mocha.getRunner().suite.ctx._runnable; | ||
const shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); | ||
const state = cy.state('current'), Subject = 'getSubjectFromChain' in cy; | ||
const stateName = state === null || state === void 0 ? void 0 : state.get('name'); | ||
let stateType; | ||
if (!shouldScanTestForAccessibility || (stateName && stateName !== command)) { | ||
return originalFn(...args); | ||
} | ||
if(state !== null && state !== void 0){ | ||
stateType = state.get('type'); | ||
} | ||
else { | ||
stateType = null; | ||
} | ||
performModifiedScan(originalFn, Subject, stateType, ...args); | ||
}); | ||
}); | ||
|
||
afterEach(() => { | ||
const attributes = Cypress.mocha.getRunner().suite.ctx.currentTest; | ||
cy.window().then(async (win) => { | ||
let shouldScanTestForAccessibility = shouldScanForAccessibility(attributes); | ||
if (!shouldScanTestForAccessibility) return cy.wrap({}); | ||
|
||
cy.wrap(performScan(win), {timeout: 30000}).then(() => { | ||
cy.wrap(performScan(win), {timeout: 10000}).then(() => { | ||
try { | ||
let os_data; | ||
if (Cypress.env("OS")) { | ||
|
@@ -317,11 +348,12 @@ afterEach(() => { | |
} | ||
}; | ||
browserStackLog(`Saving accessibility test results`); | ||
cy.wrap(saveTestResults(win, payloadToSend), {timeout: 30000}).then(() => { | ||
cy.wrap(saveTestResults(win, payloadToSend), {timeout: 10000}).then(() => { | ||
browserStackLog(`Saved accessibility test results`); | ||
}) | ||
|
||
} catch (er) { | ||
browserStackLog(`Error in saving results with error: ${er.message}`); | ||
} | ||
}) | ||
}); | ||
|
@@ -337,9 +369,11 @@ Cypress.Commands.add('performScan', () => { | |
} | ||
cy.window().then(async (win) => { | ||
browserStackLog(`Performing accessibility scan`); | ||
await performScan(win); | ||
cy.wrap(performScan(win), {timeout:10000}); | ||
}); | ||
} catch {} | ||
} catch(error) { | ||
browserStackLog(`Error in performing scan with error: ${error.message}`); | ||
} | ||
}) | ||
|
||
Cypress.Commands.add('getAccessibilityResultsSummary', () => { | ||
|
@@ -355,7 +389,9 @@ Cypress.Commands.add('getAccessibilityResultsSummary', () => { | |
browserStackLog('Getting accessibility results summary'); | ||
return await getAccessibilityResultsSummary(win); | ||
}); | ||
} catch {} | ||
} catch(error) { | ||
browserStackLog(`Error in getting accessibilty results summary with error: ${error.message}`); | ||
} | ||
|
||
}); | ||
|
||
|
@@ -376,6 +412,12 @@ Cypress.Commands.add('getAccessibilityResults', () => { | |
return await getAccessibilityResults(win); | ||
}); | ||
|
||
} catch {} | ||
} catch(error) { | ||
browserStackLog(`Error in getting accessibilty results with error: ${error.message}`); | ||
} | ||
}); | ||
|
||
Cypress.Commands.addQuery('performScanSubjectQuery', function (chaining, setTimeout) { | ||
this.set('timeout', setTimeout); | ||
return () => cy.getSubjectFromChain(chaining); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
const path = require("node:path"); | ||
const { decodeJWTToken } = require("../../helpers/utils"); | ||
const utils = require('../../helpers/utils'); | ||
|
||
const browserstackAccessibility = (on, config) => { | ||
let browser_validation = true; | ||
|
@@ -30,7 +32,16 @@ const browserstackAccessibility = (on, config) => { | |
} | ||
if (browser_validation) { | ||
const ally_path = path.dirname(process.env.ACCESSIBILITY_EXTENSION_PATH) | ||
const payload = decodeJWTToken(process.env.ACCESSIBILITY_AUTH); | ||
launchOptions.extensions.push(ally_path); | ||
if(!utils.isUndefined(payload) && !utils.isUndefined(payload.a11y_core_config) && payload.a11y_core_config.domForge === true) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. significance of these? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. other ticket SDK-1907 |
||
launchOptions.args.push("--auto-open-devtools-for-tabs"); | ||
launchOptions.preferences.default["devtools"] = launchOptions.preferences.default["devtools"] || {}; | ||
launchOptions.preferences.default["devtools"]["preferences"] = launchOptions.preferences.default["devtools"]["preferences"] || {}; | ||
launchOptions.preferences.default["devtools"]["preferences"][ | ||
"currentDockState" | ||
] = '"undocked"'; | ||
} | ||
return launchOptions | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reason for removing
scroll
from the list ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scroll is not a default cypress function. This error was occuring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this verified across all supported cy versions ?