@govtechsg/oobee 0.10.20

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 (123) hide show
  1. package/.dockerignore +22 -0
  2. package/.github/pull_request_template.md +11 -0
  3. package/.github/workflows/docker-test.yml +54 -0
  4. package/.github/workflows/image.yml +107 -0
  5. package/.github/workflows/publish.yml +18 -0
  6. package/.idea/modules.xml +8 -0
  7. package/.idea/purple-a11y.iml +9 -0
  8. package/.idea/vcs.xml +6 -0
  9. package/.prettierrc.json +12 -0
  10. package/.vscode/extensions.json +5 -0
  11. package/.vscode/settings.json +10 -0
  12. package/CODE_OF_CONDUCT.md +128 -0
  13. package/DETAILS.md +163 -0
  14. package/Dockerfile +60 -0
  15. package/INSTALLATION.md +146 -0
  16. package/INTEGRATION.md +785 -0
  17. package/LICENSE +22 -0
  18. package/README.md +587 -0
  19. package/SECURITY.md +5 -0
  20. package/__mocks__/mock-report.html +1431 -0
  21. package/__mocks__/mockFunctions.ts +32 -0
  22. package/__mocks__/mockIssues.ts +64 -0
  23. package/__mocks__/mock_all_issues/000000001.json +64 -0
  24. package/__mocks__/mock_all_issues/000000002.json +53 -0
  25. package/__mocks__/mock_all_issues/fake-file.txt +0 -0
  26. package/__tests__/logs.test.ts +25 -0
  27. package/__tests__/mergeAxeResults.test.ts +278 -0
  28. package/__tests__/utils.test.ts +118 -0
  29. package/a11y-scan-results.zip +0 -0
  30. package/eslint.config.js +53 -0
  31. package/exclusions.txt +2 -0
  32. package/gitlab-pipeline-template.yml +54 -0
  33. package/jest.config.js +1 -0
  34. package/package.json +96 -0
  35. package/scripts/copyFiles.js +44 -0
  36. package/scripts/install_oobee_dependencies.cmd +13 -0
  37. package/scripts/install_oobee_dependencies.command +101 -0
  38. package/scripts/install_oobee_dependencies.ps1 +110 -0
  39. package/scripts/oobee_shell.cmd +13 -0
  40. package/scripts/oobee_shell.command +11 -0
  41. package/scripts/oobee_shell.sh +55 -0
  42. package/scripts/oobee_shell_ps.ps1 +54 -0
  43. package/src/cli.ts +401 -0
  44. package/src/combine.ts +240 -0
  45. package/src/constants/__tests__/common.test.ts +44 -0
  46. package/src/constants/cliFunctions.ts +305 -0
  47. package/src/constants/common.ts +1840 -0
  48. package/src/constants/constants.ts +443 -0
  49. package/src/constants/errorMeta.json +319 -0
  50. package/src/constants/itemTypeDescription.ts +11 -0
  51. package/src/constants/oobeeAi.ts +141 -0
  52. package/src/constants/questions.ts +181 -0
  53. package/src/constants/sampleData.ts +187 -0
  54. package/src/crawlers/__tests__/commonCrawlerFunc.test.ts +51 -0
  55. package/src/crawlers/commonCrawlerFunc.ts +656 -0
  56. package/src/crawlers/crawlDomain.ts +877 -0
  57. package/src/crawlers/crawlIntelligentSitemap.ts +156 -0
  58. package/src/crawlers/crawlLocalFile.ts +193 -0
  59. package/src/crawlers/crawlSitemap.ts +356 -0
  60. package/src/crawlers/custom/extractAndGradeText.ts +57 -0
  61. package/src/crawlers/custom/flagUnlabelledClickableElements.ts +964 -0
  62. package/src/crawlers/custom/utils.ts +486 -0
  63. package/src/crawlers/customAxeFunctions.ts +82 -0
  64. package/src/crawlers/pdfScanFunc.ts +468 -0
  65. package/src/crawlers/runCustom.ts +117 -0
  66. package/src/index.ts +173 -0
  67. package/src/logs.ts +66 -0
  68. package/src/mergeAxeResults.ts +964 -0
  69. package/src/npmIndex.ts +284 -0
  70. package/src/screenshotFunc/htmlScreenshotFunc.ts +411 -0
  71. package/src/screenshotFunc/pdfScreenshotFunc.ts +762 -0
  72. package/src/static/ejs/partials/components/categorySelector.ejs +4 -0
  73. package/src/static/ejs/partials/components/categorySelectorDropdown.ejs +57 -0
  74. package/src/static/ejs/partials/components/pagesScannedModal.ejs +70 -0
  75. package/src/static/ejs/partials/components/reportSearch.ejs +47 -0
  76. package/src/static/ejs/partials/components/ruleOffcanvas.ejs +105 -0
  77. package/src/static/ejs/partials/components/scanAbout.ejs +263 -0
  78. package/src/static/ejs/partials/components/screenshotLightbox.ejs +13 -0
  79. package/src/static/ejs/partials/components/summaryScanAbout.ejs +141 -0
  80. package/src/static/ejs/partials/components/summaryScanResults.ejs +16 -0
  81. package/src/static/ejs/partials/components/summaryTable.ejs +20 -0
  82. package/src/static/ejs/partials/components/summaryWcagCompliance.ejs +94 -0
  83. package/src/static/ejs/partials/components/topFive.ejs +6 -0
  84. package/src/static/ejs/partials/components/wcagCompliance.ejs +70 -0
  85. package/src/static/ejs/partials/footer.ejs +21 -0
  86. package/src/static/ejs/partials/header.ejs +230 -0
  87. package/src/static/ejs/partials/main.ejs +40 -0
  88. package/src/static/ejs/partials/scripts/bootstrap.ejs +8 -0
  89. package/src/static/ejs/partials/scripts/categorySelectorDropdownScript.ejs +190 -0
  90. package/src/static/ejs/partials/scripts/categorySummary.ejs +141 -0
  91. package/src/static/ejs/partials/scripts/highlightjs.ejs +335 -0
  92. package/src/static/ejs/partials/scripts/popper.ejs +7 -0
  93. package/src/static/ejs/partials/scripts/reportSearch.ejs +248 -0
  94. package/src/static/ejs/partials/scripts/ruleOffcanvas.ejs +801 -0
  95. package/src/static/ejs/partials/scripts/screenshotLightbox.ejs +71 -0
  96. package/src/static/ejs/partials/scripts/summaryScanResults.ejs +14 -0
  97. package/src/static/ejs/partials/scripts/summaryTable.ejs +78 -0
  98. package/src/static/ejs/partials/scripts/utils.ejs +441 -0
  99. package/src/static/ejs/partials/styles/bootstrap.ejs +12375 -0
  100. package/src/static/ejs/partials/styles/highlightjs.ejs +54 -0
  101. package/src/static/ejs/partials/styles/styles.ejs +1843 -0
  102. package/src/static/ejs/partials/styles/summaryBootstrap.ejs +12458 -0
  103. package/src/static/ejs/partials/summaryHeader.ejs +70 -0
  104. package/src/static/ejs/partials/summaryMain.ejs +75 -0
  105. package/src/static/ejs/report.ejs +420 -0
  106. package/src/static/ejs/summary.ejs +47 -0
  107. package/src/static/mustache/.prettierrc +4 -0
  108. package/src/static/mustache/Attention Deficit.mustache +11 -0
  109. package/src/static/mustache/Blind.mustache +11 -0
  110. package/src/static/mustache/Cognitive.mustache +7 -0
  111. package/src/static/mustache/Colorblindness.mustache +20 -0
  112. package/src/static/mustache/Deaf.mustache +12 -0
  113. package/src/static/mustache/Deafblind.mustache +7 -0
  114. package/src/static/mustache/Dyslexia.mustache +14 -0
  115. package/src/static/mustache/Low Vision.mustache +7 -0
  116. package/src/static/mustache/Mobility.mustache +15 -0
  117. package/src/static/mustache/Sighted Keyboard Users.mustache +42 -0
  118. package/src/static/mustache/report.mustache +1709 -0
  119. package/src/types/print-message.d.ts +28 -0
  120. package/src/types/types.ts +46 -0
  121. package/src/types/xpath-to-css.d.ts +3 -0
  122. package/src/utils.ts +332 -0
  123. package/tsconfig.json +15 -0
@@ -0,0 +1,486 @@
1
+ /* eslint-disable no-shadow */
2
+ /* eslint-disable no-alert */
3
+ /* eslint-disable no-param-reassign */
4
+ /* eslint-env browser */
5
+ import path from 'path';
6
+ import { runAxeScript } from '../commonCrawlerFunc.js';
7
+ import { consoleLogger, guiInfoLog, silentLogger } from '../../logs.js';
8
+ import { guiInfoStatusTypes } from '../../constants/constants.js';
9
+ import { isSkippedUrl, urlWithoutAuth } from '../../constants/common.js';
10
+
11
+ //! For Cypress Test
12
+ // env to check if Cypress test is running
13
+ const isCypressTest = process.env.IS_CYPRESS_TEST === 'true';
14
+
15
+ export const DEBUG = false;
16
+ export const log = str => {
17
+ if (DEBUG) {
18
+ console.log(str);
19
+ }
20
+ };
21
+
22
+ export const screenshotFullPage = async (page, screenshotsDir: string, screenshotIdx) => {
23
+ const imgName = `PHScan-screenshot${screenshotIdx}.png`;
24
+ const imgPath = path.join(screenshotsDir, imgName);
25
+ const originalSize = page.viewportSize();
26
+
27
+ try {
28
+ const fullPageSize = await page.evaluate(() => ({
29
+ width: Math.max(
30
+ document.body.scrollWidth,
31
+ document.documentElement.scrollWidth,
32
+ document.body.offsetWidth,
33
+ document.documentElement.offsetWidth,
34
+ document.body.clientWidth,
35
+ document.documentElement.clientWidth,
36
+ ),
37
+ height: Math.max(
38
+ document.body.scrollHeight,
39
+ document.documentElement.scrollHeight,
40
+ document.body.offsetHeight,
41
+ document.documentElement.offsetHeight,
42
+ document.body.clientHeight,
43
+ document.documentElement.clientHeight,
44
+ ),
45
+ }));
46
+
47
+ const usesInfiniteScroll = async () => {
48
+ const prevHeight = await page.evaluate(() => document.body.scrollHeight);
49
+
50
+ await page.evaluate(() => {
51
+ window.scrollTo(0, document.body.scrollHeight);
52
+ });
53
+
54
+ const isLoadMoreContent = async () =>
55
+ new Promise(resolve => {
56
+ setTimeout(async () => {
57
+ await page.waitForLoadState('domcontentloaded');
58
+
59
+ const newHeight = await page.evaluate(
60
+ // eslint-disable-next-line no-shadow
61
+ () => document.body.scrollHeight,
62
+ );
63
+ const result = newHeight > prevHeight;
64
+
65
+ resolve(result);
66
+ }, 2500);
67
+ });
68
+
69
+ const result = await isLoadMoreContent();
70
+ return result;
71
+ };
72
+
73
+ await usesInfiniteScroll();
74
+
75
+ // scroll back to top of page for screenshot
76
+ await page.evaluate(() => {
77
+ window.scrollTo(0, 0);
78
+ });
79
+
80
+ consoleLogger.info(`Screenshot page at: ${urlWithoutAuth(page.url())}`);
81
+ silentLogger.info(`Screenshot page at: ${urlWithoutAuth(page.url())}`);
82
+
83
+ await page.screenshot({
84
+ timeout: 5000,
85
+ path: imgPath,
86
+ clip: {
87
+ x: 0,
88
+ y: 0,
89
+ width: fullPageSize.width,
90
+ height: 5400,
91
+ },
92
+ fullPage: true,
93
+ scale: 'css',
94
+ });
95
+
96
+ if (originalSize) await page.setViewportSize(originalSize);
97
+ } catch {
98
+ consoleLogger.error('Unable to take screenshot');
99
+ // Do not return screenshot path if screenshot fails
100
+ return '';
101
+ }
102
+
103
+ return `screenshots/${imgName}`; // relative path from reports folder
104
+ };
105
+
106
+ export const runAxeScan = async (
107
+ page,
108
+ includeScreenshots,
109
+ randomToken,
110
+ customFlowDetails,
111
+ dataset,
112
+ urlsCrawled,
113
+ ) => {
114
+ const result = await runAxeScript({ includeScreenshots, page, randomToken, customFlowDetails });
115
+
116
+ await dataset.pushData(result);
117
+
118
+ urlsCrawled.scanned.push({
119
+ url: urlWithoutAuth(page.url()),
120
+ pageTitle: result.pageTitle,
121
+ pageImagePath: customFlowDetails.pageImagePath,
122
+ });
123
+ };
124
+
125
+ export const processPage = async (page, processPageParams) => {
126
+ // make sure to update processPageParams' scannedIdx
127
+ processPageParams.scannedIdx += 1;
128
+
129
+ let { includeScreenshots } = processPageParams;
130
+
131
+ const {
132
+ scannedIdx,
133
+ blacklistedPatterns,
134
+ dataset,
135
+ intermediateScreenshotsPath,
136
+ urlsCrawled,
137
+ randomToken,
138
+ } = processPageParams;
139
+
140
+ try {
141
+ await page.waitForLoadState('domcontentloaded', { timeout: 10000 });
142
+ } catch {
143
+ consoleLogger.info('Unable to detect page load state');
144
+ }
145
+
146
+ consoleLogger.info(`Attempting to scan: ${page.url()}`);
147
+
148
+ const pageUrl = page.url();
149
+
150
+ if (blacklistedPatterns && isSkippedUrl(pageUrl, blacklistedPatterns)) {
151
+ const continueScan = await page.evaluate(() =>
152
+ window.confirm('Page has been excluded, would you still like to proceed with the scan?'),
153
+ );
154
+ if (!continueScan) {
155
+ urlsCrawled.userExcluded.push(pageUrl);
156
+ return;
157
+ }
158
+ }
159
+
160
+ // TODO: Check if necessary
161
+ // To skip already scanned pages
162
+ // if (urlsCrawled.scanned.some(scan => scan.url === pageUrl)) {
163
+ // page.evaluate(() => {
164
+ // window.alert('Page has already been scanned, skipping scan.');
165
+ // });
166
+ // return;
167
+ // }
168
+
169
+ try {
170
+ const initialScrollPos = await page.evaluate(() => ({
171
+ x: window.scrollX,
172
+ y: window.scrollY,
173
+ }));
174
+
175
+ const pageImagePath = await screenshotFullPage(page, intermediateScreenshotsPath, scannedIdx);
176
+
177
+ // TODO: This is a temporary fix to not take element screenshots on pages when errors out at full page screenshot
178
+ if (pageImagePath === '') {
179
+ includeScreenshots = false;
180
+ }
181
+
182
+ await runAxeScan(
183
+ page,
184
+ includeScreenshots,
185
+ randomToken,
186
+ {
187
+ pageIndex: scannedIdx,
188
+ pageImagePath,
189
+ },
190
+ dataset,
191
+ urlsCrawled,
192
+ );
193
+
194
+ guiInfoLog(guiInfoStatusTypes.SCANNED, {
195
+ numScanned: urlsCrawled.scanned.length,
196
+ urlScanned: pageUrl,
197
+ });
198
+
199
+ await page.evaluate(pos => {
200
+ window.scrollTo(pos.x, pos.y);
201
+ }, initialScrollPos);
202
+ } catch {
203
+ consoleLogger.error(`Error in scanning page: ${pageUrl}`);
204
+ }
205
+ };
206
+
207
+ export const MENU_POSITION = {
208
+ top: 'TOP',
209
+ bottom: 'BOTTOM',
210
+ };
211
+
212
+ export const updateMenu = async (page, urlsCrawled) => {
213
+ log(`Overlay menu: updating: ${page.url()}`);
214
+ await page.evaluate(
215
+ vars => {
216
+ const shadowHost = document.querySelector('#oobee-shadow-host');
217
+ if (shadowHost) {
218
+ const p = shadowHost.shadowRoot.querySelector('#oobee-p-pages-scanned');
219
+ if (p) {
220
+ p.textContent = `Pages Scanned: ${vars.urlsCrawled.scanned.length || 0}`;
221
+ }
222
+ }
223
+ },
224
+ { urlsCrawled },
225
+ );
226
+
227
+ consoleLogger.info(`Overlay menu updated`);
228
+ };
229
+
230
+ export const addOverlayMenu = async (page, urlsCrawled, menuPos) => {
231
+ await page.waitForLoadState('domcontentloaded');
232
+ consoleLogger.info(`Overlay menu: adding to ${menuPos}...`);
233
+ interface CustomWindow extends Window {
234
+ updateMenuPos: (newPos: any) => void;
235
+ handleOnScanClick: () => void;
236
+ }
237
+
238
+ // Add the overlay menu with initial styling
239
+ return page
240
+ .evaluate(
241
+ async vars => {
242
+ const menu = document.createElement('div');
243
+ menu.className = 'oobee-menu';
244
+ if (vars.menuPos === vars.MENU_POSITION.top) {
245
+ menu.style.top = '0';
246
+ } else {
247
+ menu.style.bottom = '0';
248
+ }
249
+ let isDragging = false;
250
+ let initialY;
251
+ let offsetY;
252
+
253
+ menu.addEventListener('mousedown', e => {
254
+ // The EvenTarget Object is the grandfather interface for Element
255
+ // In order to get the tagName of the item you would need polymorph it into Element
256
+ const targetElement: Element = e.target as Element;
257
+ if (targetElement.tagName.toLowerCase() !== 'button') {
258
+ e.preventDefault();
259
+ isDragging = true;
260
+ initialY = e.clientY - menu.getBoundingClientRect().top;
261
+ }
262
+ });
263
+
264
+ document.addEventListener('mousemove', e => {
265
+ if (isDragging) {
266
+ menu.style.removeProperty('bottom');
267
+ offsetY = e.clientY - initialY;
268
+ menu.style.top = `${offsetY}px`;
269
+ }
270
+ });
271
+ const customWindow = window as unknown as CustomWindow;
272
+
273
+ document.addEventListener('mouseup', () => {
274
+ // need to tell typeScript to defer first because down int he script updateMenuPos is defined
275
+ if (isDragging) {
276
+ // Snap the menu when it is below half the screen
277
+ const halfScreenHeight: number = window.innerHeight / 2;
278
+ const isTopHalf: boolean = offsetY < halfScreenHeight;
279
+ if (isTopHalf) {
280
+ menu.style.removeProperty('bottom');
281
+ menu.style.top = '0';
282
+ customWindow.updateMenuPos(vars.MENU_POSITION.top);
283
+ } else {
284
+ menu.style.removeProperty('top');
285
+ menu.style.bottom = '0';
286
+ customWindow.updateMenuPos(vars.MENU_POSITION.top);
287
+ }
288
+
289
+ isDragging = false;
290
+ }
291
+ });
292
+
293
+ const p = document.createElement('p');
294
+ p.id = 'oobee-p-pages-scanned';
295
+ p.innerText = `Pages Scanned: ${vars.urlsCrawled.scanned.length || 0}`;
296
+
297
+ const button = document.createElement('button');
298
+ button.innerText = 'Scan this page';
299
+ button.addEventListener('click', async () => {
300
+ customWindow.handleOnScanClick();
301
+ });
302
+
303
+ menu.appendChild(p);
304
+ menu.appendChild(button);
305
+
306
+ const sheet = new CSSStyleSheet();
307
+ // TODO: separate out into css file if this gets too big
308
+ sheet.replaceSync(`
309
+ .oobee-menu {
310
+ position: fixed;
311
+ left: 0;
312
+ width: 100%;
313
+ box-sizing: border-box;
314
+ background-color: rgba(0, 0, 0, 0.8);
315
+ display: flex;
316
+ justify-content: space-between;
317
+ padding: 10px;
318
+ z-index: 2147483647;
319
+ cursor: grab;
320
+ color: #fff;
321
+ }
322
+
323
+ .oobee-menu button {
324
+ background-color: #9021a6;
325
+ color: #fff;
326
+ border: none;
327
+ border-radius: 50rem;
328
+ padding: 10px 20px;
329
+ cursor: pointer;
330
+ }
331
+ `);
332
+
333
+ // shadow dom used to avoid styling from page
334
+ const shadowHost = document.createElement('div');
335
+ shadowHost.id = 'oobee-shadow-host';
336
+ const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
337
+
338
+ shadowRoot.adoptedStyleSheets = [sheet];
339
+
340
+ shadowRoot.appendChild(menu);
341
+
342
+ let currentNode = document.body;
343
+ if (document.body) {
344
+ // The <body> element exists
345
+ if (document.body.nodeName.toLowerCase() === 'frameset') {
346
+ // if currentNode is a <frameset>
347
+ // Move the variable outside the frameset then appendChild the component
348
+ while (currentNode.nodeName.toLowerCase() === 'frameset') {
349
+ currentNode = currentNode.parentElement;
350
+ }
351
+ currentNode.appendChild(shadowHost);
352
+ } else {
353
+ // currentNode is a <body>
354
+ currentNode.appendChild(shadowHost);
355
+ }
356
+ } else if (document.head) {
357
+ // The <head> element exists
358
+ // Append the variable below the head
359
+ document.head.insertAdjacentElement('afterend', shadowHost);
360
+ } else {
361
+ // Neither <body> nor <head> nor <html> exists
362
+ // Append the variable to the document
363
+ document.documentElement.appendChild(shadowHost);
364
+ }
365
+ },
366
+ { menuPos, MENU_POSITION, urlsCrawled },
367
+ )
368
+ .then(() => {
369
+ log('Overlay menu: successfully added');
370
+ })
371
+ .catch(error => {
372
+ error('Overlay menu: failed to add', error);
373
+ });
374
+ };
375
+
376
+ export const removeOverlayMenu = async page => {
377
+ await page
378
+ .evaluate(() => {
379
+ const existingOverlay = document.querySelector('#oobee-shadow-host');
380
+ if (existingOverlay) {
381
+ existingOverlay.remove();
382
+ return true;
383
+ }
384
+ return false;
385
+ })
386
+ .then(removed => {
387
+ if (removed) {
388
+ consoleLogger.info('Overlay Menu: successfully removed');
389
+ }
390
+ });
391
+ };
392
+
393
+ export const initNewPage = async (page, pageClosePromises, processPageParams, pagesDict) => {
394
+ let menuPos = MENU_POSITION.top;
395
+
396
+ // eslint-disable-next-line no-underscore-dangle
397
+ const pageId = page._guid;
398
+
399
+ page.on('dialog', () => {});
400
+
401
+ const pageClosePromise = new Promise(resolve => {
402
+ page.on('close', () => {
403
+ log(`Page: close detected: ${page.url()}`);
404
+ delete pagesDict[pageId];
405
+ resolve(true);
406
+ });
407
+ });
408
+ pageClosePromises.push(pageClosePromise);
409
+
410
+ if (!pagesDict[pageId]) {
411
+ pagesDict[pageId] = { page };
412
+ }
413
+
414
+ type handleOnScanClickFunction = () => void;
415
+
416
+ // Window functions exposed in browser
417
+ const handleOnScanClick: handleOnScanClickFunction = async () => {
418
+ log('Scan: click detected');
419
+ try {
420
+ await removeOverlayMenu(page);
421
+ await processPage(page, processPageParams);
422
+ log('Scan: success');
423
+ await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos);
424
+
425
+ Object.keys(pagesDict)
426
+ .filter(k => k !== pageId)
427
+ .forEach(k => {
428
+ updateMenu(pagesDict[k].page, processPageParams.urlsCrawled);
429
+ });
430
+ } catch (error) {
431
+ log(`Scan failed ${error}`);
432
+ }
433
+ };
434
+
435
+ // Detection of new url within page
436
+ page.on('domcontentloaded', async () => {
437
+ try {
438
+ const existingOverlay = await page.evaluate(() => {
439
+ return document.querySelector('#oobee-shadow-host');
440
+ });
441
+
442
+ consoleLogger.info(`Overlay state: ${existingOverlay}`);
443
+
444
+ if (!existingOverlay) {
445
+ consoleLogger.info(`Adding overlay menu to page: ${page.url()}`);
446
+ await addOverlayMenu(page, processPageParams.urlsCrawled, menuPos);
447
+ }
448
+
449
+ setTimeout(() => {
450
+ // Timeout here to slow things down a little
451
+ }, 1000);
452
+
453
+ //! For Cypress Test
454
+ // Auto-clicks 'Scan this page' button only once
455
+ if (isCypressTest) {
456
+ try {
457
+ await handleOnScanClick();
458
+ page.close();
459
+ } catch {
460
+ consoleLogger.info(`Error in calling handleOnScanClick, isCypressTest: ${isCypressTest}`);
461
+ }
462
+ }
463
+
464
+ consoleLogger.info(`Overlay state: ${existingOverlay}`);
465
+ } catch {
466
+ consoleLogger.info('Error in adding overlay menu to page');
467
+ silentLogger.info('Error in adding overlay menu to page');
468
+ }
469
+ });
470
+
471
+ await page.exposeFunction('handleOnScanClick', handleOnScanClick);
472
+
473
+ type UpdateMenuPosFunction = (newPos: any) => void;
474
+
475
+ // Define the updateMenuPos function
476
+ const updateMenuPos: UpdateMenuPosFunction = newPos => {
477
+ const prevPos = menuPos;
478
+ if (prevPos !== newPos) {
479
+ console.log(`Overlay menu: position updated from ${prevPos} to ${newPos}`);
480
+ menuPos = newPos;
481
+ }
482
+ };
483
+ await page.exposeFunction('updateMenuPos', updateMenuPos);
484
+
485
+ return page;
486
+ };
@@ -0,0 +1,82 @@
1
+ import { Spec } from 'axe-core';
2
+
3
+ // Custom Axe Functions for axe.config
4
+ export const customAxeConfig: Spec = {
5
+ branding: {
6
+ application: 'oobee',
7
+ },
8
+ checks: [
9
+ {
10
+ id: 'oobee-confusing-alt-text',
11
+ metadata: {
12
+ impact: 'serious',
13
+ messages: {
14
+ pass: 'The image alt text is probably useful.',
15
+ fail: "The image alt text set as 'img', 'image', 'picture', 'photo', or 'graphic' is confusing or not useful.",
16
+ },
17
+ },
18
+ },
19
+ {
20
+ id: 'oobee-accessible-label',
21
+ metadata: {
22
+ impact: 'serious',
23
+ messages: {
24
+ pass: 'The clickable element has an accessible label.',
25
+ fail: 'The clickable element does not have an accessible label.',
26
+ },
27
+ },
28
+ },
29
+ {
30
+ id: 'oobee-grading-text-contents',
31
+ metadata: {
32
+ impact: 'moderate',
33
+ messages: {
34
+ pass: 'The text content is easy to understand.',
35
+ fail: 'The text content is potentially difficult to undersatnd.',
36
+ },
37
+ },
38
+ },
39
+ ],
40
+ rules: [
41
+ { id: 'target-size', enabled: true },
42
+ {
43
+ id: 'oobee-confusing-alt-text',
44
+ selector: 'img[alt]',
45
+ enabled: true,
46
+ any: ['oobee-confusing-alt-text'],
47
+ tags: ['wcag2a', 'wcag111'],
48
+ metadata: {
49
+ description: 'Ensures image alt text is clear and useful.',
50
+ help: 'Image alt text must not be vague or unhelpful.',
51
+ helpUrl: 'https://www.deque.com/blog/great-alt-text-introduction/',
52
+ },
53
+ },
54
+ {
55
+ id: 'oobee-accessible-label',
56
+ // selector: '*', // to be set with the checker function output xpaths converted to css selectors
57
+ enabled: true,
58
+ any: ['oobee-accessible-label'],
59
+ tags: ['wcag2a', 'wcag211', 'wcag243', 'wcag412'],
60
+ metadata: {
61
+ description: 'Ensures clickable elements have an accessible label.',
62
+ help: 'Clickable elements must have accessible labels.',
63
+ helpUrl: 'https://www.deque.com/blog/accessible-aria-buttons',
64
+ },
65
+ },
66
+ {
67
+ id: 'oobee-grading-text-contents',
68
+ selector: 'html',
69
+ enabled: true,
70
+ any: ['oobee-grading-text-contents'],
71
+ tags: ['wcag2a', 'wcag315'],
72
+ metadata: {
73
+ description:
74
+ '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.',
75
+ help: 'Text content should be clear and plain to ensure that it is easily understood.',
76
+ helpUrl: 'https://www.wcag.com/uncategorized/3-1-5-reading-level/',
77
+ },
78
+ },
79
+ ],
80
+ };
81
+
82
+ export default customAxeConfig;