@a11y-skills/audit 0.2.0 → 0.3.1
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/CHANGELOG.md +54 -0
- package/README.ja.md +52 -4
- package/README.md +53 -4
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +104 -21
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/playwright/index.d.ts +1 -1
- package/dist/playwright/index.js +1 -1
- package/dist/playwright/runAutoPlayDetection.js +15 -4
- package/dist/playwright/runAutocompleteAudit.js +46 -11
- package/dist/playwright/runAxeAudit.d.ts +4 -0
- package/dist/playwright/runAxeAudit.js +26 -30
- package/dist/playwright/runFocusIndicatorCheck.d.ts +7 -0
- package/dist/playwright/runFocusIndicatorCheck.js +98 -31
- package/dist/playwright/runOrientationCheck.js +13 -7
- package/dist/playwright/runReflowCheck.js +22 -12
- package/dist/playwright/runTargetSizeCheck.js +49 -12
- package/dist/playwright/runTextSpacingCheck.js +64 -10
- package/dist/playwright/runTimeLimitDetector.js +56 -25
- package/dist/playwright/runZoomCheck.js +46 -15
- package/dist/schemas/index.d.ts +8 -1
- package/dist/schemas/index.js +397 -292
- package/dist/types.d.ts +137 -53
- package/dist/types.js +9 -0
- package/dist/utils/axe-format.d.ts +88 -0
- package/dist/utils/axe-format.js +370 -0
- package/dist/utils/layout.d.ts +2 -0
- package/dist/utils/layout.js +26 -2
- package/dist/utils/recommendations.js +2 -2
- package/dist/utils/rule-registry.d.ts +216 -0
- package/dist/utils/rule-registry.js +220 -0
- package/dist/utils/test-harness.d.ts +2 -2
- package/dist/utils/test-harness.js +5 -6
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,60 @@
|
|
|
3
3
|
All notable changes to `@a11y-skills/audit` are documented here. This project
|
|
4
4
|
adheres to [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## 0.3.1
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- `runFocusIndicatorCheck`: focus-triggered navigations slower than the
|
|
11
|
+
per-Tab settle window are no longer silently missed. The settle window is
|
|
12
|
+
now configurable via the new `navigationSettleMs` option (default: 50), and
|
|
13
|
+
a `framenavigated` listener additionally catches URL changes that commit and
|
|
14
|
+
revert within a single window. (#26)
|
|
15
|
+
|
|
16
|
+
## 0.3.0
|
|
17
|
+
|
|
18
|
+
**Breaking** — every check now returns (and saves) a single axe-style envelope
|
|
19
|
+
instead of its own ad-hoc shape. The public API is not yet stable in `0.x`, so
|
|
20
|
+
this lands as a minor release.
|
|
21
|
+
|
|
22
|
+
### Changed (breaking)
|
|
23
|
+
|
|
24
|
+
- All `runXxx()` functions return `AuditCheckResult<TDetails>`: findings are
|
|
25
|
+
normalized into `violations` / `incomplete` / `passes` / `inapplicable`
|
|
26
|
+
rule arrays (axe-style `id` / `impact` / `tags` / `helpUrl` / `nodes`), with
|
|
27
|
+
rule-level counts in `summary`. The former top-level fields (`issues`,
|
|
28
|
+
`failAA`, `overflowingElements`, `clippedElements`, ...) moved unchanged
|
|
29
|
+
under `details`.
|
|
30
|
+
- Classification is conservative: only findings whose detection has no blind
|
|
31
|
+
spot and where no WCAG exception can apply are `violations`
|
|
32
|
+
(on-focus context change, text-spacing clipping, invalid autocomplete
|
|
33
|
+
tokens). Everything else — reflow/zoom overflow, meta refresh, orientation
|
|
34
|
+
lock, missing focus styles, undersized targets — lands in `incomplete`, the
|
|
35
|
+
manual-review queue.
|
|
36
|
+
- Saved JSON files use the same envelope; the JSON Schemas were rewritten
|
|
37
|
+
accordingly (`$defs`-based shared envelope + per-check `details` schemas).
|
|
38
|
+
Pre-0.3.0 result files no longer validate.
|
|
39
|
+
- `saveAuditResult()` no longer appends a disclaimer (the envelope carries it).
|
|
40
|
+
- `runAxeAudit` builds the buckets from the raw axe results: violations and
|
|
41
|
+
incomplete keep their nodes, passes/inapplicable keep rule metadata only;
|
|
42
|
+
`details` records the execution configuration.
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- `rule-registry.ts`: per-rule metadata (`sc`, accurate per-SC `wcag*` tags,
|
|
47
|
+
`impact`, `scope`, classification) — target size split into
|
|
48
|
+
`a11y-skills/target-size-minimum` (2.5.8 AA) and
|
|
49
|
+
`a11y-skills/target-size-enhanced` (2.5.5 AAA).
|
|
50
|
+
- Pure normalization mappers (`normalize*`), `buildAuditResult()`, and the
|
|
51
|
+
opt-in `mergeNormalizedResults()` exported from the package root. The
|
|
52
|
+
buckets are re-derivable from a saved result's `details`.
|
|
53
|
+
- Element-level evidence: detail records now carry `html` (outerHTML, capped
|
|
54
|
+
at `HTML_SNIPPET_MAX_LENGTH`) and `htmlTruncated`; focus issues gained
|
|
55
|
+
`selector`. `TargetSizeIssue.exceptionAssessment`
|
|
56
|
+
(`ruled-out`/`possible`/`not-assessed`) drives the violation promotion.
|
|
57
|
+
- Tests: mapper unit tests, merge invariants, and ajv (draft 2020-12) schema
|
|
58
|
+
validation of the produced envelopes.
|
|
59
|
+
|
|
6
60
|
## 0.2.0
|
|
7
61
|
|
|
8
62
|
Phase 2 checks added (additive). Still a `0.x` preview — the function API may
|
package/README.ja.md
CHANGED
|
@@ -67,7 +67,7 @@ test("axe audit", async ({ page }, testInfo) => {
|
|
|
67
67
|
// outputFile: "axe-result.json", // 任意で上書き
|
|
68
68
|
// tags: ["wcag2a", "wcag2aa", "wcag21aa", "wcag22aa"],
|
|
69
69
|
});
|
|
70
|
-
expect(result.violationCount).toBe(0);
|
|
70
|
+
expect(result.summary.violationCount).toBe(0);
|
|
71
71
|
});
|
|
72
72
|
```
|
|
73
73
|
|
|
@@ -86,7 +86,7 @@ test("focus indicators", async ({ browser }, testInfo) => {
|
|
|
86
86
|
screenshot: true, // 既定: false
|
|
87
87
|
// contextOptions: { locale: "ja-JP" }, // browser.newContext() に転送
|
|
88
88
|
});
|
|
89
|
-
expect(result.elementsWithoutFocusStyle).toBe(0);
|
|
89
|
+
expect(result.details.elementsWithoutFocusStyle).toBe(0);
|
|
90
90
|
});
|
|
91
91
|
```
|
|
92
92
|
|
|
@@ -135,6 +135,52 @@ TEST_PAGE=https://example.com A11Y_OUTPUT_DIR=./a11y-results npx playwright test
|
|
|
135
135
|
> を `testMatch` に指定してもテストは見つかりません。上記の 1 行 re-export spec が
|
|
136
136
|
> entry を実行する正式な方法です。
|
|
137
137
|
|
|
138
|
+
## 結果形式
|
|
139
|
+
|
|
140
|
+
すべての検査は同じ axe 風の envelope を返し、同じ形式で JSON に保存します:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
interface AuditCheckResult<TDetails> {
|
|
144
|
+
source: CheckSource; // 例: "reflow-check"
|
|
145
|
+
url: string;
|
|
146
|
+
timestamp: string;
|
|
147
|
+
violations: NormalizedRuleResult[]; // 確定した違反
|
|
148
|
+
incomplete: NormalizedRuleResult[]; // 要手動確認
|
|
149
|
+
passes: NormalizedRuleResult[]; // 実行して問題が見つからなかったルール
|
|
150
|
+
inapplicable: NormalizedRuleResult[]; // 検査対象がなかったルール
|
|
151
|
+
summary: { violationCount; incompleteCount; passCount; checkedNodes? };
|
|
152
|
+
details: TDetails; // 検査固有の証跡(測定値・スクリーンショット等)
|
|
153
|
+
disclaimer: { ... };
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
各ルール結果は axe と同じ形(`id` / `impact` / `description` / `help` /
|
|
158
|
+
`helpUrl` / `tags` / `nodes[]`、`nodes[].target` / `html` / `htmlTruncated` /
|
|
159
|
+
`failureSummary`)です。独自ルールは名前空間付き
|
|
160
|
+
(`a11y-skills/focus-visible`、`a11y-skills/target-size-minimum` など)で、
|
|
161
|
+
SC ごとに正確な WCAG バージョン・レベルタグ(`wcag2aa`、`wcag21aa`、
|
|
162
|
+
`wcag22aa`、`wcag247` 形式の SC タグ)が付きます。
|
|
163
|
+
|
|
164
|
+
**分類は保守的です。** 検出に死角がなく WCAG の例外が適用され得ない場合のみ
|
|
165
|
+
`violations` に入ります(フォーカスによる文脈変化、テキストスペーシングの
|
|
166
|
+
クリップ、autocomplete の不正トークン)。それ以外の検出 — reflow/zoom の
|
|
167
|
+
オーバーフロー、meta refresh、画面方向ロック、フォーカススタイル欠如、
|
|
168
|
+
小さすぎるターゲット — は `incomplete` に入ります。`incomplete` はノイズでは
|
|
169
|
+
なく「要手動確認キュー」として扱ってください。
|
|
170
|
+
|
|
171
|
+
同一ページに対する複数検査の結果を 1 つにまとめるには:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { mergeNormalizedResults } from "@a11y-skills/audit";
|
|
175
|
+
|
|
176
|
+
const merged = mergeNormalizedResults([axeResult, reflowResult, targetResult]);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`mergeNormalizedResults` は URL 不一致で例外を投げ、ノードを
|
|
180
|
+
`target` + `failureSummary` で重複排除し、複数バケツに現れるルールは
|
|
181
|
+
`violations > incomplete > passes > inapplicable` の優先順位で統合します。
|
|
182
|
+
frame / shadow root 内の同一セレクタは区別しません。
|
|
183
|
+
|
|
138
184
|
## 結果型とスキーマ
|
|
139
185
|
|
|
140
186
|
```ts
|
|
@@ -142,8 +188,10 @@ import type { AxeAuditResult, FocusCheckResult } from "@a11y-skills/audit/schema
|
|
|
142
188
|
import { RESULT_SCHEMAS } from "@a11y-skills/audit/schemas";
|
|
143
189
|
```
|
|
144
190
|
|
|
145
|
-
`RESULT_SCHEMAS` は各検査 id に手書きの JSON Schema
|
|
146
|
-
|
|
191
|
+
`RESULT_SCHEMAS` は各検査 id に手書きの JSON Schema(draft 2020-12)を
|
|
192
|
+
対応付けており、`*-result.json` を実行時に検証できます。正規化マッパー
|
|
193
|
+
(`normalize*`)と `buildAuditResult` はパッケージルートから export されて
|
|
194
|
+
いるため、保存済み結果の `details` から 4 区分をいつでも再導出できます。
|
|
147
195
|
|
|
148
196
|
## ライセンス
|
|
149
197
|
|
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ test("axe audit", async ({ page }, testInfo) => {
|
|
|
69
69
|
// outputFile: "axe-result.json", // optional override
|
|
70
70
|
// tags: ["wcag2a", "wcag2aa", "wcag21aa", "wcag22aa"],
|
|
71
71
|
});
|
|
72
|
-
expect(result.violationCount).toBe(0);
|
|
72
|
+
expect(result.summary.violationCount).toBe(0);
|
|
73
73
|
});
|
|
74
74
|
```
|
|
75
75
|
|
|
@@ -88,7 +88,7 @@ test("focus indicators", async ({ browser }, testInfo) => {
|
|
|
88
88
|
screenshot: true, // default: false
|
|
89
89
|
// contextOptions: { locale: "ja-JP" }, // forwarded to browser.newContext()
|
|
90
90
|
});
|
|
91
|
-
expect(result.elementsWithoutFocusStyle).toBe(0);
|
|
91
|
+
expect(result.details.elementsWithoutFocusStyle).toBe(0);
|
|
92
92
|
});
|
|
93
93
|
```
|
|
94
94
|
|
|
@@ -138,6 +138,52 @@ TEST_PAGE=https://example.com A11Y_OUTPUT_DIR=./a11y-results npx playwright test
|
|
|
138
138
|
> `**/node_modules/@a11y-skills/audit/dist/test-entries/*.js` finds no tests.
|
|
139
139
|
> The one-line re-export specs above are the supported way to run the entries.
|
|
140
140
|
|
|
141
|
+
## Result format
|
|
142
|
+
|
|
143
|
+
Every check returns — and saves as JSON — the same axe-style envelope:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
interface AuditCheckResult<TDetails> {
|
|
147
|
+
source: CheckSource; // e.g. "reflow-check"
|
|
148
|
+
url: string;
|
|
149
|
+
timestamp: string;
|
|
150
|
+
violations: NormalizedRuleResult[]; // confirmed findings
|
|
151
|
+
incomplete: NormalizedRuleResult[]; // needs manual review
|
|
152
|
+
passes: NormalizedRuleResult[]; // rules that ran and found nothing
|
|
153
|
+
inapplicable: NormalizedRuleResult[]; // nothing to examine
|
|
154
|
+
summary: { violationCount; incompleteCount; passCount; checkedNodes? };
|
|
155
|
+
details: TDetails; // check-specific evidence (measurements, screenshots, ...)
|
|
156
|
+
disclaimer: { ... };
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Each rule result is axe-shaped (`id` / `impact` / `description` / `help` /
|
|
161
|
+
`helpUrl` / `tags` / `nodes[]`, with `nodes[].target` / `html` /
|
|
162
|
+
`htmlTruncated` / `failureSummary`). Custom rules are namespaced
|
|
163
|
+
(`a11y-skills/focus-visible`, `a11y-skills/target-size-minimum`, ...) and
|
|
164
|
+
tagged with accurate WCAG version/level tags (`wcag2aa`, `wcag21aa`,
|
|
165
|
+
`wcag22aa`, `wcag247`-style SC tags).
|
|
166
|
+
|
|
167
|
+
**Classification is conservative.** A finding lands in `violations` only when
|
|
168
|
+
the detection has no blind spot and no WCAG exception can apply (on-focus
|
|
169
|
+
context change, text-spacing clipping, invalid autocomplete tokens). All other
|
|
170
|
+
detections — reflow/zoom overflow, meta refresh, orientation lock, missing
|
|
171
|
+
focus styles, undersized targets — are `incomplete`: treat that bucket as the
|
|
172
|
+
manual-review queue, not as noise.
|
|
173
|
+
|
|
174
|
+
To combine several checks for the same page into one view:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { mergeNormalizedResults } from "@a11y-skills/audit";
|
|
178
|
+
|
|
179
|
+
const merged = mergeNormalizedResults([axeResult, reflowResult, targetResult]);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`mergeNormalizedResults` throws on URL mismatches, deduplicates nodes by
|
|
183
|
+
`target` + `failureSummary`, and resolves a rule appearing in several buckets
|
|
184
|
+
by priority (`violations > incomplete > passes > inapplicable`). Identical
|
|
185
|
+
selectors inside different frames or shadow roots are not distinguished.
|
|
186
|
+
|
|
141
187
|
## Result types & schemas
|
|
142
188
|
|
|
143
189
|
```ts
|
|
@@ -145,8 +191,11 @@ import type { AxeAuditResult, FocusCheckResult } from "@a11y-skills/audit/schema
|
|
|
145
191
|
import { RESULT_SCHEMAS } from "@a11y-skills/audit/schemas";
|
|
146
192
|
```
|
|
147
193
|
|
|
148
|
-
`RESULT_SCHEMAS` maps each check id to a hand-written JSON Schema
|
|
149
|
-
the `*-result.json` files at runtime.
|
|
194
|
+
`RESULT_SCHEMAS` maps each check id to a hand-written JSON Schema
|
|
195
|
+
(draft 2020-12) for validating the `*-result.json` files at runtime. The
|
|
196
|
+
normalization mappers (`normalize*`) and `buildAuditResult` are exported from
|
|
197
|
+
the package root, so the buckets can be re-derived from a saved result's
|
|
198
|
+
`details` at any time.
|
|
150
199
|
|
|
151
200
|
## License
|
|
152
201
|
|
package/dist/constants.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export declare const AUDIT_DISCLAIMER: {
|
|
|
14
14
|
};
|
|
15
15
|
/** Console disclaimer message */
|
|
16
16
|
export declare const DISCLAIMER_CONSOLE = "\nNote: Automated testing detects only ~30-40% of WCAG issues.\n Manual testing is required for complete accessibility evaluation.\n";
|
|
17
|
+
/** Maximum length of `NormalizedNode.html` / detail `html` snippets. */
|
|
18
|
+
export declare const HTML_SNIPPET_MAX_LENGTH = 300;
|
|
17
19
|
/** Default axe-core tag set (WCAG 2.0/2.1/2.2 A & AA) */
|
|
18
20
|
export declare const DEFAULT_AXE_TAGS: readonly ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"];
|
|
19
21
|
/** CSS properties to check for focus style changes */
|
|
@@ -22,6 +24,8 @@ export declare const FOCUS_STYLE_PROPERTIES: readonly ["outline", "outlineStyle"
|
|
|
22
24
|
export declare const FOCUSABLE_SELECTOR: string;
|
|
23
25
|
/** Extra tab iterations for safety margin */
|
|
24
26
|
export declare const EXTRA_TAB_ITERATIONS = 10;
|
|
27
|
+
/** Default ms to wait after each Tab press for a focus-triggered navigation to surface. */
|
|
28
|
+
export declare const DEFAULT_NAVIGATION_SETTLE_MS = 50;
|
|
25
29
|
/**
|
|
26
30
|
* Minimum overlap ratio to report as obscured (0-1)
|
|
27
31
|
* 0.2 means 20% of the focused element must be covered
|
package/dist/constants.js
CHANGED
|
@@ -21,6 +21,11 @@ Note: Automated testing detects only ~30-40% of WCAG issues.
|
|
|
21
21
|
Manual testing is required for complete accessibility evaluation.
|
|
22
22
|
`;
|
|
23
23
|
// =============================================================================
|
|
24
|
+
// Normalized result format
|
|
25
|
+
// =============================================================================
|
|
26
|
+
/** Maximum length of `NormalizedNode.html` / detail `html` snippets. */
|
|
27
|
+
export const HTML_SNIPPET_MAX_LENGTH = 300;
|
|
28
|
+
// =============================================================================
|
|
24
29
|
// Axe Audit defaults (broad WCAG coverage)
|
|
25
30
|
// =============================================================================
|
|
26
31
|
/** Default axe-core tag set (WCAG 2.0/2.1/2.2 A & AA) */
|
|
@@ -55,6 +60,8 @@ export const FOCUSABLE_SELECTOR = `
|
|
|
55
60
|
`.trim();
|
|
56
61
|
/** Extra tab iterations for safety margin */
|
|
57
62
|
export const EXTRA_TAB_ITERATIONS = 10;
|
|
63
|
+
/** Default ms to wait after each Tab press for a focus-triggered navigation to surface. */
|
|
64
|
+
export const DEFAULT_NAVIGATION_SETTLE_MS = 50;
|
|
58
65
|
// =============================================================================
|
|
59
66
|
// Focus Obscured Detection Constants (WCAG 2.4.12)
|
|
60
67
|
// =============================================================================
|
|
@@ -315,18 +322,62 @@ export const AUTOCOMPLETE_FIELD_PATTERNS = {
|
|
|
315
322
|
};
|
|
316
323
|
/** Valid autocomplete token values */
|
|
317
324
|
export const VALID_AUTOCOMPLETE_TOKENS = [
|
|
318
|
-
'off',
|
|
319
|
-
'
|
|
320
|
-
'
|
|
321
|
-
'
|
|
322
|
-
'
|
|
323
|
-
'
|
|
324
|
-
'
|
|
325
|
-
'
|
|
326
|
-
'
|
|
327
|
-
'
|
|
328
|
-
'
|
|
329
|
-
'
|
|
325
|
+
'off',
|
|
326
|
+
'on',
|
|
327
|
+
'name',
|
|
328
|
+
'honorific-prefix',
|
|
329
|
+
'given-name',
|
|
330
|
+
'additional-name',
|
|
331
|
+
'family-name',
|
|
332
|
+
'honorific-suffix',
|
|
333
|
+
'nickname',
|
|
334
|
+
'email',
|
|
335
|
+
'username',
|
|
336
|
+
'new-password',
|
|
337
|
+
'current-password',
|
|
338
|
+
'one-time-code',
|
|
339
|
+
'organization-title',
|
|
340
|
+
'organization',
|
|
341
|
+
'street-address',
|
|
342
|
+
'address-line1',
|
|
343
|
+
'address-line2',
|
|
344
|
+
'address-line3',
|
|
345
|
+
'address-level1',
|
|
346
|
+
'address-level2',
|
|
347
|
+
'address-level3',
|
|
348
|
+
'address-level4',
|
|
349
|
+
'country',
|
|
350
|
+
'country-name',
|
|
351
|
+
'postal-code',
|
|
352
|
+
'cc-name',
|
|
353
|
+
'cc-given-name',
|
|
354
|
+
'cc-additional-name',
|
|
355
|
+
'cc-family-name',
|
|
356
|
+
'cc-number',
|
|
357
|
+
'cc-exp',
|
|
358
|
+
'cc-exp-month',
|
|
359
|
+
'cc-exp-year',
|
|
360
|
+
'cc-csc',
|
|
361
|
+
'cc-type',
|
|
362
|
+
'transaction-currency',
|
|
363
|
+
'transaction-amount',
|
|
364
|
+
'language',
|
|
365
|
+
'bday',
|
|
366
|
+
'bday-day',
|
|
367
|
+
'bday-month',
|
|
368
|
+
'bday-year',
|
|
369
|
+
'sex',
|
|
370
|
+
'tel',
|
|
371
|
+
'tel-country-code',
|
|
372
|
+
'tel-national',
|
|
373
|
+
'tel-area-code',
|
|
374
|
+
'tel-local',
|
|
375
|
+
'tel-local-prefix',
|
|
376
|
+
'tel-local-suffix',
|
|
377
|
+
'tel-extension',
|
|
378
|
+
'impp',
|
|
379
|
+
'url',
|
|
380
|
+
'photo',
|
|
330
381
|
];
|
|
331
382
|
export const DEFAULT_AUTOCOMPLETE_RESULT_FILE = 'autocomplete-result.json';
|
|
332
383
|
// =============================================================================
|
|
@@ -380,28 +431,60 @@ export const DETECTION_RESULT_FILENAME = 'detection-result.json';
|
|
|
380
431
|
/** Keywords for pause/stop controls (EN/JP) */
|
|
381
432
|
export const PAUSE_KEYWORDS = [
|
|
382
433
|
// English
|
|
383
|
-
'pause',
|
|
434
|
+
'pause',
|
|
435
|
+
'stop',
|
|
436
|
+
'halt',
|
|
437
|
+
'freeze',
|
|
438
|
+
'play',
|
|
384
439
|
// Japanese
|
|
385
|
-
'一時停止',
|
|
440
|
+
'一時停止',
|
|
441
|
+
'停止',
|
|
442
|
+
'ポーズ',
|
|
443
|
+
'止める',
|
|
444
|
+
'再生',
|
|
386
445
|
];
|
|
387
446
|
/** Class name patterns indicating pause/play controls */
|
|
388
447
|
export const CONTROL_CLASS_PATTERNS = [
|
|
389
|
-
'pause',
|
|
390
|
-
'
|
|
448
|
+
'pause',
|
|
449
|
+
'play',
|
|
450
|
+
'stop',
|
|
451
|
+
'toggle',
|
|
452
|
+
'switch',
|
|
453
|
+
'control',
|
|
454
|
+
'btn-pause',
|
|
455
|
+
'btn-play',
|
|
456
|
+
'btn-stop',
|
|
391
457
|
];
|
|
392
458
|
/** Carousel-related class patterns */
|
|
393
459
|
export const CAROUSEL_PATTERNS = [
|
|
394
|
-
'carousel',
|
|
395
|
-
'
|
|
460
|
+
'carousel',
|
|
461
|
+
'slider',
|
|
462
|
+
'slide',
|
|
463
|
+
'swiper',
|
|
464
|
+
'slick',
|
|
465
|
+
'hero',
|
|
466
|
+
'banner',
|
|
467
|
+
'gallery',
|
|
468
|
+
'rotator',
|
|
396
469
|
];
|
|
397
470
|
/** Navigation control keywords */
|
|
398
471
|
export const NAV_KEYWORDS = [
|
|
399
|
-
'prev',
|
|
472
|
+
'prev',
|
|
473
|
+
'next',
|
|
474
|
+
'前',
|
|
475
|
+
'次',
|
|
476
|
+
'arrow',
|
|
477
|
+
'dot',
|
|
478
|
+
'indicator',
|
|
400
479
|
];
|
|
401
480
|
/** SVG metadata patterns to exclude from accessible names */
|
|
402
481
|
export const SVG_METADATA_PATTERNS = [
|
|
403
|
-
'created with',
|
|
404
|
-
'
|
|
482
|
+
'created with',
|
|
483
|
+
'made with',
|
|
484
|
+
'generated by',
|
|
485
|
+
'svg',
|
|
486
|
+
'icon',
|
|
487
|
+
'symbol',
|
|
405
488
|
];
|
|
406
489
|
/** Maximum parent levels to check for carousel context */
|
|
407
490
|
export const MAX_PARENT_LEVELS = 5;
|
package/dist/index.d.ts
CHANGED
|
@@ -10,4 +10,6 @@
|
|
|
10
10
|
* - `@a11y-skills/audit/schemas`: result types & JSON Schemas.
|
|
11
11
|
*/
|
|
12
12
|
export * from './types.js';
|
|
13
|
-
export { AUDIT_DISCLAIMER, DEFAULT_AXE_TAGS, REFLOW_VIEWPORT, TARGET_SIZE_AA, TARGET_SIZE_AAA, } from './constants.js';
|
|
13
|
+
export { AUDIT_DISCLAIMER, DEFAULT_AXE_TAGS, HTML_SNIPPET_MAX_LENGTH, REFLOW_VIEWPORT, TARGET_SIZE_AA, TARGET_SIZE_AAA, } from './constants.js';
|
|
14
|
+
export { RULES, getRule, type RuleKey, type RuleMeta, } from './utils/rule-registry.js';
|
|
15
|
+
export { buildAuditResult, mergeNormalizedResults, normalizeAxeResults, normalizeAutocompleteAudit, normalizeAutoPlayDetection, normalizeFocusCheck, normalizeOrientationCheck, normalizeReflowCheck, normalizeTargetSizeCheck, normalizeTextSpacingCheck, normalizeTimeLimitDetector, normalizeZoomCheck, type MergedAuditResult, type NormalizedBuckets, type RawAxeResults, type RawAxeRule, } from './utils/axe-format.js';
|
package/dist/index.js
CHANGED
|
@@ -10,4 +10,6 @@
|
|
|
10
10
|
* - `@a11y-skills/audit/schemas`: result types & JSON Schemas.
|
|
11
11
|
*/
|
|
12
12
|
export * from './types.js';
|
|
13
|
-
export { AUDIT_DISCLAIMER, DEFAULT_AXE_TAGS, REFLOW_VIEWPORT, TARGET_SIZE_AA, TARGET_SIZE_AAA, } from './constants.js';
|
|
13
|
+
export { AUDIT_DISCLAIMER, DEFAULT_AXE_TAGS, HTML_SNIPPET_MAX_LENGTH, REFLOW_VIEWPORT, TARGET_SIZE_AA, TARGET_SIZE_AAA, } from './constants.js';
|
|
14
|
+
export { RULES, getRule, } from './utils/rule-registry.js';
|
|
15
|
+
export { buildAuditResult, mergeNormalizedResults, normalizeAxeResults, normalizeAutocompleteAudit, normalizeAutoPlayDetection, normalizeFocusCheck, normalizeOrientationCheck, normalizeReflowCheck, normalizeTargetSizeCheck, normalizeTextSpacingCheck, normalizeTimeLimitDetector, normalizeZoomCheck, } from './utils/axe-format.js';
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export { runAxeAudit, type RunAxeAuditOptions } from './runAxeAudit.js';
|
|
16
16
|
export { runFocusIndicatorCheck, type RunFocusIndicatorCheckOptions, } from './runFocusIndicatorCheck.js';
|
|
17
|
-
export { runReflowCheck, type RunReflowCheckOptions } from './runReflowCheck.js';
|
|
17
|
+
export { runReflowCheck, type RunReflowCheckOptions, } from './runReflowCheck.js';
|
|
18
18
|
export { runTargetSizeCheck, type RunTargetSizeCheckOptions, } from './runTargetSizeCheck.js';
|
|
19
19
|
export { runTextSpacingCheck, type RunTextSpacingCheckOptions, } from './runTextSpacingCheck.js';
|
|
20
20
|
export { runZoomCheck, type RunZoomCheckOptions } from './runZoomCheck.js';
|
package/dist/playwright/index.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export { runAxeAudit } from './runAxeAudit.js';
|
|
16
16
|
export { runFocusIndicatorCheck, } from './runFocusIndicatorCheck.js';
|
|
17
|
-
export { runReflowCheck } from './runReflowCheck.js';
|
|
17
|
+
export { runReflowCheck, } from './runReflowCheck.js';
|
|
18
18
|
export { runTargetSizeCheck, } from './runTargetSizeCheck.js';
|
|
19
19
|
export { runTextSpacingCheck, } from './runTextSpacingCheck.js';
|
|
20
20
|
export { runZoomCheck } from './runZoomCheck.js';
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import * as path from 'node:path';
|
|
17
17
|
import { SCREENSHOT_INTERVALS, CHANGE_THRESHOLD, DEFAULT_AUTO_PLAY_OUTPUT_DIR, DETECTION_RESULT_FILENAME, } from '../constants.js';
|
|
18
|
-
import {
|
|
18
|
+
import { buildAuditResult, normalizeAutoPlayDetection, } from '../utils/axe-format.js';
|
|
19
|
+
import { generateRecommendation, printSummary, } from '../utils/recommendations.js';
|
|
19
20
|
/** Capture screenshots at configured intervals. */
|
|
20
21
|
async function captureScreenshots(page, outputDir) {
|
|
21
22
|
const screenshots = [];
|
|
@@ -114,8 +115,7 @@ export async function runAutoPlayDetection(options) {
|
|
|
114
115
|
}
|
|
115
116
|
pauseVerification = createSkippedVerification(reason);
|
|
116
117
|
}
|
|
117
|
-
const
|
|
118
|
-
url: page.url(),
|
|
118
|
+
const details = {
|
|
119
119
|
screenshotRecords: screenshots,
|
|
120
120
|
comparisons,
|
|
121
121
|
hasAutoPlayContent: hasAnyChange,
|
|
@@ -129,9 +129,20 @@ export async function runAutoPlayDetection(options) {
|
|
|
129
129
|
pauseVerification,
|
|
130
130
|
}),
|
|
131
131
|
};
|
|
132
|
+
const result = buildAuditResult({
|
|
133
|
+
source: 'auto-play-detection',
|
|
134
|
+
url: page.url(),
|
|
135
|
+
details,
|
|
136
|
+
buckets: normalizeAutoPlayDetection(details),
|
|
137
|
+
});
|
|
132
138
|
console.log('\n=== Auto-play Detection Results ===\n');
|
|
133
139
|
console.log(JSON.stringify(result, null, 2));
|
|
134
140
|
saveJsonResult(path.join(outputDir, DETECTION_RESULT_FILENAME), result);
|
|
135
|
-
printSummary({
|
|
141
|
+
printSummary({
|
|
142
|
+
hasAutoPlayContent: hasAnyChange,
|
|
143
|
+
stopsWithin5Seconds,
|
|
144
|
+
pauseControls,
|
|
145
|
+
pauseVerification,
|
|
146
|
+
}, outputDir);
|
|
136
147
|
return result;
|
|
137
148
|
}
|
|
@@ -13,13 +13,34 @@
|
|
|
13
13
|
* - Cannot confirm actual field purpose; pattern matching is heuristic
|
|
14
14
|
* - Manual verification needed for edge cases
|
|
15
15
|
*/
|
|
16
|
-
import { AUTOCOMPLETE_FIELD_PATTERNS, VALID_AUTOCOMPLETE_TOKENS, DEFAULT_AUTOCOMPLETE_RESULT_FILE, } from '../constants.js';
|
|
16
|
+
import { AUTOCOMPLETE_FIELD_PATTERNS, VALID_AUTOCOMPLETE_TOKENS, DEFAULT_AUTOCOMPLETE_RESULT_FILE, HTML_SNIPPET_MAX_LENGTH, } from '../constants.js';
|
|
17
|
+
import { buildAuditResult, normalizeAutocompleteAudit, } from '../utils/axe-format.js';
|
|
17
18
|
import { saveAuditResult, logAuditHeader, logSummary, logIssueList, logOutputPaths, } from '../utils/test-harness.js';
|
|
18
19
|
/**
|
|
19
20
|
* Collect basic form field information in browser context.
|
|
20
21
|
* Accessible names are retrieved separately via ariaSnapshot().
|
|
21
22
|
*/
|
|
22
|
-
function collectBasicFieldInfo() {
|
|
23
|
+
function collectBasicFieldInfo(args) {
|
|
24
|
+
const { htmlSnippetMaxLength } = args;
|
|
25
|
+
function getHtmlSnippet(element) {
|
|
26
|
+
let html = '';
|
|
27
|
+
try {
|
|
28
|
+
html = element.outerHTML || '';
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
html = '';
|
|
32
|
+
}
|
|
33
|
+
if (!html) {
|
|
34
|
+
return {
|
|
35
|
+
html: `<${element.tagName.toLowerCase()}>`,
|
|
36
|
+
htmlTruncated: false,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (html.length > htmlSnippetMaxLength) {
|
|
40
|
+
return { html: html.slice(0, htmlSnippetMaxLength), htmlTruncated: true };
|
|
41
|
+
}
|
|
42
|
+
return { html, htmlTruncated: false };
|
|
43
|
+
}
|
|
23
44
|
function getUniqueSelector(element, elementIndex) {
|
|
24
45
|
if (element.id) {
|
|
25
46
|
return `#${element.id}`;
|
|
@@ -36,7 +57,9 @@ function collectBasicFieldInfo() {
|
|
|
36
57
|
path.unshift(selector);
|
|
37
58
|
current = parent;
|
|
38
59
|
}
|
|
39
|
-
return path.length > 0
|
|
60
|
+
return path.length > 0
|
|
61
|
+
? path.join(' > ')
|
|
62
|
+
: `[data-index="${elementIndex}"]`;
|
|
40
63
|
}
|
|
41
64
|
const skipTypes = ['hidden', 'submit', 'reset', 'button', 'image', 'file'];
|
|
42
65
|
const fields = [];
|
|
@@ -51,6 +74,7 @@ function collectBasicFieldInfo() {
|
|
|
51
74
|
fields.push({
|
|
52
75
|
selector: getUniqueSelector(element, index),
|
|
53
76
|
tagName: el.tagName.toLowerCase(),
|
|
77
|
+
...getHtmlSnippet(element),
|
|
54
78
|
inputType,
|
|
55
79
|
name: el.name || null,
|
|
56
80
|
id: el.id || null,
|
|
@@ -106,6 +130,8 @@ function analyzeFields(fields, patterns, validTokens) {
|
|
|
106
130
|
missing.push({
|
|
107
131
|
selector: field.selector,
|
|
108
132
|
tagName: field.tagName,
|
|
133
|
+
html: field.html,
|
|
134
|
+
htmlTruncated: field.htmlTruncated,
|
|
109
135
|
inputType: field.inputType,
|
|
110
136
|
name: field.name,
|
|
111
137
|
id: field.id,
|
|
@@ -124,6 +150,8 @@ function analyzeFields(fields, patterns, validTokens) {
|
|
|
124
150
|
invalid.push({
|
|
125
151
|
selector: field.selector,
|
|
126
152
|
tagName: field.tagName,
|
|
153
|
+
html: field.html,
|
|
154
|
+
htmlTruncated: field.htmlTruncated,
|
|
127
155
|
inputType: field.inputType,
|
|
128
156
|
name: field.name,
|
|
129
157
|
id: field.id,
|
|
@@ -144,7 +172,9 @@ function analyzeFields(fields, patterns, validTokens) {
|
|
|
144
172
|
export async function runAutocompleteAudit(options) {
|
|
145
173
|
const { page, ...location } = options;
|
|
146
174
|
// Collect basic field info from DOM
|
|
147
|
-
const basicFields = await page.evaluate(collectBasicFieldInfo
|
|
175
|
+
const basicFields = await page.evaluate(collectBasicFieldInfo, {
|
|
176
|
+
htmlSnippetMaxLength: HTML_SNIPPET_MAX_LENGTH,
|
|
177
|
+
});
|
|
148
178
|
// Enhance with accessible names via ariaSnapshot()
|
|
149
179
|
const fields = [];
|
|
150
180
|
for (const basicField of basicFields) {
|
|
@@ -164,26 +194,31 @@ export async function runAutocompleteAudit(options) {
|
|
|
164
194
|
}
|
|
165
195
|
const patterns = Object.entries(AUTOCOMPLETE_FIELD_PATTERNS);
|
|
166
196
|
const { missing, invalid } = analyzeFields(fields, patterns, VALID_AUTOCOMPLETE_TOKENS);
|
|
167
|
-
const
|
|
168
|
-
url: page.url(),
|
|
197
|
+
const details = {
|
|
169
198
|
totalFieldsChecked: fields.length,
|
|
170
199
|
missingAutocomplete: missing,
|
|
171
200
|
invalidAutocomplete: invalid,
|
|
172
201
|
};
|
|
202
|
+
const result = buildAuditResult({
|
|
203
|
+
source: 'autocomplete-audit',
|
|
204
|
+
url: page.url(),
|
|
205
|
+
details,
|
|
206
|
+
buckets: normalizeAutocompleteAudit(details),
|
|
207
|
+
});
|
|
173
208
|
// Output results
|
|
174
209
|
logAuditHeader('Autocomplete Audit Results', 'WCAG 1.3.5', result.url);
|
|
175
210
|
logSummary({
|
|
176
|
-
'Total form fields':
|
|
177
|
-
'Fields missing autocomplete':
|
|
178
|
-
'Fields with invalid autocomplete':
|
|
211
|
+
'Total form fields': details.totalFieldsChecked,
|
|
212
|
+
'Fields missing autocomplete': details.missingAutocomplete.length,
|
|
213
|
+
'Fields with invalid autocomplete': details.invalidAutocomplete.length,
|
|
179
214
|
});
|
|
180
|
-
logIssueList('Missing Autocomplete',
|
|
215
|
+
logIssueList('Missing Autocomplete', details.missingAutocomplete, (el, i) => [
|
|
181
216
|
`${i + 1}. <${el.tagName}> "${el.selector}"`,
|
|
182
217
|
` name: ${el.name || 'none'}, id: ${el.id || 'none'}`,
|
|
183
218
|
` label: "${el.labelText || 'none'}"`,
|
|
184
219
|
` Expected: autocomplete="${el.expectedToken}" (matched by ${el.matchedBy})`,
|
|
185
220
|
]);
|
|
186
|
-
logIssueList('Invalid Autocomplete',
|
|
221
|
+
logIssueList('Invalid Autocomplete', details.invalidAutocomplete, (el, i) => [
|
|
187
222
|
`${i + 1}. <${el.tagName}> "${el.selector}"`,
|
|
188
223
|
` Current: autocomplete="${el.currentAutocomplete}"`,
|
|
189
224
|
` Expected: autocomplete="${el.expectedToken}"`,
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* caller is responsible for navigating the page (e.g. `await page.goto(url)`)
|
|
6
6
|
* before calling this function.
|
|
7
7
|
*
|
|
8
|
+
* The normalized buckets are built from the RAW axe results (violations and
|
|
9
|
+
* incomplete keep their nodes; passes/inapplicable keep rule metadata only),
|
|
10
|
+
* and `details` records the execution configuration.
|
|
11
|
+
*
|
|
8
12
|
* Axe-core cannot detect all accessibility issues — manual testing and the
|
|
9
13
|
* other checks in this package are still needed for complete coverage.
|
|
10
14
|
*/
|