@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.
- package/.github/workflows/publish.yml +8 -1
- package/INTEGRATION.md +50 -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 +1588 -0
- package/dist/npmIndex.js +640 -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/examples/oobee-scan-html-demo.js +51 -0
- package/examples/oobee-scan-page-demo.js +40 -0
- 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/mergeAxeResults.ts +43 -22
- package/src/npmIndex.ts +500 -131
|
@@ -55,12 +55,42 @@ Cypress.Commands.add("runOobeeA11yScan", (items = {}) => {
|
|
|
55
55
|
elementsToScan,
|
|
56
56
|
gradingReadabilityFlag,
|
|
57
57
|
);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
|
|
59
|
+
const processNodes = (nodes) => {
|
|
60
|
+
if (!nodes) return;
|
|
61
|
+
cy.wrap(nodes).each((node, index) => {
|
|
62
|
+
if (node.target && node.target.length > 0) {
|
|
63
|
+
const selector = node.target[0];
|
|
64
|
+
// Generate a unique filename
|
|
65
|
+
const filename = `oobee-screenshot-${Date.now()}-${Math.floor(Math.random() * 1000)}-${index}.png`;
|
|
66
|
+
|
|
67
|
+
// Check existence to prevent failure, then screenshot
|
|
68
|
+
cy.get("body").then($body => {
|
|
69
|
+
if ($body.find(selector).length) {
|
|
70
|
+
// We use capture: 'viewport' to be safe and overwrite true
|
|
71
|
+
cy.get(selector).first().scrollIntoView().screenshot(filename.replace('.png', ''), { capture: 'viewport', overwrite: true });
|
|
72
|
+
node.screenshotFilename = filename;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const violations = res.axeScanResults.violations;
|
|
80
|
+
const incomplete = res.axeScanResults.incomplete;
|
|
81
|
+
|
|
82
|
+
cy.wrap(violations).each((v) => processNodes(v.nodes));
|
|
83
|
+
cy.wrap(incomplete).each((v) => processNodes(v.nodes));
|
|
84
|
+
|
|
85
|
+
// Ensure screenshots are done before pushing results
|
|
86
|
+
cy.then(() => {
|
|
87
|
+
cy.task("pushOobeeA11yScanResults", {
|
|
88
|
+
res,
|
|
89
|
+
metadata,
|
|
90
|
+
elementsToClick,
|
|
91
|
+
}).then((count) => {
|
|
92
|
+
return count;
|
|
93
|
+
});
|
|
64
94
|
});
|
|
65
95
|
},
|
|
66
96
|
);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { defineConfig } from "cypress";
|
|
2
2
|
import oobeeA11yInit from "@govtechsg/oobee";
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
3
5
|
|
|
4
6
|
// viewport used in tests to optimise screenshots
|
|
5
7
|
const viewportSettings = { width: 1920, height: 1040 };
|
|
@@ -44,7 +46,49 @@ export default defineConfig({
|
|
|
44
46
|
return oobeeA11y.gradeReadability(sentences);
|
|
45
47
|
},
|
|
46
48
|
async pushOobeeA11yScanResults({ res, metadata, elementsToClick }) {
|
|
47
|
-
|
|
49
|
+
if (oobeeA11y.scanDetails.isIncludeScreenshots) {
|
|
50
|
+
const moveScreenshots = (items) => {
|
|
51
|
+
if (!items) return;
|
|
52
|
+
items.forEach(item => {
|
|
53
|
+
item.nodes.forEach((node) => {
|
|
54
|
+
if (node.screenshotFilename) {
|
|
55
|
+
const searchDir = 'cypress/screenshots';
|
|
56
|
+
|
|
57
|
+
const findFile = (dir, name) => {
|
|
58
|
+
if (!fs.existsSync(dir)) return null;
|
|
59
|
+
const files = fs.readdirSync(dir);
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
const filePath = path.join(dir, file);
|
|
62
|
+
if (fs.statSync(filePath).isDirectory()) {
|
|
63
|
+
const found = findFile(filePath, name);
|
|
64
|
+
if (found) return found;
|
|
65
|
+
} else if (file === name) {
|
|
66
|
+
return filePath;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const srcPath = findFile(searchDir, node.screenshotFilename);
|
|
73
|
+
if (srcPath) {
|
|
74
|
+
const destDir = `results/${oobeeA11y.randomToken}/screenshots`;
|
|
75
|
+
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
76
|
+
const destPath = path.join(destDir, node.screenshotFilename);
|
|
77
|
+
fs.copyFileSync(srcPath, destPath);
|
|
78
|
+
// Set screenshot path for Oobee report
|
|
79
|
+
node.screenshotPath = node.screenshotFilename;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
moveScreenshots(res.axeScanResults.violations);
|
|
87
|
+
moveScreenshots(res.axeScanResults.incomplete);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Pass disableScreenshots=true to avoid opening a new Playwright browser
|
|
91
|
+
return await oobeeA11y.pushScanResults(res, metadata, elementsToClick, undefined, true);
|
|
48
92
|
},
|
|
49
93
|
returnResultsDir() {
|
|
50
94
|
return `results/${oobeeA11y.randomToken}_${oobeeA11y.scanDetails.urlsCrawled.scanned.length}pages/report.html`;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { defineConfig } from "cypress";
|
|
2
2
|
import oobeeA11yInit from "@govtechsg/oobee";
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
3
5
|
|
|
4
6
|
interface ViewportSettings {
|
|
5
7
|
width: number;
|
|
@@ -71,7 +73,51 @@ export default defineConfig({
|
|
|
71
73
|
},
|
|
72
74
|
async pushOobeeA11yScanResults({res, metadata, elementsToClick}: { res: any, metadata: any, elementsToClick: any[] }): Promise<{ mustFix: number, goodToFix: number }> {
|
|
73
75
|
const instance = await initOobeeIfNeeded();
|
|
74
|
-
|
|
76
|
+
|
|
77
|
+
if (instance.scanDetails.isIncludeScreenshots) {
|
|
78
|
+
const moveScreenshots = (items: any[]) => {
|
|
79
|
+
if (!items) return;
|
|
80
|
+
items.forEach(item => {
|
|
81
|
+
item.nodes.forEach((node: any) => {
|
|
82
|
+
if (node.screenshotFilename) {
|
|
83
|
+
const searchDir = 'cypress/screenshots';
|
|
84
|
+
|
|
85
|
+
const findFile = (dir: string, name: string): string | null => {
|
|
86
|
+
if (!fs.existsSync(dir)) return null;
|
|
87
|
+
const files = fs.readdirSync(dir);
|
|
88
|
+
for (const file of files) {
|
|
89
|
+
const filePath = path.join(dir, file);
|
|
90
|
+
if (fs.statSync(filePath).isDirectory()) {
|
|
91
|
+
const found = findFile(filePath, name);
|
|
92
|
+
if (found) return found;
|
|
93
|
+
} else if (file === name) {
|
|
94
|
+
return filePath;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const srcPath = findFile(searchDir, node.screenshotFilename);
|
|
101
|
+
if (srcPath) {
|
|
102
|
+
const destDir = `results/${instance.randomToken}/screenshots`;
|
|
103
|
+
if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
|
|
104
|
+
const destPath = path.join(destDir, node.screenshotFilename);
|
|
105
|
+
fs.copyFileSync(srcPath, destPath);
|
|
106
|
+
// Set screenshot path for Oobee report
|
|
107
|
+
node.screenshotPath = node.screenshotFilename;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
moveScreenshots(res.axeScanResults.violations);
|
|
115
|
+
moveScreenshots(res.axeScanResults.incomplete);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Cypress task runs in Node.js and does not have access to the browser Page object
|
|
119
|
+
// Pass disableScreenshots=true to avoid opening a new Playwright browser
|
|
120
|
+
return await instance.pushScanResults(res, metadata, elementsToClick, undefined, true);
|
|
75
121
|
},
|
|
76
122
|
async returnResultsDir(): Promise<string> {
|
|
77
123
|
const instance = await initOobeeIfNeeded();
|
|
@@ -55,12 +55,42 @@ Cypress.Commands.add("runOobeeA11yScan", (items: OobeeScanOptions = {}) => {
|
|
|
55
55
|
elementsToScan,
|
|
56
56
|
gradingReadabilityFlag,
|
|
57
57
|
);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
|
|
59
|
+
const processNodes = (nodes: any[]) => {
|
|
60
|
+
if (!nodes) return;
|
|
61
|
+
cy.wrap(nodes).each((node: any, index: number) => {
|
|
62
|
+
if (node.target && node.target.length > 0) {
|
|
63
|
+
const selector = node.target[0];
|
|
64
|
+
// Generate a unique filename
|
|
65
|
+
const filename = `oobee-screenshot-${Date.now()}-${Math.floor(Math.random() * 1000)}-${index}.png`;
|
|
66
|
+
|
|
67
|
+
// Check existence to prevent failure, then screenshot
|
|
68
|
+
cy.get("body").then($body => {
|
|
69
|
+
if ($body.find(selector).length) {
|
|
70
|
+
// We use capture: 'viewport' to be safe and overwrite true
|
|
71
|
+
cy.get(selector).first().scrollIntoView().screenshot(filename.replace('.png', ''), { capture: 'viewport', overwrite: true });
|
|
72
|
+
node.screenshotFilename = filename;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const violations = res.axeScanResults.violations;
|
|
80
|
+
const incomplete = res.axeScanResults.incomplete;
|
|
81
|
+
|
|
82
|
+
cy.wrap(violations).each((v: any) => processNodes(v.nodes));
|
|
83
|
+
cy.wrap(incomplete).each((v: any) => processNodes(v.nodes));
|
|
84
|
+
|
|
85
|
+
// Ensure screenshots are done before pushing results
|
|
86
|
+
cy.then(() => {
|
|
87
|
+
cy.task("pushOobeeA11yScanResults", {
|
|
88
|
+
res,
|
|
89
|
+
metadata,
|
|
90
|
+
elementsToClick,
|
|
91
|
+
}).then((count) => {
|
|
92
|
+
return count;
|
|
93
|
+
});
|
|
64
94
|
});
|
|
65
95
|
},
|
|
66
96
|
);
|
|
@@ -40,7 +40,8 @@ const oobeeA11y = await oobeeA11yInit({
|
|
|
40
40
|
async ({ elementsToScan, gradingReadabilityFlag }) => await runA11yScan(elementsToScan, gradingReadabilityFlag),
|
|
41
41
|
{ elementsToScan, gradingReadabilityFlag },
|
|
42
42
|
);
|
|
43
|
-
|
|
43
|
+
// Pass page object to allow screenshot reuse
|
|
44
|
+
await oobeeA11y.pushScanResults(scanRes, undefined, undefined, page);
|
|
44
45
|
oobeeA11y.testThresholds(); // test the accumulated number of issue occurrences against specified thresholds. If exceed, terminate oobeeA11y instance.
|
|
45
46
|
};
|
|
46
47
|
|
|
@@ -59,7 +59,8 @@ const oobeeA11y = await oobeeA11yInit({
|
|
|
59
59
|
async ({ elementsToScan, gradingReadabilityFlag }) => await runA11yScan(elementsToScan, gradingReadabilityFlag),
|
|
60
60
|
{ elementsToScan, gradingReadabilityFlag },
|
|
61
61
|
);
|
|
62
|
-
|
|
62
|
+
// Pass page object to allow screenshot reuse
|
|
63
|
+
await oobeeA11y.pushScanResults(scanRes, undefined, undefined, page);
|
|
63
64
|
oobeeA11y.testThresholds(); // test the accumulated number of issue occurrences against specified thresholds. If exceed, terminate oobeeA11y instance.
|
|
64
65
|
};
|
|
65
66
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { scanHTML } from '../dist/npmIndex.js';
|
|
2
|
+
|
|
3
|
+
const htmlContent = `
|
|
4
|
+
<!DOCTYPE html>
|
|
5
|
+
<html lang="en">
|
|
6
|
+
<head>
|
|
7
|
+
<title>Test Page</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<h1>Accessibility Test</h1>
|
|
11
|
+
<button></button> <!-- Violation: button-name -->
|
|
12
|
+
<img src="test.jpg" /> <!-- Violation: image-alt -->
|
|
13
|
+
<div role="button">Fake</div> <!-- Violation: role-button (if interactive) -->
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const htmlContent2 = `
|
|
19
|
+
<!DOCTYPE html>
|
|
20
|
+
<html lang="en">
|
|
21
|
+
<head>
|
|
22
|
+
<title>Test Page 2</title>
|
|
23
|
+
</head>
|
|
24
|
+
<body>
|
|
25
|
+
<h1>Accessibility Test 2</h1>
|
|
26
|
+
<a href="#">Click me</a> <!-- Violation: link-name (if vague) or empty href issues -->
|
|
27
|
+
<input type="text" /> <!-- Violation: label -->
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
(async () => {
|
|
33
|
+
console.log("Scanning HTML string...");
|
|
34
|
+
try {
|
|
35
|
+
// Run scanHTML without needing full Oobee init
|
|
36
|
+
// Pass an array of HTML strings to demonstrate batch scanning
|
|
37
|
+
const results = await scanHTML(
|
|
38
|
+
[htmlContent, htmlContent2],
|
|
39
|
+
{
|
|
40
|
+
name: "Your Name",
|
|
41
|
+
email: "email@domain.com",
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
console.log(JSON.stringify(results, null, 2));
|
|
45
|
+
|
|
46
|
+
console.log(`\nScan Complete.`);
|
|
47
|
+
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error("Error during scan:", error);
|
|
50
|
+
}
|
|
51
|
+
})();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
import { scanPage } from '../dist/npmIndex.js';
|
|
3
|
+
|
|
4
|
+
(async () => {
|
|
5
|
+
console.log("Launching browser...");
|
|
6
|
+
const browser = await chromium.launch({
|
|
7
|
+
headless: false,
|
|
8
|
+
channel: 'chrome' // Use Chrome instead of Chromium
|
|
9
|
+
});
|
|
10
|
+
const page = await browser.newPage();
|
|
11
|
+
|
|
12
|
+
console.log("Navigating to test page...");
|
|
13
|
+
// Using a sample page that likely has accessibility issues
|
|
14
|
+
await page.goto('https://govtechsg.github.io/purple-banner-embeds/purple-integrated-scan-example.htm');
|
|
15
|
+
|
|
16
|
+
const page2 = await browser.newPage();
|
|
17
|
+
console.log("Navigating to second test page...");
|
|
18
|
+
await page2.goto('https://a11y.tech.gov.sg');
|
|
19
|
+
|
|
20
|
+
console.log("Scanning page...");
|
|
21
|
+
try {
|
|
22
|
+
// Run scanPage using the existing Playwright page
|
|
23
|
+
const results = await scanPage(
|
|
24
|
+
[page, page2],
|
|
25
|
+
{
|
|
26
|
+
name: "Your Name",
|
|
27
|
+
email: "email@domain.com",
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
console.log(JSON.stringify(results, null, 2));
|
|
32
|
+
|
|
33
|
+
console.log(`\nScan Complete.`);
|
|
34
|
+
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error("Error during scan:", error);
|
|
37
|
+
} finally {
|
|
38
|
+
await browser.close();
|
|
39
|
+
}
|
|
40
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@govtechsg/oobee",
|
|
3
3
|
"main": "dist/npmIndex.js",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.78-alpha1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Government Technology Agency <info@tech.gov.sg>",
|
|
7
|
+
"bin": {
|
|
8
|
+
"oobee": "./dist/cli.js"
|
|
9
|
+
},
|
|
7
10
|
"dependencies": {
|
|
8
11
|
"@aws-sdk/client-s3": "^3.948.0",
|
|
9
12
|
"@json2csv/node": "^7.0.3",
|
|
@@ -58,7 +61,7 @@
|
|
|
58
61
|
"@types/which": "^3.0.4",
|
|
59
62
|
"@types/xml2js": "^0.4.14",
|
|
60
63
|
"browserify-zlib": "^0.2.0",
|
|
61
|
-
"eslint": "^
|
|
64
|
+
"eslint": "^9.26.0",
|
|
62
65
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
63
66
|
"eslint-config-prettier": "^8.6.0",
|
|
64
67
|
"eslint-plugin-import": "^2.27.4",
|
|
@@ -76,7 +79,10 @@
|
|
|
76
79
|
"micromatch": "github:micromatch/micromatch.git#4.0.8",
|
|
77
80
|
"brace-expansion": "^1.1.12",
|
|
78
81
|
"tmp": "0.2.4",
|
|
79
|
-
"tar": "^7.5.6"
|
|
82
|
+
"tar": "^7.5.6",
|
|
83
|
+
"eslint-config-airbnb-base": {
|
|
84
|
+
"eslint": "^9.26.0"
|
|
85
|
+
}
|
|
80
86
|
},
|
|
81
87
|
"optionalDependencies": {
|
|
82
88
|
"@napi-rs/canvas-darwin-arm64": "^0.1.53",
|
package/src/constants/common.ts
CHANGED
|
@@ -1333,14 +1333,14 @@ const cloneEdgeProfileCookieFiles = (options: GlobOptionsWithFileTypesFalse, des
|
|
|
1333
1333
|
if (os.platform() === 'win32') {
|
|
1334
1334
|
profileCookiesDir = globSync('**/Network/Cookies', {
|
|
1335
1335
|
...options,
|
|
1336
|
-
ignore: 'oobee
|
|
1336
|
+
ignore: 'oobee*/**',
|
|
1337
1337
|
});
|
|
1338
1338
|
profileNamesRegex = /User Data\\(.*?)\\Network/;
|
|
1339
1339
|
} else if (os.platform() === 'darwin') {
|
|
1340
1340
|
// Ignores copying cookies from the oobee directory if it exists
|
|
1341
1341
|
profileCookiesDir = globSync('**/Cookies', {
|
|
1342
1342
|
...options,
|
|
1343
|
-
ignore: 'oobee
|
|
1343
|
+
ignore: 'oobee*/**',
|
|
1344
1344
|
});
|
|
1345
1345
|
profileNamesRegex = /Microsoft Edge\/(.*?)\/Cookies/;
|
|
1346
1346
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
+
import { createRequire } from 'module';
|
|
3
4
|
import fs from 'fs-extra';
|
|
4
5
|
import { globSync } from 'glob';
|
|
5
6
|
import which from 'which';
|
|
@@ -13,6 +14,7 @@ import { PageInfo } from '../mergeAxeResults.js';
|
|
|
13
14
|
|
|
14
15
|
const filename = fileURLToPath(import.meta.url);
|
|
15
16
|
const dirname = path.dirname(filename);
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
16
18
|
|
|
17
19
|
const maxRequestsPerCrawl = 100;
|
|
18
20
|
|
|
@@ -216,7 +218,7 @@ export const getExecutablePath = function (dir: string, file: string): string {
|
|
|
216
218
|
export const basicAuthRegex = /^.*\/\/.*:.*@.*$/i;
|
|
217
219
|
|
|
218
220
|
// for crawlers
|
|
219
|
-
export const axeScript =
|
|
221
|
+
export const axeScript = require.resolve('axe-core/axe.min.js');
|
|
220
222
|
export class UrlsCrawled {
|
|
221
223
|
siteName: string;
|
|
222
224
|
toScan: string[] = [];
|
|
@@ -151,6 +151,7 @@ const crawlDomain = async ({
|
|
|
151
151
|
req.skipNavigation = true;
|
|
152
152
|
}
|
|
153
153
|
if (isDisallowedInRobotsTxt(req.url)) return null;
|
|
154
|
+
if (isBlacklisted(req.url, blacklistedPatterns)) return null;
|
|
154
155
|
if (isUrlPdf(req.url)) {
|
|
155
156
|
// playwright headless mode does not support navigation to pdf document
|
|
156
157
|
req.skipNavigation = true;
|
package/src/mergeAxeResults.ts
CHANGED
|
@@ -12,7 +12,6 @@ import { AsyncParser, ParserOptions } from '@json2csv/node';
|
|
|
12
12
|
import zlib from 'zlib';
|
|
13
13
|
import { Base64Encode } from 'base64-stream';
|
|
14
14
|
import { pipeline } from 'stream/promises';
|
|
15
|
-
// @ts-ignore
|
|
16
15
|
import * as Sentry from '@sentry/node';
|
|
17
16
|
import constants, {
|
|
18
17
|
BrowserTypes,
|
|
@@ -1289,34 +1288,56 @@ function updateIssuesWithOccurrences(issuesList: any[], urlOccurrencesMap: Map<s
|
|
|
1289
1288
|
});
|
|
1290
1289
|
}
|
|
1291
1290
|
|
|
1291
|
+
const extractRuleAiData = (ruleId: string, totalItems: number, items: any[], callback?: () => void) => {
|
|
1292
|
+
let snippets = [];
|
|
1293
|
+
|
|
1294
|
+
if (oobeeAiRules.includes(ruleId)) {
|
|
1295
|
+
const snippetsSet = new Set();
|
|
1296
|
+
if (items) {
|
|
1297
|
+
items.forEach(item => {
|
|
1298
|
+
snippetsSet.add(oobeeAiHtmlETL(item.html));
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
snippets = [...snippetsSet];
|
|
1302
|
+
if (callback) callback();
|
|
1303
|
+
}
|
|
1304
|
+
return {
|
|
1305
|
+
snippets,
|
|
1306
|
+
occurrences: totalItems,
|
|
1307
|
+
};
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
// This is for telemetry purposes called within mergeAxeResults.ts
|
|
1311
|
+
export
|
|
1292
1312
|
const createRuleIdJson = allIssues => {
|
|
1293
1313
|
const compiledRuleJson = {};
|
|
1294
1314
|
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
rule.pagesAffected.forEach(page => {
|
|
1302
|
-
page.items.forEach(htmlItem => {
|
|
1303
|
-
snippetsSet.add(oobeeAiHtmlETL(htmlItem.html));
|
|
1315
|
+
['mustFix', 'goodToFix', 'needsReview'].forEach(category => {
|
|
1316
|
+
allIssues.items[category].rules.forEach(rule => {
|
|
1317
|
+
const allItems = rule.pagesAffected.flatMap(page => page.items || []);
|
|
1318
|
+
compiledRuleJson[rule.rule] = extractRuleAiData(rule.rule, rule.totalItems, allItems, () => {
|
|
1319
|
+
rule.pagesAffected.forEach(p => {
|
|
1320
|
+
delete p.items;
|
|
1304
1321
|
});
|
|
1305
1322
|
});
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1323
|
+
});
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
return compiledRuleJson;
|
|
1327
|
+
};
|
|
1328
|
+
|
|
1329
|
+
// This is for telemetry purposes called from npmIndex (scanPage and scanHTML) where report is not generated
|
|
1330
|
+
export const createBasicFormHTMLSnippet = filteredResults => {
|
|
1331
|
+
const compiledRuleJson = {};
|
|
1332
|
+
|
|
1333
|
+
['mustFix', 'goodToFix', 'needsReview'].forEach(category => {
|
|
1334
|
+
if (filteredResults[category] && filteredResults[category].rules) {
|
|
1335
|
+
Object.entries(filteredResults[category].rules).forEach(([ruleId, ruleVal]: [string, any]) => {
|
|
1336
|
+
compiledRuleJson[ruleId] = extractRuleAiData(ruleId, ruleVal.totalItems, ruleVal.items);
|
|
1309
1337
|
});
|
|
1310
1338
|
}
|
|
1311
|
-
|
|
1312
|
-
snippets,
|
|
1313
|
-
occurrences: rule.totalItems,
|
|
1314
|
-
};
|
|
1315
|
-
};
|
|
1339
|
+
});
|
|
1316
1340
|
|
|
1317
|
-
allIssues.items.mustFix.rules.forEach(ruleIterator);
|
|
1318
|
-
allIssues.items.goodToFix.rules.forEach(ruleIterator);
|
|
1319
|
-
allIssues.items.needsReview.rules.forEach(ruleIterator);
|
|
1320
1341
|
return compiledRuleJson;
|
|
1321
1342
|
};
|
|
1322
1343
|
|
|
@@ -1587,7 +1608,7 @@ function populateScanPagesDetail(allIssues: AllIssues): void {
|
|
|
1587
1608
|
}
|
|
1588
1609
|
|
|
1589
1610
|
// Send WCAG criteria breakdown to Sentry
|
|
1590
|
-
const sendWcagBreakdownToSentry = async (
|
|
1611
|
+
export const sendWcagBreakdownToSentry = async (
|
|
1591
1612
|
appVersion: string,
|
|
1592
1613
|
wcagBreakdown: Map<string, number>,
|
|
1593
1614
|
ruleIdJson: any,
|