@govtechsg/oobee 0.10.72 → 0.10.75
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/DETAILS.md +1 -0
- package/package.json +4 -3
- package/src/combine.ts +1 -1
- package/src/constants/common.ts +2 -2
- package/src/constants/constants.ts +2 -0
- package/src/crawlers/crawlSitemap.ts +1 -1
- package/src/mergeAxeResults.ts +60 -8
- package/src/services/s3Uploader.ts +1 -1
- package/src/static/ejs/partials/scripts/ruleModal/constants.ejs +9 -1
- package/src/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +2 -1
package/DETAILS.md
CHANGED
|
@@ -130,6 +130,7 @@ Note: Level AAA are disabled by default. Please specify `enable-wcag-aaa` in ru
|
|
|
130
130
|
| td-headers-attr | Ensure that each cell in a table that uses the headers attribute refers only to other cells in that table | Must Fix | WCAG 1.3.1 |
|
|
131
131
|
| th-has-data-cells | Ensure that `<th>` elements and elements with role=columnheader/rowheader have data cells they describe | Must Fix | WCAG 1.3.1 |
|
|
132
132
|
| video-caption | Ensures `<video>` elements have captions | Must Fix | WCAG 1.2.2 |
|
|
133
|
+
| summary-name | Ensure summary elements have discernible text | Must Fix | WCAG 4.1.2
|
|
133
134
|
| oobee-confusing-alt-text | The image alt text set as 'img', 'image', 'picture', 'photo', or 'graphic' is confusing or not useful | Must Fix | WCAG 1.1.1
|
|
134
135
|
| oobee-accessible-label | Clickable elements (i.e. elements with mouse-click interaction) must have accessible labels. | Must Fix | WCAG 2.1.1, WCAG 4.1.2 |
|
|
135
136
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@govtechsg/oobee",
|
|
3
3
|
"main": "dist/npmIndex.js",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.75",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Government Technology Agency <info@tech.gov.sg>",
|
|
7
7
|
"dependencies": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"@napi-rs/canvas": "^0.1.53",
|
|
11
11
|
"@sentry/node": "^9.13.0",
|
|
12
12
|
"@types/aws-sdk": "^0.0.42",
|
|
13
|
-
"axe-core": "^4.
|
|
13
|
+
"axe-core": "^4.11.1",
|
|
14
14
|
"axios": "^1.8.2",
|
|
15
15
|
"base64-stream": "^1.0.0",
|
|
16
16
|
"cheerio": "^1.0.0-rc.12",
|
|
@@ -75,7 +75,8 @@
|
|
|
75
75
|
"tough-cookie": "^5.0.0-rc.2",
|
|
76
76
|
"micromatch": "github:micromatch/micromatch.git#4.0.8",
|
|
77
77
|
"brace-expansion": "^1.1.12",
|
|
78
|
-
"tmp": "0.2.4"
|
|
78
|
+
"tmp": "0.2.4",
|
|
79
|
+
"tar": "^7.5.6"
|
|
79
80
|
},
|
|
80
81
|
"optionalDependencies": {
|
|
81
82
|
"@napi-rs/canvas-darwin-arm64": "^0.1.53",
|
package/src/combine.ts
CHANGED
package/src/constants/common.ts
CHANGED
|
@@ -1257,14 +1257,14 @@ const cloneChromeProfileCookieFiles = (options: GlobOptionsWithFileTypesFalse, d
|
|
|
1257
1257
|
if (os.platform() === 'win32') {
|
|
1258
1258
|
profileCookiesDir = globSync('**/Network/Cookies', {
|
|
1259
1259
|
...options,
|
|
1260
|
-
ignore: ['oobee
|
|
1260
|
+
ignore: ['oobee*/**'],
|
|
1261
1261
|
});
|
|
1262
1262
|
profileNamesRegex = /User Data\\(.*?)\\Network/;
|
|
1263
1263
|
} else if (os.platform() === 'darwin') {
|
|
1264
1264
|
// maxDepth 2 to avoid copying cookies from the oobee directory if it exists
|
|
1265
1265
|
profileCookiesDir = globSync('**/Cookies', {
|
|
1266
1266
|
...options,
|
|
1267
|
-
ignore: 'oobee
|
|
1267
|
+
ignore: 'oobee*/**',
|
|
1268
1268
|
});
|
|
1269
1269
|
profileNamesRegex = /Chrome\/(.*?)\/Cookies/;
|
|
1270
1270
|
}
|
|
@@ -610,6 +610,7 @@ export const a11yRuleShortDescriptionMap = {
|
|
|
610
610
|
'table-duplicate-name': 'Table caption and summary must not be identical',
|
|
611
611
|
'meta-viewport': 'Pages must allow zoom and text scaling',
|
|
612
612
|
'aria-allowed-role': 'Elements must use appropriate roles matching their actual behavior',
|
|
613
|
+
'summary-name': 'Summary must have discernible text'
|
|
613
614
|
};
|
|
614
615
|
|
|
615
616
|
export const a11yRuleLongDescriptionMap = {
|
|
@@ -801,6 +802,7 @@ export const a11yRuleLongDescriptionMap = {
|
|
|
801
802
|
'meta-viewport':
|
|
802
803
|
'Pages must allow users to zoom in and scale text using their browser or pinch-to-zoom on mobile devices. Disabling zoom locks people with low vision out of being able to enlarge content to read them comfortably. The viewport meta tag should allow scaling and not restrict maximum zoom.',
|
|
803
804
|
'aria-allowed-role': `Buttons, links, and interactive elements should behave the way they're marked. e.g., if something looks and acts like a button (performs an action), it should be labeled as a button. If it goes to a different page, it should be labeled as a link. When the label doesn't match the actual behavior, screen reader users get confused about what will happen when they click. When possible, use real buttons (<button>) and real links (<a>) instead of creating fake buttons or links from plain text and code.`,
|
|
805
|
+
'summary-name': 'Ensure summary elements have discernible text that clearly indicates the topic or purpose of the information that will be revealed when using the summary control.'
|
|
804
806
|
};
|
|
805
807
|
|
|
806
808
|
export const disabilityBadgesMap = {
|
package/src/mergeAxeResults.ts
CHANGED
|
@@ -210,7 +210,7 @@ const writeCsv = async (allIssues, storagePath) => {
|
|
|
210
210
|
scanCompletedAt: allIssues.endTime ? allIssues.endTime.toISOString() : '',
|
|
211
211
|
severity: severity || '',
|
|
212
212
|
issueId: issueId || '',
|
|
213
|
-
issueDescription: issueDescription || '',
|
|
213
|
+
issueDescription: a11yRuleShortDescriptionMap[issueId] || issueDescription || '',
|
|
214
214
|
wcagConformance: wcagConformance || '',
|
|
215
215
|
url: url || '',
|
|
216
216
|
pageTitle: affectedPage.pageTitle || 'No page title',
|
|
@@ -390,8 +390,37 @@ const writeHTML = async (
|
|
|
390
390
|
|
|
391
391
|
outputStream.write(prefixData);
|
|
392
392
|
|
|
393
|
-
|
|
393
|
+
// For Proxied AI environments only
|
|
394
|
+
outputStream.write(`let proxyUrl = "${process.env.PROXY_API_BASE_URL || ""}"\n`);
|
|
394
395
|
|
|
396
|
+
// Initialize GenAI feature flag
|
|
397
|
+
outputStream.write(`
|
|
398
|
+
// Fetch GenAI feature flag from backend
|
|
399
|
+
window.oobeeGenAiFeatureEnabled = false;
|
|
400
|
+
if (proxyUrl !== "" && proxyUrl !== undefined && proxyUrl !== null) {
|
|
401
|
+
(async () => {
|
|
402
|
+
try {
|
|
403
|
+
const featuresUrl = proxyUrl + '/api/ai/features';
|
|
404
|
+
const response = await fetch(featuresUrl, {
|
|
405
|
+
method: 'GET',
|
|
406
|
+
headers: { 'Accept': 'application/json' }
|
|
407
|
+
});
|
|
408
|
+
if (response.ok) {
|
|
409
|
+
const features = await response.json();
|
|
410
|
+
window.oobeeGenAiFeatureEnabled = features.genai_ui_enabled || false;
|
|
411
|
+
console.log('GenAI UI feature flag:', window.oobeeGenAiFeatureEnabled);
|
|
412
|
+
} else {
|
|
413
|
+
console.warn('Failed to fetch GenAI feature flag:', response.status);
|
|
414
|
+
}
|
|
415
|
+
} catch (error) {
|
|
416
|
+
console.warn('Error fetching GenAI feature flag:', error);
|
|
417
|
+
}
|
|
418
|
+
})();
|
|
419
|
+
} else {
|
|
420
|
+
console.warn('Skipping fetch GenAI feature as it is local report');
|
|
421
|
+
}
|
|
422
|
+
\n`)
|
|
423
|
+
|
|
395
424
|
// outputStream.write("scanData = decompressJsonObject('");
|
|
396
425
|
outputStream.write(
|
|
397
426
|
"let scanDataPromise = (async () => { console.log('Loading scanData...'); scanData = await decodeUnzipParse('",
|
|
@@ -446,6 +475,13 @@ const writeSummaryHTML = async (
|
|
|
446
475
|
fs.writeFileSync(`${storagePath}/${htmlFilename}.html`, html);
|
|
447
476
|
};
|
|
448
477
|
|
|
478
|
+
const writeSitemap = async (pagesScanned: PageInfo[], storagePath: string) => {
|
|
479
|
+
const sitemapPath = path.join(storagePath, 'sitemap.txt');
|
|
480
|
+
const content = pagesScanned.map(p => p.url).join('\n');
|
|
481
|
+
await fs.writeFile(sitemapPath, content, { encoding: 'utf-8' });
|
|
482
|
+
consoleLogger.info(`Sitemap written to ${sitemapPath}`);
|
|
483
|
+
};
|
|
484
|
+
|
|
449
485
|
const cleanUpJsonFiles = async (filesToDelete: string[]) => {
|
|
450
486
|
consoleLogger.info('Cleaning up JSON files...');
|
|
451
487
|
filesToDelete.forEach(file => {
|
|
@@ -799,10 +835,24 @@ const writeJsonAndBase64Files = async (
|
|
|
799
835
|
|
|
800
836
|
// Refactor scanIssuesSummary to reuse the scanItemsMiniReport structure by stripping out pagesAffected
|
|
801
837
|
const scanIssuesSummary = {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
838
|
+
|
|
839
|
+
// Replace rule descriptions with short descriptions from the map
|
|
840
|
+
mustFix: items.mustFix.rules.map(({ pagesAffected, ...ruleInfo }) => ({
|
|
841
|
+
...ruleInfo,
|
|
842
|
+
description: a11yRuleShortDescriptionMap[ruleInfo.rule] || ruleInfo.description,
|
|
843
|
+
})),
|
|
844
|
+
goodToFix: items.goodToFix.rules.map(({ pagesAffected, ...ruleInfo }) => ({
|
|
845
|
+
...ruleInfo,
|
|
846
|
+
description: a11yRuleShortDescriptionMap[ruleInfo.rule] || ruleInfo.description,
|
|
847
|
+
})),
|
|
848
|
+
needsReview: items.needsReview.rules.map(({ pagesAffected, ...ruleInfo }) => ({
|
|
849
|
+
...ruleInfo,
|
|
850
|
+
description: a11yRuleShortDescriptionMap[ruleInfo.rule] || ruleInfo.description,
|
|
851
|
+
})),
|
|
852
|
+
passed: items.passed.rules.map(({ pagesAffected, ...ruleInfo }) => ({
|
|
853
|
+
...ruleInfo,
|
|
854
|
+
description: a11yRuleShortDescriptionMap[ruleInfo.rule] || ruleInfo.description,
|
|
855
|
+
})),
|
|
806
856
|
};
|
|
807
857
|
|
|
808
858
|
// Write out the scanIssuesSummary JSON using the new structure
|
|
@@ -1164,7 +1214,8 @@ const getTopTenIssues = allIssues => {
|
|
|
1164
1214
|
rulesWithCounts.push({
|
|
1165
1215
|
category,
|
|
1166
1216
|
ruleId: rule.rule,
|
|
1167
|
-
description
|
|
1217
|
+
// Replace description with new Oobee short description if available
|
|
1218
|
+
description: a11yRuleShortDescriptionMap[rule.rule] || rule.description,
|
|
1168
1219
|
axeImpact: rule.axeImpact,
|
|
1169
1220
|
conformance: rule.conformance,
|
|
1170
1221
|
totalItems: rule.totalItems,
|
|
@@ -1951,6 +2002,7 @@ const generateArtifacts = async (
|
|
|
1951
2002
|
}
|
|
1952
2003
|
|
|
1953
2004
|
await writeCsv(allIssues, storagePath);
|
|
2005
|
+
await writeSitemap(pagesScanned, storagePath);
|
|
1954
2006
|
const {
|
|
1955
2007
|
scanDataJsonFilePath,
|
|
1956
2008
|
scanDataBase64FilePath,
|
|
@@ -2125,4 +2177,4 @@ export {
|
|
|
2125
2177
|
formatAboutStartTime,
|
|
2126
2178
|
};
|
|
2127
2179
|
|
|
2128
|
-
export default generateArtifacts;
|
|
2180
|
+
export default generateArtifacts;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
const a11yPlaygroundLink = 'https://a11y.tech.gov.sg
|
|
2
|
+
const a11yPlaygroundLink = 'https://a11y.tech.gov.sg/?utm_source=oobee';
|
|
3
3
|
|
|
4
4
|
const whyItMatters = {
|
|
5
5
|
accesskeys:
|
|
@@ -945,5 +945,13 @@
|
|
|
945
945
|
review: `Test with keyboard: Try clicking buttons (Space or Enter), try clicking links (Enter). Use a screen reader to hear if it's announced as a button or link. Confirm what it announces matches what it actually does.`,
|
|
946
946
|
learn: 'Review and learn more about this issue on A11y Playground',
|
|
947
947
|
},
|
|
948
|
+
'summary-name': {
|
|
949
|
+
check:
|
|
950
|
+
'Find summary elements with and check if text is inside, or has a label (aria-label or title). Check: Do they convey meaningful information about the details?',
|
|
951
|
+
fix: '(Developer) If summary are missing text or labels, add one to provide context or explanation to the details.',
|
|
952
|
+
review:
|
|
953
|
+
'Use a screen reader to navigate to the summary and ensure it is announced with clear information.',
|
|
954
|
+
learn: 'Review and learn more about this issue on A11y Playground',
|
|
955
|
+
},
|
|
948
956
|
};
|
|
949
957
|
</script>
|
|
@@ -147,6 +147,7 @@
|
|
|
147
147
|
|
|
148
148
|
function createItemCard(item, isPurpleAiRule, oobeeAiQueryLabel, aiConfig) {
|
|
149
149
|
const isGenAiSupportedRule = ['color-contrast', 'oobee-accessible-label', 'image-alt', 'listitem', 'link-in-text-block', 'target-size'].includes(aiConfig.ruleInCategory.rule);
|
|
150
|
+
const showGenAiUI = window.oobeeGenAiFeatureEnabled && isGenAiSupportedRule;
|
|
150
151
|
|
|
151
152
|
return createElementFromString(`
|
|
152
153
|
<div>
|
|
@@ -166,7 +167,7 @@
|
|
|
166
167
|
: ''
|
|
167
168
|
}
|
|
168
169
|
${isPurpleAiRule ? createAiSuggestionSection(item, oobeeAiQueryLabel, aiConfig) : ''}
|
|
169
|
-
${
|
|
170
|
+
${showGenAiUI ? createGenAiSuggestFixSection(item, aiConfig) : ''}
|
|
170
171
|
</div>
|
|
171
172
|
</div>
|
|
172
173
|
`);
|