@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,319 @@
|
|
1
|
+
{
|
2
|
+
"WCAG2.2": {
|
3
|
+
"1.3.1": {
|
4
|
+
"1": {
|
5
|
+
"STATUS": "warning",
|
6
|
+
"CATEGORY": "structure",
|
7
|
+
"RATIONALE": "Repeated characters should be avoided to represent lines or graphics, as the characters are read out one by one by reader software."
|
8
|
+
},
|
9
|
+
"2": {
|
10
|
+
"STATUS": "ignore",
|
11
|
+
"CATEGORY": "structure",
|
12
|
+
"RATIONALE": "Repeated white space should be avoided to align text. Repeated white space at the end of a line usually doesn't have any function."
|
13
|
+
},
|
14
|
+
"3": {
|
15
|
+
"STATUS": "warning",
|
16
|
+
"CATEGORY": "structure",
|
17
|
+
"RATIONALE": "Underlined text is easily confused with a hyperlink. WCAG doesn't prohibit underlining but it's bad practice."
|
18
|
+
},
|
19
|
+
"4": {
|
20
|
+
"STATUS": "ignore",
|
21
|
+
"CATEGORY": "structure",
|
22
|
+
"RATIONALE": "Semantics should not just be expressed by formatting text."
|
23
|
+
}
|
24
|
+
},
|
25
|
+
"1.3.4": {
|
26
|
+
"1": {
|
27
|
+
"STATUS": "warning",
|
28
|
+
"CATEGORY": "structure",
|
29
|
+
"RATIONALE": "Pages with different orientation are allowed as long as this is essential for the content."
|
30
|
+
}
|
31
|
+
},
|
32
|
+
"1.4.3": {
|
33
|
+
"1": {
|
34
|
+
"STATUS": "error",
|
35
|
+
"CATEGORY": "structure",
|
36
|
+
"RATIONALE": "Insufficient contrast makes the text difficult to read for people with visual impairments or colour blind people."
|
37
|
+
}
|
38
|
+
},
|
39
|
+
"1.4.4": {
|
40
|
+
"1": {
|
41
|
+
"STATUS": "warning",
|
42
|
+
"CATEGORY": "structure",
|
43
|
+
"RATIONALE": "Text not be too small, and must be readable when zoomed to 200%."
|
44
|
+
}
|
45
|
+
},
|
46
|
+
"1.4.8": {
|
47
|
+
"1": {
|
48
|
+
"STATUS": "warning",
|
49
|
+
"CATEGORY": "structure",
|
50
|
+
"RATIONALE": "The use of fonts that are difficult to read should be avoided (unless it was the intention to make the text difficult to read). Reader software will read the text out correctly, but people with poor vision, ADHD or people in the autism spectrum may have problems reading the text."
|
51
|
+
}
|
52
|
+
},
|
53
|
+
"1.4.10": {
|
54
|
+
"1": {
|
55
|
+
"STATUS": "warning",
|
56
|
+
"CATEGORY": "code",
|
57
|
+
"RATIONALE": "A figure which is on one page should have a bounding box."
|
58
|
+
}
|
59
|
+
},
|
60
|
+
"2.4.9": {
|
61
|
+
"1": {
|
62
|
+
"STATUS": "warning",
|
63
|
+
"CATEGORY": "structure",
|
64
|
+
"RATIONALE": "Links should either have an alternative text or should be associated with text that explains the underlying link. Writing out URLS without alternative text should be avoided."
|
65
|
+
}
|
66
|
+
},
|
67
|
+
"4.1.1": {
|
68
|
+
"1": {
|
69
|
+
"STATUS": "warning",
|
70
|
+
"CATEGORY": "structure",
|
71
|
+
"RATIONALE": "Merged cells in table bodies should be avoided as much as possible as this makes the table more difficult to read out."
|
72
|
+
},
|
73
|
+
"2": {
|
74
|
+
"STATUS": "warning",
|
75
|
+
"CATEGORY": "structure",
|
76
|
+
"RATIONALE": "Merged cells in table heads should be avoided as much as possible as this makes the table more difficult to read out."
|
77
|
+
},
|
78
|
+
"3": {
|
79
|
+
"STATUS": "warning",
|
80
|
+
"CATEGORY": "structure",
|
81
|
+
"RATIONALE": "Merged cells in table heads should be avoided as much as possible as this makes the table more difficult to read out."
|
82
|
+
},
|
83
|
+
"4": {
|
84
|
+
"STATUS": "critical",
|
85
|
+
"CATEGORY": "code",
|
86
|
+
"RATIONALE": "A document must be tagged in order to be accessible."
|
87
|
+
}
|
88
|
+
},
|
89
|
+
"4.1.2": {
|
90
|
+
"1": {
|
91
|
+
"STATUS": "ignore",
|
92
|
+
"CATEGORY": "structure",
|
93
|
+
"RATIONALE": "If different lines have different formats they shouldn't be marked as a single paragraph."
|
94
|
+
},
|
95
|
+
"2": {
|
96
|
+
"STATUS": "ignore",
|
97
|
+
"CATEGORY": "code",
|
98
|
+
"RATIONALE": "If different lines of text have different formats they shouldn't be part of the same span."
|
99
|
+
},
|
100
|
+
"3": {
|
101
|
+
"STATUS": "ignore",
|
102
|
+
"CATEGORY": "structure",
|
103
|
+
"RATIONALE": "Tables should not be used to format text. However, WCAG does allow for tables to be used this way so we'll ignore this warning for the moment."
|
104
|
+
},
|
105
|
+
"4": {
|
106
|
+
"STATUS": "warning",
|
107
|
+
"CATEGORY": "structure",
|
108
|
+
"RATIONALE": "If something that is not intended as a heading is tagged as a heading, this can confuse reader software for people with visual impairments. It will appear in the contents list as a heading, for example."
|
109
|
+
},
|
110
|
+
"5": {
|
111
|
+
"STATUS": "warning",
|
112
|
+
"CATEGORY": "structure",
|
113
|
+
"RATIONALE": "If something that is not intended as a heading is tagged as a heading, this can confuse reader software for people with visual impairments. It will appear in the contents list as a heading, for example."
|
114
|
+
},
|
115
|
+
"6": {
|
116
|
+
"STATUS": "warning",
|
117
|
+
"CATEGORY": "structure",
|
118
|
+
"RATIONALE": "Text that is intended as a table row must be tagged as a table element. Otherwise reader software for people with visual impairments will not recognise it as a table and will fail to read it out correctly."
|
119
|
+
},
|
120
|
+
"7": {
|
121
|
+
"STATUS": "warning",
|
122
|
+
"CATEGORY": "structure",
|
123
|
+
"RATIONALE": "Text that is intended as a table head must be tagged as a table element. Otherwise reader software for people with visual impairments will not recognise it as a table and will fail to read it out correctly."
|
124
|
+
},
|
125
|
+
"8": {
|
126
|
+
"STATUS": "warning",
|
127
|
+
"CATEGORY": "structure",
|
128
|
+
"RATIONALE": "Text that is intended as a table cell must be tagged as a table element. Otherwise reader software for people with visual impairments will not recognise it as a table and will fail to read it out correctly."
|
129
|
+
},
|
130
|
+
"9": {
|
131
|
+
"STATUS": "warning",
|
132
|
+
"CATEGORY": "structure",
|
133
|
+
"RATIONALE": "Text that is intended as a part of a table must be tagged as a part of a table. Otherwise reader software for people with visual impairments will not recognise it as a table and will fail to read it out correctly."
|
134
|
+
},
|
135
|
+
"10": {
|
136
|
+
"STATUS": "ignore",
|
137
|
+
"CATEGORY": "structure",
|
138
|
+
"RATIONALE": "Text that is intended as a heading must be tagged as a heading. Otherwise reader software for people with visual impairments will not recognise the structure of the document and may have trouble reading it out in the correct order."
|
139
|
+
},
|
140
|
+
"11": {
|
141
|
+
"STATUS": "ignore",
|
142
|
+
"CATEGORY": "structure",
|
143
|
+
"RATIONALE": "If a span was not intended as a paragraph break, then it should not be tagged as such. Otherwise reader software for people with visual impairments will take this as a new paragraph and read it out as such."
|
144
|
+
},
|
145
|
+
"12": {
|
146
|
+
"STATUS": "warning",
|
147
|
+
"CATEGORY": "structure",
|
148
|
+
"RATIONALE": "Text that is intended as a paragraph must be tagged as a paragraph. Otherwise reader software for people with visual impairments will not recognise the text as a paragraph and may become confused about the reading order if the document."
|
149
|
+
},
|
150
|
+
"13": {
|
151
|
+
"STATUS": "ignore",
|
152
|
+
"CATEGORY": "structure",
|
153
|
+
"RATIONALE": "Text that is intended as a heading must be tagged as a heading. Otherwise reader software for people with visual impairments will not recognise the text as a heading and may become confused about the reading order if the document."
|
154
|
+
},
|
155
|
+
"14": {
|
156
|
+
"STATUS": "ignore",
|
157
|
+
"CATEGORY": "structure",
|
158
|
+
"RATIONALE": "Text that is intended as a paragraph must be tagged as a paragraph. Otherwise reader software for people with visual impairments will not recognise the text as a paragraph and may become confused about the reading order if the document."
|
159
|
+
},
|
160
|
+
"15": {
|
161
|
+
"STATUS": "ignore",
|
162
|
+
"CATEGORY": "structure",
|
163
|
+
"RATIONALE": "Text that is intended as a paragraph must be tagged as a paragraph. Otherwise reader software for people with visual impairments will not recognise the text as a paragraph and may become confused about the reading order if the document."
|
164
|
+
},
|
165
|
+
"16": {
|
166
|
+
"STATUS": "warning",
|
167
|
+
"CATEGORY": "structure",
|
168
|
+
"RATIONALE": "Empty paragraphs can confuse reader software for people with visual impairments. When multiple empty paragraphs are used to create white space in a document, they could be read out explicitly as empty paragraphs which is very disorienting and annoying."
|
169
|
+
},
|
170
|
+
"17": {
|
171
|
+
"STATUS": "warning",
|
172
|
+
"CATEGORY": "structure",
|
173
|
+
"RATIONALE": "Empty spans can confuse reader software for people with visual impairments. When multiple empty spans are used to create white space in a document, they could be read out explicitly as empty lines which is very disorienting and annoying."
|
174
|
+
},
|
175
|
+
"18": {
|
176
|
+
"STATUS": "warning",
|
177
|
+
"CATEGORY": "structure",
|
178
|
+
"RATIONALE": "Empty headings can confuse reader software for people with visual impairments. When multiple empty headings are used to create white space in a document, they could be read out explicitly as empty headings which is very disorienting and annoying."
|
179
|
+
},
|
180
|
+
"19": {
|
181
|
+
"STATUS": "warning",
|
182
|
+
"CATEGORY": "structure",
|
183
|
+
"RATIONALE": "Empty headings can confuse reader software for people with visual impairments. When multiple empty headings are used to create white space in a document, they could be read out explicitly as empty headings which is very disorienting and annoying."
|
184
|
+
},
|
185
|
+
"20": {
|
186
|
+
"STATUS": "warning",
|
187
|
+
"CATEGORY": "structure",
|
188
|
+
"RATIONALE": "Captions must be tagged as such. If not, then reader software for people with visual impairments may not recognise the text as a caption that belongs with the figure."
|
189
|
+
},
|
190
|
+
"21": {
|
191
|
+
"STATUS": "warning",
|
192
|
+
"CATEGORY": "structure",
|
193
|
+
"RATIONALE": "Lists must be formatted and tagged as such. If not, then reader software for people with visual impairments may not recognise the text as a list and may fail to read it out properly."
|
194
|
+
},
|
195
|
+
"22": {
|
196
|
+
"STATUS": "warning",
|
197
|
+
"CATEGORY": "structure",
|
198
|
+
"RATIONALE": "List structures should be not be used for text which is not a list. And lists should not be interrupted by normal paragraphs."
|
199
|
+
},
|
200
|
+
"23": {
|
201
|
+
"STATUS": "warning",
|
202
|
+
"CATEGORY": "structure",
|
203
|
+
"RATIONALE": "List structures should be not be used for text which is not a list. And lists should not be interrupted by normal paragraphs."
|
204
|
+
},
|
205
|
+
"25": {
|
206
|
+
"STATUS": "warning",
|
207
|
+
"CATEGORY": "structure",
|
208
|
+
"RATIONALE": "List structures should be not be used for text which is not a list. And lists should not be interrupted by normal paragraphs."
|
209
|
+
},
|
210
|
+
"26": {
|
211
|
+
"STATUS": "warning",
|
212
|
+
"CATEGORY": "structure",
|
213
|
+
"RATIONALE": "Lists should not be misused to represent tables."
|
214
|
+
},
|
215
|
+
"27": {
|
216
|
+
"STATUS": "warning",
|
217
|
+
"CATEGORY": "structure",
|
218
|
+
"RATIONALE": "Multiple paragraphs should be tagged as such. Line breaks should not be used to delimit paragraphs."
|
219
|
+
},
|
220
|
+
"28": {
|
221
|
+
"STATUS": "warning",
|
222
|
+
"CATEGORY": "structure",
|
223
|
+
"RATIONALE": "Table of content items should be tagged as such."
|
224
|
+
},
|
225
|
+
"29": {
|
226
|
+
"STATUS": "warning",
|
227
|
+
"CATEGORY": "structure",
|
228
|
+
"RATIONALE": "Table of content items should not be empty."
|
229
|
+
},
|
230
|
+
"30": {
|
231
|
+
"STATUS": "warning",
|
232
|
+
"CATEGORY": "code",
|
233
|
+
"RATIONALE": "Table of content items should point somewhere in the document."
|
234
|
+
},
|
235
|
+
"31": {
|
236
|
+
"STATUS": "warning",
|
237
|
+
"CATEGORY": "code",
|
238
|
+
"RATIONALE": "Table of content items should point to the right place in the document."
|
239
|
+
},
|
240
|
+
"32": {
|
241
|
+
"STATUS": "warning",
|
242
|
+
"CATEGORY": "structure",
|
243
|
+
"RATIONALE": "Table of content items should be correctly aligned for readability."
|
244
|
+
},
|
245
|
+
"33": {
|
246
|
+
"STATUS": "warning",
|
247
|
+
"CATEGORY": "structure",
|
248
|
+
"RATIONALE": "Table of contents item should match the heading it refers to."
|
249
|
+
},
|
250
|
+
"34": {
|
251
|
+
"STATUS": "warning",
|
252
|
+
"CATEGORY": "structure",
|
253
|
+
"RATIONALE": "A single list should be tagged as such and not a series of smaller lists."
|
254
|
+
},
|
255
|
+
"35": {
|
256
|
+
"STATUS": "warning",
|
257
|
+
"CATEGORY": "structure",
|
258
|
+
"RATIONALE": "A single table of contents should be tagged as such and not a series of tables of contents."
|
259
|
+
},
|
260
|
+
"36": {
|
261
|
+
"STATUS": "warning",
|
262
|
+
"CATEGORY": "code",
|
263
|
+
"RATIONALE": "The number of table row tags must correspond to the actual amount of table rows."
|
264
|
+
},
|
265
|
+
"37": {
|
266
|
+
"STATUS": "warning",
|
267
|
+
"CATEGORY": "structure",
|
268
|
+
"RATIONALE": "The number of table column tags must correspond to the actual amount of table columns."
|
269
|
+
},
|
270
|
+
"38": {
|
271
|
+
"STATUS": "warning",
|
272
|
+
"CATEGORY": "structure",
|
273
|
+
"RATIONALE": "A merged cell must be tagged correctly corresponding to the amount of rows it spans."
|
274
|
+
},
|
275
|
+
"39": {
|
276
|
+
"STATUS": "warning",
|
277
|
+
"CATEGORY": "structure",
|
278
|
+
"RATIONALE": "A merged cell must be tagged correctly corresponding to the amount of columns it spans."
|
279
|
+
},
|
280
|
+
"40": {
|
281
|
+
"STATUS": "warning",
|
282
|
+
"CATEGORY": "structure",
|
283
|
+
"RATIONALE": "List items must be tagged as such."
|
284
|
+
},
|
285
|
+
"41": {
|
286
|
+
"STATUS": "warning",
|
287
|
+
"CATEGORY": "code",
|
288
|
+
"RATIONALE": "The content of a table cell should appear in a position that is coherent with its tag."
|
289
|
+
},
|
290
|
+
"42": {
|
291
|
+
"STATUS": "warning",
|
292
|
+
"CATEGORY": "code",
|
293
|
+
"RATIONALE": "The content of a table cell should appear in a position that is coherent with its tag."
|
294
|
+
},
|
295
|
+
"43": {
|
296
|
+
"STATUS": "warning",
|
297
|
+
"CATEGORY": "code",
|
298
|
+
"RATIONALE": "The content of a table cell should appear in a position that is coherent with its tag."
|
299
|
+
},
|
300
|
+
"44": {
|
301
|
+
"STATUS": "warning",
|
302
|
+
"CATEGORY": "code",
|
303
|
+
"RATIONALE": "The content of a table cell should appear in a position that is coherent with its tag."
|
304
|
+
},
|
305
|
+
"45": {
|
306
|
+
"STATUS": "warning",
|
307
|
+
"CATEGORY": "structure",
|
308
|
+
"RATIONALE": "A normal table cell should not be tagged as a table head."
|
309
|
+
}
|
310
|
+
},
|
311
|
+
"4.1.3": {
|
312
|
+
"1": {
|
313
|
+
"STATUS": "error",
|
314
|
+
"CATEGORY": "code",
|
315
|
+
"RATIONALE": "If content has been tagged inappropriately, reader software for people with visual impairments will not read the document out in the correct way or order."
|
316
|
+
}
|
317
|
+
}
|
318
|
+
}
|
319
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
const itemTypeDescription = {
|
2
|
+
mustFix:
|
3
|
+
'Issues that need to be addressed promptly, as they create significant barriers for persons with disabilities and can prevent them from accessing essential content or features.',
|
4
|
+
goodToFix:
|
5
|
+
'Issues that could pose certain challenges for persons with disabilities (PWDs), but are unlikely to completely hinder their access to essential content or features.',
|
6
|
+
needsReview:
|
7
|
+
'Occurrences could potentially be false positives, requiring human validation for accuracy.',
|
8
|
+
passed: 'Occurrences that passed the automated checks.',
|
9
|
+
};
|
10
|
+
|
11
|
+
export default itemTypeDescription;
|
@@ -0,0 +1,141 @@
|
|
1
|
+
// Purple AI rules to process axe outputted HTML into basicFormHTML snippets
|
2
|
+
// for sending to backend services to query GPT
|
3
|
+
export const oobeeAiRules = [
|
4
|
+
'aria-allowed-attr',
|
5
|
+
'aria-hidden-focus',
|
6
|
+
'aria-input-field-name',
|
7
|
+
'aria-required-attr',
|
8
|
+
'aria-required-children',
|
9
|
+
'aria-required-parent',
|
10
|
+
'aria-roles',
|
11
|
+
'aria-toggle-field-name',
|
12
|
+
'aria-valid-attr',
|
13
|
+
'aria-allowed-role',
|
14
|
+
'form-field-multiple-labels',
|
15
|
+
'label',
|
16
|
+
'scrollable-region-focusable',
|
17
|
+
'select-name',
|
18
|
+
'landmark-unique',
|
19
|
+
'meta-viewport-large',
|
20
|
+
'presentation-role-conflict',
|
21
|
+
'aria-treeitem-name',
|
22
|
+
'server-side-image-map',
|
23
|
+
'svg-img-alt',
|
24
|
+
'autocomplete-valid',
|
25
|
+
];
|
26
|
+
|
27
|
+
export const oobeeAiHtmlETL = htmlSnippet => {
|
28
|
+
// Whitelisted attributes (to not drop)
|
29
|
+
// i.e. any other attribute will be dropped
|
30
|
+
const whitelistedAttributes = [
|
31
|
+
`type`,
|
32
|
+
`tabindex`,
|
33
|
+
`lang`,
|
34
|
+
`scope`,
|
35
|
+
`alt`,
|
36
|
+
`role`,
|
37
|
+
`charset`,
|
38
|
+
`for`,
|
39
|
+
`content`,
|
40
|
+
`name`,
|
41
|
+
`onclick`,
|
42
|
+
`aria*`,
|
43
|
+
`src`,
|
44
|
+
`value`,
|
45
|
+
`href`,
|
46
|
+
`title`,
|
47
|
+
`style`,
|
48
|
+
];
|
49
|
+
|
50
|
+
// Attributes to mute
|
51
|
+
const mutedAttributeValues = [
|
52
|
+
`name`,
|
53
|
+
`data`,
|
54
|
+
`src`,
|
55
|
+
`value`,
|
56
|
+
`href`,
|
57
|
+
`title`,
|
58
|
+
`aria-describedby`,
|
59
|
+
`aria-label`,
|
60
|
+
`aria-labelledby`,
|
61
|
+
];
|
62
|
+
|
63
|
+
const sortAlphaAttributes = html => {
|
64
|
+
let entireHtml = '';
|
65
|
+
const htmlOpeningTagRegex = /<[^>]+/g;
|
66
|
+
const htmlTagmatches = html.match(htmlOpeningTagRegex);
|
67
|
+
|
68
|
+
let sortedHtmlTag;
|
69
|
+
|
70
|
+
htmlTagmatches.forEach(htmlTag => {
|
71
|
+
const closingTag = htmlTag.trim().slice(-1) === '/' ? '/>' : '>';
|
72
|
+
|
73
|
+
const htmlElementRegex = /<[^> ]+/;
|
74
|
+
const htmlElement = htmlTag.match(htmlElementRegex);
|
75
|
+
|
76
|
+
const htmlAttributeRegex = /[a-z-]+="[^"]*"/g;
|
77
|
+
const allAttributes = htmlTag.match(htmlAttributeRegex);
|
78
|
+
|
79
|
+
if (allAttributes) {
|
80
|
+
sortedHtmlTag = `${htmlElement} `;
|
81
|
+
allAttributes.sort((a, b) => {
|
82
|
+
const attributeA = a.toLowerCase();
|
83
|
+
const attributeB = b.toLowerCase();
|
84
|
+
|
85
|
+
if (attributeA < attributeB) {
|
86
|
+
return -1;
|
87
|
+
}
|
88
|
+
|
89
|
+
if (attributeA > attributeB) {
|
90
|
+
return 1;
|
91
|
+
}
|
92
|
+
|
93
|
+
return 0;
|
94
|
+
});
|
95
|
+
|
96
|
+
allAttributes.forEach((htmlAttribute, index) => {
|
97
|
+
sortedHtmlTag += htmlAttribute;
|
98
|
+
if (index !== allAttributes.length - 1) {
|
99
|
+
sortedHtmlTag += ' ';
|
100
|
+
}
|
101
|
+
});
|
102
|
+
|
103
|
+
sortedHtmlTag += closingTag;
|
104
|
+
} else {
|
105
|
+
sortedHtmlTag = htmlElement + closingTag;
|
106
|
+
}
|
107
|
+
|
108
|
+
entireHtml += sortedHtmlTag;
|
109
|
+
});
|
110
|
+
return entireHtml;
|
111
|
+
};
|
112
|
+
|
113
|
+
// For all attributes within mutedAttributeValues array
|
114
|
+
// replace their values with "something" while maintaining the attribute
|
115
|
+
const muteAttributeValues = html => {
|
116
|
+
const regex = /(\s+)([\w-]+)(\s*=\s*")([^"]*)(")/g;
|
117
|
+
|
118
|
+
// p1 is the whitespace before the attribute
|
119
|
+
// p2 is the attribute name
|
120
|
+
// p3 is the attribute value before the replacement
|
121
|
+
// p4 is the attribute value (replaced with "...")
|
122
|
+
// p5 is the closing quote of the attribute value
|
123
|
+
return html.replace(regex, (match, p1, p2, p3, p4, p5) => {
|
124
|
+
if (mutedAttributeValues.includes(p2)) {
|
125
|
+
return `${p1}${p2}${p3}...${p5}`;
|
126
|
+
}
|
127
|
+
return match;
|
128
|
+
});
|
129
|
+
};
|
130
|
+
|
131
|
+
// Drop all attributes from the HTML snippet except whitelisted
|
132
|
+
const dropAllExceptWhitelisted = html => {
|
133
|
+
const regex = new RegExp(
|
134
|
+
`(\\s+)(?!${whitelistedAttributes.join(`|`)})([\\w-]+)(\\s*=\\s*"[^"]*")`,
|
135
|
+
`g`,
|
136
|
+
);
|
137
|
+
return html.replace(regex, ``);
|
138
|
+
};
|
139
|
+
|
140
|
+
return sortAlphaAttributes(muteAttributeValues(dropAllExceptWhitelisted(htmlSnippet)));
|
141
|
+
};
|
@@ -0,0 +1,181 @@
|
|
1
|
+
import { Question } from 'inquirer';
|
2
|
+
import { Answers } from '../index.js';
|
3
|
+
import { getUserDataTxt } from '../utils.js';
|
4
|
+
import {
|
5
|
+
checkUrl,
|
6
|
+
deleteClonedProfiles,
|
7
|
+
getBrowserToRun,
|
8
|
+
getPlaywrightDeviceDetailsObject,
|
9
|
+
getUrlMessage,
|
10
|
+
getFileSitemap,
|
11
|
+
sanitizeUrlInput,
|
12
|
+
validEmail,
|
13
|
+
validName,
|
14
|
+
validateCustomFlowLabel,
|
15
|
+
} from './common.js';
|
16
|
+
import constants, { BrowserTypes, ScannerTypes } from './constants.js';
|
17
|
+
|
18
|
+
const userData = getUserDataTxt();
|
19
|
+
|
20
|
+
const questions = [];
|
21
|
+
|
22
|
+
const startScanQuestions = [
|
23
|
+
{
|
24
|
+
type: 'list',
|
25
|
+
name: 'scanner',
|
26
|
+
message: 'What would you like to scan?',
|
27
|
+
choices: [
|
28
|
+
{ name: 'Sitemap', value: ScannerTypes.SITEMAP },
|
29
|
+
{ name: 'Website', value: ScannerTypes.WEBSITE },
|
30
|
+
{ name: 'Custom', value: ScannerTypes.CUSTOM },
|
31
|
+
{ name: 'Intelligent', value: ScannerTypes.INTELLIGENT },
|
32
|
+
{ name: 'Localfile', value: ScannerTypes.LOCALFILE },
|
33
|
+
],
|
34
|
+
},
|
35
|
+
{
|
36
|
+
type: 'confirm',
|
37
|
+
name: 'headless',
|
38
|
+
message: 'Do you want Oobee to run in the background?',
|
39
|
+
choices: ['Yes', 'No'],
|
40
|
+
},
|
41
|
+
{
|
42
|
+
type: 'list',
|
43
|
+
name: 'deviceChosen',
|
44
|
+
message: 'Which screen size would you like to scan? (Use arrow keys)',
|
45
|
+
choices: ['Desktop', 'Mobile', 'Custom'],
|
46
|
+
},
|
47
|
+
{
|
48
|
+
type: 'list',
|
49
|
+
name: 'customDevice',
|
50
|
+
message: 'Custom: (use arrow keys)',
|
51
|
+
when: (answers: Answers) => answers.deviceChosen === 'Custom',
|
52
|
+
choices: ['iPhone 11', 'Samsung Galaxy S9+', 'Specify viewport'],
|
53
|
+
},
|
54
|
+
{
|
55
|
+
type: 'number',
|
56
|
+
name: 'viewportWidth',
|
57
|
+
message: 'Specify width of the viewport in pixels (e.g. 360):',
|
58
|
+
when: (answers: Answers) => answers.customDevice === 'Specify viewport',
|
59
|
+
validate: (viewport: number) => {
|
60
|
+
if (!Number.isInteger(viewport)) {
|
61
|
+
return 'Invalid viewport width. Please provide an integer.';
|
62
|
+
}
|
63
|
+
if (viewport < 320 || viewport > 1080) {
|
64
|
+
return 'Invalid viewport width! Please provide a viewport width between 320-1080 pixels.';
|
65
|
+
}
|
66
|
+
return true;
|
67
|
+
},
|
68
|
+
},
|
69
|
+
{
|
70
|
+
type: 'input',
|
71
|
+
name: 'url',
|
72
|
+
message: (answers: Answers) => getUrlMessage(answers.scanner),
|
73
|
+
// eslint-disable-next-line func-names
|
74
|
+
// eslint-disable-next-line object-shorthand
|
75
|
+
validate: async function (url: string, answers: Answers) {
|
76
|
+
if (url.toLowerCase() === 'exit') {
|
77
|
+
process.exit(1);
|
78
|
+
}
|
79
|
+
|
80
|
+
const statuses = constants.urlCheckStatuses;
|
81
|
+
const { browserToRun, clonedBrowserDataDir } = getBrowserToRun(BrowserTypes.CHROME);
|
82
|
+
const playwrightDeviceDetailsObject = getPlaywrightDeviceDetailsObject(
|
83
|
+
answers.deviceChosen,
|
84
|
+
answers.customDevice,
|
85
|
+
answers.viewportWidth,
|
86
|
+
);
|
87
|
+
|
88
|
+
const res = await checkUrl(
|
89
|
+
answers.scanner,
|
90
|
+
url,
|
91
|
+
browserToRun,
|
92
|
+
clonedBrowserDataDir,
|
93
|
+
playwrightDeviceDetailsObject,
|
94
|
+
answers.scanner === ScannerTypes.CUSTOM,
|
95
|
+
answers.header,
|
96
|
+
);
|
97
|
+
|
98
|
+
deleteClonedProfiles(browserToRun);
|
99
|
+
switch (res.status) {
|
100
|
+
case statuses.success.code:
|
101
|
+
answers.finalUrl = res.url;
|
102
|
+
return true;
|
103
|
+
case statuses.cannotBeResolved.code:
|
104
|
+
return statuses.cannotBeResolved.message;
|
105
|
+
case statuses.systemError.code:
|
106
|
+
return statuses.systemError.message;
|
107
|
+
case statuses.invalidUrl.code:
|
108
|
+
if (answers.scanner !== (ScannerTypes.SITEMAP || ScannerTypes.LOCALFILE)) {
|
109
|
+
return statuses.invalidUrl.message;
|
110
|
+
}
|
111
|
+
|
112
|
+
/* if sitemap scan is selected, treat this URL as a filepath
|
113
|
+
isFileSitemap will tell whether the filepath exists, and if it does, whether the
|
114
|
+
file is a sitemap */
|
115
|
+
const finalFilePath = getFileSitemap(url);
|
116
|
+
if (finalFilePath) {
|
117
|
+
answers.isLocalFileScan = true;
|
118
|
+
answers.finalUrl = finalFilePath;
|
119
|
+
return true;
|
120
|
+
}
|
121
|
+
if (answers.scanner === ScannerTypes.LOCALFILE) {
|
122
|
+
return statuses.notALocalFile.message;
|
123
|
+
}
|
124
|
+
return statuses.notASitemap.message;
|
125
|
+
|
126
|
+
case statuses.notASitemap.code:
|
127
|
+
return statuses.notASitemap.message;
|
128
|
+
case statuses.notALocalFile.code:
|
129
|
+
return statuses.notALocalFile.message;
|
130
|
+
}
|
131
|
+
},
|
132
|
+
filter: (input: string) => sanitizeUrlInput(input.trim()).url,
|
133
|
+
},
|
134
|
+
{
|
135
|
+
type: 'input',
|
136
|
+
name: 'customFlowLabel',
|
137
|
+
message: 'Give a preferred label to your custom scan flow (Optional)',
|
138
|
+
when: (answers: Answers) => answers.scanner === ScannerTypes.CUSTOM,
|
139
|
+
validate: (label: string) => {
|
140
|
+
const { isValid, errorMessage } = validateCustomFlowLabel(label);
|
141
|
+
if (!isValid) {
|
142
|
+
return errorMessage;
|
143
|
+
}
|
144
|
+
return true;
|
145
|
+
},
|
146
|
+
},
|
147
|
+
];
|
148
|
+
|
149
|
+
const newUserQuestions: Question[] = [
|
150
|
+
{
|
151
|
+
type: 'input',
|
152
|
+
name: 'name',
|
153
|
+
message: `Name:`,
|
154
|
+
validate: (name: string): string | boolean => {
|
155
|
+
if (!validName(name)) {
|
156
|
+
return 'Invalid name. Please provide a valid name. Only alphabets in under 50 characters allowed.';
|
157
|
+
}
|
158
|
+
return true;
|
159
|
+
},
|
160
|
+
},
|
161
|
+
{
|
162
|
+
type: 'input',
|
163
|
+
name: 'email',
|
164
|
+
message: `Email:`,
|
165
|
+
validate: (email: string): string | boolean => {
|
166
|
+
if (!validEmail(email)) {
|
167
|
+
return 'Invalid email address. Please provide a valid email address.';
|
168
|
+
}
|
169
|
+
return true;
|
170
|
+
},
|
171
|
+
},
|
172
|
+
];
|
173
|
+
|
174
|
+
if (userData) {
|
175
|
+
questions.unshift(...startScanQuestions);
|
176
|
+
} else {
|
177
|
+
newUserQuestions.push(...startScanQuestions);
|
178
|
+
questions.unshift(...newUserQuestions);
|
179
|
+
}
|
180
|
+
|
181
|
+
export default questions;
|