@govtechsg/oobee 0.10.33 → 0.10.36
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/.vscode/settings.json +1 -1
- package/DETAILS.md +58 -42
- package/Dockerfile +1 -1
- package/__mocks__/mock-report.html +1 -1
- package/package.json +1 -1
- package/src/constants/constants.ts +3 -3
- package/src/constants/itemTypeDescription.ts +3 -3
- package/src/crawlers/commonCrawlerFunc.ts +7 -1
- package/src/crawlers/crawlDomain.ts +36 -6
- package/src/crawlers/crawlSitemap.ts +12 -2
- package/src/crawlers/custom/flagUnlabelledClickableElements.ts +0 -2
- package/src/crawlers/pdfScanFunc.ts +5 -1
- package/src/mergeAxeResults.ts +38 -5
- package/src/static/ejs/partials/components/summaryScanResults.ejs +1 -1
- package/src/static/ejs/partials/components/wcagCompliance.ejs +3 -2
- package/src/static/ejs/partials/footer.ejs +13 -7
- package/src/static/ejs/partials/scripts/utils.ejs +1 -1
- package/src/static/ejs/partials/summaryMain.ejs +7 -6
- package/src/static/ejs/report.ejs +3 -3
- package/src/utils.ts +34 -11
package/.vscode/settings.json
CHANGED
package/DETAILS.md
CHANGED
@@ -14,37 +14,53 @@ Details of each issue and severity rating provided by the current scan engine.
|
|
14
14
|
|
15
15
|
## Conformance Covered
|
16
16
|
|
17
|
+
#### Definitions of Conformance Level, Must Fix, Good To Fix, Manual Review Required Required
|
18
|
+
|
19
|
+
In Oobee, issues are grouped into one of three categories:
|
20
|
+
- **Must Fix** issues includes WCAG A & AA success criteria (excluding those requiring review).
|
21
|
+
- **Good To Fix** issues includes WCAG Level AAA success criteria issues and all best practice rules that do not necessarily conform to WCAG success criterion but are industry accepted practices that improve the user experience.
|
22
|
+
- **Manual Review Required Required** occurrences could potentially be false positive, requiring human validation for accuracy.
|
23
|
+
|
17
24
|
Note: Level AAA are disabled by default. Please specify `enable-wcag-aaa` in ruleset flag to enable AAA rules.
|
18
25
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
|
28
|
-
|
29
|
-
| WCAG 1.
|
30
|
-
| WCAG 1.
|
31
|
-
| WCAG
|
32
|
-
| WCAG
|
33
|
-
| WCAG
|
34
|
-
| WCAG
|
35
|
-
| WCAG
|
36
|
-
| WCAG
|
37
|
-
| WCAG
|
38
|
-
| WCAG
|
39
|
-
| WCAG 2.
|
40
|
-
| WCAG
|
41
|
-
| WCAG
|
42
|
-
| WCAG
|
43
|
-
| WCAG
|
44
|
-
| WCAG
|
45
|
-
| WCAG 4.
|
46
|
-
|
47
|
-
|
26
|
+
#### WCAG Level of Conformance
|
27
|
+
|
28
|
+
- **Level A**: The minimum level of accessibility, addressing the most critical issues.
|
29
|
+
- **Level AA**: Builds on Level A, adding more accessibility features. This is the standard most organizations aim for, as it provides reasonable accessibility for most users.
|
30
|
+
- **Level AAA**: The highest level of accessibility, encompassing all Level A and AA criteria plus additional stringent requirements. While ideal, it's often not practical or possible for all content. Examples include providing sign language interpretation for all pre-recorded videos.
|
31
|
+
|
32
|
+
#### Breakdown of WCAG Clauses and Best Practice
|
33
|
+
|
34
|
+
| Conformance | Level | Must Fix | Good to Fix | Exclusive to Manual Review Required |
|
35
|
+
|-------------|-------|----------|-------------|--------------|
|
36
|
+
| WCAG 1.1.1 | A | Yes | | |
|
37
|
+
| WCAG 1.2.2 | A | Yes | | |
|
38
|
+
| WCAG 1.3.1 | A | Yes | | |
|
39
|
+
| WCAG 1.3.5 | AA | Yes | | |
|
40
|
+
| WCAG 1.4.1 | A | Yes | | |
|
41
|
+
| WCAG 1.4.2 | A | Yes | | |
|
42
|
+
| WCAG 1.4.3 | AA | Yes | | |
|
43
|
+
| WCAG 1.4.4 | AA | Yes | | |
|
44
|
+
| WCAG 1.4.6 | AAA | Yes | | |
|
45
|
+
| WCAG 1.4.12 | AA | Yes | | |
|
46
|
+
| WCAG 2.1.1 | A | Yes | | |
|
47
|
+
| WCAG 2.2.1 | A | Yes | | |
|
48
|
+
| WCAG 2.2.2 | A | Yes | | |
|
49
|
+
| WCAG 2.2.4 | AAA | | Yes | |
|
50
|
+
| WCAG 2.4.1 | A | Yes | | |
|
51
|
+
| WCAG 2.4.2 | A | Yes | | |
|
52
|
+
| WCAG 2.4.4 | A | Yes | | |
|
53
|
+
| WCAG 2.4.9 | AAA | | Yes | |
|
54
|
+
| WCAG 2.5.8 | AA | Yes | | |
|
55
|
+
| WCAG 3.1.1 | A | Yes | | |
|
56
|
+
| WCAG 3.1.2 | AA | Yes | | |
|
57
|
+
| WCAG 3.1.5 | AAA | | | Yes |
|
58
|
+
| WCAG 3.2.5 | AAA | | Yes | |
|
59
|
+
| WCAG 3.3.2 | A | Yes | | |
|
60
|
+
| WCAG 4.1.2 | A | Yes | | |
|
61
|
+
| Best Practice| | | Yes | |
|
62
|
+
|
63
|
+
### Summary of WCAG Clauses Supported
|
48
64
|
| Level | Count |
|
49
65
|
|-------|-------|
|
50
66
|
| A | 15 |
|
@@ -61,7 +77,7 @@ Note: Level AAA are disabled by default. Please specify `enable-wcag-aaa` in ru
|
|
61
77
|
| aria-braille-equivalent | Ensure aria-braillelabel and aria-brailleroledescription have a non-braille equivalent | Must Fix | WCAG 4.1.2 |
|
62
78
|
| aria-command-name | Ensures every ARIA button, link and menuitem has an accessible name | Must Fix | WCAG 4.1.2 |
|
63
79
|
| aria-conditional-attr | Ensures ARIA attributes are used as described in the specification of the element's role | Must Fix | WCAG 4.1.2 |
|
64
|
-
| aria-deprecated-role | Ensures elements do not use deprecated roles |
|
80
|
+
| aria-deprecated-role | Ensures elements do not use deprecated roles | Must Fix | WCAG 4.1.2 |
|
65
81
|
| aria-hidden-body | Ensures aria-hidden="true" is not present on the document body. | Must Fix | WCAG 4.1.2 |
|
66
82
|
| aria-hidden-focus | Ensures aria-hidden elements are not focusable nor contain focusable elements | Must Fix | WCAG 4.1.2 |
|
67
83
|
| aria-input-field-name | Ensures every ARIA input field has an accessible name | Must Fix | WCAG 4.1.2 |
|
@@ -89,7 +105,7 @@ Note: Level AAA are disabled by default. Please specify `enable-wcag-aaa` in ru
|
|
89
105
|
| frame-title | Ensures `<iframe>` and `<frame>` elements have an accessible name | Must Fix | WCAG 4.1.2 |
|
90
106
|
| html-has-lang | Ensures every HTML document has a lang attribute | Must Fix | WCAG 3.1.1 |
|
91
107
|
| html-lang-valid | Ensures the lang attribute of the `<html>` element has a valid value | Must Fix | WCAG 3.1.1 |
|
92
|
-
| html-xml-lang-mismatch | Ensure that HTML elements with both valid lang and xml:lang attributes agree on the base language of the page |
|
108
|
+
| html-xml-lang-mismatch | Ensure that HTML elements with both valid lang and xml:lang attributes agree on the base language of the page | Must Fix | WCAG 3.1.1 |
|
93
109
|
| image-alt | Ensures `<img>` elements have alternate text or a role of none or presentation | Must Fix | WCAG 1.1.1 |
|
94
110
|
| input-button-name | Ensures input buttons have discernible text | Must Fix | WCAG 4.1.2 |
|
95
111
|
| input-image-alt | Ensures `<input type="image">` elements have alternate text | Must Fix | WCAG 1.1.1, WCAG 4.1.2 |
|
@@ -101,12 +117,12 @@ Note: Level AAA are disabled by default. Please specify `enable-wcag-aaa` in ru
|
|
101
117
|
| marquee | Ensures `<marquee>` elements are not used | Must Fix | WCAG 2.2.2 |
|
102
118
|
| meta-refresh | Ensures `<meta http-equiv="refresh">` is not used for delayed refresh | Must Fix | WCAG 2.2.1 |
|
103
119
|
| nested-interactive | Ensures interactive controls are not nested as they are not always announced by screen readers or can cause focus problems for assistive technologies | Must Fix | WCAG 4.1.2 |
|
104
|
-
| no-autoplay-audio | Ensures `<video>` or `<audio>` elements do not autoplay audio for more than 3 seconds without a control mechanism to stop or mute the audio |
|
120
|
+
| no-autoplay-audio | Ensures `<video>` or `<audio>` elements do not autoplay audio for more than 3 seconds without a control mechanism to stop or mute the audio | Must Fix | WCAG 1.4.2 |
|
105
121
|
| object-alt | Ensures `<object>` elements have alternate text | Must Fix | WCAG 1.1.1 |
|
106
122
|
| role-img-alt | Ensures [role="img"] elements have alternate text | Must Fix | WCAG 1.1.1 |
|
107
123
|
| scrollable-region-focusable | Ensure elements that have scrollable content are accessible by keyboard | Must Fix | WCAG 2.1.1 |
|
108
124
|
| select-name | Ensures select element has an accessible name | Must Fix | WCAG 4.1.2 |
|
109
|
-
| server-side-image-map | Ensures that server-side image maps are not used |
|
125
|
+
| server-side-image-map | Ensures that server-side image maps are not used | Must Fix | WCAG 2.1.1 |
|
110
126
|
| svg-img-alt | Ensures `<svg>` elements with an img, graphics-document or graphics-symbol role have an accessible text | Must Fix | WCAG 1.1.1 |
|
111
127
|
| td-headers-attr | Ensure that each cell in a table that uses the headers attribute refers only to other cells in that table | Must Fix | WCAG 1.3.1 |
|
112
128
|
| th-has-data-cells | Ensure that `<th>` elements and elements with role=columnheader/rowheader have data cells they describe | Must Fix | WCAG 1.3.1 |
|
@@ -139,26 +155,26 @@ Note: Level AAA are disabled by default. Please specify `enable-wcag-aaa` in ru
|
|
139
155
|
|
140
156
|
| Issue ID | Issue Description | Severity | Conformance |
|
141
157
|
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------- | ----------- | ---------------------- |
|
142
|
-
| color-contrast-enhanced | Ensure the contrast between foreground and background colors meets WCAG 2 AAA enhanced contrast ratio thresholds |
|
158
|
+
| color-contrast-enhanced | Ensure the contrast between foreground and background colors meets WCAG 2 AAA enhanced contrast ratio thresholds | Good to Fix | WCAG 1.4.6 |
|
143
159
|
| identical-links-same-purpose | Ensure that links with the same accessible name serve a similar purpose | Good to Fix | WCAG 2.4.9 |
|
144
160
|
| meta-refresh-no-exceptions | Ensure <meta http-equiv="refresh"> is not used for delayed refresh | Good to Fix | WCAG 2.2.4, WCAG 3.2.5 |
|
145
|
-
| oobee-grading-text-contents | Text content should be clear and plain to ensure that it is easily understood. |
|
161
|
+
| oobee-grading-text-contents | Text content should be clear and plain to ensure that it is easily understood. | Manual Review Required Required | WCAG 3.1.5 |
|
146
162
|
|
147
163
|
## Best Practice
|
148
164
|
|
149
165
|
| Issue ID | Issue Description | Severity |
|
150
166
|
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
151
|
-
| accesskeys | Ensures every accesskey attribute value is unique |
|
167
|
+
| accesskeys | Ensures every accesskey attribute value is unique | Good to Fix |
|
152
168
|
| aria-allowed-role | Ensures role attribute has an appropriate value for the element | Good to Fix |
|
153
|
-
| aria-dialog-name | Ensures every ARIA dialog and alertdialog node has an accessible name |
|
154
|
-
| aria-text | Ensures role="text" is used on elements with no focusable descendants |
|
155
|
-
| aria-treeitem-name | Ensures every ARIA treeitem node has an accessible name |
|
169
|
+
| aria-dialog-name | Ensures every ARIA dialog and alertdialog node has an accessible name | Good to Fix |
|
170
|
+
| aria-text | Ensures role="text" is used on elements with no focusable descendants | Good to Fix |
|
171
|
+
| aria-treeitem-name | Ensures every ARIA treeitem node has an accessible name | Good to Fix |
|
156
172
|
| empty-heading | Ensures headings have discernible text | Good to Fix |
|
157
173
|
| empty-table-header | Ensures table headers have discernible text | Good to Fix |
|
158
|
-
| frame-tested | Ensures `<iframe>` and `<frame>` elements contain the axe-core script |
|
174
|
+
| frame-tested | Ensures `<iframe>` and `<frame>` elements contain the axe-core script | Good to Fix |
|
159
175
|
| heading-order | Ensures the order of headings is semantically correct | Good to Fix |
|
160
176
|
| image-redundant-alt | Ensure image alternative is not repeated as text | Good to Fix |
|
161
|
-
| label-title-only | Ensures that every form element has a visible label and is not solely labeled using hidden labels, or the title or aria-describedby attributes |
|
177
|
+
| label-title-only | Ensures that every form element has a visible label and is not solely labeled using hidden labels, or the title or aria-describedby attributes | Good to Fix |
|
162
178
|
| landmark-banner-is-top-level | Ensures the banner landmark is at top level | Good to Fix |
|
163
179
|
| landmark-complementary-is-top-level | Ensures the complementary landmark or aside is at top level | Good to Fix |
|
164
180
|
| landmark-contentinfo-is-top-level | Ensures the contentinfo landmark is at top level | Good to Fix |
|
@@ -174,5 +190,5 @@ Note: Level AAA are disabled by default. Please specify `enable-wcag-aaa` in ru
|
|
174
190
|
| region | Ensures all page content is contained by landmarks | Good to Fix |
|
175
191
|
| scope-attr-valid | Ensures the scope attribute is used correctly on tables | Good to Fix |
|
176
192
|
| skip-link | Ensure all skip links have a focusable target | Good to Fix |
|
177
|
-
| tabindex | Ensures tabindex attribute values are not greater than 0 |
|
193
|
+
| tabindex | Ensures tabindex attribute values are not greater than 0 | Good to Fix |
|
178
194
|
| table-duplicate-name | Ensure the `<caption>` element does not contain the same text as the summary attribute | Good to Fix |
|
package/Dockerfile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Use Microsoft Playwright image as base image
|
2
2
|
# Node version is v22
|
3
|
-
FROM mcr.microsoft.com/playwright:v1.50.
|
3
|
+
FROM mcr.microsoft.com/playwright:v1.50.1-noble
|
4
4
|
|
5
5
|
# Installation of packages for oobee and runner
|
6
6
|
RUN apt-get update && apt-get install -y zip git
|
@@ -1493,7 +1493,7 @@
|
|
1493
1493
|
<div class="container-fluid">
|
1494
1494
|
<div class="row text-center pt-2 pb-2">
|
1495
1495
|
<div class="col-sm-6 text-sm-left">
|
1496
|
-
<a href="mailto:
|
1496
|
+
<a href="mailto:oobee@wogaa.gov.sg">Help us improve</a>
|
1497
1497
|
<hr class="d-sm-none" />
|
1498
1498
|
</div>
|
1499
1499
|
<div class="col-sm-6 text-sm-right">
|
package/package.json
CHANGED
@@ -178,14 +178,14 @@ export const axeScript = path.join(dirname, '../../node_modules/axe-core/axe.min
|
|
178
178
|
export class UrlsCrawled {
|
179
179
|
toScan: string[] = [];
|
180
180
|
scanned: { url: string; actualUrl: string; pageTitle: string }[] = [];
|
181
|
-
invalid: string[] = [];
|
181
|
+
invalid: { url: string; actualUrl: string; pageTitle: string }[] = [];
|
182
182
|
scannedRedirects: { fromUrl: string; toUrl: string }[] = [];
|
183
183
|
notScannedRedirects: { fromUrl: string; toUrl: string }[] = [];
|
184
184
|
outOfDomain: string[] = [];
|
185
|
-
blacklisted: string[] = [];
|
185
|
+
blacklisted: { url: string; actualUrl: string; pageTitle: string }[] = [];
|
186
186
|
error: { url: string }[] = [];
|
187
187
|
exceededRequests: string[] = [];
|
188
|
-
forbidden: string[] = [];
|
188
|
+
forbidden: { url: string; actualUrl: string; pageTitle: string }[] = [];
|
189
189
|
userExcluded: { url: string; actualUrl: string; pageTitle: string }[] = [];
|
190
190
|
everything: string[] = [];
|
191
191
|
|
@@ -1,10 +1,10 @@
|
|
1
1
|
const itemTypeDescription = {
|
2
2
|
mustFix:
|
3
|
-
'
|
3
|
+
'Must Fix issues includes WCAG A & AA success criteria (excluding those requiring review).',
|
4
4
|
goodToFix:
|
5
|
-
'
|
5
|
+
'Good to Fix issues includes WCAG Level AAA success criteria issues and all best practice rules that do not necessarily conform to WCAG success criterion but are industry accepted practices that improve the user experience.',
|
6
6
|
needsReview:
|
7
|
-
'
|
7
|
+
'Manual Review Required occurrences could potentially be false positive, requiring human validation for accuracy.',
|
8
8
|
passed: 'Occurrences that passed the automated checks.',
|
9
9
|
};
|
10
10
|
|
@@ -138,9 +138,15 @@ export const filterAxeResults = (
|
|
138
138
|
|
139
139
|
nodes.forEach(node => {
|
140
140
|
const { impact } = node;
|
141
|
+
const hasWcag2a = conformance.includes('wcag2a');
|
142
|
+
const hasWcag2aa = conformance.includes('wcag2aa');
|
143
|
+
const hasWcag2aaa = conformance.includes('wcag2aaa');
|
144
|
+
|
141
145
|
if (displayNeedsReview) {
|
142
146
|
addTo(needsReview, node);
|
143
|
-
} else if (
|
147
|
+
} else if (hasWcag2aaa) {
|
148
|
+
addTo(goodToFix, node);
|
149
|
+
} else if (hasWcag2a || hasWcag2aa) {
|
144
150
|
addTo(mustFix, node);
|
145
151
|
} else {
|
146
152
|
addTo(goodToFix, node);
|
@@ -638,7 +638,12 @@ const crawlDomain = async ({
|
|
638
638
|
numScanned: urlsCrawled.scanned.length,
|
639
639
|
urlScanned: request.url,
|
640
640
|
});
|
641
|
-
urlsCrawled.blacklisted.push(
|
641
|
+
urlsCrawled.blacklisted.push({
|
642
|
+
url: request.url,
|
643
|
+
pageTitle: request.url,
|
644
|
+
actualUrl: actualUrl, // i.e. actualUrl
|
645
|
+
});
|
646
|
+
|
642
647
|
return;
|
643
648
|
}
|
644
649
|
const { pdfFileName, url } = handlePdfDownload(
|
@@ -662,7 +667,12 @@ const crawlDomain = async ({
|
|
662
667
|
numScanned: urlsCrawled.scanned.length,
|
663
668
|
urlScanned: request.url,
|
664
669
|
});
|
665
|
-
urlsCrawled.blacklisted.push(
|
670
|
+
urlsCrawled.blacklisted.push({
|
671
|
+
url: request.url,
|
672
|
+
pageTitle: request.url,
|
673
|
+
actualUrl: actualUrl, // i.e. actualUrl
|
674
|
+
});
|
675
|
+
|
666
676
|
return;
|
667
677
|
}
|
668
678
|
|
@@ -671,7 +681,12 @@ const crawlDomain = async ({
|
|
671
681
|
numScanned: urlsCrawled.scanned.length,
|
672
682
|
urlScanned: request.url,
|
673
683
|
});
|
674
|
-
urlsCrawled.blacklisted.push(
|
684
|
+
urlsCrawled.blacklisted.push({
|
685
|
+
url: request.url,
|
686
|
+
pageTitle: request.url,
|
687
|
+
actualUrl: actualUrl, // i.e. actualUrl
|
688
|
+
});
|
689
|
+
|
675
690
|
return;
|
676
691
|
}
|
677
692
|
|
@@ -691,7 +706,12 @@ const crawlDomain = async ({
|
|
691
706
|
numScanned: urlsCrawled.scanned.length,
|
692
707
|
urlScanned: request.url,
|
693
708
|
});
|
694
|
-
urlsCrawled.forbidden.push(
|
709
|
+
urlsCrawled.forbidden.push({
|
710
|
+
url: request.url,
|
711
|
+
pageTitle: request.url,
|
712
|
+
actualUrl: actualUrl, // i.e. actualUrl
|
713
|
+
});
|
714
|
+
|
695
715
|
return;
|
696
716
|
}
|
697
717
|
|
@@ -700,7 +720,12 @@ const crawlDomain = async ({
|
|
700
720
|
numScanned: urlsCrawled.scanned.length,
|
701
721
|
urlScanned: request.url,
|
702
722
|
});
|
703
|
-
urlsCrawled.invalid.push(
|
723
|
+
urlsCrawled.invalid.push({
|
724
|
+
url: request.url,
|
725
|
+
pageTitle: request.url,
|
726
|
+
actualUrl: actualUrl, // i.e. actualUrl
|
727
|
+
});
|
728
|
+
|
704
729
|
return;
|
705
730
|
}
|
706
731
|
|
@@ -779,7 +804,12 @@ const crawlDomain = async ({
|
|
779
804
|
numScanned: urlsCrawled.scanned.length,
|
780
805
|
urlScanned: request.url,
|
781
806
|
});
|
782
|
-
urlsCrawled.blacklisted.push(
|
807
|
+
urlsCrawled.blacklisted.push({
|
808
|
+
url: request.url,
|
809
|
+
pageTitle: request.url,
|
810
|
+
actualUrl: actualUrl, // i.e. actualUrl
|
811
|
+
});
|
812
|
+
|
783
813
|
}
|
784
814
|
|
785
815
|
if (followRobots) await getUrlsFromRobotsTxt(request.url, browser);
|
@@ -250,7 +250,12 @@ const crawlSitemap = async (
|
|
250
250
|
numScanned: urlsCrawled.scanned.length,
|
251
251
|
urlScanned: request.url,
|
252
252
|
});
|
253
|
-
urlsCrawled.blacklisted.push(
|
253
|
+
urlsCrawled.blacklisted.push({
|
254
|
+
url: request.url,
|
255
|
+
pageTitle: request.url,
|
256
|
+
actualUrl: actualUrl, // i.e. actualUrl
|
257
|
+
});
|
258
|
+
|
254
259
|
return;
|
255
260
|
}
|
256
261
|
// pushes download promise into pdfDownloads
|
@@ -297,7 +302,12 @@ const crawlSitemap = async (
|
|
297
302
|
numScanned: urlsCrawled.scanned.length,
|
298
303
|
urlScanned: request.url,
|
299
304
|
});
|
300
|
-
urlsCrawled.invalid.push(
|
305
|
+
urlsCrawled.invalid.push({
|
306
|
+
url: request.url,
|
307
|
+
pageTitle: request.url,
|
308
|
+
actualUrl: actualUrl, // i.e. actualUrl
|
309
|
+
});
|
310
|
+
|
301
311
|
return;
|
302
312
|
}
|
303
313
|
|
@@ -874,7 +874,6 @@ export const flagUnlabelledClickableElements = async (page: Page) => {
|
|
874
874
|
}
|
875
875
|
|
876
876
|
function flagElements() {
|
877
|
-
console.time('Accessibility Check Time');
|
878
877
|
|
879
878
|
const currentFlaggedElementsByDocument: Record<string, HTMLElement[]> = {}; // Temporary object to hold current flagged elements
|
880
879
|
|
@@ -1014,7 +1013,6 @@ export const flagUnlabelledClickableElements = async (page: Page) => {
|
|
1014
1013
|
previousFlaggedXPathsByDocument = { ...flaggedXPathsByDocument };
|
1015
1014
|
|
1016
1015
|
cleanupFlaggedElements();
|
1017
|
-
console.timeEnd('Accessibility Check Time');
|
1018
1016
|
return previousAllFlaggedElementsXPaths;
|
1019
1017
|
}
|
1020
1018
|
|
@@ -295,7 +295,11 @@ export const handlePdfDownload = (
|
|
295
295
|
numScanned: urlsCrawled.scanned.length,
|
296
296
|
urlScanned: request.url,
|
297
297
|
});
|
298
|
-
urlsCrawled.invalid.push(
|
298
|
+
urlsCrawled.invalid.push({
|
299
|
+
url: request.url,
|
300
|
+
pageTitle: url,
|
301
|
+
actualUrl: url, // i.e. actualUrl
|
302
|
+
});
|
299
303
|
}
|
300
304
|
resolve();
|
301
305
|
});
|
package/src/mergeAxeResults.ts
CHANGED
@@ -725,6 +725,8 @@ const writeJsonAndBase64Files = async (
|
|
725
725
|
scanItemsBase64FilePath: string;
|
726
726
|
scanItemsSummaryJsonFilePath: string;
|
727
727
|
scanItemsSummaryBase64FilePath: string;
|
728
|
+
scanItemsMiniReportJsonFilePath: string;
|
729
|
+
scanItemsMiniReportBase64FilePath: string;
|
728
730
|
scanDataJsonFileSize: number;
|
729
731
|
scanItemsJsonFileSize: number;
|
730
732
|
}> => {
|
@@ -777,7 +779,7 @@ const writeJsonAndBase64Files = async (
|
|
777
779
|
topTenIssues,
|
778
780
|
} = rest;
|
779
781
|
|
780
|
-
const
|
782
|
+
const summaryItemsMini = {
|
781
783
|
...items,
|
782
784
|
pagesScanned,
|
783
785
|
topTenPagesWithMostIssues,
|
@@ -789,6 +791,32 @@ const writeJsonAndBase64Files = async (
|
|
789
791
|
topTenIssues,
|
790
792
|
};
|
791
793
|
|
794
|
+
const {
|
795
|
+
jsonFilePath: scanItemsMiniReportJsonFilePath,
|
796
|
+
base64FilePath: scanItemsMiniReportBase64FilePath,
|
797
|
+
} = await writeJsonFileAndCompressedJsonFile(summaryItemsMini, storagePath, 'scanItemsSummaryMiniReport');
|
798
|
+
|
799
|
+
const summaryItems = {
|
800
|
+
mustFix: {
|
801
|
+
totalItems: items.mustFix?.totalItems || 0,
|
802
|
+
totalRuleIssues: items.mustFix?.totalRuleIssues || 0,
|
803
|
+
},
|
804
|
+
goodToFix: {
|
805
|
+
totalItems: items.goodToFix?.totalItems || 0,
|
806
|
+
totalRuleIssues: items.goodToFix?.totalRuleIssues || 0,
|
807
|
+
},
|
808
|
+
needsReview: {
|
809
|
+
totalItems: items.needsReview?.totalItems || 0,
|
810
|
+
totalRuleIssues: items.needsReview?.totalRuleIssues || 0,
|
811
|
+
},
|
812
|
+
topTenPagesWithMostIssues,
|
813
|
+
wcagLinks,
|
814
|
+
wcagPassPercentage,
|
815
|
+
totalPagesScanned,
|
816
|
+
totalPagesNotScanned,
|
817
|
+
topTenIssues,
|
818
|
+
};
|
819
|
+
|
792
820
|
const {
|
793
821
|
jsonFilePath: scanItemsSummaryJsonFilePath,
|
794
822
|
base64FilePath: scanItemsSummaryBase64FilePath,
|
@@ -801,6 +829,8 @@ const writeJsonAndBase64Files = async (
|
|
801
829
|
scanItemsBase64FilePath,
|
802
830
|
scanItemsSummaryJsonFilePath,
|
803
831
|
scanItemsSummaryBase64FilePath,
|
832
|
+
scanItemsMiniReportJsonFilePath,
|
833
|
+
scanItemsMiniReportBase64FilePath,
|
804
834
|
scanDataJsonFileSize: fs.statSync(scanDataJsonFilePath).size,
|
805
835
|
scanItemsJsonFileSize: fs.statSync(scanItemsJsonFilePath).size,
|
806
836
|
};
|
@@ -1052,8 +1082,8 @@ const flattenAndSortResults = (allIssues: AllIssues, isCustomFlow: boolean) => {
|
|
1052
1082
|
};
|
1053
1083
|
|
1054
1084
|
allIssues.topFiveMostIssues.sort((page1, page2) => page2.totalIssues - page1.totalIssues);
|
1055
|
-
allIssues.topFiveMostIssues = allIssues.topFiveMostIssues.slice(0, 5);
|
1056
1085
|
allIssues.topTenPagesWithMostIssues = allIssues.topFiveMostIssues.slice(0, 10);
|
1086
|
+
allIssues.topFiveMostIssues = allIssues.topFiveMostIssues.slice(0, 5);
|
1057
1087
|
updateIssuesWithOccurrences(allIssues.topTenPagesWithMostIssues);
|
1058
1088
|
const topTenIssues = getTopTenIssues(allIssues);
|
1059
1089
|
allIssues.topTenIssues = topTenIssues;
|
@@ -1234,7 +1264,7 @@ const generateArtifacts = async (
|
|
1234
1264
|
'',
|
1235
1265
|
`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'}`,
|
1236
1266
|
`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'}`,
|
1237
|
-
`
|
1267
|
+
`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'}`,
|
1238
1268
|
`Passed: ${allIssues.items.passed.totalItems} ${allIssues.items.passed.totalItems === 1 ? 'occurrence' : 'occurrences'}`,
|
1239
1269
|
]);
|
1240
1270
|
|
@@ -1244,7 +1274,7 @@ const generateArtifacts = async (
|
|
1244
1274
|
createScreenshotsFolder(randomToken);
|
1245
1275
|
}
|
1246
1276
|
|
1247
|
-
allIssues.wcagPassPercentage = getWcagPassPercentage(allIssues.wcagViolations);
|
1277
|
+
allIssues.wcagPassPercentage = getWcagPassPercentage(allIssues.wcagViolations, allIssues.advancedScanOptionsSummaryItems.showEnableWcagAaa);
|
1248
1278
|
consoleLogger.info(
|
1249
1279
|
`advancedScanOptionsSummaryItems is ${allIssues.advancedScanOptionsSummaryItems}`,
|
1250
1280
|
);
|
@@ -1293,6 +1323,8 @@ const generateArtifacts = async (
|
|
1293
1323
|
scanItemsBase64FilePath,
|
1294
1324
|
scanItemsSummaryJsonFilePath,
|
1295
1325
|
scanItemsSummaryBase64FilePath,
|
1326
|
+
scanItemsMiniReportJsonFilePath,
|
1327
|
+
scanItemsMiniReportBase64FilePath,
|
1296
1328
|
scanDataJsonFileSize,
|
1297
1329
|
scanItemsJsonFileSize,
|
1298
1330
|
} = await writeJsonAndBase64Files(allIssues, storagePath);
|
@@ -1306,12 +1338,13 @@ const generateArtifacts = async (
|
|
1306
1338
|
storagePath,
|
1307
1339
|
);
|
1308
1340
|
await writeSummaryHTML(allIssues, storagePath);
|
1341
|
+
|
1309
1342
|
await writeHTML(
|
1310
1343
|
allIssues,
|
1311
1344
|
storagePath,
|
1312
1345
|
'report',
|
1313
1346
|
scanDataBase64FilePath,
|
1314
|
-
resultsTooBig ?
|
1347
|
+
resultsTooBig ? scanItemsMiniReportBase64FilePath : scanItemsBase64FilePath,
|
1315
1348
|
);
|
1316
1349
|
|
1317
1350
|
if (!generateJsonFiles) {
|
@@ -21,7 +21,7 @@
|
|
21
21
|
</div>
|
22
22
|
<div class="modal-body">
|
23
23
|
<div>
|
24
|
-
Only 20 WCAG 2.2 Success Criteria (A & AA) can be checked reasonably through automated testing:
|
24
|
+
Only <a href="https://go.gov.sg/oobee-details" target="_blank">20 WCAG 2.2</a> Success Criteria (A & AA) can be checked reasonably through automated testing:
|
25
25
|
</div>
|
26
26
|
<div class="accordion my-3" id="wcagLinksAccordion">
|
27
27
|
<div class="accordion-item">
|
@@ -41,7 +41,8 @@
|
|
41
41
|
</div>
|
42
42
|
</div>
|
43
43
|
<div>
|
44
|
-
<strong
|
44
|
+
<strong><a aria-label="Manual testing guide" href="https://go.gov.sg/a11y-manual-testing" target="_blank">Manual
|
45
|
+
testing</a> is still recommended</strong> as they involve subjective judgements
|
45
46
|
and human interpretation, which cannot be fully automated.
|
46
47
|
</div>
|
47
48
|
</div>
|
@@ -1,17 +1,23 @@
|
|
1
1
|
<footer aria-label="Report footer" id="footer" class="card">
|
2
2
|
<div class="row mx-0">
|
3
3
|
<div class="col-sm-6 text-sm-start">
|
4
|
-
<%
|
5
|
-
const
|
6
|
-
encodedVersionNumber=encodeURIComponent(phAppVersion);
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
<%
|
5
|
+
const encodedUrlScanned = encodeURIComponent(urlScanned);
|
6
|
+
const encodedVersionNumber = encodeURIComponent(phAppVersion);
|
7
|
+
|
8
|
+
// Use %0A for line breaks in the body
|
9
|
+
const mailtoAddress = `oobee@wogaa.gov.sg`;
|
10
|
+
const mailtoSubject = encodeURIComponent(`Support Request - Oobee Version ${phAppVersion}`);
|
11
|
+
const mailtoBody = encodeURIComponent(`URL Scanned: ${urlScanned}\nVersion: ${phAppVersion}`);
|
12
|
+
const feedbackEmail = `mailto:${mailtoAddress}?subject=${mailtoSubject}&body=${mailtoBody}`;
|
13
|
+
%>
|
14
|
+
|
15
|
+
<a href="<%=feedbackEmail%>" aria-label="Send feedback to <%=mailtoAddress%>" target="_blank">Help us improve</a>
|
10
16
|
<hr class="d-sm-none" />
|
11
17
|
</div>
|
12
18
|
<div class="col-sm-6 text-sm-end">
|
13
19
|
Created by
|
14
|
-
<a href="https://
|
20
|
+
<a href="https://go.gov.sg/a11y" target="_blank">GovTech Accessibility Enabling Team</a> |
|
15
21
|
<a href="https://go.gov.sg/oobee-report-third-party-licenses" target="_blank">Third-Party Licenses</a>
|
16
22
|
</div>
|
17
23
|
</div>
|
@@ -13,14 +13,15 @@
|
|
13
13
|
<div class="d-flex justify-content-between align-items-center">
|
14
14
|
<span class="fw-bold"> WCAG (A & AA) Passes </span>
|
15
15
|
<span aria-label="Pass percentage" class="ms-2">
|
16
|
-
<%= wcagPassPercentage
|
16
|
+
<%= wcagPassPercentage.totalWcagChecksAA - wcagPassPercentage.totalWcagViolationsAA %> / <%= wcagPassPercentage.totalWcagChecksAA %> of automated checks
|
17
17
|
</span>
|
18
18
|
</div>
|
19
19
|
<div class="wcag-compliance-passes-bar mb-5 d-flex">
|
20
20
|
<svg width="500" role="none" height="6" fill="none" xmlns="http://www.w3.org/2000/svg"
|
21
21
|
style="display: flex; width: 100%; position: absolute">
|
22
22
|
<rect width="100%" height="10" rx="3" fill="#E7ECEE" style="justify-content: left"></rect>
|
23
|
-
<rect width="<%= wcagPassPercentage %>%" height="6" rx="3" fill="#9021a6" style=""
|
23
|
+
<rect width="<%= wcagPassPercentage.passPercentageAA %>%" height="6" rx="3" fill="#9021a6" style="">
|
24
|
+
</rect>
|
24
25
|
</svg>
|
25
26
|
</div>
|
26
27
|
<ul class="unbulleted-list">
|
@@ -35,10 +36,10 @@
|
|
35
36
|
<div class="mx-3">
|
36
37
|
<h2 class="mb-2">Summary of issues:</h2>
|
37
38
|
<p>
|
38
|
-
Only
|
39
|
-
<a href="https://
|
40
|
-
|
41
|
-
<a aria-label="Manual testing guide" href="
|
39
|
+
Only
|
40
|
+
<a href="https://go.gov.sg/oobee-details" target=" _blank">20 WCAG 2.2</a>
|
41
|
+
Success Criteria (A & AA) can be automatically checked so
|
42
|
+
<a aria-label="Manual testing guide" href="https://go.gov.sg/a11y-manual-testing" target="_blank">manual
|
42
43
|
testing</a>
|
43
44
|
is still required. For more details, please refer to the HTML report.
|
44
45
|
</p>
|
@@ -47,9 +47,9 @@
|
|
47
47
|
// Scan DATA FUNCTION TO REPLACE NA
|
48
48
|
const scanDataWCAGCompliance = () => {
|
49
49
|
const passPecentage = document.getElementById('passPercentage');
|
50
|
-
passPecentage.innerHTML = scanData.wcagPassPercentage + '
|
50
|
+
passPecentage.innerHTML = (scanData.wcagPassPercentage.totalWcagChecksAA - scanData.wcagPassPercentage.totalWcagViolationsAA) + ' / ' + scanData.wcagPassPercentage.totalWcagChecksAA + ' of automated checks';
|
51
51
|
const wcagBarProgess = document.getElementById('wcag-compliance-passes-bar-progress');
|
52
|
-
wcagBarProgess.style.width = `${scanData.wcagPassPercentage}%`; // Set this to your desired width
|
52
|
+
wcagBarProgess.style.width = `${scanData.wcagPassPercentage.passPercentageAA}%`; // Set this to your desired width
|
53
53
|
|
54
54
|
const wcagLinksList = document.getElementById('wcagLinksList');
|
55
55
|
|
@@ -94,7 +94,7 @@
|
|
94
94
|
const formattedCategoryTitles = {
|
95
95
|
mustFix: 'Must Fix',
|
96
96
|
goodToFix: 'Good to Fix',
|
97
|
-
needsReview: '
|
97
|
+
needsReview: 'Manual Review Required',
|
98
98
|
passed: 'Passed',
|
99
99
|
};
|
100
100
|
|
package/src/utils.ts
CHANGED
@@ -190,19 +190,42 @@ export const cleanUp = async pathToDelete => {
|
|
190
190
|
// timeZoneName: "longGeneric",
|
191
191
|
// });
|
192
192
|
|
193
|
-
export const getWcagPassPercentage = (
|
193
|
+
export const getWcagPassPercentage = (
|
194
|
+
wcagViolations: string[],
|
195
|
+
showEnableWcagAaa: boolean
|
196
|
+
): {
|
197
|
+
passPercentageAA: string;
|
198
|
+
totalWcagChecksAA: number;
|
199
|
+
totalWcagViolationsAA: number;
|
200
|
+
passPercentageAAandAAA: string;
|
201
|
+
totalWcagChecksAAandAAA: number;
|
202
|
+
totalWcagViolationsAAandAAA: number;
|
203
|
+
} => {
|
194
204
|
|
195
205
|
// These AAA rules should not be counted as WCAG Pass Percentage only contains A and AA
|
196
|
-
const
|
197
|
-
|
198
|
-
|
199
|
-
const
|
200
|
-
|
201
|
-
|
202
|
-
const
|
203
|
-
const
|
204
|
-
|
205
|
-
|
206
|
+
const wcagAAALinks = ['WCAG 1.4.6', 'WCAG 2.2.4', 'WCAG 2.4.9', 'WCAG 3.1.5', 'WCAG 3.2.5'];
|
207
|
+
const wcagAAA = ['wcag146', 'wcag224', 'wcag249', 'wcag315', 'wcag325'];
|
208
|
+
|
209
|
+
const wcagLinksAAandAAA = constants.wcagLinks;
|
210
|
+
|
211
|
+
const wcagViolationsAAandAAA = showEnableWcagAaa ? wcagViolations.length : null;
|
212
|
+
const totalChecksAAandAAA = showEnableWcagAaa ? Object.keys(wcagLinksAAandAAA).length : null;
|
213
|
+
const passedChecksAAandAAA = showEnableWcagAaa ? totalChecksAAandAAA - wcagViolationsAAandAAA : null;
|
214
|
+
const passPercentageAAandAAA = showEnableWcagAaa ? (totalChecksAAandAAA === 0 ? 0 : (passedChecksAAandAAA / totalChecksAAandAAA) * 100) : null;
|
215
|
+
|
216
|
+
const wcagViolationsAA = wcagViolations.filter(violation => !wcagAAA.includes(violation)).length;
|
217
|
+
const totalChecksAA = Object.keys(wcagLinksAAandAAA).filter(key => !wcagAAALinks.includes(key)).length;
|
218
|
+
const passedChecksAA = totalChecksAA - wcagViolationsAA;
|
219
|
+
const passPercentageAA = totalChecksAA === 0 ? 0 : (passedChecksAA / totalChecksAA) * 100;
|
220
|
+
|
221
|
+
return {
|
222
|
+
passPercentageAA: passPercentageAA.toFixed(2), // toFixed returns a string, which is correct here
|
223
|
+
totalWcagChecksAA: totalChecksAA,
|
224
|
+
totalWcagViolationsAA: wcagViolationsAA,
|
225
|
+
passPercentageAAandAAA: passPercentageAAandAAA ? passPercentageAAandAAA.toFixed(2) : null, // toFixed returns a string, which is correct here
|
226
|
+
totalWcagChecksAAandAAA: totalChecksAAandAAA,
|
227
|
+
totalWcagViolationsAAandAAA: wcagViolationsAAandAAA,
|
228
|
+
};
|
206
229
|
};
|
207
230
|
|
208
231
|
export const getFormattedTime = inputDate => {
|