@govtechsg/oobee 0.10.76 → 0.10.78-alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +8 -1
- package/INTEGRATION.md +50 -3
- package/dist/cli.js +252 -0
- package/dist/combine.js +221 -0
- package/dist/constants/cliFunctions.js +306 -0
- package/dist/constants/common.js +1669 -0
- package/dist/constants/constants.js +913 -0
- package/dist/constants/errorMeta.json +319 -0
- package/dist/constants/itemTypeDescription.js +7 -0
- package/dist/constants/oobeeAi.js +121 -0
- package/dist/constants/questions.js +151 -0
- package/dist/constants/sampleData.js +176 -0
- package/dist/crawlers/commonCrawlerFunc.js +428 -0
- package/dist/crawlers/crawlDomain.js +613 -0
- package/dist/crawlers/crawlIntelligentSitemap.js +135 -0
- package/dist/crawlers/crawlLocalFile.js +151 -0
- package/dist/crawlers/crawlSitemap.js +303 -0
- package/dist/crawlers/custom/escapeCssSelector.js +10 -0
- package/dist/crawlers/custom/evaluateAltText.js +11 -0
- package/dist/crawlers/custom/extractAndGradeText.js +44 -0
- package/dist/crawlers/custom/extractText.js +27 -0
- package/dist/crawlers/custom/findElementByCssSelector.js +36 -0
- package/dist/crawlers/custom/flagUnlabelledClickableElements.js +963 -0
- package/dist/crawlers/custom/framesCheck.js +37 -0
- package/dist/crawlers/custom/getAxeConfiguration.js +111 -0
- package/dist/crawlers/custom/gradeReadability.js +23 -0
- package/dist/crawlers/custom/utils.js +1024 -0
- package/dist/crawlers/custom/xPathToCss.js +147 -0
- package/dist/crawlers/guards/urlGuard.js +71 -0
- package/dist/crawlers/pdfScanFunc.js +276 -0
- package/dist/crawlers/runCustom.js +89 -0
- package/dist/exclusions.txt +7 -0
- package/dist/generateHtmlReport.js +144 -0
- package/dist/index.js +62 -0
- package/dist/logs.js +84 -0
- package/dist/mergeAxeResults.js +1588 -0
- package/dist/npmIndex.js +640 -0
- package/dist/proxyService.js +360 -0
- package/dist/runGenerateJustHtmlReport.js +16 -0
- package/dist/screenshotFunc/htmlScreenshotFunc.js +355 -0
- package/dist/screenshotFunc/pdfScreenshotFunc.js +645 -0
- package/dist/services/s3Uploader.js +127 -0
- package/dist/static/ejs/partials/components/allIssues/AllIssues.ejs +9 -0
- package/dist/static/ejs/partials/components/allIssues/CategoryBadges.ejs +82 -0
- package/dist/static/ejs/partials/components/allIssues/FilterBar.ejs +33 -0
- package/dist/static/ejs/partials/components/allIssues/IssuesTable.ejs +41 -0
- package/dist/static/ejs/partials/components/header/SiteInfo.ejs +119 -0
- package/dist/static/ejs/partials/components/header/aboutScanModal/AboutScanModal.ejs +15 -0
- package/dist/static/ejs/partials/components/header/aboutScanModal/ScanConfiguration.ejs +44 -0
- package/dist/static/ejs/partials/components/header/aboutScanModal/ScanDetails.ejs +142 -0
- package/dist/static/ejs/partials/components/prioritiseIssues/IssueDetailCard.ejs +36 -0
- package/dist/static/ejs/partials/components/prioritiseIssues/PrioritiseIssues.ejs +47 -0
- package/dist/static/ejs/partials/components/ruleModal/ruleOffcanvas.ejs +196 -0
- package/dist/static/ejs/partials/components/scannedPagesSegmentedTabs.ejs +48 -0
- package/dist/static/ejs/partials/components/screenshotLightbox.ejs +13 -0
- package/dist/static/ejs/partials/components/shared/InfoAlert.ejs +3 -0
- package/dist/static/ejs/partials/components/summaryScanAbout.ejs +141 -0
- package/dist/static/ejs/partials/components/summaryScanResults.ejs +16 -0
- package/dist/static/ejs/partials/components/summaryTable.ejs +20 -0
- package/dist/static/ejs/partials/components/summaryWcagCompliance.ejs +94 -0
- package/dist/static/ejs/partials/components/topTen.ejs +6 -0
- package/dist/static/ejs/partials/components/wcagCompliance/FailedCriteria.ejs +47 -0
- package/dist/static/ejs/partials/components/wcagCompliance/WcagCompliance.ejs +16 -0
- package/dist/static/ejs/partials/components/wcagCompliance/WcagGaugeBar.ejs +16 -0
- package/dist/static/ejs/partials/components/wcagCoverageDetails.ejs +18 -0
- package/dist/static/ejs/partials/footer.ejs +24 -0
- package/dist/static/ejs/partials/header.ejs +14 -0
- package/dist/static/ejs/partials/main.ejs +29 -0
- package/dist/static/ejs/partials/scripts/allIssues/AllIssues.ejs +376 -0
- package/dist/static/ejs/partials/scripts/bootstrap.ejs +8 -0
- package/dist/static/ejs/partials/scripts/categorySummary.ejs +141 -0
- package/dist/static/ejs/partials/scripts/decodeUnzipParse.ejs +3 -0
- package/dist/static/ejs/partials/scripts/header/SiteInfo.ejs +44 -0
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/AboutScanModal.ejs +51 -0
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanConfiguration.ejs +127 -0
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanDetails.ejs +60 -0
- package/dist/static/ejs/partials/scripts/highlightjs.ejs +335 -0
- package/dist/static/ejs/partials/scripts/popper.ejs +7 -0
- package/dist/static/ejs/partials/scripts/prioritiseIssues/IssueDetailCard.ejs +137 -0
- package/dist/static/ejs/partials/scripts/prioritiseIssues/PrioritiseIssues.ejs +214 -0
- package/dist/static/ejs/partials/scripts/prioritiseIssues/wcagSvgMap.ejs +861 -0
- package/dist/static/ejs/partials/scripts/ruleModal/constants.ejs +957 -0
- package/dist/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +353 -0
- package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +468 -0
- package/dist/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +306 -0
- package/dist/static/ejs/partials/scripts/ruleModal/utilities.ejs +483 -0
- package/dist/static/ejs/partials/scripts/scannedPagesSegmentedTabs.ejs +35 -0
- package/dist/static/ejs/partials/scripts/screenshotLightbox.ejs +75 -0
- package/dist/static/ejs/partials/scripts/summaryScanResults.ejs +14 -0
- package/dist/static/ejs/partials/scripts/summaryTable.ejs +78 -0
- package/dist/static/ejs/partials/scripts/topTen.ejs +61 -0
- package/dist/static/ejs/partials/scripts/utils.ejs +453 -0
- package/dist/static/ejs/partials/scripts/wcagCompliance/FailedCriteria.ejs +103 -0
- package/dist/static/ejs/partials/scripts/wcagCompliance/WcagGaugeBar.ejs +47 -0
- package/dist/static/ejs/partials/scripts/wcagCompliance.ejs +15 -0
- package/dist/static/ejs/partials/scripts/wcagCoverageDetails.ejs +75 -0
- package/dist/static/ejs/partials/styles/allIssues/AllIssues.ejs +384 -0
- package/dist/static/ejs/partials/styles/bootstrap.ejs +12391 -0
- package/dist/static/ejs/partials/styles/header/SiteInfo.ejs +121 -0
- package/dist/static/ejs/partials/styles/header/aboutScanModal/AboutScanModal.ejs +82 -0
- package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanConfiguration.ejs +50 -0
- package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanDetails.ejs +149 -0
- package/dist/static/ejs/partials/styles/header.ejs +7 -0
- package/dist/static/ejs/partials/styles/highlightjs.ejs +54 -0
- package/dist/static/ejs/partials/styles/prioritiseIssues/IssueDetailCard.ejs +141 -0
- package/dist/static/ejs/partials/styles/prioritiseIssues/PrioritiseIssues.ejs +204 -0
- package/dist/static/ejs/partials/styles/ruleModal/ruleOffcanvas.ejs +456 -0
- package/dist/static/ejs/partials/styles/scannedPagesSegmentedTabs.ejs +46 -0
- package/dist/static/ejs/partials/styles/shared/InfoAlert.ejs +12 -0
- package/dist/static/ejs/partials/styles/styles.ejs +1607 -0
- package/dist/static/ejs/partials/styles/summaryBootstrap.ejs +12458 -0
- package/dist/static/ejs/partials/styles/topTenCard.ejs +44 -0
- package/dist/static/ejs/partials/styles/wcagCompliance/FailedCriteria.ejs +59 -0
- package/dist/static/ejs/partials/styles/wcagCompliance/WcagGaugeBar.ejs +62 -0
- package/dist/static/ejs/partials/styles/wcagCompliance.ejs +36 -0
- package/dist/static/ejs/partials/styles/wcagCoverageDetails.ejs +33 -0
- package/dist/static/ejs/partials/summaryHeader.ejs +70 -0
- package/dist/static/ejs/partials/summaryMain.ejs +49 -0
- package/dist/static/ejs/report.ejs +226 -0
- package/dist/static/ejs/summary.ejs +47 -0
- package/dist/types/types.js +1 -0
- package/dist/utils.js +1070 -0
- package/examples/oobee-cypress-integration-js/cypress/support/e2e.js +36 -6
- package/examples/oobee-cypress-integration-js/cypress.config.js +45 -1
- package/examples/oobee-cypress-integration-ts/cypress.config.ts +47 -1
- package/examples/oobee-cypress-integration-ts/src/cypress/support/e2e.ts +36 -6
- package/examples/oobee-playwright-integration-js/oobee-playwright-demo.js +2 -1
- package/examples/oobee-playwright-integration-ts/src/oobee-playwright-demo.ts +2 -1
- package/examples/oobee-scan-html-demo.js +51 -0
- package/examples/oobee-scan-page-demo.js +40 -0
- package/package.json +9 -3
- package/src/constants/common.ts +2 -2
- package/src/constants/constants.ts +3 -1
- package/src/crawlers/crawlDomain.ts +1 -0
- package/src/crawlers/runCustom.ts +0 -1
- package/src/mergeAxeResults.ts +43 -22
- package/src/npmIndex.ts +500 -131
package/dist/npmIndex.js
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import printMessage from 'print-message';
|
|
3
|
+
import axe from 'axe-core';
|
|
4
|
+
import { JSDOM } from 'jsdom';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { EnqueueStrategy } from 'crawlee';
|
|
7
|
+
import constants, { BrowserTypes, RuleFlags, ScannerTypes } from './constants/constants.js';
|
|
8
|
+
import { deleteClonedProfiles, getBrowserToRun, getPlaywrightLaunchOptions, submitForm, } from './constants/common.js';
|
|
9
|
+
import { createCrawleeSubFolders, filterAxeResults } from './crawlers/commonCrawlerFunc.js';
|
|
10
|
+
import { createAndUpdateResultsFolders, getVersion } from './utils.js';
|
|
11
|
+
import generateArtifacts, { createBasicFormHTMLSnippet, sendWcagBreakdownToSentry } from './mergeAxeResults.js';
|
|
12
|
+
import { takeScreenshotForHTMLElements } from './screenshotFunc/htmlScreenshotFunc.js';
|
|
13
|
+
import { consoleLogger } from './logs.js';
|
|
14
|
+
import { alertMessageOptions } from './constants/cliFunctions.js';
|
|
15
|
+
import { evaluateAltText } from './crawlers/custom/evaluateAltText.js';
|
|
16
|
+
import { escapeCssSelector } from './crawlers/custom/escapeCssSelector.js';
|
|
17
|
+
import { framesCheck } from './crawlers/custom/framesCheck.js';
|
|
18
|
+
import { findElementByCssSelector } from './crawlers/custom/findElementByCssSelector.js';
|
|
19
|
+
import { flagUnlabelledClickableElements } from './crawlers/custom/flagUnlabelledClickableElements.js';
|
|
20
|
+
import xPathToCss from './crawlers/custom/xPathToCss.js';
|
|
21
|
+
import { extractText } from './crawlers/custom/extractText.js';
|
|
22
|
+
import { gradeReadability } from './crawlers/custom/gradeReadability.js';
|
|
23
|
+
const filename = fileURLToPath(import.meta.url);
|
|
24
|
+
const dirname = path.dirname(filename);
|
|
25
|
+
const getAxeScriptContent = () => {
|
|
26
|
+
return axe.source;
|
|
27
|
+
};
|
|
28
|
+
const getOobeeFunctionsScript = (disableOobee, enableWcagAaa) => {
|
|
29
|
+
return `
|
|
30
|
+
// Fix for missing __name function used by bundler
|
|
31
|
+
if (typeof __name === 'undefined') {
|
|
32
|
+
window.__name = function(fn, name) {
|
|
33
|
+
if (fn && typeof fn === 'function' && name) {
|
|
34
|
+
try {
|
|
35
|
+
Object.defineProperty(fn, 'name', { value: name, configurable: true });
|
|
36
|
+
} catch (e) {
|
|
37
|
+
// Ignore errors if name property cannot be set
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return fn;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
window.flagUnlabelledClickableElements = ${flagUnlabelledClickableElements.toString()};
|
|
45
|
+
window.evaluateAltText = ${evaluateAltText.toString()};
|
|
46
|
+
window.escapeCssSelector = ${escapeCssSelector.toString()};
|
|
47
|
+
window.framesCheck = ${framesCheck.toString()};
|
|
48
|
+
window.findElementByCssSelector = ${findElementByCssSelector.toString()};
|
|
49
|
+
|
|
50
|
+
window.xPathToCss = ${xPathToCss.toString()};
|
|
51
|
+
window.extractText = ${extractText.toString()};
|
|
52
|
+
|
|
53
|
+
function getAxeConfiguration({
|
|
54
|
+
enableWcagAaa = false,
|
|
55
|
+
gradingReadabilityFlag = '',
|
|
56
|
+
disableOobee = false,
|
|
57
|
+
}) {
|
|
58
|
+
return {
|
|
59
|
+
branding: {
|
|
60
|
+
application: 'oobee',
|
|
61
|
+
},
|
|
62
|
+
checks: [
|
|
63
|
+
{
|
|
64
|
+
id: 'oobee-confusing-alt-text',
|
|
65
|
+
metadata: {
|
|
66
|
+
impact: 'serious',
|
|
67
|
+
messages: {
|
|
68
|
+
pass: 'The image alt text is probably useful.',
|
|
69
|
+
fail: "The image alt text set as 'img', 'image', 'picture', 'photo', or 'graphic' is confusing or not useful.",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
evaluate: window.evaluateAltText,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'oobee-accessible-label',
|
|
76
|
+
metadata: {
|
|
77
|
+
impact: 'serious',
|
|
78
|
+
messages: {
|
|
79
|
+
pass: 'The clickable element has an accessible label.',
|
|
80
|
+
fail: 'The clickable element does not have an accessible label.',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
evaluate: (node) => {
|
|
84
|
+
return !node.dataset.flagged; // fail any element with a data-flagged attribute set to true
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
...((enableWcagAaa && !disableOobee)
|
|
88
|
+
? [
|
|
89
|
+
{
|
|
90
|
+
id: 'oobee-grading-text-contents',
|
|
91
|
+
metadata: {
|
|
92
|
+
impact: 'moderate',
|
|
93
|
+
messages: {
|
|
94
|
+
pass: 'The text content is easy to understand.',
|
|
95
|
+
fail: 'The text content is potentially difficult to understand.',
|
|
96
|
+
incomplete: \`The text content is potentially difficult to read, with a Flesch-Kincaid Reading Ease score of \${gradingReadabilityFlag}.\nThe target passing score is above 50, indicating content readable by university students and lower grade levels.\nA higher score reflects better readability.\`,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
evaluate: (_node) => {
|
|
100
|
+
if (gradingReadabilityFlag === '') {
|
|
101
|
+
return true; // Pass if no readability issues
|
|
102
|
+
}
|
|
103
|
+
// Fail if readability issues are detected
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
]
|
|
107
|
+
: []),
|
|
108
|
+
],
|
|
109
|
+
rules: [
|
|
110
|
+
{ id: 'target-size', enabled: true },
|
|
111
|
+
{
|
|
112
|
+
id: 'oobee-confusing-alt-text',
|
|
113
|
+
selector: 'img[alt]',
|
|
114
|
+
enabled: true,
|
|
115
|
+
any: ['oobee-confusing-alt-text'],
|
|
116
|
+
tags: ['wcag2a', 'wcag111'],
|
|
117
|
+
metadata: {
|
|
118
|
+
description: 'Ensures image alt text is clear and useful.',
|
|
119
|
+
help: 'Image alt text must not be vague or unhelpful.',
|
|
120
|
+
helpUrl: 'https://www.deque.com/blog/great-alt-text-introduction/',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'oobee-accessible-label',
|
|
125
|
+
// selector: '*', // to be set with the checker function output xpaths converted to css selectors
|
|
126
|
+
enabled: true,
|
|
127
|
+
any: ['oobee-accessible-label'],
|
|
128
|
+
tags: ['wcag2a', 'wcag211', 'wcag412'],
|
|
129
|
+
metadata: {
|
|
130
|
+
description: 'Ensures clickable elements have an accessible label.',
|
|
131
|
+
help: 'Clickable elements must have accessible labels.',
|
|
132
|
+
helpUrl: 'https://www.deque.com/blog/accessible-aria-buttons',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
...((enableWcagAaa && !disableOobee)
|
|
136
|
+
? [
|
|
137
|
+
{
|
|
138
|
+
id: 'oobee-grading-text-contents',
|
|
139
|
+
selector: 'html',
|
|
140
|
+
enabled: true,
|
|
141
|
+
any: ['oobee-grading-text-contents'],
|
|
142
|
+
tags: ['wcag2aaa', 'wcag315'],
|
|
143
|
+
metadata: {
|
|
144
|
+
description:
|
|
145
|
+
'Text content should be easy to understand for individuals with education levels up to university graduates. If the text content is difficult to understand, provide supplemental content or a version that is easy to understand.',
|
|
146
|
+
help: 'Text content should be clear and plain to ensure that it is easily understood.',
|
|
147
|
+
helpUrl: 'https://www.wcag.com/uncategorized/3-1-5-reading-level/',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
]
|
|
151
|
+
: []),
|
|
152
|
+
]
|
|
153
|
+
.filter(rule => (disableOobee ? !rule.id.startsWith('oobee') : true))
|
|
154
|
+
.concat(
|
|
155
|
+
enableWcagAaa
|
|
156
|
+
? [
|
|
157
|
+
{
|
|
158
|
+
id: 'color-contrast-enhanced',
|
|
159
|
+
enabled: true,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: 'identical-links-same-purpose',
|
|
163
|
+
enabled: true,
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'meta-refresh-no-exceptions',
|
|
167
|
+
enabled: true,
|
|
168
|
+
},
|
|
169
|
+
]
|
|
170
|
+
: [],
|
|
171
|
+
),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
window.getAxeConfiguration = getAxeConfiguration;
|
|
175
|
+
|
|
176
|
+
async function runA11yScan(elementsToScan = [], gradingReadabilityFlag = '') {
|
|
177
|
+
|
|
178
|
+
const oobeeAccessibleLabelFlaggedXpaths = (window).disableOobee
|
|
179
|
+
? []
|
|
180
|
+
: (await (window).flagUnlabelledClickableElements()).map(item => item.xpath);
|
|
181
|
+
console.log('OOBEE DEBUG: Flagged XPaths count:', oobeeAccessibleLabelFlaggedXpaths.length);
|
|
182
|
+
console.log('OOBEE DEBUG: Flagged XPaths:', oobeeAccessibleLabelFlaggedXpaths);
|
|
183
|
+
|
|
184
|
+
// Force visibility of the result in Cypress by adding to page title temporarily
|
|
185
|
+
const originalTitle = document.title;
|
|
186
|
+
document.title = '[OOBEE: ' + oobeeAccessibleLabelFlaggedXpaths.length + ' flagged] ' + originalTitle;
|
|
187
|
+
setTimeout(function() { document.title = originalTitle; }, 1000);
|
|
188
|
+
const oobeeAccessibleLabelFlaggedCssSelectors = oobeeAccessibleLabelFlaggedXpaths
|
|
189
|
+
.map(xpath => {
|
|
190
|
+
try {
|
|
191
|
+
const cssSelector = (window).xPathToCss(xpath);
|
|
192
|
+
return cssSelector;
|
|
193
|
+
} catch (e) {
|
|
194
|
+
// console.error(\`Error converting XPath to CSS: \${xpath} - \${e}\`);
|
|
195
|
+
return '';
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
.filter(item => item !== '');
|
|
199
|
+
|
|
200
|
+
(window).axe.configure((window).getAxeConfiguration({ disableOobee: (window).disableOobee, enableWcagAaa: (window).enableWcagAaa, gradingReadabilityFlag }));
|
|
201
|
+
const axeScanResults = await (window).axe.run(elementsToScan, {
|
|
202
|
+
resultTypes: ['violations', 'passes', 'incomplete'],
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (axeScanResults) {
|
|
206
|
+
['violations', 'incomplete'].forEach(type => {
|
|
207
|
+
if (axeScanResults[type]) {
|
|
208
|
+
axeScanResults[type].forEach(result => {
|
|
209
|
+
if (result.nodes) {
|
|
210
|
+
result.nodes.forEach(node => {
|
|
211
|
+
['any', 'all', 'none'].forEach(key => {
|
|
212
|
+
if (node[key]) {
|
|
213
|
+
node[key].forEach(check => {
|
|
214
|
+
if (check.message && check.message.indexOf("Axe encountered an error") !== -1) {
|
|
215
|
+
if (check.data) {
|
|
216
|
+
// console.error(check.data);
|
|
217
|
+
console.error("Axe encountered an error: " + (check.data.stack || check.data.message || JSON.stringify(check.data)));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// add custom Oobee violations
|
|
231
|
+
if (!(window).disableOobee) {
|
|
232
|
+
// handle css id selectors that start with a digit
|
|
233
|
+
const escapedCssSelectors = oobeeAccessibleLabelFlaggedCssSelectors.map((window).escapeCssSelector);
|
|
234
|
+
|
|
235
|
+
// Add oobee violations to Axe's report
|
|
236
|
+
const oobeeAccessibleLabelViolations = {
|
|
237
|
+
id: 'oobee-accessible-label',
|
|
238
|
+
impact: 'serious',
|
|
239
|
+
tags: ['wcag2a', 'wcag211', 'wcag412'],
|
|
240
|
+
description: 'Ensures clickable elements have an accessible label.',
|
|
241
|
+
help: 'Clickable elements (i.e. elements with mouse-click interaction) must have accessible labels.',
|
|
242
|
+
helpUrl: 'https://www.deque.com/blog/accessible-aria-buttons',
|
|
243
|
+
nodes: escapedCssSelectors
|
|
244
|
+
.map(cssSelector => ({
|
|
245
|
+
html: (window).findElementByCssSelector(cssSelector),
|
|
246
|
+
target: [cssSelector],
|
|
247
|
+
impact: 'serious',
|
|
248
|
+
failureSummary:
|
|
249
|
+
'Fix any of the following:\\n The clickable element does not have an accessible label.',
|
|
250
|
+
any: [
|
|
251
|
+
{
|
|
252
|
+
id: 'oobee-accessible-label',
|
|
253
|
+
data: null,
|
|
254
|
+
relatedNodes: [],
|
|
255
|
+
impact: 'serious',
|
|
256
|
+
message: 'The clickable element does not have an accessible label.',
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
all: [],
|
|
260
|
+
none: [],
|
|
261
|
+
}))
|
|
262
|
+
.filter(item => item.html),
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
axeScanResults.violations = [...axeScanResults.violations, oobeeAccessibleLabelViolations];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
pageUrl: window.location.href,
|
|
270
|
+
pageTitle: document.title,
|
|
271
|
+
axeScanResults,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
window.disableOobee=${disableOobee};
|
|
275
|
+
window.enableWcagAaa=${enableWcagAaa};
|
|
276
|
+
window.runA11yScan = runA11yScan;
|
|
277
|
+
`;
|
|
278
|
+
};
|
|
279
|
+
export const init = async ({ entryUrl, testLabel, name, email, includeScreenshots = false, viewportSettings = { width: 1000, height: 660 }, // cypress' default viewport settings
|
|
280
|
+
thresholds = { mustFix: undefined, goodToFix: undefined }, scanAboutMetadata = undefined, zip = 'oobee-scan-results', deviceChosen, strategy = EnqueueStrategy.All, ruleset = [RuleFlags.DEFAULT], specifiedMaxConcurrency = 25, followRobots = false, }) => {
|
|
281
|
+
consoleLogger.info('Starting Oobee');
|
|
282
|
+
const [date, time] = new Date().toLocaleString('sv').replaceAll(/-|:/g, '').split(' ');
|
|
283
|
+
const domain = new URL(entryUrl).hostname;
|
|
284
|
+
const sanitisedLabel = testLabel ? `_${testLabel.replaceAll(' ', '_')}` : '';
|
|
285
|
+
const randomToken = `${date}_${time}${sanitisedLabel}_${domain}`;
|
|
286
|
+
const disableOobee = ruleset.includes(RuleFlags.DISABLE_OOBEE);
|
|
287
|
+
const enableWcagAaa = ruleset.includes(RuleFlags.ENABLE_WCAG_AAA);
|
|
288
|
+
// max numbers of mustFix/goodToFix occurrences before test returns a fail
|
|
289
|
+
const { mustFix: mustFixThreshold, goodToFix: goodToFixThreshold } = thresholds;
|
|
290
|
+
process.env.CRAWLEE_STORAGE_DIR = randomToken;
|
|
291
|
+
const scanDetails = {
|
|
292
|
+
startTime: new Date(),
|
|
293
|
+
endTime: new Date(),
|
|
294
|
+
deviceChosen,
|
|
295
|
+
crawlType: ScannerTypes.CUSTOM,
|
|
296
|
+
requestUrl: entryUrl,
|
|
297
|
+
urlsCrawled: { ...constants.urlsCrawledObj },
|
|
298
|
+
isIncludeScreenshots: includeScreenshots,
|
|
299
|
+
isAllowSubdomains: strategy,
|
|
300
|
+
isEnableCustomChecks: ruleset,
|
|
301
|
+
isEnableWcagAaa: ruleset,
|
|
302
|
+
isSlowScanMode: specifiedMaxConcurrency,
|
|
303
|
+
isAdhereRobots: followRobots,
|
|
304
|
+
};
|
|
305
|
+
const urlsCrawled = { ...constants.urlsCrawledObj };
|
|
306
|
+
const { dataset } = await createCrawleeSubFolders(randomToken);
|
|
307
|
+
let mustFixIssues = 0;
|
|
308
|
+
let goodToFixIssues = 0;
|
|
309
|
+
let isInstanceTerminated = false;
|
|
310
|
+
const throwErrorIfTerminated = () => {
|
|
311
|
+
if (isInstanceTerminated) {
|
|
312
|
+
throw new Error('This instance of Oobee was terminated. Please start a new instance.');
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
const getAxeScript = () => {
|
|
316
|
+
throwErrorIfTerminated();
|
|
317
|
+
return getAxeScriptContent();
|
|
318
|
+
};
|
|
319
|
+
const getOobeeFunctions = () => {
|
|
320
|
+
throwErrorIfTerminated();
|
|
321
|
+
return getOobeeFunctionsScript(disableOobee, enableWcagAaa);
|
|
322
|
+
};
|
|
323
|
+
// Helper script for manually copy-paste testing in Chrome browser
|
|
324
|
+
/*
|
|
325
|
+
const scripts = `${getAxeScript()}\n${getOobeeFunctions()}`;
|
|
326
|
+
fs.writeFileSync(path.join(dirname, 'testScripts.txt'), scripts);
|
|
327
|
+
*/
|
|
328
|
+
const pushScanResults = async (res, metadata, elementsToClick, page, disableScreenshots = false) => {
|
|
329
|
+
throwErrorIfTerminated();
|
|
330
|
+
if (includeScreenshots && !disableScreenshots) {
|
|
331
|
+
let browserContext;
|
|
332
|
+
let browserToRun;
|
|
333
|
+
let clonedBrowserDataDir;
|
|
334
|
+
let pageToScan;
|
|
335
|
+
if (page) {
|
|
336
|
+
pageToScan = page;
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
// use chrome by default
|
|
340
|
+
const browserData = getBrowserToRun(randomToken, BrowserTypes.CHROME, false);
|
|
341
|
+
browserToRun = browserData.browserToRun;
|
|
342
|
+
clonedBrowserDataDir = browserData.clonedBrowserDataDir;
|
|
343
|
+
browserContext = await constants.launcher.launchPersistentContext(clonedBrowserDataDir, {
|
|
344
|
+
viewport: viewportSettings,
|
|
345
|
+
...getPlaywrightLaunchOptions(browserToRun),
|
|
346
|
+
});
|
|
347
|
+
const newPage = await browserContext.newPage();
|
|
348
|
+
await newPage.goto(res.pageUrl);
|
|
349
|
+
try {
|
|
350
|
+
await newPage.waitForLoadState('networkidle', { timeout: 10000 });
|
|
351
|
+
}
|
|
352
|
+
catch (e) {
|
|
353
|
+
console.log('Network idle timeout, continuing with screenshot capture...');
|
|
354
|
+
// Fall back to domcontentloaded if networkidle times out
|
|
355
|
+
await newPage.waitForLoadState('domcontentloaded', { timeout: 5000 });
|
|
356
|
+
} // click on elements to reveal hidden elements so screenshots can be taken
|
|
357
|
+
if (elementsToClick) {
|
|
358
|
+
for (const elem of elementsToClick) {
|
|
359
|
+
try {
|
|
360
|
+
await newPage.locator(elem).click();
|
|
361
|
+
}
|
|
362
|
+
catch (e) {
|
|
363
|
+
// do nothing if element is not found or not clickable
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
pageToScan = newPage;
|
|
368
|
+
}
|
|
369
|
+
res.axeScanResults.violations = await takeScreenshotForHTMLElements(res.axeScanResults.violations, pageToScan, randomToken, 3000);
|
|
370
|
+
res.axeScanResults.incomplete = await takeScreenshotForHTMLElements(res.axeScanResults.incomplete, pageToScan, randomToken, 3000);
|
|
371
|
+
if (browserContext && browserToRun) {
|
|
372
|
+
await browserContext.close();
|
|
373
|
+
deleteClonedProfiles(browserToRun, randomToken);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const pageIndex = urlsCrawled.scanned.length + 1;
|
|
377
|
+
const filteredResults = filterAxeResults(res.axeScanResults, res.pageTitle, {
|
|
378
|
+
pageIndex,
|
|
379
|
+
metadata,
|
|
380
|
+
});
|
|
381
|
+
urlsCrawled.scanned.push({
|
|
382
|
+
url: res.pageUrl.toString(),
|
|
383
|
+
actualUrl: 'tbd',
|
|
384
|
+
pageTitle: `${pageIndex}: ${res.pageTitle}`,
|
|
385
|
+
});
|
|
386
|
+
mustFixIssues += filteredResults.mustFix ? filteredResults.mustFix.totalItems : 0;
|
|
387
|
+
goodToFixIssues += filteredResults.goodToFix ? filteredResults.goodToFix.totalItems : 0;
|
|
388
|
+
await dataset.pushData(filteredResults);
|
|
389
|
+
// return counts for users to perform custom assertions if needed
|
|
390
|
+
return {
|
|
391
|
+
mustFix: filteredResults.mustFix ? filteredResults.mustFix.totalItems : 0,
|
|
392
|
+
goodToFix: filteredResults.goodToFix ? filteredResults.goodToFix.totalItems : 0,
|
|
393
|
+
};
|
|
394
|
+
};
|
|
395
|
+
const terminate = async () => {
|
|
396
|
+
throwErrorIfTerminated();
|
|
397
|
+
consoleLogger.info('Stopping Oobee');
|
|
398
|
+
isInstanceTerminated = true;
|
|
399
|
+
scanDetails.endTime = new Date();
|
|
400
|
+
scanDetails.urlsCrawled = urlsCrawled;
|
|
401
|
+
if (urlsCrawled.scanned.length === 0) {
|
|
402
|
+
printMessage([`No pages were scanned.`], alertMessageOptions);
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
await createAndUpdateResultsFolders(randomToken);
|
|
406
|
+
const pagesNotScanned = [
|
|
407
|
+
...scanDetails.urlsCrawled.error,
|
|
408
|
+
...scanDetails.urlsCrawled.invalid,
|
|
409
|
+
...scanDetails.urlsCrawled.forbidden,
|
|
410
|
+
...scanDetails.urlsCrawled.userExcluded,
|
|
411
|
+
];
|
|
412
|
+
const updatedScanAboutMetadata = {
|
|
413
|
+
viewport: {
|
|
414
|
+
width: viewportSettings.width,
|
|
415
|
+
height: viewportSettings.height,
|
|
416
|
+
},
|
|
417
|
+
...scanAboutMetadata,
|
|
418
|
+
};
|
|
419
|
+
const basicFormHTMLSnippet = await generateArtifacts(randomToken, scanDetails.requestUrl, scanDetails.crawlType, deviceChosen, scanDetails.urlsCrawled.scanned, pagesNotScanned, testLabel, updatedScanAboutMetadata, scanDetails, zip);
|
|
420
|
+
await submitForm(BrowserTypes.CHROMIUM, // browserToRun
|
|
421
|
+
'', // userDataDirectory
|
|
422
|
+
scanDetails.requestUrl, // scannedUrl
|
|
423
|
+
null, // entryUrl
|
|
424
|
+
scanDetails.crawlType, // scanType
|
|
425
|
+
email, // email
|
|
426
|
+
name, // name
|
|
427
|
+
JSON.stringify(basicFormHTMLSnippet), // scanResultsKson
|
|
428
|
+
urlsCrawled.scanned.length, // numberOfPagesScanned
|
|
429
|
+
0, 0, '{}');
|
|
430
|
+
}
|
|
431
|
+
return randomToken;
|
|
432
|
+
};
|
|
433
|
+
const testThresholds = () => {
|
|
434
|
+
// check against thresholds to fail tests
|
|
435
|
+
let isThresholdExceeded = false;
|
|
436
|
+
let thresholdFailMessage = 'Exceeded thresholds:\n';
|
|
437
|
+
if (mustFixThreshold !== undefined && mustFixIssues > mustFixThreshold) {
|
|
438
|
+
isThresholdExceeded = true;
|
|
439
|
+
thresholdFailMessage += `mustFix occurrences found: ${mustFixIssues} > ${mustFixThreshold}\n`;
|
|
440
|
+
}
|
|
441
|
+
if (goodToFixThreshold !== undefined && goodToFixIssues > goodToFixThreshold) {
|
|
442
|
+
isThresholdExceeded = true;
|
|
443
|
+
thresholdFailMessage += `goodToFix occurrences found: ${goodToFixIssues} > ${goodToFixThreshold}\n`;
|
|
444
|
+
}
|
|
445
|
+
// uncomment to reset counts if you do not want violations count to be cumulative across other pages
|
|
446
|
+
// mustFixIssues = 0;
|
|
447
|
+
// goodToFixIssues = 0;
|
|
448
|
+
if (isThresholdExceeded) {
|
|
449
|
+
terminate(); // terminate if threshold exceeded
|
|
450
|
+
throw new Error(thresholdFailMessage);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
return {
|
|
454
|
+
getAxeScript,
|
|
455
|
+
getOobeeFunctions,
|
|
456
|
+
gradeReadability,
|
|
457
|
+
pushScanResults,
|
|
458
|
+
terminate,
|
|
459
|
+
scanDetails,
|
|
460
|
+
randomToken,
|
|
461
|
+
testThresholds,
|
|
462
|
+
};
|
|
463
|
+
};
|
|
464
|
+
export default init;
|
|
465
|
+
const processAndSubmitResults = async (scanData, name, email, metadata) => {
|
|
466
|
+
const items = Array.isArray(scanData) ? scanData : [scanData];
|
|
467
|
+
const numberOfPagesScanned = items.length;
|
|
468
|
+
const allFilteredResults = items.map((item, index) => {
|
|
469
|
+
const filtered = filterAxeResults(item.axeScanResults, item.pageTitle, { pageIndex: index + 1, metadata });
|
|
470
|
+
filtered.url = item.pageUrl;
|
|
471
|
+
return filtered;
|
|
472
|
+
});
|
|
473
|
+
const mergedResults = {
|
|
474
|
+
mustFix: { totalItems: 0, rules: {} },
|
|
475
|
+
goodToFix: { totalItems: 0, rules: {} },
|
|
476
|
+
needsReview: { totalItems: 0, rules: {} },
|
|
477
|
+
// omitting passed from being processed to reduce payload size
|
|
478
|
+
// passed: { totalItems: 0, rules: {} },
|
|
479
|
+
};
|
|
480
|
+
allFilteredResults.forEach(result => {
|
|
481
|
+
['mustFix', 'goodToFix', 'needsReview'].forEach(category => {
|
|
482
|
+
const categoryResult = result[category];
|
|
483
|
+
if (categoryResult) {
|
|
484
|
+
mergedResults[category].totalItems += categoryResult.totalItems;
|
|
485
|
+
Object.entries(categoryResult.rules).forEach(([ruleId, ruleVal]) => {
|
|
486
|
+
if (!mergedResults[category].rules[ruleId]) {
|
|
487
|
+
mergedResults[category].rules[ruleId] = JSON.parse(JSON.stringify(ruleVal));
|
|
488
|
+
// Map the description to the short description if available
|
|
489
|
+
if (constants.a11yRuleShortDescriptionMap[ruleId]) {
|
|
490
|
+
mergedResults[category].rules[ruleId].description = constants.a11yRuleShortDescriptionMap[ruleId];
|
|
491
|
+
}
|
|
492
|
+
// Add url to items
|
|
493
|
+
mergedResults[category].rules[ruleId].items.forEach((item) => {
|
|
494
|
+
item.url = result.url;
|
|
495
|
+
if (item.displayNeedsReview) {
|
|
496
|
+
delete item.displayNeedsReview;
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
mergedResults[category].rules[ruleId].totalItems += ruleVal.totalItems;
|
|
502
|
+
const newItems = ruleVal.items.map((item) => {
|
|
503
|
+
const newItem = { ...item, url: result.url };
|
|
504
|
+
if (newItem.displayNeedsReview) {
|
|
505
|
+
delete newItem.displayNeedsReview;
|
|
506
|
+
}
|
|
507
|
+
return newItem;
|
|
508
|
+
});
|
|
509
|
+
mergedResults[category].rules[ruleId].items.push(...newItems);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
const basicFormHTMLSnippet = createBasicFormHTMLSnippet(mergedResults);
|
|
516
|
+
const entryUrl = items[0].pageUrl;
|
|
517
|
+
await submitForm(BrowserTypes.CHROMIUM, '', entryUrl, null, ScannerTypes.CUSTOM, email, name, JSON.stringify(basicFormHTMLSnippet), numberOfPagesScanned, 0, 0, '{}');
|
|
518
|
+
// Generate WCAG breakdown for Sentry
|
|
519
|
+
const wcagOccurrencesMap = new Map();
|
|
520
|
+
// Iterate through relevant categories to collect WCAG violation occurrences
|
|
521
|
+
['mustFix', 'goodToFix'].forEach(category => {
|
|
522
|
+
const rulesObj = mergedResults[category]?.rules;
|
|
523
|
+
if (rulesObj) {
|
|
524
|
+
Object.values(rulesObj).forEach((rule) => {
|
|
525
|
+
const count = rule.totalItems;
|
|
526
|
+
if (rule.conformance && Array.isArray(rule.conformance)) {
|
|
527
|
+
rule.conformance
|
|
528
|
+
.filter((c) => /wcag[0-9]{3,4}/.test(c))
|
|
529
|
+
.forEach((c) => {
|
|
530
|
+
const current = wcagOccurrencesMap.get(c) || 0;
|
|
531
|
+
wcagOccurrencesMap.set(c, current + count);
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
const oobeeAppVersion = getVersion();
|
|
538
|
+
await sendWcagBreakdownToSentry(oobeeAppVersion, wcagOccurrencesMap, basicFormHTMLSnippet, {
|
|
539
|
+
entryUrl: entryUrl,
|
|
540
|
+
scanType: ScannerTypes.CUSTOM,
|
|
541
|
+
browser: 'chromium', // Defaulting since we might scan HTML without browser or implicit browser
|
|
542
|
+
email: email,
|
|
543
|
+
name: name,
|
|
544
|
+
}, undefined, numberOfPagesScanned);
|
|
545
|
+
// Return original single result if only one page was scanning to maintain backward compatibility structure
|
|
546
|
+
if (numberOfPagesScanned === 1) {
|
|
547
|
+
const singleResult = allFilteredResults[0];
|
|
548
|
+
// Clean up displayNeedsReview from single result
|
|
549
|
+
['mustFix', 'goodToFix', 'needsReview'].forEach(category => {
|
|
550
|
+
const resultCategory = singleResult[category];
|
|
551
|
+
if (resultCategory && resultCategory.rules) {
|
|
552
|
+
Object.values(resultCategory.rules).forEach((rule) => {
|
|
553
|
+
// Map the description to the short description if available
|
|
554
|
+
if (constants.a11yRuleShortDescriptionMap[rule.rule]) {
|
|
555
|
+
rule.description = constants.a11yRuleShortDescriptionMap[rule.rule];
|
|
556
|
+
}
|
|
557
|
+
if (rule.items) {
|
|
558
|
+
rule.items.forEach((item) => {
|
|
559
|
+
// Ensure item URL matches the result URL
|
|
560
|
+
item.url = singleResult.url;
|
|
561
|
+
if (item.displayNeedsReview) {
|
|
562
|
+
delete item.displayNeedsReview;
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
return singleResult;
|
|
570
|
+
}
|
|
571
|
+
return mergedResults;
|
|
572
|
+
};
|
|
573
|
+
// This is an experimental feature to scan static HTML code without the need for Playwright browser
|
|
574
|
+
export const scanHTML = async (htmlContent, config) => {
|
|
575
|
+
const { name, email, pageUrl = 'raw-html', pageTitle = 'HTML Content', metadata = '', ruleset = [RuleFlags.DEFAULT], } = config;
|
|
576
|
+
const enableWcagAaa = ruleset.includes(RuleFlags.ENABLE_WCAG_AAA);
|
|
577
|
+
const tags = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'];
|
|
578
|
+
if (enableWcagAaa) {
|
|
579
|
+
tags.push('wcag2aaa');
|
|
580
|
+
}
|
|
581
|
+
const htmlItems = Array.isArray(htmlContent) ? htmlContent : [htmlContent];
|
|
582
|
+
const scanData = [];
|
|
583
|
+
for (let i = 0; i < htmlItems.length; i++) {
|
|
584
|
+
const htmlString = htmlItems[i];
|
|
585
|
+
const dom = new JSDOM(htmlString);
|
|
586
|
+
// Configure axe for node environment
|
|
587
|
+
// eslint-disable-next-line no-await-in-loop
|
|
588
|
+
const axeScanResults = await axe.run(dom.window.document.documentElement, {
|
|
589
|
+
runOnly: {
|
|
590
|
+
type: 'tag',
|
|
591
|
+
values: tags,
|
|
592
|
+
},
|
|
593
|
+
resultTypes: ['violations', 'passes', 'incomplete'],
|
|
594
|
+
});
|
|
595
|
+
scanData.push({
|
|
596
|
+
axeScanResults,
|
|
597
|
+
pageUrl: htmlItems.length > 1 ? `${pageUrl}-${i + 1}` : pageUrl,
|
|
598
|
+
pageTitle: htmlItems.length > 1 ? `${pageTitle} ${i + 1}` : pageTitle,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
return processAndSubmitResults(scanData, name, email, metadata);
|
|
602
|
+
};
|
|
603
|
+
export const scanPage = async (pages, config) => {
|
|
604
|
+
const { name, email, pageTitle, metadata = '', ruleset = [RuleFlags.DEFAULT], } = config;
|
|
605
|
+
const disableOobee = ruleset.includes(RuleFlags.DISABLE_OOBEE);
|
|
606
|
+
const enableWcagAaa = ruleset.includes(RuleFlags.ENABLE_WCAG_AAA);
|
|
607
|
+
const axeScript = getAxeScriptContent();
|
|
608
|
+
const oobeeFunctions = getOobeeFunctionsScript(disableOobee, enableWcagAaa);
|
|
609
|
+
const pagesArray = Array.isArray(pages) ? pages : [pages];
|
|
610
|
+
const scanData = [];
|
|
611
|
+
for (const page of pagesArray) {
|
|
612
|
+
await page.evaluate(`${axeScript}\n${oobeeFunctions}`);
|
|
613
|
+
// Run the scan inside the page
|
|
614
|
+
const consoleListener = (msg) => {
|
|
615
|
+
if (msg.type() === 'error') {
|
|
616
|
+
console.error(`[Browser Console] ${msg.text()}`);
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
page.on('console', consoleListener);
|
|
620
|
+
try {
|
|
621
|
+
const scanResult = await page.evaluate(async () => {
|
|
622
|
+
return window.runA11yScan();
|
|
623
|
+
});
|
|
624
|
+
scanData.push({
|
|
625
|
+
axeScanResults: scanResult.axeScanResults,
|
|
626
|
+
pageUrl: page.url(),
|
|
627
|
+
pageTitle: await page.title(),
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
finally {
|
|
631
|
+
page.off('console', consoleListener);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// Allow override of page title if scanning a single page
|
|
635
|
+
if (!Array.isArray(pages) && pageTitle) {
|
|
636
|
+
scanData[0].pageTitle = pageTitle;
|
|
637
|
+
}
|
|
638
|
+
return processAndSubmitResults(scanData, name, email, metadata);
|
|
639
|
+
};
|
|
640
|
+
export { RuleFlags };
|