@govtechsg/oobee 0.10.51 → 0.10.58
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/bump-package-version.yml +58 -0
- package/.github/workflows/image.yml +38 -17
- package/DETAILS.md +5 -2
- package/INTEGRATION.md +57 -53
- package/README.md +4 -1
- package/__tests__/test-sitemap-url-patterns.xml +105 -0
- package/exclusions.txt +1 -0
- package/package.json +7 -6
- package/src/cli.ts +35 -2
- package/src/combine.ts +10 -7
- package/src/constants/cliFunctions.ts +9 -0
- package/src/constants/common.ts +95 -105
- package/src/constants/constants.ts +47 -2
- package/src/crawlers/commonCrawlerFunc.ts +84 -5
- package/src/crawlers/crawlDomain.ts +93 -160
- package/src/crawlers/crawlIntelligentSitemap.ts +40 -36
- package/src/crawlers/crawlLocalFile.ts +77 -35
- package/src/crawlers/crawlSitemap.ts +156 -89
- package/src/crawlers/pdfScanFunc.ts +2 -0
- package/src/index.ts +2 -0
- package/src/logs.ts +4 -2
- package/src/mergeAxeResults.ts +20 -9
- package/src/npmIndex.ts +1 -1
- package/src/screenshotFunc/htmlScreenshotFunc.ts +7 -5
- package/src/screenshotFunc/pdfScreenshotFunc.ts +2 -2
- package/src/static/ejs/partials/components/wcagCompliance.ejs +1 -1
- package/src/static/ejs/partials/scripts/ruleOffcanvas.ejs +1 -0
- package/src/static/ejs/partials/styles/styles.ejs +11 -0
- package/src/static/ejs/report.ejs +14 -1
- package/src/utils.ts +3 -3
package/src/mergeAxeResults.ts
CHANGED
@@ -78,6 +78,7 @@ type AllIssues = {
|
|
78
78
|
htmlETL: any;
|
79
79
|
rules: string[];
|
80
80
|
};
|
81
|
+
siteName: string;
|
81
82
|
startTime: Date;
|
82
83
|
endTime: Date;
|
83
84
|
urlScanned: string;
|
@@ -130,7 +131,6 @@ const extractFileNames = async (directory: string): Promise<string[]> => {
|
|
130
131
|
.then(allFiles => allFiles.filter(file => path.extname(file).toLowerCase() === '.json'))
|
131
132
|
.catch(readdirError => {
|
132
133
|
consoleLogger.info('An error has occurred when retrieving files, please try again.');
|
133
|
-
silentLogger.error(`(extractFileNames) - ${readdirError}`);
|
134
134
|
throw readdirError;
|
135
135
|
});
|
136
136
|
};
|
@@ -140,7 +140,6 @@ const parseContentToJson = async rPath =>
|
|
140
140
|
.then(content => JSON.parse(content))
|
141
141
|
.catch(parseError => {
|
142
142
|
consoleLogger.info('An error has occurred when parsing the content, please try again.');
|
143
|
-
silentLogger.error(`(parseContentToJson) - ${parseError}`);
|
144
143
|
});
|
145
144
|
|
146
145
|
const writeCsv = async (allIssues, storagePath) => {
|
@@ -684,7 +683,7 @@ async function compressJsonFileStreaming(inputPath: string, outputPath: string)
|
|
684
683
|
// Pipe the streams:
|
685
684
|
// read -> gzip -> base64 -> write
|
686
685
|
await pipeline(readStream, gzip, base64Encode, writeStream);
|
687
|
-
|
686
|
+
consoleLogger.info(`File successfully compressed and saved to ${outputPath}`);
|
688
687
|
}
|
689
688
|
|
690
689
|
const writeJsonFileAndCompressedJsonFile = async (
|
@@ -1559,7 +1558,7 @@ const sendWcagBreakdownToSentry = async (
|
|
1559
1558
|
const wcagCriteriaBreakdown: Record<string, any> = {};
|
1560
1559
|
|
1561
1560
|
// Tag app version
|
1562
|
-
tags
|
1561
|
+
tags.version = appVersion;
|
1563
1562
|
|
1564
1563
|
// Get dynamic WCAG criteria map once
|
1565
1564
|
const wcagCriteriaMap = await getWcagCriteriaMap();
|
@@ -1638,7 +1637,7 @@ const sendWcagBreakdownToSentry = async (
|
|
1638
1637
|
tags['WCAG-MustFix-Occurrences'] = String(allIssues.items.mustFix.totalItems);
|
1639
1638
|
tags['WCAG-GoodToFix-Occurrences'] = String(allIssues.items.goodToFix.totalItems);
|
1640
1639
|
tags['WCAG-NeedsReview-Occurrences'] = String(allIssues.items.needsReview.totalItems);
|
1641
|
-
|
1640
|
+
|
1642
1641
|
// Add number of pages scanned tag
|
1643
1642
|
tags['Pages-Scanned-Count'] = String(allIssues.totalPagesScanned);
|
1644
1643
|
} else if (pagesScannedCount > 0) {
|
@@ -1667,7 +1666,7 @@ const sendWcagBreakdownToSentry = async (
|
|
1667
1666
|
...(userData && userData.userId ? { id: userData.userId } : {}),
|
1668
1667
|
},
|
1669
1668
|
extra: {
|
1670
|
-
additionalScanMetadata: ruleIdJson != null ? JSON.stringify(ruleIdJson)
|
1669
|
+
additionalScanMetadata: ruleIdJson != null ? JSON.stringify(ruleIdJson) : '{}',
|
1671
1670
|
wcagBreakdown: wcagCriteriaBreakdown,
|
1672
1671
|
reportCounts: allIssues
|
1673
1672
|
? {
|
@@ -1766,6 +1765,7 @@ const generateArtifacts = async (
|
|
1766
1765
|
htmlETL: oobeeAiHtmlETL,
|
1767
1766
|
rules: oobeeAiRules,
|
1768
1767
|
},
|
1768
|
+
siteName: (pagesScanned[0]?.pageTitle ?? '').replace(/^\d+\s*:\s*/, '').trim(),
|
1769
1769
|
startTime: scanDetails.startTime ? scanDetails.startTime : new Date(),
|
1770
1770
|
endTime: scanDetails.endTime ? scanDetails.endTime : new Date(),
|
1771
1771
|
urlScanned,
|
@@ -1845,7 +1845,6 @@ const generateArtifacts = async (
|
|
1845
1845
|
}),
|
1846
1846
|
).catch(flattenIssuesError => {
|
1847
1847
|
consoleLogger.info('An error has occurred when flattening the issues, please try again.');
|
1848
|
-
silentLogger.error(flattenIssuesError.stack);
|
1849
1848
|
});
|
1850
1849
|
|
1851
1850
|
flattenAndSortResults(allIssues, isCustomFlow);
|
@@ -1853,6 +1852,10 @@ const generateArtifacts = async (
|
|
1853
1852
|
printMessage([
|
1854
1853
|
'Scan Summary',
|
1855
1854
|
'',
|
1855
|
+
`Site Name: ${allIssues.siteName}`,
|
1856
|
+
`URL: ${allIssues.urlScanned}`,
|
1857
|
+
`Pages Scanned: ${allIssues.totalPagesScanned}`,
|
1858
|
+
'',
|
1856
1859
|
`Must Fix: ${allIssues.items.mustFix.rules.length} ${Object.keys(allIssues.items.mustFix.rules).length === 1 ? 'issue' : 'issues'} / ${allIssues.items.mustFix.totalItems} ${allIssues.items.mustFix.totalItems === 1 ? 'occurrence' : 'occurrences'}`,
|
1857
1860
|
`Good to Fix: ${allIssues.items.goodToFix.rules.length} ${Object.keys(allIssues.items.goodToFix.rules).length === 1 ? 'issue' : 'issues'} / ${allIssues.items.goodToFix.totalItems} ${allIssues.items.goodToFix.totalItems === 1 ? 'occurrence' : 'occurrences'}`,
|
1858
1861
|
`Manual Review Required: ${allIssues.items.needsReview.rules.length} ${Object.keys(allIssues.items.needsReview.rules).length === 1 ? 'issue' : 'issues'} / ${allIssues.items.needsReview.totalItems} ${allIssues.items.needsReview.totalItems === 1 ? 'occurrence' : 'occurrences'}`,
|
@@ -1882,8 +1885,13 @@ const generateArtifacts = async (
|
|
1882
1885
|
allIssues.advancedScanOptionsSummaryItems.disableOobee,
|
1883
1886
|
);
|
1884
1887
|
|
1885
|
-
|
1886
|
-
|
1888
|
+
consoleLogger.info(`Site Name: ${allIssues.siteName}`);
|
1889
|
+
consoleLogger.info(`URL: ${allIssues.urlScanned}`);
|
1890
|
+
consoleLogger.info(`Pages Scanned: ${allIssues.totalPagesScanned}`);
|
1891
|
+
consoleLogger.info(`Start Time: ${allIssues.startTime}`);
|
1892
|
+
consoleLogger.info(`End Time: ${allIssues.endTime}`);
|
1893
|
+
const elapsedSeconds = (new Date(allIssues.endTime).getTime() - new Date(allIssues.startTime).getTime()) / 1000;
|
1894
|
+
consoleLogger.info(`Elapsed Time: ${elapsedSeconds}s`);
|
1887
1895
|
|
1888
1896
|
const getAxeImpactCount = (allIssues: AllIssues) => {
|
1889
1897
|
const impactCount = {
|
@@ -2044,6 +2052,9 @@ const generateArtifacts = async (
|
|
2044
2052
|
console.error('Error sending WCAG data to Sentry:', error);
|
2045
2053
|
}
|
2046
2054
|
|
2055
|
+
if (process.env.RUNNING_FROM_PH_GUI || process.env.OOBEE_VERBOSE)
|
2056
|
+
console.log('Report generated successfully');
|
2057
|
+
|
2047
2058
|
return ruleIdJson;
|
2048
2059
|
};
|
2049
2060
|
|
package/src/npmIndex.ts
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
import { createHash } from 'crypto';
|
3
3
|
import fs from 'fs';
|
4
4
|
import path from 'path';
|
5
|
-
import {
|
5
|
+
// import { silentLogger } from '../logs.js';
|
6
6
|
import { Result } from 'axe-core';
|
7
7
|
import { Page } from 'playwright';
|
8
8
|
import { NodeResultWithScreenshot, ResultWithScreenshot } from '../crawlers/commonCrawlerFunc.js';
|
@@ -21,9 +21,11 @@ export const takeScreenshotForHTMLElements = async (
|
|
21
21
|
|
22
22
|
for (const violation of violations) {
|
23
23
|
if (screenshotCount >= maxScreenshots) {
|
24
|
-
|
24
|
+
/*
|
25
|
+
silentLogger.warn(
|
25
26
|
`Skipping screenshots for ${violation.id} as maxScreenshots (${maxScreenshots}) exceeded. You can increase it by specifying a higher value when calling takeScreenshotForHTMLElements.`,
|
26
27
|
);
|
28
|
+
*/
|
27
29
|
newViolations.push(violation);
|
28
30
|
continue;
|
29
31
|
}
|
@@ -32,7 +34,7 @@ export const takeScreenshotForHTMLElements = async (
|
|
32
34
|
|
33
35
|
// Check if rule ID is 'oobee-grading-text-contents' and skip screenshot logic
|
34
36
|
if (rule === 'oobee-grading-text-contents') {
|
35
|
-
//
|
37
|
+
// silentLogger.info('Skipping screenshot for rule oobee-grading-text-contents');
|
36
38
|
newViolations.push(violation); // Make sure it gets added
|
37
39
|
continue;
|
38
40
|
}
|
@@ -57,13 +59,13 @@ export const takeScreenshotForHTMLElements = async (
|
|
57
59
|
nodeWithScreenshotPath.screenshotPath = screenshotPath;
|
58
60
|
screenshotCount++;
|
59
61
|
} else {
|
60
|
-
|
62
|
+
// silentLogger.info(`Element at ${currLocator} is not visible`);
|
61
63
|
}
|
62
64
|
|
63
65
|
break; // Stop looping after finding the first visible locator
|
64
66
|
}
|
65
67
|
} catch (e) {
|
66
|
-
|
68
|
+
// silentLogger.info(`Unable to take element screenshot at ${selector}`);
|
67
69
|
}
|
68
70
|
}
|
69
71
|
newViolationNodes.push(nodeWithScreenshotPath);
|
@@ -12,7 +12,7 @@ import { Canvas, createCanvas, SKRSContext2D } from '@napi-rs/canvas';
|
|
12
12
|
import assert from 'assert';
|
13
13
|
import path from 'path';
|
14
14
|
import { fileURLToPath } from 'url';
|
15
|
-
import { silentLogger } from '../logs.js';
|
15
|
+
import { consoleLogger, silentLogger } from '../logs.js';
|
16
16
|
import { TransformedRuleObject } from '../crawlers/pdfScanFunc.js';
|
17
17
|
import { IBboxLocation, StructureTree, ViewportSize } from '../types/types.js';
|
18
18
|
|
@@ -213,7 +213,7 @@ const annotateAndSave = (origCanvas: Canvas, screenshotPath: string, viewport: V
|
|
213
213
|
try {
|
214
214
|
fs.writeFileSync(indexedScreenshotPath, croppedImage);
|
215
215
|
} catch (e) {
|
216
|
-
|
216
|
+
consoleLogger.error('Error in writing screenshot:', e);
|
217
217
|
}
|
218
218
|
|
219
219
|
canvasFactory.destroy({ canvas: croppedCanvas, context: croppedCtx });
|
@@ -29,7 +29,7 @@
|
|
29
29
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
30
30
|
data-bs-target="#wcagLinksAccordionContent" aria-expanded="false"
|
31
31
|
aria-controls="wcagLinksAccordionContent">
|
32
|
-
20 (A & AA) and
|
32
|
+
20 (A & AA) and 6 (AAA) WCAG Success Criteria
|
33
33
|
</button>
|
34
34
|
</div>
|
35
35
|
<div id="wcagLinksAccordionContent" class="accordion-collapse collapse"
|
@@ -22,6 +22,7 @@ category summary is clicked %>
|
|
22
22
|
// wcag1410: 'https://www.w3.org/TR/WCAG22/#reflow',
|
23
23
|
wcag1412: 'https://www.w3.org/TR/WCAG22/#text-spacing',
|
24
24
|
wcag211: 'https://www.w3.org/TR/WCAG22/#keyboard',
|
25
|
+
wcag213: 'https://www.w3.org/WAI/WCAG22/Understanding/keyboard-no-exception.html', // AAA
|
25
26
|
wcag221: 'https://www.w3.org/TR/WCAG22/#timing-adjustable',
|
26
27
|
wcag222: 'https://www.w3.org/TR/WCAG22/#pause-stop-hide',
|
27
28
|
wcag224: 'https://www.w3.org/TR/WCAG22/#interruptions', // AAA
|
@@ -668,6 +668,17 @@
|
|
668
668
|
#pagesScannedModal .not-scanned-url {
|
669
669
|
word-wrap: break-word;
|
670
670
|
}
|
671
|
+
#pagesScannedModal .metadata-text {
|
672
|
+
word-wrap: break-word;
|
673
|
+
font-size: 0.875rem;
|
674
|
+
color: var(--coral-red-100);
|
675
|
+
}
|
676
|
+
#pagesScannedModal .metadata-inline {
|
677
|
+
display: flex;
|
678
|
+
align-items: center;
|
679
|
+
gap: 0.5rem;
|
680
|
+
height: 16px;
|
681
|
+
}
|
671
682
|
#pagesScannedModal p {
|
672
683
|
overflow: hidden;
|
673
684
|
white-space: nowrap;
|
@@ -298,7 +298,20 @@
|
|
298
298
|
|
299
299
|
pagesNotScanned.forEach((page, index) => {
|
300
300
|
var listItem = document.createElement('li');
|
301
|
-
|
301
|
+
const url = page.url || page;
|
302
|
+
const metadata = page.metadata || page;
|
303
|
+
|
304
|
+
const linkHTML = `<a class="not-scanned-url" href="${url}" target="_blank">${url}</a>`;
|
305
|
+
const metadataHTML = `
|
306
|
+
<div class="metadata-inline">
|
307
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none">
|
308
|
+
<path d="M12 19.875C9.91142 19.875 7.90838 19.0453 6.43153 17.5685C4.95468 16.0916 4.125 14.0886 4.125 12C4.125 9.91142 4.95468 7.90838 6.43153 6.43153C7.90838 4.95468 9.91142 4.125 12 4.125C14.0886 4.125 16.0916 4.95468 17.5685 6.43153C19.0453 7.90838 19.875 9.91142 19.875 12C19.875 14.0886 19.0453 16.0916 17.5685 17.5685C16.0916 19.0453 14.0886 19.875 12 19.875ZM12 21C14.3869 21 16.6761 20.0518 18.364 18.364C20.0518 16.6761 21 14.3869 21 12C21 9.61305 20.0518 7.32387 18.364 5.63604C16.6761 3.94821 14.3869 3 12 3C9.61305 3 7.32387 3.94821 5.63604 5.63604C3.94821 7.32387 3 9.61305 3 12C3 14.3869 3.94821 16.6761 5.63604 18.364C7.32387 20.0518 9.61305 21 12 21Z" fill="#f26949"/>
|
309
|
+
<path d="M10.8772 15.3751C10.8772 15.2274 10.9063 15.0811 10.9628 14.9446C11.0194 14.8081 11.1022 14.6841 11.2067 14.5796C11.3112 14.4752 11.4352 14.3923 11.5717 14.3357C11.7082 14.2792 11.8545 14.2501 12.0022 14.2501C12.1499 14.2501 12.2962 14.2792 12.4327 14.3357C12.5692 14.3923 12.6932 14.4752 12.7977 14.5796C12.9022 14.6841 12.985 14.8081 13.0416 14.9446C13.0981 15.0811 13.1272 15.2274 13.1272 15.3751C13.1272 15.6735 13.0087 15.9596 12.7977 16.1706C12.5867 16.3816 12.3006 16.5001 12.0022 16.5001C11.7038 16.5001 11.4177 16.3816 11.2067 16.1706C10.9957 15.9596 10.8772 15.6735 10.8772 15.3751ZM10.9874 8.61949C10.9725 8.47756 10.9875 8.33407 11.0315 8.19832C11.0756 8.06257 11.1477 7.9376 11.2432 7.83152C11.3387 7.72544 11.4554 7.64062 11.5857 7.58256C11.7161 7.52449 11.8572 7.49449 11.9999 7.49449C12.1427 7.49449 12.2838 7.52449 12.4142 7.58256C12.5445 7.64062 12.6612 7.72544 12.7567 7.83152C12.8522 7.9376 12.9243 8.06257 12.9683 8.19832C13.0124 8.33407 13.0274 8.47756 13.0124 8.61949L12.6187 12.5649C12.6055 12.7199 12.5346 12.8642 12.42 12.9695C12.3054 13.0747 12.1555 13.133 11.9999 13.133C11.8444 13.133 11.6945 13.0747 11.5799 12.9695C11.4653 12.8642 11.3944 12.7199 11.3812 12.5649L10.9874 8.61949Z" fill="#f26949"/>
|
310
|
+
</svg>
|
311
|
+
<span class="metadata-text">${metadata}</span>
|
312
|
+
</div>`;
|
313
|
+
|
314
|
+
listItem.innerHTML = `${linkHTML}${metadataHTML}`;
|
302
315
|
pagesNotScannedList.appendChild(listItem);
|
303
316
|
});
|
304
317
|
}
|
package/src/utils.ts
CHANGED
@@ -209,8 +209,8 @@ export const getWcagPassPercentage = (
|
|
209
209
|
totalWcagViolationsAAandAAA: number;
|
210
210
|
} => {
|
211
211
|
// These AAA rules should not be counted as WCAG Pass Percentage only contains A and AA
|
212
|
-
const wcagAAALinks = ['WCAG 1.4.6', 'WCAG 2.2.4', 'WCAG 2.4.9', 'WCAG 3.1.5', 'WCAG 3.2.5'];
|
213
|
-
const wcagAAA = ['wcag146', 'wcag224', 'wcag249', 'wcag315', 'wcag325'];
|
212
|
+
const wcagAAALinks = ['WCAG 1.4.6', 'WCAG 2.2.4', 'WCAG 2.4.9', 'WCAG 3.1.5', 'WCAG 3.2.5', 'WCAG 2.1.3'];
|
213
|
+
const wcagAAA = ['wcag146', 'wcag224', 'wcag249', 'wcag315', 'wcag325', 'wcag213'];
|
214
214
|
|
215
215
|
const wcagLinksAAandAAA = constants.wcagLinks;
|
216
216
|
|
@@ -783,7 +783,7 @@ export const retryFunction = async <T>(func: () => Promise<T>, maxAttempt: numbe
|
|
783
783
|
const result = await func();
|
784
784
|
return result;
|
785
785
|
} catch (error) {
|
786
|
-
|
786
|
+
// do nothing, just retry
|
787
787
|
}
|
788
788
|
}
|
789
789
|
throw new Error('Maximum number of attempts reached');
|