@govtechsg/oobee 0.10.39 → 0.10.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/docker-test.yml +1 -1
- package/README.md +2 -0
- package/REPORTS.md +431 -0
- package/package.json +3 -2
- package/src/cli.ts +2 -11
- package/src/constants/common.ts +68 -52
- package/src/constants/constants.ts +81 -1
- package/src/constants/oobeeAi.ts +6 -6
- package/src/constants/questions.ts +3 -2
- package/src/crawlers/commonCrawlerFunc.ts +45 -16
- package/src/crawlers/crawlDomain.ts +83 -102
- package/src/crawlers/crawlIntelligentSitemap.ts +21 -19
- package/src/crawlers/crawlSitemap.ts +121 -110
- package/src/crawlers/custom/findElementByCssSelector.ts +1 -1
- package/src/crawlers/custom/flagUnlabelledClickableElements.ts +593 -558
- package/src/crawlers/custom/xPathToCss.ts +10 -10
- package/src/crawlers/pdfScanFunc.ts +67 -26
- package/src/crawlers/runCustom.ts +1 -1
- package/src/index.ts +3 -4
- package/src/logs.ts +1 -1
- package/src/mergeAxeResults.ts +305 -242
- package/src/npmIndex.ts +12 -8
- package/src/screenshotFunc/htmlScreenshotFunc.ts +8 -20
- package/src/screenshotFunc/pdfScreenshotFunc.ts +34 -1
- package/src/types/text-readability.d.ts +3 -0
- package/src/types/types.ts +1 -1
- package/src/utils.ts +340 -50
- package/src/xPathToCss.ts +0 -186
- package/src/xPathToCssCypress.ts +0 -178
package/src/npmIndex.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import fs from 'fs';
|
2
2
|
import path from 'path';
|
3
3
|
import printMessage from 'print-message';
|
4
|
-
import axe, { ImpactValue } from 'axe-core';
|
4
|
+
import axe, { AxeResults, ImpactValue } from 'axe-core';
|
5
5
|
import { fileURLToPath } from 'url';
|
6
6
|
import { EnqueueStrategy } from 'crawlee';
|
7
7
|
import constants, { BrowserTypes, RuleFlags, ScannerTypes } from './constants/constants.js';
|
@@ -16,7 +16,7 @@ import { createCrawleeSubFolders, filterAxeResults } from './crawlers/commonCraw
|
|
16
16
|
import { createAndUpdateResultsFolders, createDetailsAndLogs } from './utils.js';
|
17
17
|
import generateArtifacts from './mergeAxeResults.js';
|
18
18
|
import { takeScreenshotForHTMLElements } from './screenshotFunc/htmlScreenshotFunc.js';
|
19
|
-
import { silentLogger } from './logs.js';
|
19
|
+
import { consoleLogger, silentLogger } from './logs.js';
|
20
20
|
import { alertMessageOptions } from './constants/cliFunctions.js';
|
21
21
|
import { evaluateAltText } from './crawlers/custom/evaluateAltText.js';
|
22
22
|
import { escapeCssSelector } from './crawlers/custom/escapeCssSelector.js';
|
@@ -24,7 +24,7 @@ import { framesCheck } from './crawlers/custom/framesCheck.js';
|
|
24
24
|
import { findElementByCssSelector } from './crawlers/custom/findElementByCssSelector.js';
|
25
25
|
import { getAxeConfiguration } from './crawlers/custom/getAxeConfiguration.js';
|
26
26
|
import { flagUnlabelledClickableElements } from './crawlers/custom/flagUnlabelledClickableElements.js';
|
27
|
-
import
|
27
|
+
import xPathToCss from './crawlers/custom/xPathToCss.js';
|
28
28
|
import { extractText } from './crawlers/custom/extractText.js';
|
29
29
|
import { gradeReadability } from './crawlers/custom/gradeReadability.js';
|
30
30
|
|
@@ -65,7 +65,7 @@ export const init = async ({
|
|
65
65
|
specifiedMaxConcurrency?: number;
|
66
66
|
followRobots?: boolean;
|
67
67
|
}) => {
|
68
|
-
|
68
|
+
consoleLogger.info('Starting Oobee');
|
69
69
|
|
70
70
|
const [date, time] = new Date().toLocaleString('sv').replaceAll(/-|:/g, '').split(' ');
|
71
71
|
const domain = new URL(entryUrl).hostname;
|
@@ -126,7 +126,7 @@ export const init = async ({
|
|
126
126
|
const cssSelector = xPathToCss(xpath);
|
127
127
|
return cssSelector;
|
128
128
|
} catch (e) {
|
129
|
-
|
129
|
+
consoleLogger.error(`Error converting XPath to CSS: ${xpath} - ${e}`);
|
130
130
|
return '';
|
131
131
|
}
|
132
132
|
})
|
@@ -197,7 +197,11 @@ export const init = async ({
|
|
197
197
|
`;
|
198
198
|
};
|
199
199
|
|
200
|
-
const pushScanResults = async (
|
200
|
+
const pushScanResults = async (
|
201
|
+
res: { pageUrl: string; pageTitle: string; axeScanResults: AxeResults },
|
202
|
+
metadata: string,
|
203
|
+
elementsToClick: string[],
|
204
|
+
) => {
|
201
205
|
throwErrorIfTerminated();
|
202
206
|
if (includeScreenshots) {
|
203
207
|
// use chrome by default
|
@@ -211,7 +215,7 @@ export const init = async ({
|
|
211
215
|
await page.waitForLoadState('networkidle');
|
212
216
|
|
213
217
|
// click on elements to reveal hidden elements so screenshots can be taken
|
214
|
-
elementsToClick?.forEach(async elem => {
|
218
|
+
elementsToClick?.forEach(async (elem: string) => {
|
215
219
|
try {
|
216
220
|
await page.locator(elem).click();
|
217
221
|
} catch (e) {
|
@@ -259,7 +263,7 @@ export const init = async ({
|
|
259
263
|
|
260
264
|
const terminate = async () => {
|
261
265
|
throwErrorIfTerminated();
|
262
|
-
|
266
|
+
consoleLogger.info('Stopping Oobee');
|
263
267
|
isInstanceTerminated = true;
|
264
268
|
scanDetails.endTime = new Date();
|
265
269
|
scanDetails.urlsCrawled = urlsCrawled;
|
@@ -7,14 +7,14 @@ import { Result } from 'axe-core';
|
|
7
7
|
import { Page } from 'playwright';
|
8
8
|
import { NodeResultWithScreenshot, ResultWithScreenshot } from '../crawlers/commonCrawlerFunc.js';
|
9
9
|
|
10
|
-
const screenshotMap = {}; // Map of screenshot hashkey to its buffer value and screenshot path
|
10
|
+
const screenshotMap: Record<string, string> = {}; // Map of screenshot hashkey to its buffer value and screenshot path
|
11
11
|
|
12
12
|
export const takeScreenshotForHTMLElements = async (
|
13
13
|
violations: Result[],
|
14
14
|
page: Page,
|
15
15
|
randomToken: string,
|
16
16
|
locatorTimeout = 2000,
|
17
|
-
maxScreenshots =
|
17
|
+
maxScreenshots = 100,
|
18
18
|
): Promise<ResultWithScreenshot[]> => {
|
19
19
|
const newViolations: ResultWithScreenshot[] = [];
|
20
20
|
let screenshotCount = 0;
|
@@ -75,30 +75,18 @@ export const takeScreenshotForHTMLElements = async (
|
|
75
75
|
return newViolations;
|
76
76
|
};
|
77
77
|
|
78
|
-
const generateBufferHash = (buffer: Buffer) => {
|
78
|
+
const generateBufferHash = (buffer: Buffer): string => {
|
79
79
|
const hash = createHash('sha256');
|
80
80
|
hash.update(buffer);
|
81
81
|
return hash.digest('hex');
|
82
82
|
};
|
83
83
|
|
84
|
-
const
|
85
|
-
|
86
|
-
|
87
|
-
};
|
88
|
-
|
89
|
-
const getIdenticalScreenshotKey = (buffer: Buffer) => {
|
90
|
-
for (const hashKey in screenshotMap) {
|
91
|
-
const isIdentical = isSameBufferHash(buffer, hashKey);
|
92
|
-
if (isIdentical) return hashKey;
|
93
|
-
}
|
94
|
-
return undefined;
|
95
|
-
};
|
96
|
-
|
97
|
-
const getScreenshotPath = (buffer: Buffer, randomToken: string) => {
|
98
|
-
let hashKey = getIdenticalScreenshotKey(buffer);
|
84
|
+
const getScreenshotPath = (buffer: Buffer, randomToken: string): string => {
|
85
|
+
let hashKey = generateBufferHash(buffer);
|
86
|
+
const existingPath = screenshotMap[hashKey];
|
99
87
|
// If exists identical entry in screenshot map, get its filepath
|
100
|
-
if (
|
101
|
-
return
|
88
|
+
if (existingPath) {
|
89
|
+
return existingPath;
|
102
90
|
}
|
103
91
|
// Create new entry in screenshot map
|
104
92
|
hashKey = generateBufferHash(buffer);
|
@@ -1,3 +1,10 @@
|
|
1
|
+
// Monkey patch Path2D to avoid PDF.js crashing
|
2
|
+
(globalThis as any).Path2D = class {
|
3
|
+
constructor(_path?: string) {}
|
4
|
+
rect(_x: number, _y: number, _width: number, _height: number) {}
|
5
|
+
addPath(_path: any, _transform?: any) {}
|
6
|
+
};
|
7
|
+
|
1
8
|
import _ from 'lodash';
|
2
9
|
import { getDocument, PDFPageProxy } from 'pdfjs-dist';
|
3
10
|
import fs from 'fs';
|
@@ -25,11 +32,36 @@ interface pathObject {
|
|
25
32
|
annot?: number;
|
26
33
|
}
|
27
34
|
|
35
|
+
// Use safe canvas to avoid Path2D issues
|
36
|
+
function createSafeCanvas(width: number, height: number) {
|
37
|
+
const canvas = createCanvas(width, height);
|
38
|
+
const ctx = canvas.getContext('2d');
|
39
|
+
|
40
|
+
// Patch clip/stroke/fill/etc. to skip if Path2D is passed
|
41
|
+
const wrapIgnorePath2D = (fn: Function) =>
|
42
|
+
function (...args: any[]) {
|
43
|
+
if (args.length > 0 && args[0] instanceof (globalThis as any).Path2D) {
|
44
|
+
// Skip the operation
|
45
|
+
return;
|
46
|
+
}
|
47
|
+
return fn.apply(this, args);
|
48
|
+
};
|
49
|
+
|
50
|
+
ctx.clip = wrapIgnorePath2D(ctx.clip);
|
51
|
+
ctx.fill = wrapIgnorePath2D(ctx.fill);
|
52
|
+
ctx.stroke = wrapIgnorePath2D(ctx.stroke);
|
53
|
+
ctx.isPointInPath = wrapIgnorePath2D(ctx.isPointInPath);
|
54
|
+
ctx.isPointInStroke = wrapIgnorePath2D(ctx.isPointInStroke);
|
55
|
+
|
56
|
+
return canvas;
|
57
|
+
}
|
58
|
+
|
59
|
+
// CanvasFactory for Node.js
|
28
60
|
function NodeCanvasFactory() {}
|
29
61
|
NodeCanvasFactory.prototype = {
|
30
62
|
create: function NodeCanvasFactory_create(width: number, height: number) {
|
31
63
|
assert(width > 0 && height > 0, 'Invalid canvas size');
|
32
|
-
const canvas =
|
64
|
+
const canvas = createSafeCanvas(width, height);
|
33
65
|
const context = canvas.getContext('2d');
|
34
66
|
return {
|
35
67
|
canvas,
|
@@ -69,6 +101,7 @@ export async function getPdfScreenshots(
|
|
69
101
|
const newItems = _.cloneDeep(items);
|
70
102
|
const loadingTask = getDocument({
|
71
103
|
url: pdfFilePath,
|
104
|
+
canvasFactory,
|
72
105
|
standardFontDataUrl: path.join(dirname, '../node_modules/pdfjs-dist/standard_fonts/'),
|
73
106
|
disableFontFace: true,
|
74
107
|
verbosity: 0,
|