@d-zero/a11y-check-axe-scenario 0.2.0
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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/axe-puppeteer.d.ts +3 -0
- package/dist/axe-puppeteer.js +17 -0
- package/dist/axe.d.ts +3 -0
- package/dist/axe.js +36 -0
- package/dist/convert-results-from-node.d.ts +8 -0
- package/dist/convert-results-from-node.js +85 -0
- package/dist/convert-results-from-violations.d.ts +4 -0
- package/dist/convert-results-from-violations.js +41 -0
- package/dist/detect-level.d.ts +1 -0
- package/dist/detect-level.js +9 -0
- package/dist/detect-version.d.ts +1 -0
- package/dist/detect-version.js +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/infer-explanation.d.ts +4 -0
- package/dist/infer-explanation.js +390 -0
- package/dist/pargraph.d.ts +2 -0
- package/dist/pargraph.js +6 -0
- package/dist/sc.d.ts +1 -0
- package/dist/sc.js +23 -0
- package/dist/tags-to-scs.d.ts +2 -0
- package/dist/tags-to-scs.js +23 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.js +1 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 D-ZERO Co., Ltd.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# `@d-zero/a11y-check-axe-scenario`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AxePuppeteer } from '@axe-core/puppeteer';
|
|
2
|
+
import { delay } from '@d-zero/shared/delay';
|
|
3
|
+
export async function runAxePuppeteer(page, lang, log) {
|
|
4
|
+
const mod = await import(`axe-core/locales/${lang}.json`, {
|
|
5
|
+
with: { type: 'json' },
|
|
6
|
+
});
|
|
7
|
+
const locale = mod.default;
|
|
8
|
+
log(`Analyze%dots%`);
|
|
9
|
+
const axeResults = await new AxePuppeteer(page)
|
|
10
|
+
.configure({
|
|
11
|
+
locale,
|
|
12
|
+
})
|
|
13
|
+
.analyze();
|
|
14
|
+
log(`Found ${axeResults.violations.length} violations, ${axeResults.incomplete.length} incomplete issues`);
|
|
15
|
+
await delay(600);
|
|
16
|
+
return axeResults;
|
|
17
|
+
}
|
package/dist/axe.d.ts
ADDED
package/dist/axe.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createScenario } from '@d-zero/a11y-check-core';
|
|
2
|
+
import { Cache } from '@d-zero/shared/cache';
|
|
3
|
+
import { convertResultsFromViolations } from './convert-results-from-violations.js';
|
|
4
|
+
const scenarioId = 'a11y-check/axe';
|
|
5
|
+
export default createScenario((options) => {
|
|
6
|
+
const cache = new Cache(scenarioId, options?.cacheDir);
|
|
7
|
+
return {
|
|
8
|
+
id: scenarioId,
|
|
9
|
+
async exec(page, sizeName, log) {
|
|
10
|
+
if (options?.cache === false) {
|
|
11
|
+
await cache.clear();
|
|
12
|
+
}
|
|
13
|
+
const axeLog = (message) => log(`🪓 ${message}`);
|
|
14
|
+
const key = (await page.url()) + '#' + sizeName;
|
|
15
|
+
const cached = await cache.load(key, (key, value) => {
|
|
16
|
+
if (key === 'timestamp') {
|
|
17
|
+
return new Date(Date.parse(value));
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
});
|
|
21
|
+
if (cached) {
|
|
22
|
+
return {
|
|
23
|
+
violations: await convertResultsFromViolations(page, cached, sizeName, options?.screenshot ?? false, axeLog),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const axeResults = await page.axe({
|
|
27
|
+
lang: options?.lang ?? 'ja',
|
|
28
|
+
log: axeLog,
|
|
29
|
+
});
|
|
30
|
+
await cache.store(key, axeResults);
|
|
31
|
+
return {
|
|
32
|
+
violations: await convertResultsFromViolations(page, axeResults, sizeName, options?.screenshot ?? false, axeLog),
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Style } from '@d-zero/a11y-check-core';
|
|
2
|
+
import type { Page } from '@d-zero/puppeteer-page';
|
|
3
|
+
import type { NodeResult } from 'axe-core';
|
|
4
|
+
export declare function convertResultsFromNode(page: Page, node: NodeResult, screenshot: boolean, log: (log: string) => void): Promise<{
|
|
5
|
+
screenshot: string | null;
|
|
6
|
+
style: Style | null;
|
|
7
|
+
landmark: string | null;
|
|
8
|
+
} | null>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { hash } from '@d-zero/shared/hash';
|
|
3
|
+
export async function convertResultsFromNode(page, node, screenshot, log) {
|
|
4
|
+
const target = node.target[0] && typeof node.target[0] === 'string' ? node.target[0] : null;
|
|
5
|
+
if (!target) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
log(`Node: ${target}`);
|
|
9
|
+
let screenshotName = null;
|
|
10
|
+
if (screenshot && target) {
|
|
11
|
+
log(`Screenshot: ${target}`);
|
|
12
|
+
const url = await page.url();
|
|
13
|
+
const ssName = hash(url + target) + '.png';
|
|
14
|
+
const elementScreenshot = await page.elementScreenshot(target, {
|
|
15
|
+
path: path.resolve(process.cwd(), '.cache', ssName),
|
|
16
|
+
});
|
|
17
|
+
if (elementScreenshot) {
|
|
18
|
+
screenshotName = ssName;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const style = await page.evaluate((selector) => {
|
|
22
|
+
const el = document.querySelector(selector);
|
|
23
|
+
if (!el) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const style = globalThis.getComputedStyle(el);
|
|
27
|
+
const closest = {
|
|
28
|
+
closestBackgroundColor: null,
|
|
29
|
+
closestBackgroundImage: null,
|
|
30
|
+
};
|
|
31
|
+
let current = el.parentElement;
|
|
32
|
+
while (current) {
|
|
33
|
+
const currentStyle = globalThis.getComputedStyle(current);
|
|
34
|
+
if (currentStyle.backgroundColor !== 'rgba(0, 0, 0, 0)') {
|
|
35
|
+
closest.closestBackgroundColor = currentStyle.backgroundColor;
|
|
36
|
+
}
|
|
37
|
+
if (currentStyle.backgroundImage !== 'none') {
|
|
38
|
+
closest.closestBackgroundImage = currentStyle.backgroundImage;
|
|
39
|
+
}
|
|
40
|
+
if (closest.closestBackgroundColor || closest.closestBackgroundImage) {
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
current = current.parentElement;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
color: style.color,
|
|
47
|
+
backgroundColor: style.backgroundColor,
|
|
48
|
+
backgroundImage: style.backgroundImage,
|
|
49
|
+
...closest,
|
|
50
|
+
};
|
|
51
|
+
}, target);
|
|
52
|
+
log(`Get landmark: ${target}`);
|
|
53
|
+
const landmark = await page.evaluate((selector) => {
|
|
54
|
+
const el = document.querySelector(selector);
|
|
55
|
+
if (!el) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
if (el.closest('header, [role="banner"]')) {
|
|
59
|
+
return 'header';
|
|
60
|
+
}
|
|
61
|
+
if (el.closest('footer, [role="contentinfo"]')) {
|
|
62
|
+
return 'footer';
|
|
63
|
+
}
|
|
64
|
+
if (el.closest('nav, [role="navigation"]')) {
|
|
65
|
+
return 'nav';
|
|
66
|
+
}
|
|
67
|
+
if (el.closest('main, [role="main"]')) {
|
|
68
|
+
return 'main';
|
|
69
|
+
}
|
|
70
|
+
if (el.closest('aside, [role="complementary"]')) {
|
|
71
|
+
return 'aside';
|
|
72
|
+
}
|
|
73
|
+
const identicalComponent = el.closest('[id]');
|
|
74
|
+
if (identicalComponent) {
|
|
75
|
+
return `#${identicalComponent.id}`;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}, target);
|
|
79
|
+
log(`Style: ${JSON.stringify(style)}`);
|
|
80
|
+
return {
|
|
81
|
+
screenshot: screenshotName,
|
|
82
|
+
style,
|
|
83
|
+
landmark,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Violation } from '@d-zero/a11y-check-core';
|
|
2
|
+
import type { Page } from '@d-zero/puppeteer-page';
|
|
3
|
+
import type { AxeResults } from 'axe-core';
|
|
4
|
+
export declare function convertResultsFromViolations(page: Page, axeResults: AxeResults, sizeName: string, screenshot: boolean, log: (log: string) => void): Promise<Violation[]>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { convertResultsFromNode } from './convert-results-from-node.js';
|
|
2
|
+
import { detectLevel } from './detect-level.js';
|
|
3
|
+
import { inferExplanation } from './infer-explanation.js';
|
|
4
|
+
import { p } from './pargraph.js';
|
|
5
|
+
import { tagsToSCs } from './tags-to-scs.js';
|
|
6
|
+
export async function convertResultsFromViolations(page, axeResults, sizeName, screenshot, log) {
|
|
7
|
+
const results = [];
|
|
8
|
+
const violations = [...axeResults.incomplete, ...axeResults.violations];
|
|
9
|
+
for (const violation of violations) {
|
|
10
|
+
const nodeResults = violation.nodes;
|
|
11
|
+
for (const node of nodeResults) {
|
|
12
|
+
const nodeResult = await convertResultsFromNode(page, node, screenshot, log);
|
|
13
|
+
const explanation = inferExplanation(violation.id, node, nodeResult?.style ?? null);
|
|
14
|
+
results.push({
|
|
15
|
+
id: '',
|
|
16
|
+
url: await page.url(),
|
|
17
|
+
tool: `${axeResults.testEngine.name} (v${axeResults.testEngine.version})`,
|
|
18
|
+
timestamp: new Date(axeResults.timestamp),
|
|
19
|
+
component: nodeResult?.landmark ?? null,
|
|
20
|
+
environment: sizeName,
|
|
21
|
+
targetNode: {
|
|
22
|
+
value: node.html,
|
|
23
|
+
},
|
|
24
|
+
asIs: {
|
|
25
|
+
value: p(`${violation.help}\n(${violation.helpUrl})`, violation.description, node.failureSummary),
|
|
26
|
+
},
|
|
27
|
+
toBe: {
|
|
28
|
+
value: p(explanation?.main),
|
|
29
|
+
},
|
|
30
|
+
explanation: {
|
|
31
|
+
value: p(explanation?.help),
|
|
32
|
+
},
|
|
33
|
+
wcagVersion: violation.tags.includes('wcag2a') ? 'WCAG2.0' : 'WCAG2.1',
|
|
34
|
+
scNumber: tagsToSCs(violation.tags),
|
|
35
|
+
level: detectLevel(violation.tags),
|
|
36
|
+
screenshot: nodeResult?.screenshot ?? null,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return results;
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function detectLevel(tags: readonly string[]): "A" | "AA" | "AAA" | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function detectLevel(tags: readonly string[]): "A" | "AA" | "AAA" | null;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AxeRuleId, Explanation } from './types.js';
|
|
2
|
+
import type { Style } from '@d-zero/a11y-check-core';
|
|
3
|
+
import type { NodeResult } from 'axe-core';
|
|
4
|
+
export declare function inferExplanation(id: AxeRuleId, node: NodeResult, style: Style | null): Explanation | null;
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { colorContrastCheck, ColorContrastError } from '@d-zero/a11y-check-core';
|
|
2
|
+
import { br, p } from './pargraph.js';
|
|
3
|
+
export function inferExplanation(id, node, style) {
|
|
4
|
+
switch (id) {
|
|
5
|
+
case 'accesskeys': {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
case 'area-alt': {
|
|
9
|
+
return {
|
|
10
|
+
main: 'alt属性に「______」を設定してください。',
|
|
11
|
+
help: '',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
case 'aria-allowed-attr': {
|
|
15
|
+
return {
|
|
16
|
+
main: 'aria属性に「______」を設定してください。',
|
|
17
|
+
help: '',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
case 'aria-allowed-role': {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
case 'aria-braille-equivalent': {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
case 'aria-command-name': {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
case 'aria-conditional-attr': {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
case 'aria-deprecated-role': {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
case 'aria-dialog-name': {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
case 'aria-hidden-body': {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
case 'aria-hidden-focus': {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
case 'aria-input-field-name': {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
case 'aria-meter-name': {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
case 'aria-progressbar-name': {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
case 'aria-prohibited-attr': {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
case 'aria-required-attr': {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
case 'aria-required-children': {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
case 'aria-required-parent': {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
case 'aria-roledescription': {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
case 'aria-roles': {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
case 'aria-text': {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
case 'aria-toggle-field-name': {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
case 'aria-tooltip-name': {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
case 'aria-treeitem-name': {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
case 'aria-valid-attr-value': {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
case 'aria-valid-attr': {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
case 'audio-caption': {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
case 'autocomplete-valid': {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
case 'avoid-inline-spacing': {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
case 'blink': {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
case 'button-name': {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
case 'bypass': {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
case 'color-contrast-enhanced': {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
case 'color-contrast': {
|
|
111
|
+
if (node.any?.[0]) {
|
|
112
|
+
let main = '';
|
|
113
|
+
let contrastResult = style ? colorContrastCheck(style) : null;
|
|
114
|
+
let needCheck = null;
|
|
115
|
+
let message = null;
|
|
116
|
+
switch (contrastResult) {
|
|
117
|
+
case null: {
|
|
118
|
+
needCheck = 'N/A';
|
|
119
|
+
message = '要素のスタイル取得に失敗しました。';
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
case ColorContrastError.DOES_NOT_DETERMINE_FOREGROUND: {
|
|
123
|
+
needCheck = 'N/A';
|
|
124
|
+
message = 'フォントの色が判定できません。';
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case ColorContrastError.DOES_NOT_DETERMINE_BACKGROUND: {
|
|
128
|
+
needCheck = 'WARNING';
|
|
129
|
+
message = '背景色が判定できないかったので#FFFFFFとして判定しています。';
|
|
130
|
+
contrastResult = style
|
|
131
|
+
? colorContrastCheck({
|
|
132
|
+
...style,
|
|
133
|
+
backgroundColor: 'rgb(255, 255, 255)',
|
|
134
|
+
})
|
|
135
|
+
: null;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case ColorContrastError.FOREGROUND_COLOR_HAS_ALPHA: {
|
|
139
|
+
needCheck = 'N/A';
|
|
140
|
+
message = 'フォントの色に透明度が設定されています。';
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case ColorContrastError.BACKGROUND_COLOR_HAS_ALPHA: {
|
|
144
|
+
needCheck = 'N/A';
|
|
145
|
+
message = '背景色に透明度が設定されています。';
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
default: {
|
|
149
|
+
if (node.any[0].message.includes('判定できません')) {
|
|
150
|
+
needCheck = 'WARNING';
|
|
151
|
+
}
|
|
152
|
+
if (contrastResult.AA === false) {
|
|
153
|
+
main = p('AAのコントラスト比を満たしていません。', br(`前景色: ${contrastResult.foreground.hex}`, `背景色: ${contrastResult.background.hex}`, `コントラスト比: ${contrastResult.ratioText}`), 'コントラスト比は4.5:1を満たすようにしてください。');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
main,
|
|
159
|
+
help: p(needCheck ? `${needCheck} 目視で確認してください。` : null, node.any[0].message, message, typeof contrastResult === 'number'
|
|
160
|
+
? null
|
|
161
|
+
: br(`前景色: ${contrastResult?.foreground.hex ?? '判定不可'}`, `背景色: ${contrastResult?.background.hex ?? '判定不可'}`, `コントラスト比: ${contrastResult?.ratioText ?? '判定不可'}`, `AA: ${contrastResult?.AA ? '適合' : '不適合'}`, `AAA: ${contrastResult?.AAA ? '適合' : '不適合'}`), br(`font-size: ${node.any[0].data.fontSize}`, `font-weight: ${node.any[0].data.fontWeight}`, `color: ${style?.color ?? null}`, `background-color: ${style?.backgroundColor ?? null}`, `background-image: ${style?.backgroundImage ?? null}`, `先祖要素 background-color: ${style?.closestBackgroundColor ?? null}`, `先祖要素 background-image: ${style?.closestBackgroundImage ?? null}`)),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
case 'css-orientation-lock': {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
case 'definition-list': {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
// cspell:disable-next-line
|
|
173
|
+
case 'dlitem': {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
case 'document-title': {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
case 'duplicate-id-active': {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
case 'duplicate-id-aria': {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
case 'duplicate-id': {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
case 'empty-heading': {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
case 'empty-table-header': {
|
|
192
|
+
// td要素に変更してください。
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
case 'focus-order-semantics': {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
case 'form-field-multiple-labels': {
|
|
199
|
+
// 「性別」のラベルはfieldset/legend要素をつかって複数のラジオボタンをグルーピングするように修正してください。
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
case 'frame-focusable-content': {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
case 'frame-tested': {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
case 'frame-title-unique': {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
case 'frame-title': {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
case 'heading-order': {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
case 'hidden-content': {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
case 'html-has-lang': {
|
|
221
|
+
return {
|
|
222
|
+
main: 'html要素にlang属性を設定してください。日本語の場合は「ja」、英語の場合は「en」を設定してください。',
|
|
223
|
+
help: '',
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
case 'html-lang-valid': {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
case 'html-xml-lang-mismatch': {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
case 'identical-links-same-purpose': {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
case 'image-alt': {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
case 'image-redundant-alt': {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
case 'input-button-name': {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
case 'input-image-alt': {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
case 'label-content-name-mismatch': {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
case 'label-title-only': {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
case 'label': {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
case 'landmark-banner-is-top-level': {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
case 'landmark-complementary-is-top-level': {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
case 'landmark-contentinfo-is-top-level': {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
case 'landmark-main-is-top-level': {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
case 'landmark-no-duplicate-banner': {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
case 'landmark-no-duplicate-contentinfo': {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
case 'landmark-no-duplicate-main': {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
case 'landmark-one-main': {
|
|
278
|
+
return {
|
|
279
|
+
main: 'ページのメインコンテンツをmain要素で囲んでください。header要素やfooter要素とは別の領域が望ましいです。',
|
|
280
|
+
help: '',
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
case 'landmark-unique': {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
case 'link-in-text-block': {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
case 'link-name': {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
case 'list': {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
case 'listitem': {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
case 'marquee': {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
case 'meta-refresh-no-exceptions': {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
case 'meta-refresh': {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
case 'meta-viewport-large': {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
case 'meta-viewport': {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
case 'nested-interactive': {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
case 'no-autoplay-audio': {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
case 'object-alt': {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
case 'p-as-heading': {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
case 'page-has-heading-one': {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
case 'presentation-role-conflict': {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
case 'region': {
|
|
332
|
+
return {
|
|
333
|
+
main: 'main要素、header要素、footer要素などのランドマーク要素のいずれかの中にあるようにしてください。',
|
|
334
|
+
help: '',
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
case 'role-img-alt': {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
case 'scope-attr-valid': {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
case 'scrollable-region-focusable': {
|
|
344
|
+
// tabindex="0"を設定してください。
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
case 'select-name': {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
case 'server-side-image-map': {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
case 'skip-link': {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
case 'summary-name': {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
case 'svg-img-alt': {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
case 'tabindex': {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
case 'table-duplicate-name': {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
case 'table-fake-caption': {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
case 'target-size': {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
case 'td-has-header': {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
case 'td-headers-attr': {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
case 'th-has-data-cells': {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
case 'valid-lang': {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
case 'video-caption': {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
package/dist/pargraph.js
ADDED
package/dist/sc.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sc(tags: string[]): string;
|
package/dist/sc.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { wcag } from '@d-zero/db-wcag';
|
|
2
|
+
const scVersions = new Set(
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
Object.keys(wcag.successCriterions['wcag_2.2'].en));
|
|
5
|
+
export function sc(tags) {
|
|
6
|
+
return tags.map(_sc).filter(Boolean).join('\n');
|
|
7
|
+
}
|
|
8
|
+
function _sc(tag) {
|
|
9
|
+
if (!tag.startsWith('wcag') || tag.endsWith('a')) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const num = /\d+/.exec(tag)?.[0];
|
|
13
|
+
if (!num) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
for (const version of scVersions) {
|
|
17
|
+
const crushedNum = version.replaceAll('.', '');
|
|
18
|
+
if (tag === crushedNum) {
|
|
19
|
+
return version;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare function tagsToSCs(tags: string[]): string;
|
|
2
|
+
export declare function tagToSC(tag: string): "1.1.1" | "1.2.1" | "1.2.2" | "1.2.3" | "1.2.4" | "1.2.5" | "1.2.6" | "1.2.7" | "1.2.8" | "1.2.9" | "1.3.1" | "1.3.2" | "1.3.3" | "1.3.4" | "1.3.5" | "1.3.6" | "1.4.1" | "1.4.2" | "1.4.3" | "1.4.4" | "1.4.5" | "1.4.6" | "1.4.7" | "1.4.8" | "1.4.9" | "1.4.10" | "1.4.11" | "1.4.12" | "1.4.13" | "2.1.1" | "2.1.2" | "2.1.3" | "2.1.4" | "2.2.1" | "2.2.2" | "2.2.3" | "2.2.4" | "2.2.5" | "2.2.6" | "2.3.1" | "2.3.2" | "2.3.3" | "2.4.1" | "2.4.2" | "2.4.3" | "2.4.4" | "2.4.5" | "2.4.6" | "2.4.7" | "2.4.8" | "2.4.9" | "2.4.10" | "2.4.11" | "2.4.12" | "2.4.13" | "2.5.1" | "2.5.2" | "2.5.3" | "2.5.4" | "2.5.5" | "2.5.6" | "2.5.7" | "2.5.8" | "3.1.1" | "3.1.2" | "3.1.3" | "3.1.4" | "3.1.5" | "3.1.6" | "3.2.1" | "3.2.2" | "3.2.3" | "3.2.4" | "3.2.5" | "3.2.6" | "3.3.1" | "3.3.2" | "3.3.3" | "3.3.4" | "3.3.5" | "3.3.6" | "3.3.7" | "3.3.8" | "3.3.9" | "4.1.2" | "4.1.3" | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { wcag } from '@d-zero/db-wcag';
|
|
2
|
+
const scVersions = new Set(
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
Object.keys(wcag.successCriterions['wcag_2.2'].en));
|
|
5
|
+
export function tagsToSCs(tags) {
|
|
6
|
+
return tags.map(tagToSC).filter(Boolean).join('\n');
|
|
7
|
+
}
|
|
8
|
+
export function tagToSC(tag) {
|
|
9
|
+
if (!tag.startsWith('wcag') || tag.endsWith('a')) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const num = /\d+/.exec(tag)?.[0];
|
|
13
|
+
if (!num) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
for (const version of scVersions) {
|
|
17
|
+
const crushedNum = version.replaceAll('.', '');
|
|
18
|
+
if (num === crushedNum) {
|
|
19
|
+
return version;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CoreOptions } from '@d-zero/a11y-check-core';
|
|
2
|
+
export type A11yCheckAxeOptions = CoreOptions & {
|
|
3
|
+
readonly lang?: string;
|
|
4
|
+
};
|
|
5
|
+
export type Explanation = {
|
|
6
|
+
main: string;
|
|
7
|
+
help: string;
|
|
8
|
+
};
|
|
9
|
+
export type AxeRuleId = 'area-alt' | 'aria-allowed-attr' | 'aria-braille-equivalent' | 'aria-command-name' | 'aria-conditional-attr' | 'aria-deprecated-role' | 'aria-hidden-body' | 'aria-hidden-focus' | 'aria-input-field-name' | 'aria-meter-name' | 'aria-progressbar-name' | 'aria-prohibited-attr' | 'aria-required-attr' | 'aria-required-children' | 'aria-required-parent' | 'aria-roles' | 'aria-toggle-field-name' | 'aria-tooltip-name' | 'aria-valid-attr-value' | 'aria-valid-attr' | 'blink' | 'button-name' | 'bypass' | 'color-contrast' | 'definition-list' | 'dlitem' | 'document-title' | 'duplicate-id-aria' | 'form-field-multiple-labels' | 'frame-focusable-content' | 'frame-title-unique' | 'frame-title' | 'html-has-lang' | 'html-lang-valid' | 'html-xml-lang-mismatch' | 'image-alt' | 'input-button-name' | 'input-image-alt' | 'label' | 'link-in-text-block' | 'link-name' | 'list' | 'listitem' | 'marquee' | 'meta-refresh' | 'meta-viewport' | 'nested-interactive' | 'no-autoplay-audio' | 'object-alt' | 'role-img-alt' | 'scrollable-region-focusable' | 'select-name' | 'server-side-image-map' | 'summary-name' | 'svg-img-alt' | 'td-headers-attr' | 'th-has-data-cells' | 'valid-lang' | 'video-caption' | 'autocomplete-valid' | 'avoid-inline-spacing' | 'target-size' | 'accesskeys' | 'aria-allowed-role' | 'aria-dialog-name' | 'aria-text' | 'aria-treeitem-name' | 'empty-heading' | 'empty-table-header' | 'frame-tested' | 'heading-order' | 'image-redundant-alt' | 'label-title-only' | 'landmark-banner-is-top-level' | 'landmark-complementary-is-top-level' | 'landmark-contentinfo-is-top-level' | 'landmark-main-is-top-level' | 'landmark-no-duplicate-banner' | 'landmark-no-duplicate-contentinfo' | 'landmark-no-duplicate-main' | 'landmark-one-main' | 'landmark-unique' | 'meta-viewport-large' | 'page-has-heading-one' | 'presentation-role-conflict' | 'region' | 'scope-attr-valid' | 'skip-link' | 'tabindex' | 'table-duplicate-name' | 'color-contrast-enhanced' | 'identical-links-same-purpose' | 'meta-refresh-no-exceptions' | 'css-orientation-lock' | 'focus-order-semantics' | 'hidden-content' | 'label-content-name-mismatch' | 'p-as-heading' | 'table-fake-caption' | 'td-has-header' | 'aria-roledescription' | 'audio-caption' | 'duplicate-id-active' | 'duplicate-id';
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@d-zero/a11y-check-axe-scenario",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Accessibility Checker Axe Scenario",
|
|
5
|
+
"author": "D-ZERO",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"private": false,
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"watch": "tsc --watch",
|
|
24
|
+
"clean": "tsc --build --clean"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@d-zero/a11y-check-core": "0.2.0",
|
|
28
|
+
"@d-zero/db-wcag": "1.0.0-alpha.1",
|
|
29
|
+
"@d-zero/shared": "0.6.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@d-zero/puppeteer-page": "0.2.0",
|
|
33
|
+
"axe-core": "4.10.2"
|
|
34
|
+
},
|
|
35
|
+
"gitHead": "1eb1c03400580040119121e11ffb33acd876fe1b"
|
|
36
|
+
}
|