@govtechsg/oobee 0.10.87 → 0.10.88
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/docker-push-ghcr.yml +49 -0
- package/DETAILS_OUTPUT_EXAMPLES.md +178 -0
- package/Dockerfile +6 -7
- package/dist/combine.js +1 -0
- package/dist/crawlers/commonCrawlerFunc.js +523 -2
- package/dist/crawlers/crawlLocalFile.js +2 -2
- package/dist/crawlers/custom/extractAndGradeText.js +1 -1
- package/dist/crawlers/custom/getAxeConfiguration.js +26 -21
- package/dist/crawlers/custom/gradeReadability.js +1 -1
- package/dist/npmIndex.js +16 -12
- package/dist/screenshotFunc/htmlScreenshotFunc.js +67 -0
- package/dist/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +7 -4
- package/dist/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +7 -4
- package/dist/static/ejs/partials/scripts/ruleModal/utilities.ejs +2 -1
- package/examples/oobee-test-details-runner.js +214 -0
- package/examples/test-violations.html +42 -0
- package/package.json +1 -1
- package/src/combine.ts +1 -0
- package/src/crawlers/commonCrawlerFunc.ts +625 -2
- package/src/crawlers/crawlLocalFile.ts +4 -1
- package/src/crawlers/custom/extractAndGradeText.ts +1 -1
- package/src/crawlers/custom/getAxeConfiguration.ts +25 -23
- package/src/crawlers/custom/gradeReadability.ts +1 -1
- package/src/npmIndex.ts +17 -12
- package/src/screenshotFunc/htmlScreenshotFunc.ts +81 -1
- package/src/static/ejs/partials/scripts/ruleModal/itemCardRenderer.ejs +7 -4
- package/src/static/ejs/partials/scripts/ruleModal/pageAccordionBuilder.ejs +7 -4
- package/src/static/ejs/partials/scripts/ruleModal/utilities.ejs +2 -1
package/dist/npmIndex.js
CHANGED
|
@@ -6,10 +6,10 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
import { EnqueueStrategy } from 'crawlee';
|
|
7
7
|
import constants, { BrowserTypes, RuleFlags, ScannerTypes, a11yRuleShortDescriptionMap, a11yRuleLongDescriptionMap, a11yRuleStepByStepGuide } from './constants/constants.js';
|
|
8
8
|
import { deleteClonedProfiles, getBrowserToRun, getPlaywrightLaunchOptions, submitForm, } from './constants/common.js';
|
|
9
|
-
import { createCrawleeSubFolders, filterAxeResults } from './crawlers/commonCrawlerFunc.js';
|
|
9
|
+
import { createCrawleeSubFolders, enrichViolationMessages, filterAxeResults } from './crawlers/commonCrawlerFunc.js';
|
|
10
10
|
import { createAndUpdateResultsFolders, getVersion } from './utils.js';
|
|
11
11
|
import generateArtifacts, { createBasicFormHTMLSnippet, sendWcagBreakdownToSentry } from './mergeAxeResults.js';
|
|
12
|
-
import { takeScreenshotForHTMLElements } from './screenshotFunc/htmlScreenshotFunc.js';
|
|
12
|
+
import { enrichColorContrastDOMContext, takeScreenshotForHTMLElements } from './screenshotFunc/htmlScreenshotFunc.js';
|
|
13
13
|
import { consoleLogger } from './logs.js';
|
|
14
14
|
import { alertMessageOptions } from './constants/cliFunctions.js';
|
|
15
15
|
import { evaluateAltText } from './crawlers/custom/evaluateAltText.js';
|
|
@@ -50,6 +50,13 @@ const getOobeeFunctionsScript = (disableOobee, enableWcagAaa) => {
|
|
|
50
50
|
window.xPathToCss = ${xPathToCss.toString()};
|
|
51
51
|
window.extractText = ${extractText.toString()};
|
|
52
52
|
|
|
53
|
+
function getReadabilityInterpretation(score) {
|
|
54
|
+
const num = parseFloat(score);
|
|
55
|
+
if (Number.isNaN(num)) return '';
|
|
56
|
+
if (num > 30) return 'It is targeted for junior college (JC) level comprehension and above.';
|
|
57
|
+
return 'It is targeted for university graduate level comprehension and above.';
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
function getAxeConfiguration({
|
|
54
61
|
enableWcagAaa = false,
|
|
55
62
|
gradingReadabilityFlag = '',
|
|
@@ -84,7 +91,7 @@ const getOobeeFunctionsScript = (disableOobee, enableWcagAaa) => {
|
|
|
84
91
|
return !node.dataset.flagged; // fail any element with a data-flagged attribute set to true
|
|
85
92
|
},
|
|
86
93
|
},
|
|
87
|
-
...((enableWcagAaa && !disableOobee)
|
|
94
|
+
...((enableWcagAaa && !disableOobee && gradingReadabilityFlag !== '')
|
|
88
95
|
? [
|
|
89
96
|
{
|
|
90
97
|
id: 'oobee-grading-text-contents',
|
|
@@ -92,16 +99,11 @@ const getOobeeFunctionsScript = (disableOobee, enableWcagAaa) => {
|
|
|
92
99
|
impact: 'moderate',
|
|
93
100
|
messages: {
|
|
94
101
|
pass: 'The text content is easy to understand.',
|
|
95
|
-
fail:
|
|
96
|
-
incomplete: \`
|
|
102
|
+
fail: \`Text content is potentially difficult to read.\n It scored \${gradingReadabilityFlag} out of 50 on the Flesch-Kincaid Readability Test.\n \${getReadabilityInterpretation(gradingReadabilityFlag)}\`,
|
|
103
|
+
incomplete: \`Text content is potentially difficult to read.\n It scored \${gradingReadabilityFlag} out of 50 on the Flesch-Kincaid Readability Test.\n \${getReadabilityInterpretation(gradingReadabilityFlag)}\`,
|
|
97
104
|
},
|
|
98
105
|
},
|
|
99
|
-
evaluate: (_node) =>
|
|
100
|
-
if (gradingReadabilityFlag === '') {
|
|
101
|
-
return true; // Pass if no readability issues
|
|
102
|
-
}
|
|
103
|
-
// Fail if readability issues are detected
|
|
104
|
-
},
|
|
106
|
+
evaluate: (_node) => false,
|
|
105
107
|
},
|
|
106
108
|
]
|
|
107
109
|
: []),
|
|
@@ -132,7 +134,7 @@ const getOobeeFunctionsScript = (disableOobee, enableWcagAaa) => {
|
|
|
132
134
|
helpUrl: 'https://www.deque.com/blog/accessible-aria-buttons',
|
|
133
135
|
},
|
|
134
136
|
},
|
|
135
|
-
...((enableWcagAaa && !disableOobee)
|
|
137
|
+
...((enableWcagAaa && !disableOobee && gradingReadabilityFlag !== '')
|
|
136
138
|
? [
|
|
137
139
|
{
|
|
138
140
|
id: 'oobee-grading-text-contents',
|
|
@@ -629,6 +631,8 @@ export const scanPage = async (pages, config) => {
|
|
|
629
631
|
const scanResult = await page.evaluate(async () => {
|
|
630
632
|
return window.runA11yScan();
|
|
631
633
|
});
|
|
634
|
+
await enrichViolationMessages(scanResult.axeScanResults, page);
|
|
635
|
+
await enrichColorContrastDOMContext(scanResult.axeScanResults.violations, page);
|
|
632
636
|
scanData.push({
|
|
633
637
|
axeScanResults: scanResult.axeScanResults,
|
|
634
638
|
pageUrl: page.url(),
|
|
@@ -3,6 +3,73 @@ import { createHash } from 'crypto';
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
const screenshotMap = {}; // Map of screenshot hashkey to its buffer value and screenshot path
|
|
6
|
+
export const enrichColorContrastDOMContext = async (violations, page) => {
|
|
7
|
+
for (const violation of violations) {
|
|
8
|
+
if (violation.id !== 'color-contrast' && violation.id !== 'color-contrast-enhanced')
|
|
9
|
+
continue;
|
|
10
|
+
for (const node of violation.nodes) {
|
|
11
|
+
const { target } = node;
|
|
12
|
+
const selector = target.length === 1 && typeof target[0] === 'string' ? target[0] : null;
|
|
13
|
+
if (!selector)
|
|
14
|
+
continue;
|
|
15
|
+
try {
|
|
16
|
+
const domContext = await page
|
|
17
|
+
.evaluate((sel) => {
|
|
18
|
+
const el = document.querySelector(sel);
|
|
19
|
+
if (!el)
|
|
20
|
+
return null;
|
|
21
|
+
const style = window.getComputedStyle(el);
|
|
22
|
+
const bgImage = style.backgroundImage;
|
|
23
|
+
const hasGradient = bgImage !== 'none' && bgImage.includes('gradient');
|
|
24
|
+
const hasBackgroundImage = bgImage !== 'none' && !hasGradient;
|
|
25
|
+
let hasReducedOpacity = parseFloat(style.opacity) < 1;
|
|
26
|
+
let ancestorHasGradient = false;
|
|
27
|
+
let ancestorHasBackgroundImage = false;
|
|
28
|
+
let ancestor = el.parentElement;
|
|
29
|
+
while (ancestor && ancestor.tagName !== 'HTML') {
|
|
30
|
+
const anStyle = window.getComputedStyle(ancestor);
|
|
31
|
+
if (!hasReducedOpacity && parseFloat(anStyle.opacity) < 1) {
|
|
32
|
+
hasReducedOpacity = true;
|
|
33
|
+
}
|
|
34
|
+
const anBgImg = anStyle.backgroundImage;
|
|
35
|
+
if (anBgImg !== 'none') {
|
|
36
|
+
if (!ancestorHasGradient && anBgImg.includes('gradient')) {
|
|
37
|
+
ancestorHasGradient = true;
|
|
38
|
+
}
|
|
39
|
+
else if (!ancestorHasBackgroundImage) {
|
|
40
|
+
ancestorHasBackgroundImage = true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
ancestor = ancestor.parentElement;
|
|
44
|
+
}
|
|
45
|
+
const mixBlendMode = style.mixBlendMode !== 'normal' ? style.mixBlendMode : null;
|
|
46
|
+
const backdropFilter = style.backdropFilter && style.backdropFilter !== 'none'
|
|
47
|
+
? style.backdropFilter
|
|
48
|
+
: null;
|
|
49
|
+
const filter = style.filter && style.filter !== 'none' ? style.filter : null;
|
|
50
|
+
return {
|
|
51
|
+
backgroundImage: bgImage !== 'none' ? bgImage : '',
|
|
52
|
+
hasGradient,
|
|
53
|
+
hasBackgroundImage,
|
|
54
|
+
ancestorHasGradient,
|
|
55
|
+
ancestorHasBackgroundImage,
|
|
56
|
+
hasReducedOpacity,
|
|
57
|
+
mixBlendMode,
|
|
58
|
+
backdropFilter,
|
|
59
|
+
filter,
|
|
60
|
+
};
|
|
61
|
+
}, selector)
|
|
62
|
+
.catch(() => null);
|
|
63
|
+
if (domContext) {
|
|
64
|
+
node.contrastDOMContext = domContext;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Non-critical; proceed without DOM context
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
6
73
|
export const takeScreenshotForHTMLElements = async (violations, page, randomToken, locatorTimeout = 2000, maxScreenshots = 100) => {
|
|
7
74
|
const newViolations = [];
|
|
8
75
|
let screenshotCount = 0;
|
|
@@ -239,14 +239,17 @@
|
|
|
239
239
|
${item.xpath ? createXpathSection(item.xpath) : ''}
|
|
240
240
|
${createElementSection(item)}
|
|
241
241
|
${
|
|
242
|
-
|
|
243
|
-
|
|
242
|
+
(() => {
|
|
243
|
+
const showDetails = item.displayNeedsReview || ['color-contrast', 'color-contrast-enhanced', 'oobee-grading-text-contents', 'target-size', 'valid-lang'].includes(aiConfig.ruleInCategory.rule);
|
|
244
|
+
return showDetails && item.message
|
|
245
|
+
? `<div class="d-flex justify-content-between g-one">
|
|
244
246
|
<div class="fw-semibold page-item-card-section-title">Details</div>
|
|
245
247
|
<div class="page-item-card-section-content">
|
|
246
|
-
${generateItemMessageElement(item.displayNeedsReview, item.message)}
|
|
248
|
+
${generateItemMessageElement(item.displayNeedsReview || true, item.message)}
|
|
247
249
|
</div>
|
|
248
250
|
</div>`
|
|
249
|
-
|
|
251
|
+
: '';
|
|
252
|
+
})()
|
|
250
253
|
}
|
|
251
254
|
${isPurpleAiRule ? createAiSuggestionSection(item, oobeeAiQueryLabel, aiConfig) : ''}
|
|
252
255
|
${showGenAiUI ? createGenAiSuggestFixSection(item, aiConfig) : ''}
|
|
@@ -393,14 +393,17 @@
|
|
|
393
393
|
</div>
|
|
394
394
|
|
|
395
395
|
${
|
|
396
|
-
|
|
397
|
-
|
|
396
|
+
(() => {
|
|
397
|
+
const showDetails = item.displayNeedsReview || ['color-contrast', 'color-contrast-enhanced', 'oobee-grading-text-contents', 'target-size', 'valid-lang'].includes(aiConfig.ruleInCategory.rule);
|
|
398
|
+
return showDetails && item.message
|
|
399
|
+
? `<div class="d-flex justify-content-between g-one modal-view">
|
|
398
400
|
<div class="fw-semibold page-item-card-section-title">Details</div>
|
|
399
401
|
<div class="page-item-card-section-content">
|
|
400
|
-
${generateItemMessageElement(item.displayNeedsReview, item.message)}
|
|
402
|
+
${generateItemMessageElement(item.displayNeedsReview || true, item.message)}
|
|
401
403
|
</div>
|
|
402
404
|
</div>`
|
|
403
|
-
|
|
405
|
+
: '';
|
|
406
|
+
})()
|
|
404
407
|
}
|
|
405
408
|
${isPurpleAiRule ? createAiSuggestionSection(item, oobeeAiQueryLabel, aiConfig) : ''}
|
|
406
409
|
</div>
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
if (htmlEscapedMessageArray.length === 1) {
|
|
54
54
|
return `<p class="mb-0">${htmlEscapedMessageArray[0]}</p>`;
|
|
55
55
|
} else {
|
|
56
|
-
|
|
56
|
+
const [first, ...rest] = htmlEscapedMessageArray;
|
|
57
|
+
return `<p class="mb-0">${first}</p><ul>${rest.map(m => `<li>${m}</li>`).join('')}</ul>`;
|
|
57
58
|
}
|
|
58
59
|
} else {
|
|
59
60
|
let i = 0;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Details Output Demo
|
|
3
|
+
*
|
|
4
|
+
* Runs scanPage against intentionally non-compliant test pages to capture the
|
|
5
|
+
* enriched Details messages for: color-contrast, color-contrast-enhanced,
|
|
6
|
+
* target-size, valid-lang, and oobee-grading-text-contents.
|
|
7
|
+
*
|
|
8
|
+
* Usage: node examples/details-runner.js
|
|
9
|
+
*/
|
|
10
|
+
import { chromium } from 'playwright';
|
|
11
|
+
import { scanPage } from '../dist/npmIndex.js';
|
|
12
|
+
import { gradeReadability } from '../dist/crawlers/custom/gradeReadability.js';
|
|
13
|
+
|
|
14
|
+
// --- Test HTML pages ---
|
|
15
|
+
|
|
16
|
+
const colorContrastHTML = `
|
|
17
|
+
<!DOCTYPE html>
|
|
18
|
+
<html lang="en">
|
|
19
|
+
<head><title>Color Contrast Test</title></head>
|
|
20
|
+
<body style="background-color: #ffffff;">
|
|
21
|
+
<h1>Color Contrast Violations</h1>
|
|
22
|
+
<p style="color: #999999; font-size: 14px;">This light gray text on white background fails AA contrast</p>
|
|
23
|
+
<p style="color: #aaaaaa; font-size: 14px; background-color: #f0f0f0;">Very light gray on light gray</p>
|
|
24
|
+
<button style="background-color: #55aa99; color: #e8ffe8; font-size: 12px;">Low contrast button</button>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const colorContrastEnhancedHTML = `
|
|
30
|
+
<!DOCTYPE html>
|
|
31
|
+
<html lang="en">
|
|
32
|
+
<head><title>Color Contrast Enhanced Test</title></head>
|
|
33
|
+
<body style="background-color: #ffffff;">
|
|
34
|
+
<h1>Color Contrast Enhanced AAA Violations</h1>
|
|
35
|
+
<p style="color: #757575; font-size: 14px;">This text passes AA but fails AAA needs 7 to 1</p>
|
|
36
|
+
<p style="color: #6b6b6b; font-size: 12px;">Small text needs 7 to 1 for AAA</p>
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
const targetSizeHTML = `
|
|
42
|
+
<!DOCTYPE html>
|
|
43
|
+
<html lang="en">
|
|
44
|
+
<head><title>Target Size Test</title>
|
|
45
|
+
<style>
|
|
46
|
+
body { font-family: sans-serif; padding: 40px; }
|
|
47
|
+
.icon-link {
|
|
48
|
+
display: inline-block;
|
|
49
|
+
width: 16px;
|
|
50
|
+
height: 16px;
|
|
51
|
+
font-size: 10px;
|
|
52
|
+
line-height: 16px;
|
|
53
|
+
text-align: center;
|
|
54
|
+
text-decoration: none;
|
|
55
|
+
color: #333;
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
59
|
+
</head>
|
|
60
|
+
<body>
|
|
61
|
+
<main>
|
|
62
|
+
<h1>Icon-sized interactive targets</h1>
|
|
63
|
+
<a href="/a" class="icon-link" style="width: 16px; height: 16px;">A</a>
|
|
64
|
+
<a href="/b" class="icon-link" style="width: 16px; height: 16px;">B</a>
|
|
65
|
+
<a href="/c" class="icon-link" style="width: 16px; height: 16px;">C</a>
|
|
66
|
+
</main>
|
|
67
|
+
</body>
|
|
68
|
+
</html>
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
const validLangHTML = `
|
|
72
|
+
<!DOCTYPE html>
|
|
73
|
+
<html lang="x-sindarin">
|
|
74
|
+
<head><title>Valid Lang Test</title></head>
|
|
75
|
+
<body>
|
|
76
|
+
<main>
|
|
77
|
+
<h1>Valid Lang Violation</h1>
|
|
78
|
+
<p>This page uses a private-use language subtag that is not valid according to BCP 47.</p>
|
|
79
|
+
<div lang="x-klingon">This section also has an invalid private-use lang tag with some sample text content for context.</div>
|
|
80
|
+
</main>
|
|
81
|
+
</body>
|
|
82
|
+
</html>
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
const readabilityHTML = `
|
|
86
|
+
<!DOCTYPE html>
|
|
87
|
+
<html lang="en">
|
|
88
|
+
<head><title>Readability Test</title></head>
|
|
89
|
+
<body>
|
|
90
|
+
<main>
|
|
91
|
+
<h1>Building Safety Standards</h1>
|
|
92
|
+
<p>The committee reviewed the proposed changes to the building safety standards last Thursday. Members noted that the current regulations do not address modern construction materials adequately. Several technical amendments were suggested to improve clarity for contractors and inspectors. The revised standards will require additional testing for fire resistance in commercial properties. Public consultation on these proposed changes will remain open until the end of next quarter. Building owners should review the draft guidelines to understand potential compliance requirements.</p>
|
|
93
|
+
</main>
|
|
94
|
+
</body>
|
|
95
|
+
</html>
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
// --- Helpers ---
|
|
99
|
+
|
|
100
|
+
function extractMessages(result, ruleId) {
|
|
101
|
+
const messages = [];
|
|
102
|
+
for (const category of ['mustFix', 'goodToFix', 'needsReview']) {
|
|
103
|
+
const rules = result?.[category]?.rules;
|
|
104
|
+
if (rules && rules[ruleId]) {
|
|
105
|
+
const rule = rules[ruleId];
|
|
106
|
+
messages.push({
|
|
107
|
+
category,
|
|
108
|
+
rule: rule.rule || ruleId,
|
|
109
|
+
description: rule.description,
|
|
110
|
+
totalItems: rule.totalItems,
|
|
111
|
+
items: rule.items?.map(item => ({
|
|
112
|
+
html: item.html || item.element,
|
|
113
|
+
message: item.message,
|
|
114
|
+
})),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return messages;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- Main ---
|
|
122
|
+
|
|
123
|
+
(async () => {
|
|
124
|
+
console.log("Launching browser...");
|
|
125
|
+
const browser = await chromium.launch({ headless: true });
|
|
126
|
+
const output = {};
|
|
127
|
+
|
|
128
|
+
// 1. Color Contrast (AA)
|
|
129
|
+
console.log("Scanning: color-contrast...");
|
|
130
|
+
try {
|
|
131
|
+
const page = await browser.newPage();
|
|
132
|
+
await page.setContent(colorContrastHTML);
|
|
133
|
+
const result = await scanPage(page, {
|
|
134
|
+
name: "Test", email: "test@test.com", pageTitle: "Color Contrast Test",
|
|
135
|
+
});
|
|
136
|
+
output['color-contrast'] = extractMessages(result, 'color-contrast');
|
|
137
|
+
await page.close();
|
|
138
|
+
} catch (e) { console.error("color-contrast error:", e.message); }
|
|
139
|
+
|
|
140
|
+
// 2. Color Contrast Enhanced (AAA)
|
|
141
|
+
console.log("Scanning: color-contrast-enhanced...");
|
|
142
|
+
try {
|
|
143
|
+
const page = await browser.newPage();
|
|
144
|
+
await page.setContent(colorContrastEnhancedHTML);
|
|
145
|
+
const result = await scanPage(page, {
|
|
146
|
+
name: "Test", email: "test@test.com", pageTitle: "Color Contrast Enhanced Test",
|
|
147
|
+
ruleset: ['default', 'enable-wcag-aaa'],
|
|
148
|
+
});
|
|
149
|
+
output['color-contrast-enhanced'] = extractMessages(result, 'color-contrast-enhanced');
|
|
150
|
+
await page.close();
|
|
151
|
+
} catch (e) { console.error("color-contrast-enhanced error:", e.message); }
|
|
152
|
+
|
|
153
|
+
// 3. Target Size
|
|
154
|
+
console.log("Scanning: target-size...");
|
|
155
|
+
try {
|
|
156
|
+
const page = await browser.newPage();
|
|
157
|
+
await page.setContent(targetSizeHTML);
|
|
158
|
+
const result = await scanPage(page, {
|
|
159
|
+
name: "Test", email: "test@test.com", pageTitle: "Target Size Test",
|
|
160
|
+
});
|
|
161
|
+
output['target-size'] = extractMessages(result, 'target-size');
|
|
162
|
+
await page.close();
|
|
163
|
+
} catch (e) { console.error("target-size error:", e.message); }
|
|
164
|
+
|
|
165
|
+
// 4. Valid Lang
|
|
166
|
+
console.log("Scanning: valid-lang...");
|
|
167
|
+
try {
|
|
168
|
+
const page = await browser.newPage();
|
|
169
|
+
await page.setContent(validLangHTML);
|
|
170
|
+
const result = await scanPage(page, {
|
|
171
|
+
name: "Test", email: "test@test.com", pageTitle: "Valid Lang Test",
|
|
172
|
+
});
|
|
173
|
+
output['valid-lang'] = extractMessages(result, 'valid-lang');
|
|
174
|
+
await page.close();
|
|
175
|
+
} catch (e) { console.error("valid-lang error:", e.message); }
|
|
176
|
+
|
|
177
|
+
// 5. Readability (oobee-grading-text-contents)
|
|
178
|
+
console.log("Scanning: oobee-grading-text-contents...");
|
|
179
|
+
try {
|
|
180
|
+
const page = await browser.newPage();
|
|
181
|
+
await page.setContent(readabilityHTML);
|
|
182
|
+
|
|
183
|
+
// Simulate what the crawler does: extract text, grade readability
|
|
184
|
+
const textContent = readabilityHTML.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
|
|
185
|
+
const sentences = textContent.split(/(?<=[.!?])\s+/).filter(s => s.trim().length > 0);
|
|
186
|
+
const flag = gradeReadability(sentences);
|
|
187
|
+
console.log(` Readability flag: "${flag}"`);
|
|
188
|
+
|
|
189
|
+
if (flag) {
|
|
190
|
+
const score = parseFloat(flag);
|
|
191
|
+
let interpretation = '';
|
|
192
|
+
if (score > 30) interpretation = 'It is targeted for junior college (JC) level comprehension and above.';
|
|
193
|
+
else interpretation = 'It is targeted for university graduate level comprehension and above.';
|
|
194
|
+
|
|
195
|
+
output['oobee-grading-text-contents'] = [{
|
|
196
|
+
category: 'needsReview',
|
|
197
|
+
rule: 'oobee-grading-text-contents',
|
|
198
|
+
description: 'Page content must use clear, plain language',
|
|
199
|
+
items: [{
|
|
200
|
+
html: '<html lang="en">...</html>',
|
|
201
|
+
message: `Text content is potentially difficult to read. It scored ${flag} out of 50 on the Flesch-Kincaid Readability Test. ${interpretation}`,
|
|
202
|
+
}],
|
|
203
|
+
}];
|
|
204
|
+
} else {
|
|
205
|
+
console.log(" Score filtered out (<=0 or >50). No violation triggered.");
|
|
206
|
+
}
|
|
207
|
+
await page.close();
|
|
208
|
+
} catch (e) { console.error("readability error:", e.message); }
|
|
209
|
+
|
|
210
|
+
await browser.close();
|
|
211
|
+
|
|
212
|
+
// Print results
|
|
213
|
+
console.log("\n" + JSON.stringify(output, null, 2));
|
|
214
|
+
})();
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<title>Combined Accessibility Test</title>
|
|
5
|
+
<style>
|
|
6
|
+
body { font-family: sans-serif; padding: 40px; }
|
|
7
|
+
.icon-link {
|
|
8
|
+
display: inline-block;
|
|
9
|
+
width: 16px;
|
|
10
|
+
height: 16px;
|
|
11
|
+
font-size: 10px;
|
|
12
|
+
line-height: 16px;
|
|
13
|
+
text-align: center;
|
|
14
|
+
text-decoration: none;
|
|
15
|
+
color: #333;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
}
|
|
18
|
+
</style>
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<main>
|
|
22
|
+
<h1>Accessibility Violations Test Page</h1>
|
|
23
|
+
|
|
24
|
+
<h2>Color Contrast</h2>
|
|
25
|
+
<p style="color: #999999; font-size: 14px;">This light gray text on white background fails AA contrast</p>
|
|
26
|
+
<p style="color: #aaaaaa; font-size: 14px; background-color: #f0f0f0;">Very light gray on light gray</p>
|
|
27
|
+
<button style="background-color: #55aa99; color: #e8ffe8; font-size: 12px;">Low contrast button</button>
|
|
28
|
+
|
|
29
|
+
<h2>Target Size</h2>
|
|
30
|
+
<a href="/a" class="icon-link" style="width: 16px; height: 16px;">A</a>
|
|
31
|
+
<a href="/b" class="icon-link" style="width: 16px; height: 16px;">B</a>
|
|
32
|
+
<a href="/c" class="icon-link" style="width: 16px; height: 16px;">C</a>
|
|
33
|
+
|
|
34
|
+
<h2>Valid Lang</h2>
|
|
35
|
+
<div lang="x-klingon">This section has an invalid private-use lang tag with some sample text content for context.</div>
|
|
36
|
+
|
|
37
|
+
<h2>Readability</h2>
|
|
38
|
+
<p>The committee reviewed the proposed changes to the building safety standards last Thursday. Members noted that the current regulations do not address modern construction materials adequately. Several technical amendments were suggested to improve clarity for contractors and inspectors. The revised standards will require additional testing for fire resistance in commercial properties. Public consultation on these proposed changes will remain open until the end of next quarter. Building owners should review the draft guidelines to understand potential compliance requirements.</p>
|
|
39
|
+
|
|
40
|
+
</main>
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
package/package.json
CHANGED
package/src/combine.ts
CHANGED