@govtechsg/oobee 0.10.76 → 0.10.77
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/publish.yml +8 -1
- package/INTEGRATION.md +7 -3
- package/dist/cli.js +252 -0
- package/dist/combine.js +221 -0
- package/dist/constants/cliFunctions.js +306 -0
- package/dist/constants/common.js +1669 -0
- package/dist/constants/constants.js +913 -0
- package/dist/constants/errorMeta.json +319 -0
- package/dist/constants/itemTypeDescription.js +7 -0
- package/dist/constants/oobeeAi.js +121 -0
- package/dist/constants/questions.js +151 -0
- package/dist/constants/sampleData.js +176 -0
- package/dist/crawlers/commonCrawlerFunc.js +428 -0
- package/dist/crawlers/crawlDomain.js +613 -0
- package/dist/crawlers/crawlIntelligentSitemap.js +135 -0
- package/dist/crawlers/crawlLocalFile.js +151 -0
- package/dist/crawlers/crawlSitemap.js +303 -0
- package/dist/crawlers/custom/escapeCssSelector.js +10 -0
- package/dist/crawlers/custom/evaluateAltText.js +11 -0
- package/dist/crawlers/custom/extractAndGradeText.js +44 -0
- package/dist/crawlers/custom/extractText.js +27 -0
- package/dist/crawlers/custom/findElementByCssSelector.js +36 -0
- package/dist/crawlers/custom/flagUnlabelledClickableElements.js +963 -0
- package/dist/crawlers/custom/framesCheck.js +37 -0
- package/dist/crawlers/custom/getAxeConfiguration.js +111 -0
- package/dist/crawlers/custom/gradeReadability.js +23 -0
- package/dist/crawlers/custom/utils.js +1024 -0
- package/dist/crawlers/custom/xPathToCss.js +147 -0
- package/dist/crawlers/guards/urlGuard.js +71 -0
- package/dist/crawlers/pdfScanFunc.js +276 -0
- package/dist/crawlers/runCustom.js +89 -0
- package/dist/exclusions.txt +7 -0
- package/dist/generateHtmlReport.js +144 -0
- package/dist/index.js +62 -0
- package/dist/logs.js +84 -0
- package/dist/mergeAxeResults.js +1571 -0
- package/dist/npmIndex.js +429 -0
- package/dist/proxyService.js +360 -0
- package/dist/runGenerateJustHtmlReport.js +16 -0
- package/dist/screenshotFunc/htmlScreenshotFunc.js +355 -0
- package/dist/screenshotFunc/pdfScreenshotFunc.js +645 -0
- package/dist/services/s3Uploader.js +127 -0
- package/dist/static/ejs/partials/components/allIssues/AllIssues.ejs +9 -0
- package/dist/static/ejs/partials/components/allIssues/CategoryBadges.ejs +82 -0
- package/dist/static/ejs/partials/components/allIssues/FilterBar.ejs +33 -0
- package/dist/static/ejs/partials/components/allIssues/IssuesTable.ejs +41 -0
- package/dist/static/ejs/partials/components/header/SiteInfo.ejs +119 -0
- package/dist/static/ejs/partials/components/header/aboutScanModal/AboutScanModal.ejs +15 -0
- package/dist/static/ejs/partials/components/header/aboutScanModal/ScanConfiguration.ejs +44 -0
- package/dist/static/ejs/partials/components/header/aboutScanModal/ScanDetails.ejs +142 -0
- package/dist/static/ejs/partials/components/prioritiseIssues/IssueDetailCard.ejs +36 -0
- package/dist/static/ejs/partials/components/prioritiseIssues/PrioritiseIssues.ejs +47 -0
- package/dist/static/ejs/partials/components/ruleModal/ruleOffcanvas.ejs +196 -0
- package/dist/static/ejs/partials/components/scannedPagesSegmentedTabs.ejs +48 -0
- package/dist/static/ejs/partials/components/screenshotLightbox.ejs +13 -0
- package/dist/static/ejs/partials/components/shared/InfoAlert.ejs +3 -0
- package/dist/static/ejs/partials/components/summaryScanAbout.ejs +141 -0
- package/dist/static/ejs/partials/components/summaryScanResults.ejs +16 -0
- package/dist/static/ejs/partials/components/summaryTable.ejs +20 -0
- package/dist/static/ejs/partials/components/summaryWcagCompliance.ejs +94 -0
- package/dist/static/ejs/partials/components/topTen.ejs +6 -0
- package/dist/static/ejs/partials/components/wcagCompliance/FailedCriteria.ejs +47 -0
- package/dist/static/ejs/partials/components/wcagCompliance/WcagCompliance.ejs +16 -0
- package/dist/static/ejs/partials/components/wcagCompliance/WcagGaugeBar.ejs +16 -0
- package/dist/static/ejs/partials/components/wcagCoverageDetails.ejs +18 -0
- package/dist/static/ejs/partials/footer.ejs +24 -0
- package/dist/static/ejs/partials/header.ejs +14 -0
- package/dist/static/ejs/partials/main.ejs +29 -0
- package/dist/static/ejs/partials/scripts/allIssues/AllIssues.ejs +376 -0
- package/dist/static/ejs/partials/scripts/bootstrap.ejs +8 -0
- package/dist/static/ejs/partials/scripts/categorySummary.ejs +141 -0
- package/dist/static/ejs/partials/scripts/decodeUnzipParse.ejs +3 -0
- package/dist/static/ejs/partials/scripts/header/SiteInfo.ejs +44 -0
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/AboutScanModal.ejs +51 -0
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanConfiguration.ejs +127 -0
- package/dist/static/ejs/partials/scripts/header/aboutScanModal/ScanDetails.ejs +60 -0
- package/dist/static/ejs/partials/scripts/highlightjs.ejs +335 -0
- package/dist/static/ejs/partials/scripts/popper.ejs +7 -0
- package/dist/static/ejs/partials/scripts/prioritiseIssues/IssueDetailCard.ejs +137 -0
- package/dist/static/ejs/partials/scripts/prioritiseIssues/PrioritiseIssues.ejs +214 -0
- package/dist/static/ejs/partials/scripts/prioritiseIssues/wcagSvgMap.ejs +861 -0
- package/dist/static/ejs/partials/scripts/ruleModal/constants.ejs +957 -0
- package/dist/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +353 -0
- package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +468 -0
- package/dist/static/ejs/partials/scripts/ruleModal/ruleOffcanvas.ejs +306 -0
- package/dist/static/ejs/partials/scripts/ruleModal/utilities.ejs +483 -0
- package/dist/static/ejs/partials/scripts/scannedPagesSegmentedTabs.ejs +35 -0
- package/dist/static/ejs/partials/scripts/screenshotLightbox.ejs +75 -0
- package/dist/static/ejs/partials/scripts/summaryScanResults.ejs +14 -0
- package/dist/static/ejs/partials/scripts/summaryTable.ejs +78 -0
- package/dist/static/ejs/partials/scripts/topTen.ejs +61 -0
- package/dist/static/ejs/partials/scripts/utils.ejs +453 -0
- package/dist/static/ejs/partials/scripts/wcagCompliance/FailedCriteria.ejs +103 -0
- package/dist/static/ejs/partials/scripts/wcagCompliance/WcagGaugeBar.ejs +47 -0
- package/dist/static/ejs/partials/scripts/wcagCompliance.ejs +15 -0
- package/dist/static/ejs/partials/scripts/wcagCoverageDetails.ejs +75 -0
- package/dist/static/ejs/partials/styles/allIssues/AllIssues.ejs +384 -0
- package/dist/static/ejs/partials/styles/bootstrap.ejs +12391 -0
- package/dist/static/ejs/partials/styles/header/SiteInfo.ejs +121 -0
- package/dist/static/ejs/partials/styles/header/aboutScanModal/AboutScanModal.ejs +82 -0
- package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanConfiguration.ejs +50 -0
- package/dist/static/ejs/partials/styles/header/aboutScanModal/ScanDetails.ejs +149 -0
- package/dist/static/ejs/partials/styles/header.ejs +7 -0
- package/dist/static/ejs/partials/styles/highlightjs.ejs +54 -0
- package/dist/static/ejs/partials/styles/prioritiseIssues/IssueDetailCard.ejs +141 -0
- package/dist/static/ejs/partials/styles/prioritiseIssues/PrioritiseIssues.ejs +204 -0
- package/dist/static/ejs/partials/styles/ruleModal/ruleOffcanvas.ejs +456 -0
- package/dist/static/ejs/partials/styles/scannedPagesSegmentedTabs.ejs +46 -0
- package/dist/static/ejs/partials/styles/shared/InfoAlert.ejs +12 -0
- package/dist/static/ejs/partials/styles/styles.ejs +1607 -0
- package/dist/static/ejs/partials/styles/summaryBootstrap.ejs +12458 -0
- package/dist/static/ejs/partials/styles/topTenCard.ejs +44 -0
- package/dist/static/ejs/partials/styles/wcagCompliance/FailedCriteria.ejs +59 -0
- package/dist/static/ejs/partials/styles/wcagCompliance/WcagGaugeBar.ejs +62 -0
- package/dist/static/ejs/partials/styles/wcagCompliance.ejs +36 -0
- package/dist/static/ejs/partials/styles/wcagCoverageDetails.ejs +33 -0
- package/dist/static/ejs/partials/summaryHeader.ejs +70 -0
- package/dist/static/ejs/partials/summaryMain.ejs +49 -0
- package/dist/static/ejs/report.ejs +226 -0
- package/dist/static/ejs/summary.ejs +47 -0
- package/dist/types/types.js +1 -0
- package/dist/utils.js +1070 -0
- package/examples/oobee-cypress-integration-js/cypress/support/e2e.js +36 -6
- package/examples/oobee-cypress-integration-js/cypress.config.js +45 -1
- package/examples/oobee-cypress-integration-ts/cypress.config.ts +47 -1
- package/examples/oobee-cypress-integration-ts/src/cypress/support/e2e.ts +36 -6
- package/examples/oobee-playwright-integration-js/oobee-playwright-demo.js +2 -1
- package/examples/oobee-playwright-integration-ts/src/oobee-playwright-demo.ts +2 -1
- package/package.json +9 -3
- package/src/constants/common.ts +2 -2
- package/src/constants/constants.ts +3 -1
- package/src/crawlers/crawlDomain.ts +1 -0
- package/src/crawlers/runCustom.ts +0 -1
- package/src/npmIndex.ts +42 -24
|
@@ -11,8 +11,15 @@ jobs:
|
|
|
11
11
|
# Setup .npmrc file to publish to npm
|
|
12
12
|
- uses: actions/setup-node@v3
|
|
13
13
|
with:
|
|
14
|
-
node-version: '
|
|
14
|
+
node-version: '22.x'
|
|
15
15
|
registry-url: 'https://registry.npmjs.org'
|
|
16
|
+
|
|
17
|
+
- run: npm ci
|
|
18
|
+
continue-on-error: false
|
|
19
|
+
|
|
20
|
+
- run: npm run build
|
|
21
|
+
continue-on-error: false
|
|
22
|
+
|
|
16
23
|
- run: npm publish
|
|
17
24
|
env:
|
|
18
25
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/INTEGRATION.md
CHANGED
|
@@ -106,15 +106,17 @@ Get the Oobee custom functions to run accessibility scan. Call this after `getAx
|
|
|
106
106
|
|
|
107
107
|
- Object consisting of the current page url, current page title and axe scan result. `{ pageUrl, pageTitle, axeScanResults }`
|
|
108
108
|
|
|
109
|
-
`async pushScanResults(res, metadata, elementsToClick)`
|
|
109
|
+
`async pushScanResults(res, metadata, elementsToClick, page, disableScreenshots)`
|
|
110
110
|
|
|
111
111
|
Process scan results to be included in the report.
|
|
112
112
|
|
|
113
113
|
Parameter(s):
|
|
114
114
|
|
|
115
115
|
- `res`: Object consisting of the current page url, current page title and axe scan result. ` {pageUrl, pageTitle, axeScanResults}`
|
|
116
|
-
- `metadata` (optional): Additional information to be
|
|
117
|
-
- `elementsToClick` (optional):
|
|
116
|
+
- `metadata` (optional): Additional string information to be stored. Useful for distinguishing between different states of the same page. Note: This feature is not implemented in the report HTML currently.
|
|
117
|
+
- `elementsToClick` (optional): A list of CSS selectors to click to reveal hidden elements (like modals or menus) before taking screenshots. This is only used if Oobee launches its own browser for screenshots (i.e., when `page` is not provided and `disableScreenshots` is false). Reproducing the state allows the screenshots to capture the violating elements correctly.
|
|
118
|
+
- `page` (optional): Playwright Page object. If provided, reuses the existing page for taking screenshots of affected elements.
|
|
119
|
+
- `disableScreenshots` (optional): Boolean to disable screenshot capturing. Useful when integrating with tools like Cypress where the browser context cannot be easily shared with Node.js.
|
|
118
120
|
|
|
119
121
|
Returns:
|
|
120
122
|
|
|
@@ -144,6 +146,8 @@ With reference to an instance of Oobee as `oobeeA11y`:
|
|
|
144
146
|
- It is possible to run the scan for specific sections or elements in the page. One way to do this is to pass an array of CSS selectors of the elements to be scanned into `runA11yScan`. For example, `runA11yScan(['#my-component', 'button'])`. Other acceptable forms of argument can be found [here](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter).
|
|
145
147
|
4. Pass the scan results back into the NodeJS environment where `oobeeA11y` is in.
|
|
146
148
|
5. Push the results using `await oobeeA11y.pushScanResults(scanResults)`.
|
|
149
|
+
- For Playwright, pass the `page` object as the 4th argument: `await oobeeA11y.pushScanResults(scanResults, undefined, undefined, page)`. This optimizes performance by reusing the existing browser for screenshots.
|
|
150
|
+
- For Cypress, to avoid launching a separate browser for screenshots, you must handle screenshot capturing manually and set `disableScreenshots` to `true`. Refer to the provided Cypress examples for the implementation details.
|
|
147
151
|
6. Repeat steps 2-5 as many times as desired.
|
|
148
152
|
7. Terminate Oobee by using `await oobeeA11y.terminate()`. A folder containing the details and report of your scan will be created, under the directory `results` which can be found in your project's root directory.
|
|
149
153
|
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import _yargs from 'yargs';
|
|
3
|
+
import { hideBin } from 'yargs/helpers';
|
|
4
|
+
import printMessage from 'print-message';
|
|
5
|
+
import { devices } from 'playwright';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { setHeadlessMode, getVersion, getStoragePath, listenForCleanUp, cleanUpAndExit } from './utils.js';
|
|
9
|
+
import { checkUrl, prepareData, validEmail, validName, getScreenToScan, validateDirPath, validateFilePath, validateCustomFlowLabel, } from './constants/common.js';
|
|
10
|
+
import constants, { ScannerTypes } from './constants/constants.js';
|
|
11
|
+
import { cliOptions, messageOptions } from './constants/cliFunctions.js';
|
|
12
|
+
import combineRun from './combine.js';
|
|
13
|
+
import { consoleLogger } from './logs.js';
|
|
14
|
+
const appVersion = getVersion();
|
|
15
|
+
const yargs = _yargs(hideBin(process.argv));
|
|
16
|
+
const options = yargs
|
|
17
|
+
.version(false)
|
|
18
|
+
.usage(`Oobee version: ${appVersion}
|
|
19
|
+
Usage: npm run cli -- -c <crawler> -d <device> -w <viewport> -u <url> OPTIONS`)
|
|
20
|
+
.strictOptions(true)
|
|
21
|
+
.options(cliOptions)
|
|
22
|
+
.example([
|
|
23
|
+
[
|
|
24
|
+
`To scan sitemap of website:', 'npm run cli -- -c [ 1 | sitemap ] -u <url_link> [ -d <device> | -w <viewport_width> ]`,
|
|
25
|
+
],
|
|
26
|
+
[
|
|
27
|
+
`To scan a website', 'npm run cli -- -c [ 2 | website ] -u <url_link> [ -d <device> | -w <viewport_width> ]`,
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
`To start a custom flow scan', 'npm run cli -- -c [ 3 | custom ] -u <url_link> [ -d <device> | -w <viewport_width> ]`,
|
|
31
|
+
],
|
|
32
|
+
])
|
|
33
|
+
.coerce('d', option => {
|
|
34
|
+
const device = devices[option];
|
|
35
|
+
if (!device && option !== 'Desktop' && option !== 'Mobile') {
|
|
36
|
+
printMessage([`Invalid device. Please provide an existing device to start the scan.`], messageOptions);
|
|
37
|
+
cleanUpAndExit(1);
|
|
38
|
+
}
|
|
39
|
+
return option;
|
|
40
|
+
})
|
|
41
|
+
.coerce('w', option => {
|
|
42
|
+
if (!option || Number.isNaN(option)) {
|
|
43
|
+
printMessage([`Invalid viewport width. Please provide a number. `], messageOptions);
|
|
44
|
+
cleanUpAndExit(1);
|
|
45
|
+
}
|
|
46
|
+
else if (option < 320 || option > 1080) {
|
|
47
|
+
printMessage(['Invalid viewport width! Please provide a viewport width between 320-1080 pixels.'], messageOptions);
|
|
48
|
+
cleanUpAndExit(1);
|
|
49
|
+
}
|
|
50
|
+
return option;
|
|
51
|
+
})
|
|
52
|
+
.coerce('p', option => {
|
|
53
|
+
if (!Number.isInteger(option) || Number(option) <= 0) {
|
|
54
|
+
printMessage([`Invalid maximum number of pages. Please provide a positive integer.`], messageOptions);
|
|
55
|
+
cleanUpAndExit(1);
|
|
56
|
+
}
|
|
57
|
+
return option;
|
|
58
|
+
})
|
|
59
|
+
.coerce('t', option => {
|
|
60
|
+
if (!Number.isInteger(option) || Number(option) <= 0) {
|
|
61
|
+
printMessage([`Invalid number for max concurrency. Please provide a positive integer.`], messageOptions);
|
|
62
|
+
cleanUpAndExit(1);
|
|
63
|
+
}
|
|
64
|
+
return option;
|
|
65
|
+
})
|
|
66
|
+
.coerce('k', nameEmail => {
|
|
67
|
+
if (nameEmail.indexOf(':') === -1) {
|
|
68
|
+
printMessage([`Invalid format. Please provide your name and email address separated by ":"`], messageOptions);
|
|
69
|
+
cleanUpAndExit(1);
|
|
70
|
+
}
|
|
71
|
+
const [name, email] = nameEmail.split(':');
|
|
72
|
+
if (name === '' || name === undefined || name === null) {
|
|
73
|
+
printMessage([`Please provide your name.`], messageOptions);
|
|
74
|
+
cleanUpAndExit(1);
|
|
75
|
+
}
|
|
76
|
+
if (!validName(name)) {
|
|
77
|
+
printMessage([`Invalid name. Please provide a valid name.`], messageOptions);
|
|
78
|
+
cleanUpAndExit(1);
|
|
79
|
+
}
|
|
80
|
+
if (!validEmail(email)) {
|
|
81
|
+
printMessage([`Invalid email address. Please provide a valid email address.`], messageOptions);
|
|
82
|
+
cleanUpAndExit(1);
|
|
83
|
+
}
|
|
84
|
+
return nameEmail;
|
|
85
|
+
})
|
|
86
|
+
.coerce('e', option => {
|
|
87
|
+
const validationErrors = validateDirPath(option);
|
|
88
|
+
if (validationErrors) {
|
|
89
|
+
printMessage([`Invalid exportDirectory directory path. ${validationErrors}`], messageOptions);
|
|
90
|
+
cleanUpAndExit(1);
|
|
91
|
+
}
|
|
92
|
+
return option;
|
|
93
|
+
})
|
|
94
|
+
.coerce('x', option => {
|
|
95
|
+
const filename = fileURLToPath(import.meta.url);
|
|
96
|
+
const dirname = `${path.dirname(filename)}/../`; // check in the parent of dist directory
|
|
97
|
+
try {
|
|
98
|
+
return validateFilePath(option, dirname);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
printMessage([`Invalid blacklistedPatternsFilename file path. ${err}`], messageOptions);
|
|
102
|
+
cleanUpAndExit(1);
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
.coerce('i', option => {
|
|
106
|
+
const { choices } = cliOptions.i;
|
|
107
|
+
if (!choices.includes(option)) {
|
|
108
|
+
printMessage([`Invalid value for fileTypes. Please provide valid keywords: ${choices.join(', ')}.`], messageOptions);
|
|
109
|
+
cleanUpAndExit(1);
|
|
110
|
+
}
|
|
111
|
+
return option;
|
|
112
|
+
})
|
|
113
|
+
.coerce('j', option => {
|
|
114
|
+
const { isValid, errorMessage } = validateCustomFlowLabel(option);
|
|
115
|
+
if (!isValid) {
|
|
116
|
+
printMessage([errorMessage], messageOptions);
|
|
117
|
+
cleanUpAndExit(1);
|
|
118
|
+
}
|
|
119
|
+
return option;
|
|
120
|
+
})
|
|
121
|
+
.coerce('a', option => {
|
|
122
|
+
const { choices } = cliOptions.a;
|
|
123
|
+
if (!choices.includes(option)) {
|
|
124
|
+
printMessage([`Invalid value for additional. Please provide valid keywords: ${choices.join(', ')}.`], messageOptions);
|
|
125
|
+
cleanUpAndExit(1);
|
|
126
|
+
}
|
|
127
|
+
return option;
|
|
128
|
+
})
|
|
129
|
+
.coerce('q', option => {
|
|
130
|
+
try {
|
|
131
|
+
JSON.parse(option);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// default to empty object
|
|
135
|
+
return '{}';
|
|
136
|
+
}
|
|
137
|
+
return option;
|
|
138
|
+
})
|
|
139
|
+
.coerce('m', option => {
|
|
140
|
+
return option;
|
|
141
|
+
})
|
|
142
|
+
.check(argvs => {
|
|
143
|
+
if (argvs.scanner === ScannerTypes.CUSTOM && argvs.maxpages) {
|
|
144
|
+
throw new Error('-p or --maxpages is only available in website, sitemap and local file scans.');
|
|
145
|
+
}
|
|
146
|
+
return true;
|
|
147
|
+
})
|
|
148
|
+
.check(argvs => {
|
|
149
|
+
if (argvs.scanner !== ScannerTypes.WEBSITE && argvs.strategy) {
|
|
150
|
+
throw new Error('-s or --strategy is only available in website scans.');
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
})
|
|
154
|
+
.coerce('l', (option) => {
|
|
155
|
+
const duration = Number(option);
|
|
156
|
+
if (isNaN(duration) || duration < 0) {
|
|
157
|
+
printMessage(['Invalid scan duration. Please provide a positive number of seconds.'], messageOptions);
|
|
158
|
+
cleanUpAndExit(1);
|
|
159
|
+
}
|
|
160
|
+
return duration;
|
|
161
|
+
})
|
|
162
|
+
.check(argvs => {
|
|
163
|
+
if (argvs.scanner === ScannerTypes.CUSTOM && typeof argvs.scanDuration === 'number' && argvs.scanDuration > 0) {
|
|
164
|
+
throw new Error('-l or --scanDuration is not allowed for custom flow scans.');
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
})
|
|
168
|
+
.conflicts('d', 'w')
|
|
169
|
+
.parse();
|
|
170
|
+
const scanInit = async (argvs) => {
|
|
171
|
+
const updatedArgvs = { ...argvs };
|
|
172
|
+
// Cannot use data.browser and data.isHeadless as the connectivity check comes first before prepareData
|
|
173
|
+
setHeadlessMode(updatedArgvs.browserToRun, updatedArgvs.headless);
|
|
174
|
+
const statuses = constants.urlCheckStatuses;
|
|
175
|
+
let data;
|
|
176
|
+
try {
|
|
177
|
+
data = await prepareData(updatedArgvs);
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
consoleLogger.error(`Error preparing data: ${e.message}\n${e.stack}`);
|
|
181
|
+
cleanUpAndExit(1);
|
|
182
|
+
}
|
|
183
|
+
// Executes cleanUp script if error encountered
|
|
184
|
+
listenForCleanUp(data.randomToken);
|
|
185
|
+
const res = await checkUrl(data.type, data.entryUrl, data.browser, data.userDataDirectory, data.playwrightDeviceDetailsObject, data.extraHTTPHeaders, data.fileTypes);
|
|
186
|
+
if (res.httpStatus)
|
|
187
|
+
consoleLogger.info(`Connectivity Check HTTP Response Code: ${res.httpStatus}`);
|
|
188
|
+
if (res.status === statuses.success.code) {
|
|
189
|
+
data.url = res.url;
|
|
190
|
+
if (process.env.OOBEE_VALIDATE_URL) {
|
|
191
|
+
consoleLogger.info('Url is valid');
|
|
192
|
+
cleanUpAndExit(0, data.randomToken);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// fall through (continue normal flow after success)
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const match = Object.values(statuses).find((s) => s.code === res.status);
|
|
199
|
+
const msg = match && 'message' in match ? match.message : 'Unknown error';
|
|
200
|
+
printMessage([msg], messageOptions);
|
|
201
|
+
consoleLogger.info(msg);
|
|
202
|
+
cleanUpAndExit(res.status);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (process.env.OOBEE_VERBOSE) {
|
|
206
|
+
const randomTokenMessage = {
|
|
207
|
+
type: 'randomToken',
|
|
208
|
+
payload: `${data.randomToken}`,
|
|
209
|
+
};
|
|
210
|
+
if (process.send) {
|
|
211
|
+
process.send(JSON.stringify(randomTokenMessage));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const screenToScan = getScreenToScan(data.deviceChosen, data.customDevice, data.viewportWidth);
|
|
215
|
+
printMessage([`Oobee version: ${appVersion}`, 'Starting scan...'], messageOptions);
|
|
216
|
+
consoleLogger.info(`Oobee version: ${appVersion}`);
|
|
217
|
+
await combineRun(data, screenToScan);
|
|
218
|
+
return getStoragePath(data.randomToken);
|
|
219
|
+
};
|
|
220
|
+
const optionsAnswer = {
|
|
221
|
+
scanner: options.scanner,
|
|
222
|
+
header: options.header,
|
|
223
|
+
browserToRun: options.browserToRun,
|
|
224
|
+
zip: options.zip,
|
|
225
|
+
url: options.url,
|
|
226
|
+
finalUrl: options.finalUrl,
|
|
227
|
+
headless: options.headless,
|
|
228
|
+
maxpages: options.maxpages,
|
|
229
|
+
metadata: options.metadata,
|
|
230
|
+
safeMode: options.safeMode,
|
|
231
|
+
strategy: options.strategy,
|
|
232
|
+
fileTypes: options.fileTypes,
|
|
233
|
+
nameEmail: options.nameEmail,
|
|
234
|
+
additional: options.additional,
|
|
235
|
+
customDevice: options.customDevice,
|
|
236
|
+
deviceChosen: options.deviceChosen,
|
|
237
|
+
followRobots: options.followRobots,
|
|
238
|
+
customFlowLabel: options.customFlowLabel,
|
|
239
|
+
viewportWidth: options.viewportWidth,
|
|
240
|
+
isLocalFileScan: options.isLocalFileScan,
|
|
241
|
+
exportDirectory: options.exportDirectory,
|
|
242
|
+
clonedBrowserDataDir: options.clonedBrowserDataDir,
|
|
243
|
+
specifiedMaxConcurrency: options.specifiedMaxConcurrency,
|
|
244
|
+
blacklistedPatternsFilename: options.blacklistedPatternsFilename,
|
|
245
|
+
playwrightDeviceDetailsObject: options.playwrightDeviceDetailsObject,
|
|
246
|
+
ruleset: options.ruleset,
|
|
247
|
+
generateJsonFiles: options.generateJsonFiles,
|
|
248
|
+
scanDuration: options.scanDuration,
|
|
249
|
+
};
|
|
250
|
+
await scanInit(optionsAnswer);
|
|
251
|
+
cleanUpAndExit(0);
|
|
252
|
+
export default options;
|
package/dist/combine.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import printMessage from 'print-message';
|
|
2
|
+
import { pathToFileURL } from 'url';
|
|
3
|
+
import crawlSitemap from './crawlers/crawlSitemap.js';
|
|
4
|
+
import crawlDomain from './crawlers/crawlDomain.js';
|
|
5
|
+
import crawlLocalFile from './crawlers/crawlLocalFile.js';
|
|
6
|
+
import crawlIntelligentSitemap from './crawlers/crawlIntelligentSitemap.js';
|
|
7
|
+
import generateArtifacts from './mergeAxeResults.js';
|
|
8
|
+
import { getHost, createAndUpdateResultsFolders, cleanUpAndExit, getStoragePath } from './utils.js';
|
|
9
|
+
import { ScannerTypes, UrlsCrawled } from './constants/constants.js';
|
|
10
|
+
import { getBlackListedPatterns, submitForm } from './constants/common.js';
|
|
11
|
+
import { consoleLogger } from './logs.js';
|
|
12
|
+
import runCustom from './crawlers/runCustom.js';
|
|
13
|
+
import { alertMessageOptions } from './constants/cliFunctions.js';
|
|
14
|
+
import { isS3UploadEnabled, getS3MetadataFromEnv, getS3UploadPrefix, uploadFolderToS3, } from './services/s3Uploader.js';
|
|
15
|
+
// Class exports
|
|
16
|
+
export class ViewportSettingsClass {
|
|
17
|
+
constructor(deviceChosen, customDevice, viewportWidth, playwrightDeviceDetailsObject) {
|
|
18
|
+
this.deviceChosen = deviceChosen;
|
|
19
|
+
this.customDevice = customDevice;
|
|
20
|
+
this.viewportWidth = viewportWidth;
|
|
21
|
+
this.playwrightDeviceDetailsObject = playwrightDeviceDetailsObject;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const combineRun = async (details, deviceToScan) => {
|
|
25
|
+
const envDetails = { ...details };
|
|
26
|
+
const { type, url, nameEmail, randomToken, deviceChosen, customDevice, viewportWidth, playwrightDeviceDetailsObject, maxRequestsPerCrawl, browser, userDataDirectory, strategy, // Allow subdomains: if checked, = 'same-domain'
|
|
27
|
+
specifiedMaxConcurrency, // Slow scan mode: if checked, = '1'
|
|
28
|
+
fileTypes, blacklistedPatternsFilename, includeScreenshots, // Include screenshots: if checked, = 'true'
|
|
29
|
+
followRobots, // Adhere to robots.txt: if checked, = 'true'
|
|
30
|
+
metadata, customFlowLabel = 'None', extraHTTPHeaders, safeMode, zip, ruleset, // Enable custom checks, Enable WCAG AAA: if checked, = 'enable-wcag-aaa')
|
|
31
|
+
generateJsonFiles, scanDuration, } = envDetails;
|
|
32
|
+
process.env.CRAWLEE_LOG_LEVEL = 'ERROR';
|
|
33
|
+
process.env.CRAWLEE_STORAGE_DIR = randomToken;
|
|
34
|
+
if (process.env.CRAWLEE_SYSTEM_INFO_V2 === undefined) {
|
|
35
|
+
// Set the environment variable to enable system info v2
|
|
36
|
+
// Resolves issue with when wmic is not installed on Windows
|
|
37
|
+
process.env.CRAWLEE_SYSTEM_INFO_V2 = '1';
|
|
38
|
+
}
|
|
39
|
+
const host = type === ScannerTypes.SITEMAP || type === ScannerTypes.LOCALFILE ? '' : getHost(url);
|
|
40
|
+
let blacklistedPatterns = null;
|
|
41
|
+
try {
|
|
42
|
+
blacklistedPatterns = getBlackListedPatterns(blacklistedPatternsFilename);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
consoleLogger.error(error);
|
|
46
|
+
cleanUpAndExit(1);
|
|
47
|
+
}
|
|
48
|
+
// remove basic-auth credentials from URL
|
|
49
|
+
const finalUrl = !(type === ScannerTypes.SITEMAP || type === ScannerTypes.LOCALFILE)
|
|
50
|
+
? new URL(url)
|
|
51
|
+
: new URL(pathToFileURL(url));
|
|
52
|
+
// Use the string version of finalUrl to reduce logic at submitForm
|
|
53
|
+
const finalUrlString = finalUrl.toString();
|
|
54
|
+
const scanDetails = {
|
|
55
|
+
startTime: new Date(),
|
|
56
|
+
endTime: new Date(),
|
|
57
|
+
crawlType: type,
|
|
58
|
+
requestUrl: finalUrl,
|
|
59
|
+
urlsCrawled: new UrlsCrawled(),
|
|
60
|
+
isIncludeScreenshots: envDetails.includeScreenshots,
|
|
61
|
+
isAllowSubdomains: envDetails.strategy,
|
|
62
|
+
isEnableCustomChecks: envDetails.ruleset,
|
|
63
|
+
isEnableWcagAaa: envDetails.ruleset,
|
|
64
|
+
isSlowScanMode: envDetails.specifiedMaxConcurrency,
|
|
65
|
+
isAdhereRobots: envDetails.followRobots,
|
|
66
|
+
deviceChosen: deviceToScan,
|
|
67
|
+
nameEmail: undefined,
|
|
68
|
+
};
|
|
69
|
+
// Parse nameEmail and add it to scanDetails for use in generateArtifacts
|
|
70
|
+
if (nameEmail) {
|
|
71
|
+
const [name, email] = nameEmail.split(':');
|
|
72
|
+
scanDetails.nameEmail = { name, email };
|
|
73
|
+
}
|
|
74
|
+
const viewportSettings = new ViewportSettingsClass(deviceChosen, customDevice, viewportWidth, playwrightDeviceDetailsObject);
|
|
75
|
+
let urlsCrawledObj;
|
|
76
|
+
let uiCustomFlowLabel;
|
|
77
|
+
let durationExceeded = false;
|
|
78
|
+
switch (type) {
|
|
79
|
+
case ScannerTypes.CUSTOM:
|
|
80
|
+
const res = await runCustom(url, randomToken, viewportSettings, blacklistedPatterns, includeScreenshots, customFlowLabel && customFlowLabel !== 'None' ? customFlowLabel : '');
|
|
81
|
+
urlsCrawledObj = res.urlsCrawled;
|
|
82
|
+
uiCustomFlowLabel = res.customFlowLabel;
|
|
83
|
+
break;
|
|
84
|
+
case ScannerTypes.SITEMAP:
|
|
85
|
+
const sitemapResult = await crawlSitemap({
|
|
86
|
+
sitemapUrl: url,
|
|
87
|
+
randomToken,
|
|
88
|
+
host,
|
|
89
|
+
viewportSettings,
|
|
90
|
+
maxRequestsPerCrawl,
|
|
91
|
+
browser,
|
|
92
|
+
userDataDirectory,
|
|
93
|
+
specifiedMaxConcurrency,
|
|
94
|
+
fileTypes,
|
|
95
|
+
blacklistedPatterns,
|
|
96
|
+
includeScreenshots,
|
|
97
|
+
extraHTTPHeaders,
|
|
98
|
+
scanDuration,
|
|
99
|
+
});
|
|
100
|
+
urlsCrawledObj = sitemapResult.urlsCrawled;
|
|
101
|
+
durationExceeded = sitemapResult.durationExceeded;
|
|
102
|
+
break;
|
|
103
|
+
case ScannerTypes.LOCALFILE:
|
|
104
|
+
const localFileResult = await crawlLocalFile({
|
|
105
|
+
url,
|
|
106
|
+
randomToken,
|
|
107
|
+
host,
|
|
108
|
+
viewportSettings,
|
|
109
|
+
maxRequestsPerCrawl,
|
|
110
|
+
browser,
|
|
111
|
+
userDataDirectory,
|
|
112
|
+
specifiedMaxConcurrency,
|
|
113
|
+
fileTypes,
|
|
114
|
+
blacklistedPatterns,
|
|
115
|
+
includeScreenshots,
|
|
116
|
+
extraHTTPHeaders,
|
|
117
|
+
scanDuration,
|
|
118
|
+
});
|
|
119
|
+
if (localFileResult) {
|
|
120
|
+
if ('urlsCrawled' in localFileResult) {
|
|
121
|
+
urlsCrawledObj = localFileResult.urlsCrawled;
|
|
122
|
+
durationExceeded = localFileResult.durationExceeded;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
urlsCrawledObj = localFileResult;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
case ScannerTypes.INTELLIGENT:
|
|
130
|
+
const intelligentResult = await crawlIntelligentSitemap(url, randomToken, host, viewportSettings, maxRequestsPerCrawl, browser, userDataDirectory, strategy, specifiedMaxConcurrency, fileTypes, blacklistedPatterns, includeScreenshots, followRobots, extraHTTPHeaders, safeMode, scanDuration);
|
|
131
|
+
urlsCrawledObj = intelligentResult.urlsCrawled;
|
|
132
|
+
durationExceeded = intelligentResult.durationExceeded;
|
|
133
|
+
break;
|
|
134
|
+
case ScannerTypes.WEBSITE:
|
|
135
|
+
const websiteResult = await crawlDomain({
|
|
136
|
+
url,
|
|
137
|
+
randomToken,
|
|
138
|
+
host,
|
|
139
|
+
viewportSettings,
|
|
140
|
+
maxRequestsPerCrawl,
|
|
141
|
+
browser,
|
|
142
|
+
userDataDirectory,
|
|
143
|
+
strategy,
|
|
144
|
+
specifiedMaxConcurrency,
|
|
145
|
+
fileTypes,
|
|
146
|
+
blacklistedPatterns,
|
|
147
|
+
includeScreenshots,
|
|
148
|
+
followRobots,
|
|
149
|
+
extraHTTPHeaders,
|
|
150
|
+
scanDuration,
|
|
151
|
+
safeMode,
|
|
152
|
+
ruleset,
|
|
153
|
+
});
|
|
154
|
+
urlsCrawledObj = websiteResult.urlsCrawled;
|
|
155
|
+
durationExceeded = websiteResult.durationExceeded;
|
|
156
|
+
break;
|
|
157
|
+
default:
|
|
158
|
+
consoleLogger.error(`type: ${type} not defined`);
|
|
159
|
+
cleanUpAndExit(1);
|
|
160
|
+
}
|
|
161
|
+
scanDetails.endTime = new Date();
|
|
162
|
+
scanDetails.urlsCrawled = urlsCrawledObj;
|
|
163
|
+
if (scanDetails.urlsCrawled) {
|
|
164
|
+
if (scanDetails.urlsCrawled.scanned.length > 0) {
|
|
165
|
+
await createAndUpdateResultsFolders(randomToken);
|
|
166
|
+
const pagesNotScanned = [
|
|
167
|
+
...urlsCrawledObj.error,
|
|
168
|
+
...urlsCrawledObj.invalid,
|
|
169
|
+
...urlsCrawledObj.forbidden,
|
|
170
|
+
...urlsCrawledObj.userExcluded,
|
|
171
|
+
];
|
|
172
|
+
const basicFormHTMLSnippet = await generateArtifacts(randomToken, url, type, deviceToScan, urlsCrawledObj.scanned, pagesNotScanned, uiCustomFlowLabel && uiCustomFlowLabel.length > 0
|
|
173
|
+
? uiCustomFlowLabel
|
|
174
|
+
: customFlowLabel || 'None', undefined, scanDetails, zip, generateJsonFiles);
|
|
175
|
+
const [name, email] = nameEmail.split(':');
|
|
176
|
+
// Upload results to S3 if environment variables are set
|
|
177
|
+
if (isS3UploadEnabled()) {
|
|
178
|
+
const siteName = (urlsCrawledObj.scanned[0]?.pageTitle ?? '')
|
|
179
|
+
.replace(/^\d+\s*:\s*/, '')
|
|
180
|
+
.trim();
|
|
181
|
+
const scanMetadata = getS3MetadataFromEnv(siteName, durationExceeded);
|
|
182
|
+
const s3Prefix = getS3UploadPrefix();
|
|
183
|
+
if (scanMetadata && s3Prefix) {
|
|
184
|
+
try {
|
|
185
|
+
const storagePath = getStoragePath(randomToken);
|
|
186
|
+
consoleLogger.info('Starting S3 upload...');
|
|
187
|
+
consoleLogger.info(`Upload path: ${s3Prefix}`);
|
|
188
|
+
const uploadedFiles = await uploadFolderToS3(storagePath, s3Prefix, scanMetadata);
|
|
189
|
+
consoleLogger.info(`Successfully uploaded ${uploadedFiles.length} files to S3`);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
193
|
+
consoleLogger.error(`Failed to upload results to S3: ${errorMessage}`);
|
|
194
|
+
// Don't fail the scan if S3 upload fails
|
|
195
|
+
consoleLogger.warn('Continuing without S3 upload...');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
consoleLogger.warn('S3 upload enabled but metadata/prefix not available');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
consoleLogger.info('S3 upload not enabled (missing environment variables)');
|
|
204
|
+
}
|
|
205
|
+
await submitForm(browser, userDataDirectory, url, // scannedUrl
|
|
206
|
+
new URL(finalUrlString).href, // entryUrl
|
|
207
|
+
type, email, name, JSON.stringify(basicFormHTMLSnippet), urlsCrawledObj.scanned.length, urlsCrawledObj.scannedRedirects.length, pagesNotScanned.length, metadata);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
// No page were scanned because the URL loaded does not meet the crawler requirements
|
|
211
|
+
printMessage([`No pages were scanned.`], alertMessageOptions);
|
|
212
|
+
cleanUpAndExit(1, randomToken, true);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
// No page were scanned because the URL loaded does not meet the crawler requirements
|
|
217
|
+
printMessage([`No pages were scanned.`], alertMessageOptions);
|
|
218
|
+
cleanUpAndExit(1, randomToken, true);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
export default combineRun;
|