@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.
- package/.dockerignore +22 -0
- package/.github/pull_request_template.md +11 -0
- package/.github/workflows/docker-test.yml +54 -0
- package/.github/workflows/image.yml +107 -0
- package/.github/workflows/publish.yml +18 -0
- package/.idea/modules.xml +8 -0
- package/.idea/purple-a11y.iml +9 -0
- package/.idea/vcs.xml +6 -0
- package/.prettierrc.json +12 -0
- package/.vscode/extensions.json +5 -0
- package/.vscode/settings.json +10 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/DETAILS.md +163 -0
- package/Dockerfile +60 -0
- package/INSTALLATION.md +146 -0
- package/INTEGRATION.md +785 -0
- package/LICENSE +22 -0
- package/README.md +587 -0
- package/SECURITY.md +5 -0
- package/__mocks__/mock-report.html +1431 -0
- package/__mocks__/mockFunctions.ts +32 -0
- package/__mocks__/mockIssues.ts +64 -0
- package/__mocks__/mock_all_issues/000000001.json +64 -0
- package/__mocks__/mock_all_issues/000000002.json +53 -0
- package/__mocks__/mock_all_issues/fake-file.txt +0 -0
- package/__tests__/logs.test.ts +25 -0
- package/__tests__/mergeAxeResults.test.ts +278 -0
- package/__tests__/utils.test.ts +118 -0
- package/a11y-scan-results.zip +0 -0
- package/eslint.config.js +53 -0
- package/exclusions.txt +2 -0
- package/gitlab-pipeline-template.yml +54 -0
- package/jest.config.js +1 -0
- package/package.json +96 -0
- package/scripts/copyFiles.js +44 -0
- package/scripts/install_oobee_dependencies.cmd +13 -0
- package/scripts/install_oobee_dependencies.command +101 -0
- package/scripts/install_oobee_dependencies.ps1 +110 -0
- package/scripts/oobee_shell.cmd +13 -0
- package/scripts/oobee_shell.command +11 -0
- package/scripts/oobee_shell.sh +55 -0
- package/scripts/oobee_shell_ps.ps1 +54 -0
- package/src/cli.ts +401 -0
- package/src/combine.ts +240 -0
- package/src/constants/__tests__/common.test.ts +44 -0
- package/src/constants/cliFunctions.ts +305 -0
- package/src/constants/common.ts +1840 -0
- package/src/constants/constants.ts +443 -0
- package/src/constants/errorMeta.json +319 -0
- package/src/constants/itemTypeDescription.ts +11 -0
- package/src/constants/oobeeAi.ts +141 -0
- package/src/constants/questions.ts +181 -0
- package/src/constants/sampleData.ts +187 -0
- package/src/crawlers/__tests__/commonCrawlerFunc.test.ts +51 -0
- package/src/crawlers/commonCrawlerFunc.ts +656 -0
- package/src/crawlers/crawlDomain.ts +877 -0
- package/src/crawlers/crawlIntelligentSitemap.ts +156 -0
- package/src/crawlers/crawlLocalFile.ts +193 -0
- package/src/crawlers/crawlSitemap.ts +356 -0
- package/src/crawlers/custom/extractAndGradeText.ts +57 -0
- package/src/crawlers/custom/flagUnlabelledClickableElements.ts +964 -0
- package/src/crawlers/custom/utils.ts +486 -0
- package/src/crawlers/customAxeFunctions.ts +82 -0
- package/src/crawlers/pdfScanFunc.ts +468 -0
- package/src/crawlers/runCustom.ts +117 -0
- package/src/index.ts +173 -0
- package/src/logs.ts +66 -0
- package/src/mergeAxeResults.ts +964 -0
- package/src/npmIndex.ts +284 -0
- package/src/screenshotFunc/htmlScreenshotFunc.ts +411 -0
- package/src/screenshotFunc/pdfScreenshotFunc.ts +762 -0
- package/src/static/ejs/partials/components/categorySelector.ejs +4 -0
- package/src/static/ejs/partials/components/categorySelectorDropdown.ejs +57 -0
- package/src/static/ejs/partials/components/pagesScannedModal.ejs +70 -0
- package/src/static/ejs/partials/components/reportSearch.ejs +47 -0
- package/src/static/ejs/partials/components/ruleOffcanvas.ejs +105 -0
- package/src/static/ejs/partials/components/scanAbout.ejs +263 -0
- package/src/static/ejs/partials/components/screenshotLightbox.ejs +13 -0
- package/src/static/ejs/partials/components/summaryScanAbout.ejs +141 -0
- package/src/static/ejs/partials/components/summaryScanResults.ejs +16 -0
- package/src/static/ejs/partials/components/summaryTable.ejs +20 -0
- package/src/static/ejs/partials/components/summaryWcagCompliance.ejs +94 -0
- package/src/static/ejs/partials/components/topFive.ejs +6 -0
- package/src/static/ejs/partials/components/wcagCompliance.ejs +70 -0
- package/src/static/ejs/partials/footer.ejs +21 -0
- package/src/static/ejs/partials/header.ejs +230 -0
- package/src/static/ejs/partials/main.ejs +40 -0
- package/src/static/ejs/partials/scripts/bootstrap.ejs +8 -0
- package/src/static/ejs/partials/scripts/categorySelectorDropdownScript.ejs +190 -0
- package/src/static/ejs/partials/scripts/categorySummary.ejs +141 -0
- package/src/static/ejs/partials/scripts/highlightjs.ejs +335 -0
- package/src/static/ejs/partials/scripts/popper.ejs +7 -0
- package/src/static/ejs/partials/scripts/reportSearch.ejs +248 -0
- package/src/static/ejs/partials/scripts/ruleOffcanvas.ejs +801 -0
- package/src/static/ejs/partials/scripts/screenshotLightbox.ejs +71 -0
- package/src/static/ejs/partials/scripts/summaryScanResults.ejs +14 -0
- package/src/static/ejs/partials/scripts/summaryTable.ejs +78 -0
- package/src/static/ejs/partials/scripts/utils.ejs +441 -0
- package/src/static/ejs/partials/styles/bootstrap.ejs +12375 -0
- package/src/static/ejs/partials/styles/highlightjs.ejs +54 -0
- package/src/static/ejs/partials/styles/styles.ejs +1843 -0
- package/src/static/ejs/partials/styles/summaryBootstrap.ejs +12458 -0
- package/src/static/ejs/partials/summaryHeader.ejs +70 -0
- package/src/static/ejs/partials/summaryMain.ejs +75 -0
- package/src/static/ejs/report.ejs +420 -0
- package/src/static/ejs/summary.ejs +47 -0
- package/src/static/mustache/.prettierrc +4 -0
- package/src/static/mustache/Attention Deficit.mustache +11 -0
- package/src/static/mustache/Blind.mustache +11 -0
- package/src/static/mustache/Cognitive.mustache +7 -0
- package/src/static/mustache/Colorblindness.mustache +20 -0
- package/src/static/mustache/Deaf.mustache +12 -0
- package/src/static/mustache/Deafblind.mustache +7 -0
- package/src/static/mustache/Dyslexia.mustache +14 -0
- package/src/static/mustache/Low Vision.mustache +7 -0
- package/src/static/mustache/Mobility.mustache +15 -0
- package/src/static/mustache/Sighted Keyboard Users.mustache +42 -0
- package/src/static/mustache/report.mustache +1709 -0
- package/src/types/print-message.d.ts +28 -0
- package/src/types/types.ts +46 -0
- package/src/types/xpath-to-css.d.ts +3 -0
- package/src/utils.ts +332 -0
- 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;
|