@govtechsg/oobee 0.10.86 → 0.10.88
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-push-ghcr.yml +49 -0
- package/.github/workflows/image.yml +2 -3
- package/DETAILS_OUTPUT_EXAMPLES.md +178 -0
- package/Dockerfile +6 -7
- package/dist/cli.js +18 -5
- package/dist/combine.js +3 -0
- package/dist/constants/cliFunctions.js +2 -2
- package/dist/constants/common.js +55 -13
- package/dist/crawlers/commonCrawlerFunc.js +523 -2
- package/dist/crawlers/crawlDomain.js +38 -13
- package/dist/crawlers/crawlIntelligentSitemap.js +62 -30
- package/dist/crawlers/crawlLocalFile.js +2 -2
- package/dist/crawlers/crawlSitemap.js +44 -5
- package/dist/crawlers/custom/extractAndGradeText.js +1 -1
- package/dist/crawlers/custom/getAxeConfiguration.js +26 -21
- package/dist/crawlers/custom/gradeReadability.js +1 -1
- package/dist/crawlers/custom/utils.js +81 -40
- package/dist/generateHtmlReport.js +18 -11
- package/dist/mergeAxeResults/itemReferences.js +60 -25
- package/dist/mergeAxeResults/sentryTelemetry.js +4 -1
- package/dist/mergeAxeResults.js +18 -9
- package/dist/npmIndex.js +16 -12
- package/dist/screenshotFunc/htmlScreenshotFunc.js +67 -0
- package/dist/static/ejs/partials/scripts/decodeUnzipParse.ejs +6 -3
- package/dist/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +45 -6
- package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +8 -5
- package/dist/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +4 -4
- package/dist/static/ejs/partials/scripts/ruleModal/utilities.ejs +2 -1
- package/dist/static/ejs/summary.ejs +18 -12
- package/dist/utils.js +4 -3
- package/examples/oobee-test-details-runner.js +214 -0
- package/examples/test-violations.html +42 -0
- package/fix-summary-html-oom-pr.md +62 -0
- package/package.json +5 -5
- package/src/cli.ts +19 -5
- package/src/combine.ts +3 -0
- package/src/constants/cliFunctions.ts +2 -2
- package/src/constants/common.ts +65 -12
- package/src/crawlers/commonCrawlerFunc.ts +625 -2
- package/src/crawlers/crawlDomain.ts +39 -13
- package/src/crawlers/crawlIntelligentSitemap.ts +63 -30
- package/src/crawlers/crawlLocalFile.ts +4 -1
- package/src/crawlers/crawlSitemap.ts +50 -3
- package/src/crawlers/custom/extractAndGradeText.ts +1 -1
- package/src/crawlers/custom/getAxeConfiguration.ts +25 -23
- package/src/crawlers/custom/gradeReadability.ts +1 -1
- package/src/crawlers/custom/utils.ts +99 -43
- package/src/generateHtmlReport.ts +21 -11
- package/src/mergeAxeResults/itemReferences.ts +70 -26
- package/src/mergeAxeResults/sentryTelemetry.ts +4 -1
- package/src/mergeAxeResults.ts +21 -11
- package/src/npmIndex.ts +17 -12
- package/src/screenshotFunc/htmlScreenshotFunc.ts +81 -1
- package/src/static/ejs/partials/scripts/decodeUnzipParse.ejs +6 -3
- package/src/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +45 -6
- package/src/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +8 -5
- package/src/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +4 -4
- package/src/static/ejs/partials/scripts/ruleModal/utilities.ejs +2 -1
- package/src/static/ejs/summary.ejs +18 -12
- package/src/utils.ts +4 -3
- package/testStaticJSScanner.html +1 -1
|
@@ -28,8 +28,11 @@ async function decodeUnzipParse(input) {
|
|
|
28
28
|
offset += arr.length;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
// Step 2: Decompress with pako (GZIP)
|
|
32
|
-
|
|
31
|
+
// Step 2: Decompress with pako (GZIP) to bytes first to avoid large-string
|
|
32
|
+
// construction inside pako for very large payloads.
|
|
33
|
+
const decompressedBytes = pako.ungzip(merged);
|
|
34
|
+
|
|
35
|
+
const decompressed = new TextDecoder().decode(decompressedBytes);
|
|
33
36
|
|
|
34
37
|
// Step 3: Parse JSON
|
|
35
38
|
return JSON.parse(decompressed);
|
|
@@ -37,4 +40,4 @@ async function decodeUnzipParse(input) {
|
|
|
37
40
|
throw new Error(`Failed to decode/unzip/parse: ${err.message}`);
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
|
-
</script>
|
|
43
|
+
</script>
|
|
@@ -1,10 +1,44 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Rebuilds the item list for a page from pre-computed htmlGroups when the light report omits page.items.
|
|
4
|
+
*/
|
|
5
|
+
function buildItemsFromHtmlGroupsForPage(page, ruleInCategory) {
|
|
6
|
+
const htmlGroups = ruleInCategory.htmlGroups || {};
|
|
7
|
+
const resolvedItems = [];
|
|
8
|
+
|
|
9
|
+
Object.values(htmlGroups).forEach(groupData => {
|
|
10
|
+
if (!Array.isArray(groupData.pageUrls) || !groupData.pageUrls.includes(page.url)) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
resolvedItems.push({
|
|
15
|
+
html: groupData.html,
|
|
16
|
+
xpath: groupData.xpath,
|
|
17
|
+
message: groupData.message,
|
|
18
|
+
screenshotPath: groupData.screenshotPath,
|
|
19
|
+
displayNeedsReview: groupData.displayNeedsReview,
|
|
20
|
+
pageUrl: page.url,
|
|
21
|
+
pageTitle: page.pageTitle || page.metadata
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return resolvedItems;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The embedded report payload now omits page.items and rebuilds occurrences from
|
|
30
|
+
* htmlGroups + page metadata. Keep the older page.items resolution logic below
|
|
31
|
+
* commented for an easy rollback if we need to restore mixed payload support.
|
|
4
32
|
*/
|
|
5
33
|
function resolveItemReferencesForPage(page, ruleInCategory) {
|
|
34
|
+
return buildItemsFromHtmlGroupsForPage(page, ruleInCategory);
|
|
35
|
+
|
|
36
|
+
/*
|
|
6
37
|
const items = page.items || [];
|
|
7
|
-
|
|
38
|
+
|
|
39
|
+
if (items.length === 0) {
|
|
40
|
+
return buildItemsFromHtmlGroupsForPage(page, ruleInCategory);
|
|
41
|
+
}
|
|
8
42
|
|
|
9
43
|
const isReference = typeof items[0] === 'string';
|
|
10
44
|
|
|
@@ -27,6 +61,7 @@
|
|
|
27
61
|
pageTitle: page.pageTitle || page.metadata
|
|
28
62
|
};
|
|
29
63
|
}
|
|
64
|
+
|
|
30
65
|
// Fallback: parse composite key
|
|
31
66
|
const nullByteIndex = compositeKey.indexOf('\x00');
|
|
32
67
|
const html = nullByteIndex !== -1 ? compositeKey.slice(0, nullByteIndex) : compositeKey;
|
|
@@ -40,6 +75,7 @@
|
|
|
40
75
|
pageTitle: page.pageTitle || page.metadata
|
|
41
76
|
};
|
|
42
77
|
});
|
|
78
|
+
*/
|
|
43
79
|
}
|
|
44
80
|
|
|
45
81
|
function buildItemCardsWithPagination(accordionId, category, ruleInCategory, page, index) {
|
|
@@ -203,14 +239,17 @@
|
|
|
203
239
|
${item.xpath ? createXpathSection(item.xpath) : ''}
|
|
204
240
|
${createElementSection(item)}
|
|
205
241
|
${
|
|
206
|
-
|
|
207
|
-
|
|
242
|
+
(() => {
|
|
243
|
+
const showDetails = item.displayNeedsReview || ['color-contrast', 'color-contrast-enhanced', 'oobee-grading-text-contents', 'target-size', 'valid-lang'].includes(aiConfig.ruleInCategory.rule);
|
|
244
|
+
return showDetails && item.message
|
|
245
|
+
? `<div class="d-flex justify-content-between g-one">
|
|
208
246
|
<div class="fw-semibold page-item-card-section-title">Details</div>
|
|
209
247
|
<div class="page-item-card-section-content">
|
|
210
|
-
${generateItemMessageElement(item.displayNeedsReview, item.message)}
|
|
248
|
+
${generateItemMessageElement(item.displayNeedsReview || true, item.message)}
|
|
211
249
|
</div>
|
|
212
250
|
</div>`
|
|
213
|
-
|
|
251
|
+
: '';
|
|
252
|
+
})()
|
|
214
253
|
}
|
|
215
254
|
${isPurpleAiRule ? createAiSuggestionSection(item, oobeeAiQueryLabel, aiConfig) : ''}
|
|
216
255
|
${showGenAiUI ? createGenAiSuggestFixSection(item, aiConfig) : ''}
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
// Use pre-computed htmlGroups for count if available, otherwise use pages
|
|
87
87
|
const count = isHtmlGrouping && selectedCategory.htmlGroups
|
|
88
88
|
? Object.keys(selectedCategory.htmlGroups).length
|
|
89
|
-
: selectedCategory.pagesAffected.length;
|
|
89
|
+
: (selectedCategory.pagesAffectedCount || selectedCategory.pagesAffected.length);
|
|
90
90
|
if (isHtmlGrouping) {
|
|
91
91
|
dropdownTitle.innerText = `HTML elements affected by this issue (${count})`;
|
|
92
92
|
} else {
|
|
@@ -393,14 +393,17 @@
|
|
|
393
393
|
</div>
|
|
394
394
|
|
|
395
395
|
${
|
|
396
|
-
|
|
397
|
-
|
|
396
|
+
(() => {
|
|
397
|
+
const showDetails = item.displayNeedsReview || ['color-contrast', 'color-contrast-enhanced', 'oobee-grading-text-contents', 'target-size', 'valid-lang'].includes(aiConfig.ruleInCategory.rule);
|
|
398
|
+
return showDetails && item.message
|
|
399
|
+
? `<div class="d-flex justify-content-between g-one modal-view">
|
|
398
400
|
<div class="fw-semibold page-item-card-section-title">Details</div>
|
|
399
401
|
<div class="page-item-card-section-content">
|
|
400
|
-
${generateItemMessageElement(item.displayNeedsReview, item.message)}
|
|
402
|
+
${generateItemMessageElement(item.displayNeedsReview || true, item.message)}
|
|
401
403
|
</div>
|
|
402
404
|
</div>`
|
|
403
|
-
|
|
405
|
+
: '';
|
|
406
|
+
})()
|
|
404
407
|
}
|
|
405
408
|
${isPurpleAiRule ? createAiSuggestionSection(item, oobeeAiQueryLabel, aiConfig) : ''}
|
|
406
409
|
</div>
|
|
@@ -270,8 +270,8 @@ include('./pageAccordionBuilder') %> <%- include('./constants') %>
|
|
|
270
270
|
if (!Array.isArray(rule.pagesAffected)) return;
|
|
271
271
|
|
|
272
272
|
rule.pagesAffected.sort((a, b) => {
|
|
273
|
-
const lenA = Array.isArray(a.items) ? a.items.length : 0;
|
|
274
|
-
const lenB = Array.isArray(b.items) ? b.items.length : 0;
|
|
273
|
+
const lenA = Array.isArray(a.items) ? a.items.length : a.itemsCount || 0;
|
|
274
|
+
const lenB = Array.isArray(b.items) ? b.items.length : b.itemsCount || 0;
|
|
275
275
|
return lenB - lenA; // DESC
|
|
276
276
|
});
|
|
277
277
|
});
|
|
@@ -295,10 +295,10 @@ include('./pageAccordionBuilder') %> <%- include('./constants') %>
|
|
|
295
295
|
dropdownToggle.innerText = `${ruleInCategory.totalItems} Total occ.`;
|
|
296
296
|
dropdownToggle.setAttribute('aria-label', occurrencesText);
|
|
297
297
|
document.getElementById('expandedRuleDropdownTitle').innerText =
|
|
298
|
-
`Pages affected by this issue (${ruleInCategory.pagesAffected.length})`;
|
|
298
|
+
`Pages affected by this issue (${(ruleInCategory.pagesAffectedCount || ruleInCategory.pagesAffected.length)})`;
|
|
299
299
|
buildExpandedRuleCategoryContent(category, ruleInCategory);
|
|
300
300
|
document.getElementById('expandedRulePageContent').innerText =
|
|
301
|
-
`Total ${ruleInCategory.pagesAffected.length} affected pages`;
|
|
301
|
+
`Total ${(ruleInCategory.pagesAffectedCount || ruleInCategory.pagesAffected.length)} affected pages`;
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
304
|
});
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
if (htmlEscapedMessageArray.length === 1) {
|
|
54
54
|
return `<p class="mb-0">${htmlEscapedMessageArray[0]}</p>`;
|
|
55
55
|
} else {
|
|
56
|
-
|
|
56
|
+
const [first, ...rest] = htmlEscapedMessageArray;
|
|
57
|
+
return `<p class="mb-0">${first}</p><ul>${rest.map(m => `<li>${m}</li>`).join('')}</ul>`;
|
|
57
58
|
}
|
|
58
59
|
} else {
|
|
59
60
|
let i = 0;
|
|
@@ -21,18 +21,24 @@
|
|
|
21
21
|
%>
|
|
22
22
|
<script>
|
|
23
23
|
const scanItems = <%- JSON.stringify(
|
|
24
|
-
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
['mustFix','goodToFix','needsReview','passed'].reduce((acc, cat) => {
|
|
25
|
+
if (items[cat]) {
|
|
26
|
+
acc[cat] = {
|
|
27
|
+
description: items[cat].description,
|
|
28
|
+
totalItems: items[cat].totalItems,
|
|
29
|
+
totalRuleIssues: items[cat].totalRuleIssues,
|
|
30
|
+
rules: (items[cat].rules || []).map(rule => ({
|
|
31
|
+
rule: rule.rule,
|
|
32
|
+
description: rule.description,
|
|
33
|
+
helpUrl: rule.helpUrl,
|
|
34
|
+
conformance: rule.conformance,
|
|
35
|
+
totalItems: rule.totalItems,
|
|
36
|
+
pagesAffected: { length: (rule.pagesAffected || []).length },
|
|
37
|
+
})),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return acc;
|
|
41
|
+
}, {})
|
|
36
42
|
).replace(/<\//g, '<\\/') %>
|
|
37
43
|
</script>
|
|
38
44
|
<%- include('partials/scripts/summaryTable') %>
|
package/src/utils.ts
CHANGED
|
@@ -5,6 +5,7 @@ import fs from 'fs-extra';
|
|
|
5
5
|
import axe, { Rule } from 'axe-core';
|
|
6
6
|
import { v4 as uuidv4 } from 'uuid';
|
|
7
7
|
import { getDomain } from 'tldts';
|
|
8
|
+
import { normalizeUrl } from '@apify/utilities';
|
|
8
9
|
import constants, {
|
|
9
10
|
BrowserTypes,
|
|
10
11
|
destinationPath,
|
|
@@ -1078,13 +1079,13 @@ export const randomThreeDigitNumberString = () => {
|
|
|
1078
1079
|
return String(threeDigitNumber);
|
|
1079
1080
|
};
|
|
1080
1081
|
|
|
1082
|
+
export const normUrl = (u: string): string => (u ? normalizeUrl(u) || u : '');
|
|
1083
|
+
|
|
1081
1084
|
export const isFollowStrategy = (link1: string, link2: string, rule: string): boolean => {
|
|
1085
|
+
if (rule === 'all') return true;
|
|
1082
1086
|
try {
|
|
1083
1087
|
const parsedLink1 = new URL(link1);
|
|
1084
1088
|
const parsedLink2 = new URL(link2);
|
|
1085
|
-
if (rule === 'all') {
|
|
1086
|
-
return true;
|
|
1087
|
-
}
|
|
1088
1089
|
if (rule === 'same-origin') {
|
|
1089
1090
|
return parsedLink1.origin === parsedLink2.origin;
|
|
1090
1091
|
}
|
package/testStaticJSScanner.html
CHANGED
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
<li>
|
|
187
187
|
<strong>Add the script</strong> — include the scanner before your closing
|
|
188
188
|
<code></body></code> tag:
|
|
189
|
-
<pre><script src="https://cdn.jsdelivr.net/gh/GovTechSG/oobee@
|
|
189
|
+
<pre><script src="https://cdn.jsdelivr.net/gh/GovTechSG/oobee@0.10.86/oobee-client-scanner.js"></script></pre>
|
|
190
190
|
|
|
191
191
|
<br>This points to the <code>v0.10.86</code> release. Update the version tag as needed for newer releases.
|
|
192
192
|
</li>
|