@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.
Files changed (137) hide show
  1. package/.github/workflows/publish.yml +8 -1
  2. package/INTEGRATION.md +50 -3
  3. package/dist/cli.js +252 -0
  4. package/dist/combine.js +221 -0
  5. package/dist/constants/cliFunctions.js +306 -0
  6. package/dist/constants/common.js +1669 -0
  7. package/dist/constants/constants.js +913 -0
  8. package/dist/constants/errorMeta.json +319 -0
  9. package/dist/constants/itemTypeDescription.js +7 -0
  10. package/dist/constants/oobeeAi.js +121 -0
  11. package/dist/constants/questions.js +151 -0
  12. package/dist/constants/sampleData.js +176 -0
  13. package/dist/crawlers/commonCrawlerFunc.js +428 -0
  14. package/dist/crawlers/crawlDomain.js +613 -0
  15. package/dist/crawlers/crawlIntelligentSitemap.js +135 -0
  16. package/dist/crawlers/crawlLocalFile.js +151 -0
  17. package/dist/crawlers/crawlSitemap.js +303 -0
  18. package/dist/crawlers/custom/escapeCssSelector.js +10 -0
  19. package/dist/crawlers/custom/evaluateAltText.js +11 -0
  20. package/dist/crawlers/custom/extractAndGradeText.js +44 -0
  21. package/dist/crawlers/custom/extractText.js +27 -0
  22. package/dist/crawlers/custom/findElementByCssSelector.js +36 -0
  23. package/dist/crawlers/custom/flagUnlabelledClickableElements.js +963 -0
  24. package/dist/crawlers/custom/framesCheck.js +37 -0
  25. package/dist/crawlers/custom/getAxeConfiguration.js +111 -0
  26. package/dist/crawlers/custom/gradeReadability.js +23 -0
  27. package/dist/crawlers/custom/utils.js +1024 -0
  28. package/dist/crawlers/custom/xPathToCss.js +147 -0
  29. package/dist/crawlers/guards/urlGuard.js +71 -0
  30. package/dist/crawlers/pdfScanFunc.js +276 -0
  31. package/dist/crawlers/runCustom.js +89 -0
  32. package/dist/exclusions.txt +7 -0
  33. package/dist/generateHtmlReport.js +144 -0
  34. package/dist/index.js +62 -0
  35. package/dist/logs.js +84 -0
  36. package/dist/mergeAxeResults.js +1588 -0
  37. package/dist/npmIndex.js +640 -0
  38. package/dist/proxyService.js +360 -0
  39. package/dist/runGenerateJustHtmlReport.js +16 -0
  40. package/dist/screenshotFunc/htmlScreenshotFunc.js +355 -0
  41. package/dist/screenshotFunc/pdfScreenshotFunc.js +645 -0
  42. package/dist/services/s3Uploader.js +127 -0
  43. package/dist/static/ejs/partials/components/allIssues/AllIssues.ejs +9 -0
  44. package/dist/static/ejs/partials/components/allIssues/CategoryBadges.ejs +82 -0
  45. package/dist/static/ejs/partials/components/allIssues/FilterBar.ejs +33 -0
  46. package/dist/static/ejs/partials/components/allIssues/IssuesTable.ejs +41 -0
  47. package/dist/static/ejs/partials/components/header/SiteInfo.ejs +119 -0
  48. package/dist/static/ejs/partials/components/header/aboutScanModal/AboutScanModal.ejs +15 -0
  49. package/dist/static/ejs/partials/components/header/aboutScanModal/ScanConfiguration.ejs +44 -0
  50. package/dist/static/ejs/partials/components/header/aboutScanModal/ScanDetails.ejs +142 -0
  51. package/dist/static/ejs/partials/components/prioritiseIssues/IssueDetailCard.ejs +36 -0
  52. package/dist/static/ejs/partials/components/prioritiseIssues/PrioritiseIssues.ejs +47 -0
  53. package/dist/static/ejs/partials/components/ruleModal/ruleOffcanvas.ejs +196 -0
  54. package/dist/static/ejs/partials/components/scannedPagesSegmentedTabs.ejs +48 -0
  55. package/dist/static/ejs/partials/components/screenshotLightbox.ejs +13 -0
  56. package/dist/static/ejs/partials/components/shared/InfoAlert.ejs +3 -0
  57. package/dist/static/ejs/partials/components/summaryScanAbout.ejs +141 -0
  58. package/dist/static/ejs/partials/components/summaryScanResults.ejs +16 -0
  59. package/dist/static/ejs/partials/components/summaryTable.ejs +20 -0
  60. package/dist/static/ejs/partials/components/summaryWcagCompliance.ejs +94 -0
  61. package/dist/static/ejs/partials/components/topTen.ejs +6 -0
  62. package/dist/static/ejs/partials/components/wcagCompliance/FailedCriteria.ejs +47 -0
  63. package/dist/static/ejs/partials/components/wcagCompliance/WcagCompliance.ejs +16 -0
  64. package/dist/static/ejs/partials/components/wcagCompliance/WcagGaugeBar.ejs +16 -0
  65. package/dist/static/ejs/partials/components/wcagCoverageDetails.ejs +18 -0
  66. package/dist/static/ejs/partials/footer.ejs +24 -0
  67. package/dist/static/ejs/partials/header.ejs +14 -0
  68. package/dist/static/ejs/partials/main.ejs +29 -0
  69. package/dist/static/ejs/partials/scripts/allIssues/AllIssues.ejs +376 -0
  70. package/dist/static/ejs/partials/scripts/bootstrap.ejs +8 -0
  71. package/dist/static/ejs/partials/scripts/categorySummary.ejs +141 -0
  72. package/dist/static/ejs/partials/scripts/decodeUnzipParse.ejs +3 -0
  73. package/dist/static/ejs/partials/scripts/header/SiteInfo.ejs +44 -0
  74. package/dist/static/ejs/partials/scripts/header/aboutScanModal/AboutScanModal.ejs +51 -0
  75. package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanConfiguration.ejs +127 -0
  76. package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanDetails.ejs +60 -0
  77. package/dist/static/ejs/partials/scripts/highlightjs.ejs +335 -0
  78. package/dist/static/ejs/partials/scripts/popper.ejs +7 -0
  79. package/dist/static/ejs/partials/scripts/prioritiseIssues/IssueDetailCard.ejs +137 -0
  80. package/dist/static/ejs/partials/scripts/prioritiseIssues/PrioritiseIssues.ejs +214 -0
  81. package/dist/static/ejs/partials/scripts/prioritiseIssues/wcagSvgMap.ejs +861 -0
  82. package/dist/static/ejs/partials/scripts/ruleModal/constants.ejs +957 -0
  83. package/dist/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +353 -0
  84. package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +468 -0
  85. package/dist/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +306 -0
  86. package/dist/static/ejs/partials/scripts/ruleModal/utilities.ejs +483 -0
  87. package/dist/static/ejs/partials/scripts/scannedPagesSegmentedTabs.ejs +35 -0
  88. package/dist/static/ejs/partials/scripts/screenshotLightbox.ejs +75 -0
  89. package/dist/static/ejs/partials/scripts/summaryScanResults.ejs +14 -0
  90. package/dist/static/ejs/partials/scripts/summaryTable.ejs +78 -0
  91. package/dist/static/ejs/partials/scripts/topTen.ejs +61 -0
  92. package/dist/static/ejs/partials/scripts/utils.ejs +453 -0
  93. package/dist/static/ejs/partials/scripts/wcagCompliance/FailedCriteria.ejs +103 -0
  94. package/dist/static/ejs/partials/scripts/wcagCompliance/WcagGaugeBar.ejs +47 -0
  95. package/dist/static/ejs/partials/scripts/wcagCompliance.ejs +15 -0
  96. package/dist/static/ejs/partials/scripts/wcagCoverageDetails.ejs +75 -0
  97. package/dist/static/ejs/partials/styles/allIssues/AllIssues.ejs +384 -0
  98. package/dist/static/ejs/partials/styles/bootstrap.ejs +12391 -0
  99. package/dist/static/ejs/partials/styles/header/SiteInfo.ejs +121 -0
  100. package/dist/static/ejs/partials/styles/header/aboutScanModal/AboutScanModal.ejs +82 -0
  101. package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanConfiguration.ejs +50 -0
  102. package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanDetails.ejs +149 -0
  103. package/dist/static/ejs/partials/styles/header.ejs +7 -0
  104. package/dist/static/ejs/partials/styles/highlightjs.ejs +54 -0
  105. package/dist/static/ejs/partials/styles/prioritiseIssues/IssueDetailCard.ejs +141 -0
  106. package/dist/static/ejs/partials/styles/prioritiseIssues/PrioritiseIssues.ejs +204 -0
  107. package/dist/static/ejs/partials/styles/ruleModal/ruleOffcanvas.ejs +456 -0
  108. package/dist/static/ejs/partials/styles/scannedPagesSegmentedTabs.ejs +46 -0
  109. package/dist/static/ejs/partials/styles/shared/InfoAlert.ejs +12 -0
  110. package/dist/static/ejs/partials/styles/styles.ejs +1607 -0
  111. package/dist/static/ejs/partials/styles/summaryBootstrap.ejs +12458 -0
  112. package/dist/static/ejs/partials/styles/topTenCard.ejs +44 -0
  113. package/dist/static/ejs/partials/styles/wcagCompliance/FailedCriteria.ejs +59 -0
  114. package/dist/static/ejs/partials/styles/wcagCompliance/WcagGaugeBar.ejs +62 -0
  115. package/dist/static/ejs/partials/styles/wcagCompliance.ejs +36 -0
  116. package/dist/static/ejs/partials/styles/wcagCoverageDetails.ejs +33 -0
  117. package/dist/static/ejs/partials/summaryHeader.ejs +70 -0
  118. package/dist/static/ejs/partials/summaryMain.ejs +49 -0
  119. package/dist/static/ejs/report.ejs +226 -0
  120. package/dist/static/ejs/summary.ejs +47 -0
  121. package/dist/types/types.js +1 -0
  122. package/dist/utils.js +1070 -0
  123. package/examples/oobee-cypress-integration-js/cypress/support/e2e.js +36 -6
  124. package/examples/oobee-cypress-integration-js/cypress.config.js +45 -1
  125. package/examples/oobee-cypress-integration-ts/cypress.config.ts +47 -1
  126. package/examples/oobee-cypress-integration-ts/src/cypress/support/e2e.ts +36 -6
  127. package/examples/oobee-playwright-integration-js/oobee-playwright-demo.js +2 -1
  128. package/examples/oobee-playwright-integration-ts/src/oobee-playwright-demo.ts +2 -1
  129. package/examples/oobee-scan-html-demo.js +51 -0
  130. package/examples/oobee-scan-page-demo.js +40 -0
  131. package/package.json +9 -3
  132. package/src/constants/common.ts +2 -2
  133. package/src/constants/constants.ts +3 -1
  134. package/src/crawlers/crawlDomain.ts +1 -0
  135. package/src/crawlers/runCustom.ts +0 -1
  136. package/src/mergeAxeResults.ts +43 -22
  137. package/src/npmIndex.ts +500 -131
@@ -0,0 +1,913 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import { createRequire } from 'module';
4
+ import fs from 'fs-extra';
5
+ import { globSync } from 'glob';
6
+ import which from 'which';
7
+ import os from 'os';
8
+ import { spawnSync } from 'child_process';
9
+ import { chromium } from 'playwright';
10
+ import * as Sentry from '@sentry/node';
11
+ import { consoleLogger } from '../logs.js';
12
+ const filename = fileURLToPath(import.meta.url);
13
+ const dirname = path.dirname(filename);
14
+ const require = createRequire(import.meta.url);
15
+ const maxRequestsPerCrawl = 100;
16
+ export const blackListedFileExtensions = [
17
+ 'css',
18
+ 'js',
19
+ 'txt',
20
+ 'mp3',
21
+ 'mp4',
22
+ 'jpg',
23
+ 'jpeg',
24
+ 'png',
25
+ 'svg',
26
+ 'gif',
27
+ 'woff',
28
+ 'zip',
29
+ 'webp',
30
+ 'json',
31
+ 'xml',
32
+ ];
33
+ export const getIntermediateScreenshotsPath = (datasetsPath) => `${datasetsPath}/screenshots`;
34
+ export const destinationPath = (storagePath) => `${storagePath}/screenshots`;
35
+ /** Get the path to Default Profile in the Chrome Data Directory
36
+ * as per https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md
37
+ * @returns path to Default Profile in the Chrome Data Directory
38
+ */
39
+ export const getDefaultChromeDataDir = () => {
40
+ try {
41
+ let defaultChromeDataDir = null;
42
+ if (os.platform() === 'win32') {
43
+ defaultChromeDataDir = path.join(os.homedir(), 'AppData', 'Local', 'Google', 'Chrome', 'User Data');
44
+ }
45
+ else if (os.platform() === 'darwin') {
46
+ defaultChromeDataDir = path.join(os.homedir(), 'Library', 'Application Support', 'Google', 'Chrome');
47
+ }
48
+ if (defaultChromeDataDir && fs.existsSync(defaultChromeDataDir)) {
49
+ return defaultChromeDataDir;
50
+ }
51
+ return null;
52
+ }
53
+ catch (error) {
54
+ console.error(`Error in getDefaultChromeDataDir(): ${error}`);
55
+ }
56
+ };
57
+ /**
58
+ * Get the path to Default Profile in the Edge Data Directory
59
+ * @returns path to Default Profile in the Edge Data Directory
60
+ */
61
+ export const getDefaultEdgeDataDir = () => {
62
+ try {
63
+ let defaultEdgeDataDir = null;
64
+ if (os.platform() === 'win32') {
65
+ defaultEdgeDataDir = path.join(os.homedir(), 'AppData', 'Local', 'Microsoft', 'Edge', 'User Data');
66
+ }
67
+ else if (os.platform() === 'darwin') {
68
+ defaultEdgeDataDir = path.join(os.homedir(), 'Library', 'Application Support', 'Microsoft Edge');
69
+ }
70
+ if (defaultEdgeDataDir && fs.existsSync(defaultEdgeDataDir)) {
71
+ return defaultEdgeDataDir;
72
+ }
73
+ return null;
74
+ }
75
+ catch (error) {
76
+ console.error(`Error in getDefaultEdgeDataDir(): ${error}`);
77
+ }
78
+ };
79
+ export const getDefaultChromiumDataDir = () => {
80
+ try {
81
+ let defaultChromiumDataDir = null;
82
+ if (os.platform() === 'win32') {
83
+ defaultChromiumDataDir = path.join(os.homedir(), 'AppData', 'Local', 'Chromium', 'User Data');
84
+ }
85
+ else if (os.platform() === 'darwin') {
86
+ defaultChromiumDataDir = path.join(os.homedir(), 'Library', 'Application Support', 'Chromium');
87
+ }
88
+ else {
89
+ defaultChromiumDataDir = path.join(process.cwd(), 'Chromium Support');
90
+ try {
91
+ fs.mkdirSync(defaultChromiumDataDir, { recursive: true }); // Use { recursive: true } to create parent directories if they don't exist
92
+ }
93
+ catch {
94
+ defaultChromiumDataDir = '/tmp';
95
+ }
96
+ consoleLogger.info(`Using Chromium support directory at ${defaultChromiumDataDir}`);
97
+ }
98
+ if (defaultChromiumDataDir && fs.existsSync(defaultChromiumDataDir)) {
99
+ return defaultChromiumDataDir;
100
+ }
101
+ return null;
102
+ }
103
+ catch (error) {
104
+ consoleLogger.error(`Error in getDefaultChromiumDataDir(): ${error}`);
105
+ }
106
+ };
107
+ export function removeQuarantineFlag(searchPattern, allowedRoot = process.cwd()) {
108
+ if (os.platform() !== 'darwin')
109
+ return;
110
+ const matches = globSync(searchPattern, {
111
+ absolute: true,
112
+ nodir: true,
113
+ dot: true,
114
+ follow: false, // don't follow symlinks
115
+ });
116
+ const root = path.resolve(allowedRoot);
117
+ for (const p of matches) {
118
+ const resolved = path.resolve(p);
119
+ // Ensure the file is under the allowed root (containment check)
120
+ if (!resolved.startsWith(root + path.sep))
121
+ continue;
122
+ // lstat: skip if not a regular file or if it's a symlink
123
+ let st;
124
+ try {
125
+ st = fs.lstatSync(resolved);
126
+ }
127
+ catch {
128
+ continue;
129
+ }
130
+ if (!st.isFile() || st.isSymbolicLink())
131
+ continue;
132
+ // basic filename sanity: no control chars
133
+ const base = path.basename(resolved);
134
+ if (/[\x00-\x1F]/.test(base))
135
+ continue;
136
+ // Use absolute binary path and terminate options with "--"
137
+ const proc = spawnSync('/usr/bin/xattr', ['-d', 'com.apple.quarantine', '--', resolved], {
138
+ stdio: ['ignore', 'ignore', 'pipe'],
139
+ });
140
+ // Optional: inspect errors (common benign case is "No such xattr")
141
+ if (proc.status !== 0) {
142
+ const err = proc.stderr?.toString() || '';
143
+ // swallow benign errors; otherwise log if you have a logger
144
+ if (!/No such xattr/i.test(err)) {
145
+ // console.warn(`xattr failed for ${resolved}: ${err.trim()}`);
146
+ }
147
+ }
148
+ }
149
+ }
150
+ export const getExecutablePath = function (dir, file) {
151
+ let execPaths = globSync(`${dir}/${file}`, { absolute: true, nodir: true });
152
+ if (execPaths.length === 0) {
153
+ const execInPATH = which.sync(file, { nothrow: true });
154
+ if (execInPATH) {
155
+ return fs.realpathSync(execInPATH);
156
+ }
157
+ const splitPath = os.platform() === 'win32' ? process.env.PATH.split(';') : process.env.PATH.split(':');
158
+ for (const path in splitPath) {
159
+ execPaths = globSync(`${path}/${file}`, { absolute: true, nodir: true });
160
+ if (execPaths.length !== 0)
161
+ return fs.realpathSync(execPaths[0]);
162
+ }
163
+ return null;
164
+ }
165
+ removeQuarantineFlag(execPaths[0]);
166
+ return execPaths[0];
167
+ };
168
+ /**
169
+ * Matches the pattern user:password@domain.com
170
+ */
171
+ export const basicAuthRegex = /^.*\/\/.*:.*@.*$/i;
172
+ // for crawlers
173
+ export const axeScript = require.resolve('axe-core/axe.min.js');
174
+ export class UrlsCrawled {
175
+ constructor(urlsCrawled) {
176
+ this.toScan = [];
177
+ this.scanned = [];
178
+ this.invalid = [];
179
+ this.scannedRedirects = [];
180
+ this.notScannedRedirects = [];
181
+ this.outOfDomain = [];
182
+ this.blacklisted = [];
183
+ this.error = [];
184
+ this.exceededRequests = [];
185
+ this.forbidden = [];
186
+ this.userExcluded = [];
187
+ this.everything = [];
188
+ if (urlsCrawled) {
189
+ Object.assign(this, urlsCrawled);
190
+ }
191
+ }
192
+ }
193
+ const urlsCrawledObj = new UrlsCrawled();
194
+ /* eslint-disable no-unused-vars */
195
+ export var ScannerTypes;
196
+ (function (ScannerTypes) {
197
+ ScannerTypes["SITEMAP"] = "Sitemap";
198
+ ScannerTypes["WEBSITE"] = "Website";
199
+ ScannerTypes["CUSTOM"] = "Custom";
200
+ ScannerTypes["INTELLIGENT"] = "Intelligent";
201
+ ScannerTypes["LOCALFILE"] = "LocalFile";
202
+ })(ScannerTypes || (ScannerTypes = {}));
203
+ /* eslint-enable no-unused-vars */
204
+ export var FileTypes;
205
+ (function (FileTypes) {
206
+ FileTypes["All"] = "all";
207
+ FileTypes["PdfOnly"] = "pdf-only";
208
+ FileTypes["HtmlOnly"] = "html-only";
209
+ })(FileTypes || (FileTypes = {}));
210
+ export function getEnumKey(enumObj, value) {
211
+ return Object.keys(enumObj).find(k => enumObj[k] === value);
212
+ }
213
+ export const guiInfoStatusTypes = {
214
+ SCANNED: 'scanned',
215
+ SKIPPED: 'skipped',
216
+ COMPLETED: 'completed',
217
+ ERROR: 'error',
218
+ DUPLICATE: 'duplicate',
219
+ };
220
+ let launchOptionsArgs = [];
221
+ // Check if running in docker container
222
+ if (fs.existsSync('/.dockerenv')) {
223
+ launchOptionsArgs = ['--disable-gpu', '--no-sandbox', '--disable-dev-shm-usage'];
224
+ }
225
+ export const impactOrder = {
226
+ minor: 0,
227
+ moderate: 1,
228
+ serious: 2,
229
+ critical: 3,
230
+ };
231
+ /**
232
+ * Suppresses the "Setting the NODE_TLS_REJECT_UNAUTHORIZED
233
+ * environment variable to '0' is insecure" warning,
234
+ * then disables TLS validation globally.
235
+ */
236
+ export function suppressTlsRejectWarning() {
237
+ // Monkey-patch process.emitWarning
238
+ const originalEmitWarning = process.emitWarning;
239
+ process.emitWarning = (warning, ...args) => {
240
+ const msg = typeof warning === 'string' ? warning : warning.message;
241
+ if (msg.includes('NODE_TLS_REJECT_UNAUTHORIZED')) {
242
+ // swallow only that one warning
243
+ return;
244
+ }
245
+ // forward everything else
246
+ originalEmitWarning.call(process, warning, ...args);
247
+ };
248
+ // Now turn off cert validation
249
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
250
+ }
251
+ suppressTlsRejectWarning();
252
+ export const sentryConfig = {
253
+ dsn: process.env.OOBEE_SENTRY_DSN ||
254
+ 'https://3b8c7ee46b06f33815a1301b6713ebc3@o4509047624761344.ingest.us.sentry.io/4509327783559168',
255
+ tracesSampleRate: 1.0, // Capture 100% of transactions for performance monitoring
256
+ profilesSampleRate: 1.0, // Capture 100% of profiles
257
+ };
258
+ // Function to set Sentry user ID from userData.txt
259
+ export const setSentryUser = (userId) => {
260
+ if (userId) {
261
+ Sentry.setUser({ id: userId });
262
+ }
263
+ };
264
+ // Legacy code start - Google Sheets submission
265
+ export const formDataFields = {
266
+ formUrl: `https://docs.google.com/forms/d/e/1FAIpQLSem5C8fyNs5TiU5Vv2Y63-SH7CHN86f-LEPxeN_1u_ldUbgUA/formResponse`, // prod
267
+ entryUrlField: 'entry.1562345227',
268
+ redirectUrlField: 'entry.473072563',
269
+ scanTypeField: 'entry.1148680657',
270
+ emailField: 'entry.52161304',
271
+ nameField: 'entry.1787318910',
272
+ resultsField: 'entry.904051439',
273
+ numberOfPagesScannedField: 'entry.238043773',
274
+ additionalPageDataField: 'entry.2090887881',
275
+ metadataField: 'entry.1027769131',
276
+ };
277
+ // Legacy code end - Google Sheets submission
278
+ export const sitemapPaths = [
279
+ '/sitemap.xml',
280
+ '/sitemap/sitemap.xml',
281
+ '/sitemap-index.xml',
282
+ '/sitemap_index.xml',
283
+ '/sitemapindex.xml',
284
+ '/sitemap/index.xml',
285
+ '/sitemap1.xml',
286
+ '/sitemap/',
287
+ '/post-sitemap',
288
+ '/page-sitemap',
289
+ '/sitemap.txt',
290
+ '/sitemap.php',
291
+ '/sitemap.xml.bz2',
292
+ '/sitemap.xml.xz',
293
+ '/sitemap_index.xml.bz2',
294
+ '/sitemap_index.xml.xz',
295
+ ];
296
+ // Remember to update getWcagPassPercentage() in src/utils/utils.ts if you change this
297
+ const wcagLinks = {
298
+ 'WCAG 1.1.1': 'https://www.w3.org/TR/WCAG22/#non-text-content',
299
+ 'WCAG 1.2.2': 'https://www.w3.org/TR/WCAG22/#captions-prerecorded',
300
+ 'WCAG 1.3.1': 'https://www.w3.org/TR/WCAG22/#info-and-relationships',
301
+ // 'WCAG 1.3.4': 'https://www.w3.org/TR/WCAG22/#orientation', - TODO: review for veraPDF
302
+ 'WCAG 1.3.5': 'https://www.w3.org/TR/WCAG22/#identify-input-purpose',
303
+ 'WCAG 1.4.1': 'https://www.w3.org/TR/WCAG22/#use-of-color',
304
+ 'WCAG 1.4.2': 'https://www.w3.org/TR/WCAG22/#audio-control',
305
+ 'WCAG 1.4.3': 'https://www.w3.org/TR/WCAG22/#contrast-minimum',
306
+ 'WCAG 1.4.4': 'https://www.w3.org/TR/WCAG22/#resize-text',
307
+ 'WCAG 1.4.6': 'https://www.w3.org/TR/WCAG22/#contrast-enhanced', // AAA
308
+ // 'WCAG 1.4.10': 'https://www.w3.org/TR/WCAG22/#reflow', - TODO: review for veraPDF
309
+ 'WCAG 1.4.12': 'https://www.w3.org/TR/WCAG22/#text-spacing',
310
+ 'WCAG 2.1.1': 'https://www.w3.org/TR/WCAG22/#keyboard',
311
+ 'WCAG 2.1.3': 'https://www.w3.org/WAI/WCAG22/Understanding/keyboard-no-exception.html', // AAA
312
+ 'WCAG 2.2.1': 'https://www.w3.org/TR/WCAG22/#timing-adjustable',
313
+ 'WCAG 2.2.2': 'https://www.w3.org/TR/WCAG22/#pause-stop-hide',
314
+ 'WCAG 2.2.4': 'https://www.w3.org/TR/WCAG22/#interruptions', // AAA
315
+ 'WCAG 2.4.1': 'https://www.w3.org/TR/WCAG22/#bypass-blocks',
316
+ 'WCAG 2.4.2': 'https://www.w3.org/TR/WCAG22/#page-titled',
317
+ 'WCAG 2.4.4': 'https://www.w3.org/TR/WCAG22/#link-purpose-in-context',
318
+ 'WCAG 2.4.9': 'https://www.w3.org/TR/WCAG22/#link-purpose-link-only', // AAA
319
+ 'WCAG 2.5.8': 'https://www.w3.org/TR/WCAG22/#target-size-minimum',
320
+ 'WCAG 3.1.1': 'https://www.w3.org/TR/WCAG22/#language-of-page',
321
+ 'WCAG 3.1.2': 'https://www.w3.org/TR/WCAG22/#language-of-parts',
322
+ 'WCAG 3.1.5': 'https://www.w3.org/TR/WCAG22/#reading-level', // AAA
323
+ 'WCAG 3.2.5': 'https://www.w3.org/TR/WCAG22/#change-on-request', // AAA
324
+ 'WCAG 3.3.2': 'https://www.w3.org/TR/WCAG22/#labels-or-instructions',
325
+ 'WCAG 4.1.2': 'https://www.w3.org/TR/WCAG22/#name-role-value',
326
+ };
327
+ const wcagCriteriaLabels = {
328
+ 'WCAG 1.1.1': 'A',
329
+ 'WCAG 1.2.2': 'A',
330
+ 'WCAG 1.3.1': 'A',
331
+ 'WCAG 1.3.5': 'AA',
332
+ 'WCAG 1.4.1': 'A',
333
+ 'WCAG 1.4.2': 'A',
334
+ 'WCAG 1.4.3': 'AA',
335
+ 'WCAG 1.4.4': 'AA',
336
+ 'WCAG 1.4.6': 'AAA',
337
+ 'WCAG 1.4.12': 'AA',
338
+ 'WCAG 2.1.1': 'A',
339
+ 'WCAG 2.1.3': 'AAA',
340
+ 'WCAG 2.2.1': 'A',
341
+ 'WCAG 2.2.2': 'A',
342
+ 'WCAG 2.2.4': 'AAA',
343
+ 'WCAG 2.4.1': 'A',
344
+ 'WCAG 2.4.2': 'A',
345
+ 'WCAG 2.4.4': 'A',
346
+ 'WCAG 2.4.9': 'AAA',
347
+ 'WCAG 2.5.8': 'AA',
348
+ 'WCAG 3.1.1': 'A',
349
+ 'WCAG 3.1.2': 'AA',
350
+ 'WCAG 3.1.5': 'AAA',
351
+ 'WCAG 3.2.5': 'AAA',
352
+ 'WCAG 3.3.2': 'A',
353
+ 'WCAG 4.1.2': 'A',
354
+ };
355
+ const urlCheckStatuses = {
356
+ success: { code: 0 },
357
+ invalidUrl: { code: 11, message: 'Invalid URL. Please check and try again.' },
358
+ cannotBeResolved: {
359
+ code: 12,
360
+ message: 'URL cannot be accessed. Please verify whether the website exists.',
361
+ },
362
+ errorStatusReceived: {
363
+ // unused for now
364
+ code: 13,
365
+ message: 'Provided URL cannot be accessed. Server responded with code ', // append it with the response code received,
366
+ },
367
+ systemError: { code: 14, message: 'Something went wrong when verifying the URL. Please try again in a few minutes. If this issue persists, please contact the Oobee team.' },
368
+ notASitemap: { code: 15, message: 'Invalid sitemap URL format. Please enter a valid sitemap URL ending with .XML or .TXT e.g. https://www.example.com/sitemap.xml.' },
369
+ unauthorised: { code: 16, message: 'Login required. Please enter your credentials and try again.' },
370
+ // browserError means engine could not find a browser to run the scan
371
+ browserError: {
372
+ code: 17,
373
+ message: 'Incompatible browser. Please ensure you are using Chrome or Edge browser.',
374
+ },
375
+ sslProtocolError: {
376
+ code: 18,
377
+ message: 'SSL certificate error. Please check the SSL configuration of your website and try again.',
378
+ },
379
+ notALocalFile: {
380
+ code: 19,
381
+ message: 'Uploaded file format is incorrect. Please upload a HTML, PDF, XML or TXT file.',
382
+ },
383
+ notAPdf: { code: 20, message: 'URL/file format is incorrect. Please upload a PDF file.' },
384
+ notASupportedDocument: {
385
+ code: 21,
386
+ message: 'Uploaded file format is incorrect. Please upload a HTML, PDF, XML or TXT file.',
387
+ },
388
+ connectionRefused: {
389
+ code: 22,
390
+ message: 'Connection refused. Please try again in a few minutes. If this issue persists, please contact the Oobee team.',
391
+ },
392
+ timedOut: {
393
+ code: 23,
394
+ message: 'Request timed out. Please try again in a few minutes. If this issue persists, please contact the Oobee team.',
395
+ },
396
+ };
397
+ /* eslint-disable no-unused-vars */
398
+ export var BrowserTypes;
399
+ (function (BrowserTypes) {
400
+ BrowserTypes["CHROMIUM"] = "chromium";
401
+ BrowserTypes["CHROME"] = "chrome";
402
+ BrowserTypes["EDGE"] = "msedge";
403
+ })(BrowserTypes || (BrowserTypes = {}));
404
+ /* eslint-enable no-unused-vars */
405
+ const xmlSitemapTypes = {
406
+ xml: 0,
407
+ xmlIndex: 1,
408
+ rss: 2,
409
+ atom: 3,
410
+ unknown: 4,
411
+ };
412
+ const forbiddenCharactersInDirPath = ['<', '>', ':', '"', '\\', '/', '|', '?', '*'];
413
+ const reserveFileNameKeywords = [
414
+ 'CON',
415
+ 'PRN',
416
+ 'AUX',
417
+ 'NUL',
418
+ 'COM1',
419
+ 'COM2',
420
+ 'COM3',
421
+ 'COM4',
422
+ 'COM5',
423
+ 'COM6',
424
+ 'COM7',
425
+ 'COM8',
426
+ 'COM9',
427
+ 'LPT1',
428
+ 'LPT2',
429
+ 'LPT3',
430
+ 'LPT4',
431
+ 'LPT5',
432
+ 'LPT6',
433
+ 'LPT7',
434
+ 'LPT8',
435
+ 'LPT9',
436
+ ];
437
+ export const a11yRuleShortDescriptionMap = {
438
+ 'aria-meter-name': 'Meter elements need accessible labels',
439
+ 'aria-progressbar-name': 'Progress bars need accessible labels',
440
+ 'image-alt': 'Meaningful images need text descriptions',
441
+ 'input-image-alt': 'Image buttons need action labels',
442
+ 'object-alt': 'Embedded objects need identifying labels',
443
+ 'oobee-confusing-alt-text': 'Replace vague image descriptions with meaningful text',
444
+ 'role-img-alt': 'Elements marked as images need text descriptions',
445
+ 'svg-img-alt': 'Vector graphics marked as images need text descriptions',
446
+ 'video-caption': 'Videos need captions with transcript tracks',
447
+ 'aria-required-children': 'ARIA roles must contain their required child elements',
448
+ 'aria-required-parent': 'ARIA roles must be contained within their required parent elements',
449
+ 'definition-list': 'Glossaries must use proper term and definition structure',
450
+ dlitem: 'Term and definition elements must be contained in definition lists',
451
+ list: 'Bullet and numbered lists must only contain list items as direct children',
452
+ listitem: 'List items must be placed inside a list container',
453
+ 'td-headers-attr': 'Table headers must clearly identify their relationship to cells',
454
+ 'th-has-data-cells': 'Table headers must be connected to their data cells',
455
+ 'autocomplete-valid': 'Form fields must use valid autocomplete attributes',
456
+ 'link-in-text-block': 'Links must be visually distinct beyond color alone',
457
+ 'avoid-inline-spacing': 'Page layouts must allow users to adjust text spacing',
458
+ 'no-autoplay-audio': 'Pages must not auto-play audio or must allow control',
459
+ 'color-contrast': 'Text and background colors must meet minimum contrast requirements',
460
+ 'color-contrast-enhanced': 'Text and background colors must meet enhanced contrast requirements',
461
+ 'frame-focusable-content': 'Frames and iframes with interactive content must be keyboard accessible',
462
+ 'server-side-image-map': 'Replace server-side image maps with client-side image maps',
463
+ 'scrollable-region-focusable': 'Elements within scrollable regions must be keyboard accessible',
464
+ 'oobee-accessible-label': 'Clickable elements must have accessible labels',
465
+ 'meta-refresh': 'Pages must not use timed automatic refresh',
466
+ blink: 'Blinking elements must not be used',
467
+ marquee: 'Marquee animated elements must not be used',
468
+ 'meta-refresh-no-exceptions': 'Pages must not use automatic timed refresh',
469
+ bypass: 'Pages must provide a way to bypass repeated blocks',
470
+ 'document-title': 'Every page must have a descriptive title',
471
+ 'link-name': 'Links must have descriptive accessible labels',
472
+ 'area-alt': 'Clickable areas in image maps must have labels',
473
+ 'identical-links-same-purpose': 'Links with identical text must have accessible labels describing their purpose',
474
+ 'target-size': 'Clickable elements must be large enough or have sufficient spacing',
475
+ 'html-has-lang': 'Every page must declare its language',
476
+ 'html-lang-valid': 'Page language declaration must use valid language codes',
477
+ 'html-xml-lang-mismatch': 'Make different page language settings match',
478
+ 'valid-lang': 'Elements in different languages must use valid language codes',
479
+ 'oobee-grading-text-contents': 'Page content must use clear, plain language',
480
+ 'form-field-multiple-labels': 'Form fields must have only one label element',
481
+ 'aria-allowed-attr': 'ARIA attributes must be used with appropriate roles',
482
+ 'aria-braille-equivalent': 'Braille abbreviated labels must have full text equivalents',
483
+ 'aria-command-name': 'Elements that use ARIA labels must have an accessible name.',
484
+ 'aria-conditional-attr': 'ARIA attributes must not create conflicting or indeterminate states',
485
+ 'aria-deprecated-role': 'Remove outdated accessibility (ARIA) roles',
486
+ 'aria-hidden-body': 'The page body must not be hidden from screen readers',
487
+ 'aria-hidden-focus': 'Hidden elements must not contain keyboard-focusable content',
488
+ 'aria-input-field-name': 'Custom input fields must have accessible labels',
489
+ 'aria-prohibited-attr': 'Remove ARIA attributes not allowed on these elements',
490
+ 'aria-required-attr': 'Add required ARIA attributes for accessibility roles',
491
+ 'aria-roles': 'Elements must use valid, supported accessibility roles',
492
+ 'aria-toggle-field-name': 'Toggle switches, checkboxes and radio buttons must have descriptive labels',
493
+ 'aria-tooltip-name': 'Tooltips must have accessible names',
494
+ 'aria-valid-attr': 'ARIA attributes must use correct syntax and valid names',
495
+ 'aria-valid-attr-value': 'ARIA attributes must use valid values',
496
+ 'button-name': 'Buttons must have descriptive text or labels',
497
+ 'duplicate-id-aria': 'Element IDs must be unique on the page',
498
+ 'frame-title': 'Frames and iframes must have descriptive titles',
499
+ 'frame-title-unique': 'Each frame must have a unique, descriptive title',
500
+ 'input-button-name': 'Input buttons must have descriptive text or values',
501
+ label: 'Form fields must have associated labels',
502
+ 'nested-interactive': 'Interactive elements must not be nested inside each other',
503
+ 'select-name': 'Selected dropdowns must have associated labels',
504
+ accesskeys: 'Custom keyboard shortcuts must be unique',
505
+ 'aria-dialog-name': 'Dialog popups must have descriptive titles',
506
+ 'aria-text': 'Text elements must not contain focusable content',
507
+ 'aria-treeitem-name': 'Tree view items must have accessible names',
508
+ 'empty-heading': 'Headings must contain descriptive text and not be hidden',
509
+ 'empty-table-header': 'Table headers must contain descriptive text',
510
+ 'frame-tested': 'Frames and iframes must be tested for accessibility',
511
+ 'heading-order': 'Heading levels must follow logical order',
512
+ 'image-redundant-alt': 'Image descriptions must not repeat surrounding text',
513
+ 'label-title-only': 'Form fields should have visible labels',
514
+ 'landmark-banner-is-top-level': "Header region or banner elements must be at the page's top level",
515
+ 'landmark-complementary-is-top-level': "Sidebar/complementary region must be at the page's top level",
516
+ 'landmark-contentinfo-is-top-level': "Footer region must be at the page's top level",
517
+ 'landmark-main-is-top-level': "Main content region must be at the page's top level",
518
+ 'landmark-no-duplicate-banner': 'Pages must have only one header region',
519
+ 'landmark-no-duplicate-contentinfo': 'Pages must have only one footer region',
520
+ 'landmark-no-duplicate-main': 'Pages must have only one main content region',
521
+ 'landmark-one-main': 'Every page must have a main content region',
522
+ 'landmark-unique': 'Page landmarks must be unique or clearly distinguished',
523
+ 'meta-viewport-large': 'Pages must allow zoom and scaling',
524
+ 'page-has-heading-one': 'Every page must have one main H1 heading',
525
+ 'presentation-role-conflict': 'Decorative elements must not be interactive or focusable',
526
+ region: 'All page content must be within marked landmarks or regions',
527
+ 'scope-attr-valid': 'Table header scope attributes must be correct',
528
+ 'skip-link': 'Skip links must have valid, reachable targets',
529
+ tabindex: 'Elements must not have positive tabindex values',
530
+ 'table-duplicate-name': 'Table caption and summary must not be identical',
531
+ 'meta-viewport': 'Pages must allow zoom and text scaling',
532
+ 'aria-allowed-role': 'Elements must use appropriate roles matching their actual behavior',
533
+ 'summary-name': 'Summary must have discernible text'
534
+ };
535
+ export const a11yRuleLongDescriptionMap = {
536
+ 'aria-meter-name': 'Meters are visual indicators that show measurements (like how much storage is used) and need text labels. This helps people using screen readers understand what the meter is tracking.',
537
+ 'aria-progressbar-name': "Progress bars are visual indicators showing completion status and need clear labels describing what's being loaded or processed. This helps people using screen readers know what progress they're watching.",
538
+ 'image-alt': 'Meaningful images (photos, charts, diagrams and other visuals) that communicate important information need text descriptions (called "alt text"). This helps people using screen readers understand what the image shows instead of just hearing/reading out as "image".',
539
+ 'input-image-alt': 'When a button uses only an image instead of text, that image needs a label that describes the button\'s action (called an "accessible name"). e.g., a delete button with a trash can icon should be labeled "Delete" not just "trash can". This helps people using screen readers know what action the button performs.',
540
+ 'object-alt': 'Embedded content, such as PDFs, videos, interactive maps, or other objects need a label that identifies what it is (called an "accessible name"). This helps people using screen readers understand what the object is and what it does. e.g., "View the 2024 annual report (PDF)" or "Video: Company overview (3 minutes)."',
541
+ 'oobee-confusing-alt-text': 'Images that already have alt text (text descriptions for images) but use vague words like "image" "photo", need to be rewritten with actual descriptions of what the image shows. e.g., instead of alt text that says "photo," it should describe what the photo shows: "Team members at the 2024 conference".',
542
+ 'role-img-alt': 'When design elements are marked with image role (a technical way to treat elements as images), they need text descriptions (called "accessible names"). This helps people using screen readers understand what each element represents. e.g., an icon marked as an image needs a description like "Settings icon" not just "image".',
543
+ 'svg-img-alt': 'Vector graphics (scalable graphics created with code called SVGs), that are marked with image role (treated as images) need text descriptions (called "accessible names"). This helps people using screen readers understand what the graphic represents. e.g., an SVG logo should be labeled "Company logo".',
544
+ 'video-caption': 'Videos need captions that show what people are saying and important sounds (captions provided through <track> elements in HTML). This helps people who are deaf or hard of hearing understand video content. Captions should be synchronized with the video and readable.',
545
+ 'aria-required-children': 'Certain accessibility roles (ARIA roles, attributes that tell screen readers what type of element something is) require specific child elements nested inside them to work correctly. e.g., a menu role should contain "menu item" elements inside it. Without the proper child elements, screen readers cannot interpret the structure and the control won\'t work as intended.',
546
+ 'aria-required-parent': 'Certain accessibility roles (ARIA roles, attributes that tell screen readers what type of element something is) require specific parent elements to contain them. e.g., a tab element should be inside a "tab list" parent. When a role is outside its required parent, screen readers cannot understand the relationship and structure, breaking the functionality.',
547
+ 'definition-list': 'Glossaries and FAQs that pair terms with definitions must use proper structure (called a definition list). This means only term and definition elements should be direct children—no other content mixed in directly. This helps screen readers announce which definitions belong to which terms.',
548
+ dlitem: 'Terms and their definitions must always be grouped inside a definition list (a special structure for glossaries and FAQs). When they appear outside this structure, screen readers cannot understand they are related.',
549
+ list: "When you create a bullet list or numbered list, only list item elements should be immediate children of the list container. This structure helps screen readers announce the list properly and count items correctly. (Note: list items themselves can contain other content like paragraphs, links, or formatting—that's allowed.)",
550
+ listitem: 'List item elements should only exist inside a list container (bullet list or numbered list). When list items appear outside a list container, screen readers cannot understand they are part of a list, breaking the list structure.',
551
+ 'td-headers-attr': 'Table headers must clearly identify their purpose in relation to the cells they describe, whether they are column headers or row headers. e.g, a column header might be "Revenue" and a row header might be "Q1". Without clear header relationships, screen readers cannot help users understand what data they\'re reading.',
552
+ 'th-has-data-cells': 'Table headers must be correctly labeled and connected to the data cells they describe. This relationship helps screen reader users understand which header applies to which data cell.',
553
+ 'autocomplete-valid': 'Form fields need correct autocomplete attributes (coded hints that tell browsers what type of information goes in each field). When autocomplete attributes follow the specification, browsers can prefill information correctly. This helps people with cognitive disabilities and slow typists.',
554
+ 'link-in-text-block': 'Links must look different from regular text in ways other than just color (like underlining or special styling). This helps people with color blindness and low vision identify which text is clickable.',
555
+ 'avoid-inline-spacing': "Users should be able to adjust text spacing in their browser settings (spacing is measured in units like ems, not percentages). When CSS styles don't have fixed line-spacing values, users with low vision can increase spacing to read comfortably. This helps people who need wider spacing to read without losing content.",
556
+ 'no-autoplay-audio': 'Pages with audio or video must not auto-play sound when the page loads, unless the sound is very brief (3 seconds or less). Audio that auto-plays longer than 3 seconds must have clear pause/stop controls. This helps people with hearing aids, those who use multiple tabs, and those who need to focus on reading.',
557
+ 'color-contrast': 'Text and background colors need enough contrast ratio (AA level—the baseline accessibility requirement) to be readable. This helps people with low vision see text clearly and read without strain.',
558
+ 'color-contrast-enhanced': 'For enhanced accessibility, text and background colors should meet AAA level contrast (higher than the baseline AA requirement). This provides very high contrast and helps people with low vision see text with minimal strain.',
559
+ 'frame-focusable-content': 'Frames and iframes that contain interactive content need to be accessible via keyboard. When users navigate using Tab, they should be able to reach and interact with content inside the frame. This helps people who navigate only with keyboards.',
560
+ 'server-side-image-map': "Image maps that use server-side clicking (where the server determines what was clicked based on coordinates) don't work with keyboard navigation. Replace them with client-side image maps (HTML-based maps) so everyone can use them via keyboard or any input method.",
561
+ 'scrollable-region-focusable': "Scrollable sections that contain interactive elements need to be accessible by keyboard and screen reader. Users should be able to scroll using the keyboard or a screen reader to reach/read all contents inside the scrollable regions. This helps people who can't use a mouse or those using screen readers.",
562
+ 'oobee-accessible-label': 'Clickable elements (buttons, links, etc) need clear, accessible labels that describe what will happen when clicked. This helps screen reader users understand the purpose of each clickable element.',
563
+ 'meta-refresh': 'Pages should not automatically refresh using timed refresh (meta refresh with delays under 20 hours). Automatic page refreshes interrupt users while reading and frustrate those trying to focus on content. If refresh is necessary, users should control it with a button or link.',
564
+ blink: 'Blinking or flashing text should not be used. This helps people with motion sensitivity, seizure disorders, and those who find flashing content distracting or disorienting.',
565
+ marquee: 'Scrolling or animated text (marquee elements) should not be used. Moving text is difficult to read and causes problems for people with attention disorders, motion sensitivities, or those with low vision. Content should be static or controlled by the user.',
566
+ 'meta-refresh-no-exceptions': 'Pages must not automatically refresh using meta refresh or similar timed mechanisms. Automatic page refreshes interrupt users reading or using the page, and especially frustrate people with attention disabilities or those trying to focus. If page updates are needed, users should have control.',
567
+ bypass: 'Pages must provide a way for users to bypass repeated content blocks (e.g. navbars, sidebars, main, headings, footers). One common way to do this is through skip links. However, pages must also have a main landmark (a marked main content area) so screen readers and keyboard users can jump directly to the primary content. This helps users navigate pages more efficiently.',
568
+ 'document-title': "Every page needs a unique, descriptive title that appears in the browser tab and is read first by screen readers. The title should help users understand what page they're on. This is especially important for people using screen readers who rely on the page title to understand context.",
569
+ 'link-name': "Links need clear, descriptive text or labels that explain where the link goes or what it does. This helps screen reader users understand the link's purpose without reading surrounding context. Links should have an accessible name (either visible text or a programmatic label).",
570
+ 'area-alt': 'Image maps (images where different clickable regions have different links or actions) must have text labels for each clickable area (called alt text on area elements). Each clickable region should have a descriptive label explaining where it links or what happens when clicked. This helps screen reader users understand what each area does without relying on the image.',
571
+ 'identical-links-same-purpose': 'When links use the same text but go to different destinations, they need additional accessible labels (like aria-label attributes) to distinguish them. This helps screen reader users understand the purpose of each link when they see the same text repeated.',
572
+ 'target-size': 'Clickable elements (buttons, links, form fields, etc) need to be at least 24 pixels in size or have adequate spacing between them. This helps people with mobility issues and those using mobile devices to accurately tap or click without missing or accidentally clicking the wrong element.',
573
+ 'html-has-lang': 'Every page (and any frames or iframes within it) must declare its primary language using a language attribute (lang). This helps screen readers pronounce text with the correct accent and pronunciation, and helps translation tools work correctly.',
574
+ 'html-lang-valid': 'The language declared on the page must use a valid ISO language code (like "en" for English, "fr" for French). Invalid or nonstandard language codes prevent screen readers and translation tools from working correctly.',
575
+ 'html-xml-lang-mismatch': 'Language declarations using different formats (HTML and XML) need to match. If they disagree (e.g., lang="en" and xml:lang="fr" on the same element), screen readers and translation tools become confused about the content language.',
576
+ 'valid-lang': 'When parts of a page use different languages (like a Spanish quote in an English article), those elements must be tagged with valid language codes. Invalid language codes prevent screen readers from switching to the correct pronunciation for that language.',
577
+ 'oobee-grading-text-contents': 'Text on the page should be clear and use simple language. This helps people with cognitive disabilities and non-native speakers understand content. Avoid jargon, long complex sentences, and unclear references.',
578
+ 'form-field-multiple-labels': "Form fields should only have one label element associated with them. Multiple label elements cause screen readers to announce conflicting information and confuse users about the field's purpose.",
579
+ 'aria-allowed-attr': "ARIA attributes (accessibility attributes) must be used correctly with elements that support them. Using unsupported ARIA attributes on elements creates conflicting or incorrect screen reader announcements. This prevents users from understanding the element's purpose.",
580
+ 'aria-braille-equivalent': 'When braille-specific abbreviated text is used as a label (like using aria-label="vol" for "volume"), a full text equivalent must also be provided. This ensures non-braille screen reader users and braille display users both understand the label correctly.',
581
+ 'aria-command-name': 'Interactive command elements like role="button", role="link", must have clear, accessible labels. Labels can be visible text, aria-label attributes, or title attributes. Without labels, screen reader users don\'t know what each command does or where links go.',
582
+ 'aria-conditional-attr': 'When ARIA attributes (accessibility attributes) are used on an element, they should not conflict with what the element actually does. Conflicting attributes create confusion about what the element is or what will happen when clicked. e.g., a checkbox is not checked but is aria-checked=true, conflicts for screen reader vs visual readers.',
583
+ 'aria-deprecated-role': 'Some accessibility roles (ARIA roles—code attributes that tell screen readers what type of element something is) are outdated and no longer recommended. Using current, supported roles ensures screen readers announce elements correctly. Outdated roles may cause screen readers to announce elements incorrectly or not at all.',
584
+ 'aria-hidden-body': 'The main page content (the body element) cannot be marked as hidden from screen readers (using aria-hidden="true"). Hiding the page body makes the entire page inaccessible to screen reader users. This is a critical error that breaks accessibility completely.',
585
+ 'aria-hidden-focus': 'Elements marked as hidden from screen readers (aria-hidden="true") should not contain interactive elements like buttons, links, or form fields that can receive keyboard focus. If hidden content is focusable, keyboard users can tab into it but won\'t hear what it is, becoming confused or stuck.',
586
+ 'aria-input-field-name': "Custom input fields (created with code to look like text boxes, dropdowns etc) must have accessible labels that describe what information should be entered. Without labels, screen reader users don't know what to type.",
587
+ 'aria-prohibited-attr': "Certain ARIA attributes (accessibility attributes) are only allowed on specific element types. Using prohibited attributes on the wrong elements causes screen readers to become confused about the element's behavior. This creates conflicting or ignored announcements.",
588
+ 'aria-required-attr': "Certain accessibility roles require specific attributes to work correctly. e.g., a slider role needs aria-valuemin, aria-valuemax, and aria-valuenow to function properly. Without required attributes, screen readers cannot announce the element's current state or allow users to interact with it correctly.",
589
+ 'aria-roles': 'Elements must use valid ARIA roles from the official list. Invalid, misspelled, or unsupported role names confuse screen readers and prevent them from announcing elements correctly. This causes screen reader users to misunderstand what elements do.',
590
+ 'aria-toggle-field-name': 'Toggle switches and custom checkbox / radio button controls need clear labels that describe what is being toggled. e.g., a toggle should be labeled "Dark mode", not just "Toggle". This helps screen reader users understand what will change when they activate it.',
591
+ 'aria-tooltip-name': "Tooltips must have clear, accessible names. The name should describe what happens when the associated control is activated. This helps screen reader users understand a button's purpose before clicking.",
592
+ 'aria-valid-attr': 'ARIA attributes must be spelled correctly and use valid, documented names. Misspelled or unsupported attribute names are ignored by screen readers, causing missing or incorrect announcements. e.g., "aria-labell" (misspelled) won\'t work; it must be "aria-label".',
593
+ 'aria-valid-attr-value': 'ARIA attributes need valid values from the official specification. Using invalid values (like misspelled or unsupported values) prevents screen readers from interpreting the attribute correctly. e.g., aria-pressed must use "true" or "false", not "yes" or "no".',
594
+ 'button-name': "Every button must have descriptive text that explains what happens when clicked. This can be visible text inside the button, or a programmatic label (like aria-label or title attribute). Without clear text, screen reader users don't know what the button does.",
595
+ 'duplicate-id-aria': 'Every HTML ID on a page must be unique. When the same ID is used multiple times, it breaks connections between labels and form fields, and confuses accessibility tools. For example, if two form fields both have id="email", a label pointing to one won\'t work correctly.',
596
+ 'frame-title': 'Every frame or iframe (embedded content like maps, videos, widgets etc) must have a descriptive title attribute. The title helps screen reader users understand what content is in the frame before entering it.',
597
+ 'frame-title-unique': 'When a page has multiple frames or iframes, each must have a unique title. If multiple frames share the same title, screen reader users cannot distinguish between them. e.g., a page with two maps needs titles like "Store locations map" and "Service area map"—not both "Map".',
598
+ 'input-button-name': 'Buttons created using HTML input elements (like <input type="button">) must have descriptive text. This can be the value attribute for submit/button types, or alt text for image buttons. Screen reader users need to know what the button does.',
599
+ label: "Every form field (text input, checkbox etc) needs a label that describes what information should be entered. Labels can be visible text associated with the field, or programmatic labels (aria-label). Without labels, screen reader users don't know what the field is for.",
600
+ 'nested-interactive': 'Buttons, links, and other interactive elements should not be nested inside one another. e.g., a link should not contain a button, and a button should not contain a link. Nested interactive elements confuse screen readers about which element is clickable and create unexpected keyboard behavior.',
601
+ 'select-name': "Selected dropdowns (HTML <select> elements) must have labels that describe what choice the dropdown controls. Without labels, screen reader users don't know what selections they're making. Labels can be visible text or programmatic labels.",
602
+ accesskeys: 'Custom keyboard shortcuts (accesskey attributes) must be unique across the page. Duplicate or conflicting access keys cause unexpected behavior when users try to use them. Additionally, access keys should not conflict with browser (like Ctrl+S), screen reader, or system shortcuts.',
603
+ 'aria-dialog-name': 'Dialog boxes and modal popups must have accessible names (titles) that describe their purpose. When a dialog opens, screen reader users should hear what the dialog is for. This can be visible text at the top of the dialog or an aria-label attribute.',
604
+ 'aria-text': 'Elements marked with role="text" (indicating non-interactive text) should not contain interactive elements like buttons, links, or form fields. If elements marked as role="text" contains focusable elements, keyboard and screen reader users become confused about what they can interact with when they tab through the page.',
605
+ 'aria-treeitem-name': 'Items in tree structures (e.g., navigation tree) or expandable lists (e.g., file explorer) must have clear, accessible names that describe each item. Without names, screen reader users cannot distinguish between different tree items or understand what each represents.',
606
+ 'empty-heading': 'Headings must not be empty or marked hidden. Every heading should have text that describes the section it introduces. Empty headings confuse screen reader users and break the document structure.',
607
+ 'empty-table-header': 'Table header cells (<th> elements) must contain text that describes the column or row. Empty headers make tables unreadable for screen reader users who cannot see the visual layout to infer what each column represents.',
608
+ 'frame-tested': 'All frames and iframes on a page should be tested with accessibility scanning tools (Oobee) to ensure embedded content is accessible. Testing tools need access to frame content to identify issues. Without testing frames, accessibility problems inside them may be missed.',
609
+ 'heading-order': "Headings must follow a logical, hierarchical order: H1 (page title), then H2 (main sections), then H3 (subsections), etc. Headings should increase by only one level at a time. e.g., you shouldn't jump from H1 directly to H3. This helps screen reader users understand the page structure and navigate it correctly.",
610
+ 'image-redundant-alt': "When an image's alt text repeats text already visible on the page, screen reader users hear the same information twice—once as text, once as alt text. Alt text should provide new or clarifying information, not duplicate existing text. If an image is purely decorative or just illustrates text already present, its alt can be empty.",
611
+ 'label-title-only': 'Form fields need visible text labels next to them, not just hidden labels or tooltips that only appear on hover. Visible labels help all users (screen reader users and sighted users) understand what to enter. Placeholders and hidden labels are not sufficient.',
612
+ 'landmark-banner-is-top-level': 'The header/banner landmark (the main page header with site title and navigation) should be at the top level of the page, not nested inside the main content area or other landmarks. When headers are nested, keyboard users cannot easily skip to the main content and cannot navigate page structure correctly.',
613
+ 'landmark-complementary-is-top-level': 'The sidebar or complementary content landmark (supporting content like related links or sidebars) should be at the top level of the page, not nested inside the main content. When sidebars are nested, keyboard and screen reader users cannot easily navigate to them and may not realize they exist.',
614
+ 'landmark-contentinfo-is-top-level': 'The footer or contentinfo landmark (page footer with copyright, links, contact info) should be at the top level of the page, not nested inside the main content. When footers are nested, keyboard users cannot easily navigate to them and must scroll through all content to find footer information.',
615
+ 'landmark-main-is-top-level': 'The main content landmark should be at the top level of the page, directly accessible. When main content is nested inside other landmarks or regions, keyboard users must navigate through unnecessary layers to reach the primary page content.',
616
+ 'landmark-no-duplicate-banner': "A page should have only one main header/banner landmark. When multiple headers exist, screen reader and keyboard users become confused about page structure. They don't know which header is the main one or why there are duplicates.",
617
+ 'landmark-no-duplicate-contentinfo': "A page should have only one main footer/contentinfo landmark. Multiple footers confuse screen reader and keyboard users about page structure. They don't know which footer is the main one or why duplicates exist.",
618
+ 'landmark-no-duplicate-main': 'A page should have only one main content landmark. When multiple main regions are marked, screen reader and keyboard users become confused about where the primary content actually is. They don\'t know which region is the "real" main content.',
619
+ 'landmark-one-main': "Every page needs one designated main content landmark (a marked region containing the page's primary content). This helps screen reader and keyboard users navigate directly to the most important content without having to skip through navigation, sidebars, or headers.",
620
+ 'landmark-unique': 'When a page has multiple landmarks of the same type (like two sidebars), each should have a unique label or title. This helps screen reader and keyboard users distinguish between them. e.g., instead of two unlabeled "navigation" regions, they should be labeled "Left sidebar" and "Right sidebar".',
621
+ 'meta-viewport-large': 'Pages must allow users to zoom in and scale content. When zoom is blocked, people with low vision cannot enlarge text and controls to read them comfortably. The viewport meta tag should allow scaling and not restrict maximum zoom.',
622
+ 'page-has-heading-one': 'Every page should have one or more H1 heading that serves as the main topic. The H1 helps screen reader users quickly understand what the page is about and provides a structural anchor for the document.',
623
+ 'presentation-role-conflict': 'Elements marked with role="presentation" or role="none" (which tells assistive technology to ignore them as they\'re decorative) should not be focusable or have interactive behavior. If an element is marked as decorative but is also focusable or interactive, there\'s a conflict—keyboard users can tab to it but won\'t understand what it is.',
624
+ region: "Every piece of content on a page should be within a marked landmark or region (like header, main, footer, sidebar, or navigation). Orphaned content that's not inside any landmark can be missed by screen reader users. Marking content into regions helps keyboard users skip between sections and understand page organization.",
625
+ 'scope-attr-valid': 'Table headers should have scope attributes that correctly identify whether they\'re column headers (scope="col") or row headers (scope="row"). The scope attribute tells screen readers which header applies to which cells. Incorrect scope values confuse screen readers about cell relationships.',
626
+ 'skip-link': 'Skip links should be the first focusable element on a page (appear when you press Tab). When clicked, they should jump directly to the main content—which means the target (the element it points to) must exist and be reachable. Without a valid target, the skip link is broken and useless.',
627
+ tabindex: 'The tabindex attribute should never have positive values (like tabindex="1"). Positive tabindex values override the natural page order and cause keyboard navigation to become confusing and chaotic—jumping around the page unpredictably.',
628
+ 'table-duplicate-name': 'Tables should not have both a caption and a summary that say exactly the same thing. This causes screen reader users to hear the same information announced twice. The caption should briefly describe the table, and any summary should add additional context or explanation—not repeat the caption word-for-word.',
629
+ 'meta-viewport': 'Pages must allow users to zoom in and scale text using their browser or pinch-to-zoom on mobile devices. Disabling zoom locks people with low vision out of being able to enlarge content to read them comfortably. The viewport meta tag should allow scaling and not restrict maximum zoom.',
630
+ 'aria-allowed-role': `Buttons, links, and interactive elements should behave the way they're marked. e.g., if something looks and acts like a button (performs an action), it should be labeled as a button. If it goes to a different page, it should be labeled as a link. When the label doesn't match the actual behavior, screen reader users get confused about what will happen when they click. When possible, use real buttons (<button>) and real links (<a>) instead of creating fake buttons or links from plain text and code.`,
631
+ 'summary-name': 'Ensure summary elements have discernible text that clearly indicates the topic or purpose of the information that will be revealed when using the summary control.'
632
+ };
633
+ export const disabilityBadgesMap = {
634
+ 'aria-meter-name': ['Visual'],
635
+ 'aria-progressbar-name': ['Visual'],
636
+ 'image-alt': ['Visual'],
637
+ 'input-image-alt': ['Visual'],
638
+ 'object-alt': ['Visual'],
639
+ 'oobee-confusing-alt-text': ['Visual', 'Learning'],
640
+ 'role-img-alt': ['Visual'],
641
+ 'svg-img-alt': ['Visual'],
642
+ 'video-caption': ['Hearing'],
643
+ 'aria-required-children': ['Visual'],
644
+ 'aria-required-parent': ['Visual'],
645
+ 'definition-list': ['Visual'],
646
+ dlitem: ['Visual'],
647
+ list: ['Visual'],
648
+ listitem: ['Visual'],
649
+ 'td-headers-attr': ['Visual'],
650
+ 'th-has-data-cells': ['Visual'],
651
+ 'autocomplete-valid': ['Learning'],
652
+ 'link-in-text-block': ['Visual', 'Learning'],
653
+ 'avoid-inline-spacing': ['Visual', 'Learning'],
654
+ 'no-autoplay-audio': ['Hearing', 'Learning'],
655
+ 'color-contrast': ['Visual'],
656
+ 'color-contrast-enhanced': ['Visual'],
657
+ 'frame-focusable-content': ['Motor', 'Visual'],
658
+ 'server-side-image-map': ['Motor', 'Visual'],
659
+ 'scrollable-region-focusable': ['Motor', 'Visual'],
660
+ 'oobee-accessible-label': ['Motor', 'Visual'],
661
+ 'meta-refresh': ['Learning'],
662
+ blink: ['Learning', 'Visual'],
663
+ marquee: ['Learning', 'Visual'],
664
+ 'meta-refresh-no-exceptions': ['Learning'],
665
+ bypass: ['Visual', 'Learning'],
666
+ 'document-title': ['Visual', 'Learning'],
667
+ 'link-name': ['Visual', 'Learning'],
668
+ 'area-alt': ['Visual', 'Learning'],
669
+ 'identical-links-same-purpose': ['Motor'],
670
+ 'target-size': ['Learning'],
671
+ 'html-has-lang': ['Learning'],
672
+ 'html-lang-valid': ['Learning'],
673
+ 'html-xml-lang-mismatch': ['Learning'],
674
+ 'valid-lang': ['Learning'],
675
+ 'oobee-grading-text-contents': ['Learning', 'Visual'],
676
+ 'form-field-multiple-labels': ['Visual'],
677
+ 'aria-allowed-attr': ['Visual'],
678
+ 'aria-braille-equivalent': ['Visual'],
679
+ 'aria-command-name': ['Visual'],
680
+ 'aria-conditional-attr': ['Visual'],
681
+ 'aria-deprecated-role': ['Visual'],
682
+ 'aria-hidden-body': ['Visual', 'Motor'],
683
+ 'aria-hidden-focus': ['Visual'],
684
+ 'aria-input-field-name': ['Visual'],
685
+ 'aria-prohibited-attr': ['Visual'],
686
+ 'aria-required-attr': ['Visual'],
687
+ 'aria-roles': ['Visual'],
688
+ 'aria-toggle-field-name': ['Visual'],
689
+ 'aria-tooltip-name': ['Visual'],
690
+ 'aria-valid-attr': ['Visual'],
691
+ 'aria-valid-attr-value': ['Visual'],
692
+ 'button-name': ['Visual'],
693
+ 'duplicate-id-aria': ['Visual'],
694
+ 'frame-title': ['Visual'],
695
+ 'frame-title-unique': ['Visual'],
696
+ 'input-button-name': ['Visual'],
697
+ label: ['Motor', 'Learning', 'Visual'],
698
+ 'nested-interactive': ['Visual'],
699
+ 'select-name': ['Visual'],
700
+ accesskeys: ['Motor', 'Learning'],
701
+ 'aria-allowed-role': ['Visual'],
702
+ 'aria-dialog-name': ['Visual', 'Learning'],
703
+ 'aria-text': ['Visual'],
704
+ 'aria-treeitem-name': ['Visual'],
705
+ 'empty-heading': ['Visual', 'Learning'],
706
+ 'empty-table-header': ['Visual'],
707
+ 'frame-tested': ['Visual'],
708
+ 'heading-order': ['Visual', 'Learning'],
709
+ 'image-redundant-alt': ['Visual'],
710
+ 'label-title-only': ['Visual'],
711
+ 'landmark-banner-is-top-level': ['Visual'],
712
+ 'landmark-complementary-is-top-level': ['Visual'],
713
+ 'landmark-contentinfo-is-top-level': ['Visual'],
714
+ 'landmark-main-is-top-level': ['Visual'],
715
+ 'landmark-no-duplicate-banner': ['Visual'],
716
+ 'landmark-no-duplicate-contentinfo': ['Visual'],
717
+ 'landmark-no-duplicate-main': ['Visual'],
718
+ 'landmark-one-main': ['Visual'],
719
+ 'landmark-unique': ['Visual'],
720
+ 'meta-viewport-large': ['Learning', 'Visual'],
721
+ 'page-has-heading-one': ['Visual', 'Learning'],
722
+ 'presentation-role-conflict': ['Visual'],
723
+ region: ['Visual'],
724
+ 'scope-attr-valid': ['Visual'],
725
+ 'skip-link': ['Motor', 'Learning', 'Visual'],
726
+ tabindex: ['Motor'],
727
+ 'meta-viewport': ['Visual'],
728
+ };
729
+ export default {
730
+ cliZipFileName: 'oobee-scan-results.zip',
731
+ exportDirectory: undefined,
732
+ maxRequestsPerCrawl,
733
+ maxConcurrency: 25,
734
+ urlsCrawledObj,
735
+ impactOrder,
736
+ launchOptionsArgs,
737
+ xmlSitemapTypes,
738
+ urlCheckStatuses,
739
+ launcher: chromium,
740
+ pdfScanResultFileName: 'pdf-scan-results.json',
741
+ forbiddenCharactersInDirPath,
742
+ reserveFileNameKeywords,
743
+ wcagLinks,
744
+ wcagCriteriaLabels,
745
+ a11yRuleShortDescriptionMap,
746
+ disabilityBadgesMap,
747
+ robotsTxtUrls: null,
748
+ userDataDirectory: null, // This will be set later in the code
749
+ randomToken: null, // This will be set later in the code
750
+ // Track all active Crawlee / Playwright resources for cleanup
751
+ resources: {
752
+ crawlers: new Set(),
753
+ browserContexts: new Set(),
754
+ browsers: new Set(),
755
+ },
756
+ };
757
+ export const rootPath = dirname;
758
+ export const wcagWebPage = 'https://www.w3.org/TR/WCAG22/';
759
+ const latestAxeVersion = '4.9';
760
+ export const axeVersion = latestAxeVersion;
761
+ export const axeWebPage = `https://dequeuniversity.com/rules/axe/${latestAxeVersion}/`;
762
+ export const saflyIconSelector = `#__safly_icon`;
763
+ export const cssQuerySelectors = [
764
+ ':not(a):is([role="link"]',
765
+ 'button[onclick])',
766
+ 'a:not([href])',
767
+ '[role="button"]:not(a[href])', // Add this line to select elements with role="button" where it is not <a> with href
768
+ ];
769
+ export var RuleFlags;
770
+ (function (RuleFlags) {
771
+ RuleFlags["DEFAULT"] = "default";
772
+ RuleFlags["DISABLE_OOBEE"] = "disable-oobee";
773
+ RuleFlags["ENABLE_WCAG_AAA"] = "enable-wcag-aaa";
774
+ })(RuleFlags || (RuleFlags = {}));
775
+ // Note: Not all status codes will appear as Crawler will handle it as best effort first. E.g. try to handle redirect
776
+ export const STATUS_CODE_METADATA = {
777
+ // Custom Codes for Oobee's use
778
+ 0: 'Page Excluded',
779
+ 1: 'Not A Supported Document',
780
+ 2: 'Web Crawler Errored',
781
+ // 599 is set because Crawlee returns response status 100, 102, 103 as 599
782
+ 599: 'Uncommon Response Status Code Received',
783
+ // This is Status OK but thrown when the crawler cannot scan the page
784
+ 200: 'Oobee was not able to scan the page due to access restrictions or compatibility issues',
785
+ // 1xx - Informational
786
+ 100: '100 - Continue',
787
+ 101: '101 - Switching Protocols',
788
+ 102: '102 - Processing',
789
+ 103: '103 - Early Hints',
790
+ // 2xx - Browser Doesn't Support
791
+ 204: '204 - No Content',
792
+ 205: '205 - Reset Content',
793
+ // 3xx - Redirection
794
+ 300: '300 - Multiple Choices',
795
+ 301: '301 - Moved Permanently',
796
+ 302: '302 - Found',
797
+ 303: '303 - See Other',
798
+ 304: '304 - Not Modified',
799
+ 305: '305 - Use Proxy',
800
+ 307: '307 - Temporary Redirect',
801
+ 308: '308 - Permanent Redirect',
802
+ // 4xx - Client Error
803
+ 400: '400 - Bad Request',
804
+ 401: '401 - Unauthorized',
805
+ 402: '402 - Payment Required',
806
+ 403: '403 - Forbidden',
807
+ 404: '404 - Not Found',
808
+ 405: '405 - Method Not Allowed',
809
+ 406: '406 - Not Acceptable',
810
+ 407: '407 - Proxy Authentication Required',
811
+ 408: '408 - Request Timeout',
812
+ 409: '409 - Conflict',
813
+ 410: '410 - Gone',
814
+ 411: '411 - Length Required',
815
+ 412: '412 - Precondition Failed',
816
+ 413: '413 - Payload Too Large',
817
+ 414: '414 - URI Too Long',
818
+ 415: '415 - Unsupported Media Type',
819
+ 416: '416 - Range Not Satisfiable',
820
+ 417: '417 - Expectation Failed',
821
+ 418: "418 - I'm a teapot",
822
+ 421: '421 - Misdirected Request',
823
+ 422: '422 - Unprocessable Content',
824
+ 423: '423 - Locked',
825
+ 424: '424 - Failed Dependency',
826
+ 425: '425 - Too Early',
827
+ 426: '426 - Upgrade Required',
828
+ 428: '428 - Precondition Required',
829
+ 429: '429 - Too Many Requests',
830
+ 431: '431 - Request Header Fields Too Large',
831
+ 451: '451 - Unavailable For Legal Reasons',
832
+ // 5xx - Server Error
833
+ 500: '500 - Internal Server Error',
834
+ 501: '501 - Not Implemented',
835
+ 502: '502 - Bad Gateway',
836
+ 503: '503 - Service Unavailable',
837
+ 504: '504 - Gateway Timeout',
838
+ 505: '505 - HTTP Version Not Supported',
839
+ 506: '506 - Variant Also Negotiates',
840
+ 507: '507 - Insufficient Storage',
841
+ 508: '508 - Loop Detected',
842
+ 510: '510 - Not Extended',
843
+ 511: '511 - Network Authentication Required',
844
+ };
845
+ // Elements that should not be clicked or enqueued
846
+ // With reference from https://chromeenterprise.google/policies/url-patterns/
847
+ export const disallowedListOfPatterns = [
848
+ '#',
849
+ 'mailto:',
850
+ 'tel:',
851
+ 'sms:',
852
+ 'skype:',
853
+ 'zoommtg:',
854
+ 'msteams:',
855
+ 'whatsapp:',
856
+ 'slack:',
857
+ 'viber:',
858
+ 'tg:',
859
+ 'line:',
860
+ 'meet:',
861
+ 'facetime:',
862
+ 'imessage:',
863
+ 'discord:',
864
+ 'sgnl:',
865
+ 'webex:',
866
+ 'intent:',
867
+ 'ms-outlook:',
868
+ 'ms-onedrive:',
869
+ 'ms-word:',
870
+ 'ms-excel:',
871
+ 'ms-powerpoint:',
872
+ 'ms-office:',
873
+ 'onenote:',
874
+ 'vs:',
875
+ 'chrome-extension:',
876
+ 'chrome-search:',
877
+ 'chrome:',
878
+ 'chrome-untrusted:',
879
+ 'devtools:',
880
+ 'isolated-app:',
881
+ ];
882
+ export const disallowedSelectorPatterns = disallowedListOfPatterns
883
+ .map(pattern => `a[href^="${pattern}"]`)
884
+ .join(',')
885
+ .replace(/\s+/g, '');
886
+ export const WCAGclauses = {
887
+ '1.1.1': 'Provide text alternatives',
888
+ '1.2.2': 'Add captions to videos',
889
+ '1.3.1': 'Use proper headings and lists',
890
+ '1.3.5': 'Clearly label common fields',
891
+ '1.4.1': 'Add cues beyond color',
892
+ '1.4.2': 'Control any autoplay audio',
893
+ '1.4.3': 'Ensure text is easy to read',
894
+ '1.4.4': 'Allow zoom without breaking layout',
895
+ '1.4.6': 'Ensure very high text contrast',
896
+ '1.4.12': 'Let users adjust text spacing',
897
+ '2.1.1': 'Everything works by keyboard',
898
+ '2.1.3': 'Everything works only by keyboard',
899
+ '2.2.1': 'Let users extend time limits',
900
+ '2.2.2': 'Let users stop motion',
901
+ '2.2.4': 'Let users control alerts',
902
+ '2.4.1': 'Add skip navigation',
903
+ '2.4.2': 'Write clear page titles',
904
+ '2.4.4': 'Say where links go',
905
+ '2.4.9': 'Links make sense on their own',
906
+ '2.5.8': 'Buttons must be easy to tap',
907
+ '3.1.1': "Declare the page's language",
908
+ '3.1.2': 'Show when language changes',
909
+ '3.1.5': 'Keep content easy to read',
910
+ '3.2.5': "Don't auto-change settings",
911
+ '3.3.2': 'Label fields and options',
912
+ '4.1.2': 'Make buttons and inputs readable',
913
+ };