@govtechsg/oobee 0.10.76 → 0.10.78-alpha1

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.
Files changed (137) hide show
  1. package/.github/workflows/publish.yml +8 -1
  2. package/INTEGRATION.md +50 -3
  3. package/dist/cli.js +252 -0
  4. package/dist/combine.js +221 -0
  5. package/dist/constants/cliFunctions.js +306 -0
  6. package/dist/constants/common.js +1669 -0
  7. package/dist/constants/constants.js +913 -0
  8. package/dist/constants/errorMeta.json +319 -0
  9. package/dist/constants/itemTypeDescription.js +7 -0
  10. package/dist/constants/oobeeAi.js +121 -0
  11. package/dist/constants/questions.js +151 -0
  12. package/dist/constants/sampleData.js +176 -0
  13. package/dist/crawlers/commonCrawlerFunc.js +428 -0
  14. package/dist/crawlers/crawlDomain.js +613 -0
  15. package/dist/crawlers/crawlIntelligentSitemap.js +135 -0
  16. package/dist/crawlers/crawlLocalFile.js +151 -0
  17. package/dist/crawlers/crawlSitemap.js +303 -0
  18. package/dist/crawlers/custom/escapeCssSelector.js +10 -0
  19. package/dist/crawlers/custom/evaluateAltText.js +11 -0
  20. package/dist/crawlers/custom/extractAndGradeText.js +44 -0
  21. package/dist/crawlers/custom/extractText.js +27 -0
  22. package/dist/crawlers/custom/findElementByCssSelector.js +36 -0
  23. package/dist/crawlers/custom/flagUnlabelledClickableElements.js +963 -0
  24. package/dist/crawlers/custom/framesCheck.js +37 -0
  25. package/dist/crawlers/custom/getAxeConfiguration.js +111 -0
  26. package/dist/crawlers/custom/gradeReadability.js +23 -0
  27. package/dist/crawlers/custom/utils.js +1024 -0
  28. package/dist/crawlers/custom/xPathToCss.js +147 -0
  29. package/dist/crawlers/guards/urlGuard.js +71 -0
  30. package/dist/crawlers/pdfScanFunc.js +276 -0
  31. package/dist/crawlers/runCustom.js +89 -0
  32. package/dist/exclusions.txt +7 -0
  33. package/dist/generateHtmlReport.js +144 -0
  34. package/dist/index.js +62 -0
  35. package/dist/logs.js +84 -0
  36. package/dist/mergeAxeResults.js +1588 -0
  37. package/dist/npmIndex.js +640 -0
  38. package/dist/proxyService.js +360 -0
  39. package/dist/runGenerateJustHtmlReport.js +16 -0
  40. package/dist/screenshotFunc/htmlScreenshotFunc.js +355 -0
  41. package/dist/screenshotFunc/pdfScreenshotFunc.js +645 -0
  42. package/dist/services/s3Uploader.js +127 -0
  43. package/dist/static/ejs/partials/components/allIssues/AllIssues.ejs +9 -0
  44. package/dist/static/ejs/partials/components/allIssues/CategoryBadges.ejs +82 -0
  45. package/dist/static/ejs/partials/components/allIssues/FilterBar.ejs +33 -0
  46. package/dist/static/ejs/partials/components/allIssues/IssuesTable.ejs +41 -0
  47. package/dist/static/ejs/partials/components/header/SiteInfo.ejs +119 -0
  48. package/dist/static/ejs/partials/components/header/aboutScanModal/AboutScanModal.ejs +15 -0
  49. package/dist/static/ejs/partials/components/header/aboutScanModal/ScanConfiguration.ejs +44 -0
  50. package/dist/static/ejs/partials/components/header/aboutScanModal/ScanDetails.ejs +142 -0
  51. package/dist/static/ejs/partials/components/prioritiseIssues/IssueDetailCard.ejs +36 -0
  52. package/dist/static/ejs/partials/components/prioritiseIssues/PrioritiseIssues.ejs +47 -0
  53. package/dist/static/ejs/partials/components/ruleModal/ruleOffcanvas.ejs +196 -0
  54. package/dist/static/ejs/partials/components/scannedPagesSegmentedTabs.ejs +48 -0
  55. package/dist/static/ejs/partials/components/screenshotLightbox.ejs +13 -0
  56. package/dist/static/ejs/partials/components/shared/InfoAlert.ejs +3 -0
  57. package/dist/static/ejs/partials/components/summaryScanAbout.ejs +141 -0
  58. package/dist/static/ejs/partials/components/summaryScanResults.ejs +16 -0
  59. package/dist/static/ejs/partials/components/summaryTable.ejs +20 -0
  60. package/dist/static/ejs/partials/components/summaryWcagCompliance.ejs +94 -0
  61. package/dist/static/ejs/partials/components/topTen.ejs +6 -0
  62. package/dist/static/ejs/partials/components/wcagCompliance/FailedCriteria.ejs +47 -0
  63. package/dist/static/ejs/partials/components/wcagCompliance/WcagCompliance.ejs +16 -0
  64. package/dist/static/ejs/partials/components/wcagCompliance/WcagGaugeBar.ejs +16 -0
  65. package/dist/static/ejs/partials/components/wcagCoverageDetails.ejs +18 -0
  66. package/dist/static/ejs/partials/footer.ejs +24 -0
  67. package/dist/static/ejs/partials/header.ejs +14 -0
  68. package/dist/static/ejs/partials/main.ejs +29 -0
  69. package/dist/static/ejs/partials/scripts/allIssues/AllIssues.ejs +376 -0
  70. package/dist/static/ejs/partials/scripts/bootstrap.ejs +8 -0
  71. package/dist/static/ejs/partials/scripts/categorySummary.ejs +141 -0
  72. package/dist/static/ejs/partials/scripts/decodeUnzipParse.ejs +3 -0
  73. package/dist/static/ejs/partials/scripts/header/SiteInfo.ejs +44 -0
  74. package/dist/static/ejs/partials/scripts/header/aboutScanModal/AboutScanModal.ejs +51 -0
  75. package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanConfiguration.ejs +127 -0
  76. package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanDetails.ejs +60 -0
  77. package/dist/static/ejs/partials/scripts/highlightjs.ejs +335 -0
  78. package/dist/static/ejs/partials/scripts/popper.ejs +7 -0
  79. package/dist/static/ejs/partials/scripts/prioritiseIssues/IssueDetailCard.ejs +137 -0
  80. package/dist/static/ejs/partials/scripts/prioritiseIssues/PrioritiseIssues.ejs +214 -0
  81. package/dist/static/ejs/partials/scripts/prioritiseIssues/wcagSvgMap.ejs +861 -0
  82. package/dist/static/ejs/partials/scripts/ruleModal/constants.ejs +957 -0
  83. package/dist/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +353 -0
  84. package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +468 -0
  85. package/dist/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +306 -0
  86. package/dist/static/ejs/partials/scripts/ruleModal/utilities.ejs +483 -0
  87. package/dist/static/ejs/partials/scripts/scannedPagesSegmentedTabs.ejs +35 -0
  88. package/dist/static/ejs/partials/scripts/screenshotLightbox.ejs +75 -0
  89. package/dist/static/ejs/partials/scripts/summaryScanResults.ejs +14 -0
  90. package/dist/static/ejs/partials/scripts/summaryTable.ejs +78 -0
  91. package/dist/static/ejs/partials/scripts/topTen.ejs +61 -0
  92. package/dist/static/ejs/partials/scripts/utils.ejs +453 -0
  93. package/dist/static/ejs/partials/scripts/wcagCompliance/FailedCriteria.ejs +103 -0
  94. package/dist/static/ejs/partials/scripts/wcagCompliance/WcagGaugeBar.ejs +47 -0
  95. package/dist/static/ejs/partials/scripts/wcagCompliance.ejs +15 -0
  96. package/dist/static/ejs/partials/scripts/wcagCoverageDetails.ejs +75 -0
  97. package/dist/static/ejs/partials/styles/allIssues/AllIssues.ejs +384 -0
  98. package/dist/static/ejs/partials/styles/bootstrap.ejs +12391 -0
  99. package/dist/static/ejs/partials/styles/header/SiteInfo.ejs +121 -0
  100. package/dist/static/ejs/partials/styles/header/aboutScanModal/AboutScanModal.ejs +82 -0
  101. package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanConfiguration.ejs +50 -0
  102. package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanDetails.ejs +149 -0
  103. package/dist/static/ejs/partials/styles/header.ejs +7 -0
  104. package/dist/static/ejs/partials/styles/highlightjs.ejs +54 -0
  105. package/dist/static/ejs/partials/styles/prioritiseIssues/IssueDetailCard.ejs +141 -0
  106. package/dist/static/ejs/partials/styles/prioritiseIssues/PrioritiseIssues.ejs +204 -0
  107. package/dist/static/ejs/partials/styles/ruleModal/ruleOffcanvas.ejs +456 -0
  108. package/dist/static/ejs/partials/styles/scannedPagesSegmentedTabs.ejs +46 -0
  109. package/dist/static/ejs/partials/styles/shared/InfoAlert.ejs +12 -0
  110. package/dist/static/ejs/partials/styles/styles.ejs +1607 -0
  111. package/dist/static/ejs/partials/styles/summaryBootstrap.ejs +12458 -0
  112. package/dist/static/ejs/partials/styles/topTenCard.ejs +44 -0
  113. package/dist/static/ejs/partials/styles/wcagCompliance/FailedCriteria.ejs +59 -0
  114. package/dist/static/ejs/partials/styles/wcagCompliance/WcagGaugeBar.ejs +62 -0
  115. package/dist/static/ejs/partials/styles/wcagCompliance.ejs +36 -0
  116. package/dist/static/ejs/partials/styles/wcagCoverageDetails.ejs +33 -0
  117. package/dist/static/ejs/partials/summaryHeader.ejs +70 -0
  118. package/dist/static/ejs/partials/summaryMain.ejs +49 -0
  119. package/dist/static/ejs/report.ejs +226 -0
  120. package/dist/static/ejs/summary.ejs +47 -0
  121. package/dist/types/types.js +1 -0
  122. package/dist/utils.js +1070 -0
  123. package/examples/oobee-cypress-integration-js/cypress/support/e2e.js +36 -6
  124. package/examples/oobee-cypress-integration-js/cypress.config.js +45 -1
  125. package/examples/oobee-cypress-integration-ts/cypress.config.ts +47 -1
  126. package/examples/oobee-cypress-integration-ts/src/cypress/support/e2e.ts +36 -6
  127. package/examples/oobee-playwright-integration-js/oobee-playwright-demo.js +2 -1
  128. package/examples/oobee-playwright-integration-ts/src/oobee-playwright-demo.ts +2 -1
  129. package/examples/oobee-scan-html-demo.js +51 -0
  130. package/examples/oobee-scan-page-demo.js +40 -0
  131. package/package.json +9 -3
  132. package/src/constants/common.ts +2 -2
  133. package/src/constants/constants.ts +3 -1
  134. package/src/crawlers/crawlDomain.ts +1 -0
  135. package/src/crawlers/runCustom.ts +0 -1
  136. package/src/mergeAxeResults.ts +43 -22
  137. package/src/npmIndex.ts +500 -131
@@ -0,0 +1,306 @@
1
+ import printMessage from 'print-message';
2
+ import { BrowserTypes, RuleFlags, ScannerTypes } from './constants.js';
3
+ import { cleanUpAndExit } from '../utils.js';
4
+ export const messageOptions = {
5
+ border: false,
6
+ marginTop: 2,
7
+ marginBottom: 2,
8
+ };
9
+ export const alertMessageOptions = {
10
+ border: true,
11
+ borderColor: 'red',
12
+ };
13
+ export const cliOptions = {
14
+ c: {
15
+ alias: 'scanner',
16
+ describe: 'Type of scan, 1) sitemap, 2) website crawl, 3) custom flow, 4) intelligent 5) local file',
17
+ requiresArg: true,
18
+ coerce: option => {
19
+ const choices = ['sitemap', 'website', 'custom', 'intelligent', 'localfile'];
20
+ let resolvedOption = option;
21
+ if (typeof option === 'number') {
22
+ // Will also allow integer choices
23
+ if (Number.isInteger(resolvedOption) &&
24
+ resolvedOption > 0 &&
25
+ resolvedOption <= choices.length) {
26
+ resolvedOption = choices[resolvedOption - 1];
27
+ }
28
+ }
29
+ switch (resolvedOption) {
30
+ case 'sitemap':
31
+ return ScannerTypes.SITEMAP;
32
+ case 'website':
33
+ return ScannerTypes.WEBSITE;
34
+ case 'custom':
35
+ return ScannerTypes.CUSTOM;
36
+ case 'localfile':
37
+ return ScannerTypes.LOCALFILE;
38
+ case 'intelligent':
39
+ return ScannerTypes.INTELLIGENT;
40
+ default:
41
+ printMessage([
42
+ `Invalid option: ${resolvedOption}`,
43
+ `Please enter an integer (1 to ${choices.length}) or keywords (${choices.join(', ')}).`,
44
+ ], messageOptions);
45
+ cleanUpAndExit(1);
46
+ return null;
47
+ }
48
+ },
49
+ demandOption: true,
50
+ },
51
+ u: {
52
+ alias: 'url',
53
+ describe: 'Website URL you want to scan',
54
+ type: 'string',
55
+ demandOption: true,
56
+ },
57
+ d: {
58
+ alias: 'customDevice',
59
+ describe: 'Device you want to scan',
60
+ type: 'string',
61
+ demandOption: false,
62
+ },
63
+ w: {
64
+ alias: 'viewportWidth',
65
+ describe: 'Viewport width (in pixels) you want to scan',
66
+ type: 'number',
67
+ demandOption: false,
68
+ },
69
+ o: {
70
+ alias: 'zip',
71
+ describe: 'Zip filename to save results',
72
+ type: 'string',
73
+ demandOption: false,
74
+ },
75
+ p: {
76
+ alias: 'maxpages',
77
+ describe: 'Maximum number of pages to scan (default: 100). Only available in website and sitemap scans',
78
+ type: 'number',
79
+ demandOption: false,
80
+ },
81
+ f: {
82
+ alias: 'safeMode',
83
+ describe: 'Disable dynamically clicking of page buttons and links to find links, which resolve issues on some websites. [yes / no]',
84
+ type: 'string',
85
+ requiresArg: true,
86
+ default: 'no',
87
+ demandOption: false,
88
+ coerce: (value) => {
89
+ if (value.toLowerCase() === 'yes') {
90
+ return true;
91
+ }
92
+ if (value.toLowerCase() === 'no') {
93
+ return false;
94
+ }
95
+ throw new Error(`Invalid value "${value}" for -f, --safeMode. Use "yes" or "no".`);
96
+ },
97
+ },
98
+ h: {
99
+ alias: 'headless',
100
+ describe: 'Run the scan in headless mode. [yes / no]',
101
+ type: 'string',
102
+ requiresArg: true,
103
+ default: 'yes',
104
+ demandOption: false,
105
+ coerce: (value) => {
106
+ if (value.toLowerCase() === 'yes') {
107
+ return true;
108
+ }
109
+ if (value.toLowerCase() === 'no') {
110
+ return false;
111
+ }
112
+ throw new Error(`Invalid value "${value}" for -h, --headless. Use "yes" or "no".`);
113
+ },
114
+ },
115
+ b: {
116
+ alias: 'browserToRun',
117
+ describe: 'Browser to run the scan on: 1) Chromium, 2) Chrome, 3) Edge. Defaults to Chromium.',
118
+ requiresArg: true,
119
+ coerce: option => {
120
+ const choices = ['chromium', 'chrome', 'edge'];
121
+ let resolvedOption = option;
122
+ if (typeof option === 'number') {
123
+ // Will also allow integer choices
124
+ if (Number.isInteger(resolvedOption) &&
125
+ resolvedOption > 0 &&
126
+ resolvedOption <= choices.length) {
127
+ resolvedOption = choices[resolvedOption - 1];
128
+ }
129
+ }
130
+ switch (resolvedOption) {
131
+ case 'chromium':
132
+ return BrowserTypes.CHROMIUM;
133
+ case 'chrome':
134
+ return BrowserTypes.CHROME;
135
+ case 'edge':
136
+ return BrowserTypes.EDGE;
137
+ default:
138
+ printMessage([
139
+ `Invalid option: ${resolvedOption}`,
140
+ `Please enter an integer (1 to ${choices.length}) or keywords (${choices.join(', ')}).`,
141
+ ], messageOptions);
142
+ cleanUpAndExit(1);
143
+ return null;
144
+ }
145
+ },
146
+ demandOption: false,
147
+ },
148
+ s: {
149
+ alias: 'strategy',
150
+ describe: 'Crawls up to general (same parent) domains, or only specific hostname. Defaults to "same-domain".',
151
+ choices: ['same-domain', 'same-hostname'],
152
+ requiresArg: true,
153
+ demandOption: false,
154
+ },
155
+ e: {
156
+ alias: 'exportDirectory',
157
+ describe: 'Preferred directory to store scan results. Path is relative to your home directory.',
158
+ type: 'string',
159
+ requiresArg: true,
160
+ demandOption: false,
161
+ },
162
+ j: {
163
+ alias: 'customFlowLabel',
164
+ describe: 'Give Custom Flow Scan a label for easier reference in the report',
165
+ type: 'string',
166
+ requiresArg: true,
167
+ demandOption: false,
168
+ },
169
+ k: {
170
+ alias: 'nameEmail',
171
+ describe: `To personalise your experience, we will be collecting your name, email address and app usage data. Your information fully complies with GovTech’s Privacy Policy. Please provide your name and email address in this format "John Doe:john@domain.com".`,
172
+ type: 'string',
173
+ demandOption: true,
174
+ },
175
+ t: {
176
+ alias: 'specifiedMaxConcurrency',
177
+ describe: 'Maximum number of pages to scan concurrently. Use for sites with throttling. Defaults to 25.',
178
+ type: 'number',
179
+ demandOption: false,
180
+ },
181
+ i: {
182
+ alias: 'fileTypes',
183
+ describe: 'File types to include in the scan. Defaults to html-only.',
184
+ type: 'string',
185
+ choices: ['all', 'pdf-only', 'html-only'],
186
+ demandOption: false,
187
+ requiresArg: true,
188
+ default: 'html-only',
189
+ },
190
+ x: {
191
+ alias: 'blacklistedPatternsFilename',
192
+ describe: 'Txt file that has a list of pattern of domains to exclude from accessibility scan separated by new line',
193
+ type: 'string',
194
+ demandOption: false,
195
+ },
196
+ a: {
197
+ alias: 'additional',
198
+ describe: 'Additional features to include in the report: \nscreenshots - Include element screenshots in the generated report \nnone - Exclude all additional features in the generated report',
199
+ type: 'string',
200
+ default: 'screenshots',
201
+ choices: ['screenshots', 'none'],
202
+ requiresArg: true,
203
+ demandOption: false,
204
+ },
205
+ q: {
206
+ alias: 'metadata',
207
+ describe: 'Json string that contains additional scan metadata for telemetry purposes. Defaults to "{}"',
208
+ type: 'string',
209
+ default: '{}',
210
+ demandOption: false,
211
+ },
212
+ r: {
213
+ alias: 'followRobots',
214
+ describe: 'Crawler adheres to robots.txt rules if it exists. [yes / no]',
215
+ type: 'string',
216
+ requiresArg: true,
217
+ default: 'no',
218
+ demandOption: false,
219
+ coerce: (value) => {
220
+ if (value.toLowerCase() === 'yes') {
221
+ return true;
222
+ }
223
+ if (value.toLowerCase() === 'no') {
224
+ return false;
225
+ }
226
+ throw new Error(`Invalid value "${value}" for -r, --followRobots. Use "yes" or "no".`);
227
+ },
228
+ },
229
+ m: {
230
+ alias: 'header',
231
+ describe: 'The HTTP authentication header keys and their respective values to enable crawler access to restricted resources.',
232
+ type: 'string',
233
+ requiresArg: true,
234
+ demandOption: false,
235
+ },
236
+ y: {
237
+ alias: 'ruleset',
238
+ describe: 'Specify scan ruleset for accessibility checks',
239
+ type: 'string',
240
+ choices: ['default', 'disable-oobee', 'enable-wcag-aaa', 'disable-oobee,enable-wcag-aaa'],
241
+ demandOption: false,
242
+ requiresArg: true,
243
+ default: 'default',
244
+ coerce: option => {
245
+ const validChoices = Object.values(RuleFlags);
246
+ const userChoices = String(option).split(',');
247
+ const invalidUserChoices = userChoices.filter(choice => !validChoices.includes(choice));
248
+ if (invalidUserChoices.length > 0) {
249
+ printMessage([
250
+ `Invalid values ${invalidUserChoices.join(',')} for -y, --ruleset. Please provide valid values: ${validChoices.join(', ')}.`,
251
+ ], messageOptions);
252
+ cleanUpAndExit(1);
253
+ }
254
+ if (userChoices.length > 1 && userChoices.includes('default')) {
255
+ printMessage([
256
+ `default and ${userChoices.filter(choice => choice !== 'default').join(',')} are mutually exclusive`,
257
+ ], messageOptions);
258
+ cleanUpAndExit(1);
259
+ }
260
+ return userChoices;
261
+ },
262
+ },
263
+ g: {
264
+ alias: 'generateJsonFiles',
265
+ describe: `Generate two gzipped and base64-encoded JSON files containing the results of the accessibility scan:\n
266
+ 1. scanData.json.gz.b64: Provides an overview of the scan, including:
267
+ - WCAG compliance score
268
+ - Violated WCAG clauses
269
+ - Metadata (e.g., scan start and end times)
270
+ - Pages scanned and skipped
271
+ 2. scanItems.json.gz.b64: Contains detailed information about detected accessibility issues, including:
272
+ - Severity levels
273
+ - Issue descriptions
274
+ - Related WCAG guidelines
275
+ - URL of the pages violated the WCAG clauses
276
+ Useful for in-depth analysis or integration with external reporting tools.\n
277
+ To obtain the JSON files, you need to base64-decode the file followed by gunzip. For example:\n
278
+ (macOS) base64 -D -i scanData.json.gz.b64 | gunzip > scanData.json\n
279
+ (linux) base64 -d scanData.json.gz.b64 | gunzip > scanData.json\n
280
+ `,
281
+ type: 'string',
282
+ requiresArg: true,
283
+ default: 'no',
284
+ demandOption: false,
285
+ coerce: value => {
286
+ const validYes = ['yes', 'y'];
287
+ const validNo = ['no', 'n'];
288
+ if (validYes.includes(value.toLowerCase())) {
289
+ return true;
290
+ }
291
+ if (validNo.includes(value.toLowerCase())) {
292
+ return false;
293
+ }
294
+ throw new Error(`Invalid value "${value}" for --generate. Use "yes", "y", "no", or "n".`);
295
+ },
296
+ },
297
+ l: {
298
+ alias: 'scanDuration',
299
+ describe: 'Maximum scan duration in seconds (0 means unlimited)',
300
+ type: 'number',
301
+ requiresArg: true,
302
+ default: 0,
303
+ demandOption: false,
304
+ coerce: val => Number(val),
305
+ },
306
+ };