WCAG適合性との対応
個々のルールの結果がWCAGの達成基準(SC)とどう関係するか、そして全体の適合性についてsurea11yが言えること・言えないことを説明します。
3つの層
- アトミックルール(
checksResults[]):それぞれが1つの規範的な判定を行います。たとえば「この<img>にはalt属性があるか」です。すべてのルールはルールカタログを参照してください。 - ファセット:WCAGの達成基準は、たいてい1つのルールで決定論的に判定できる範囲より大きなものです。内部では、各達成基準を名前付きの「ファセット」に分けています(たとえば1.1.1「非テキストコンテンツ」には
img-alt-attr-present、text-alternative-quality、decorative-nullなどのファセットがあります)。ファセットはsrc/coverage/wcag-facets.jsで管理され、それぞれfull(ルールが高い確度で判定する)、partial(ルールがその一部を判定する。各ルールの範囲に関する注記を参照)、manual(安全な自動化のヒューリスティックがまったくない)のいずれかに分類されています。npm run coverageを実行すると、達成基準ごとのファセットの内訳であるcoverage/coverage-report.mdが再生成されます。 - 複合ルール(WCAG達成基準ごとの集約ルール)(
rulesResults[]):1つの達成基準に対応するすべてのアトミックルールを自動生成で集約したものです。何十ものアトミックな結果を自分でまとめなくても、達成基準ごとにpass/fail/cantTell/notApplicableの判定が1つ得られます。全一覧を参照してください(たとえばwcag-1.1.1-non-text-contentは21件のアトミックルールを集約しています)。
複合ルールの判定結果の決まり方
その複合ルールを構成するアトミックルールに対して、決まった優先順位で決定論的に評価されます。
| 条件 | 複合ルールの判定結果 |
|---|---|
構成ルールのいずれかがfail | fail |
failはないが、構成ルールのいずれかがcantTell、または一覧にある構成ルールが1つでも実行されなかった | cantTell |
すべての構成ルールがnotApplicableで、その達成基準が適用されるすべてのケースをルールが見つけられる場合 | notApplicable |
すべての構成ルールがnotApplicableで、それ以外の達成基準の場合 | cantTell(composite.rollup.cantTell.partialCoverage) |
| それ以外(すべて実行され、failもcantTellもなく、すべてがN/Aではない)で、その達成基準が求めることをすべてルールがチェックする場合 | pass |
| それ以外で、ほかの達成基準の場合 | cantTell(composite.rollup.cantTell.partialCheck) |
達成基準がpassになるとき、適用されないとき
1.14からは、達成基準がpassになるのは、その達成基準が求めることをすべてルールがチェックする場合だけです。WCAGでも、達成基準はそのすべてが満たされたときにだけ満たされたとみなされます。ルールがすべてpassでも、わかるのはルールがチェックする部分が満たされていることだけです。html-lang-attr-presentは<html lang="fr">が言語を示していることを確かめますが、ページがフランス語で書かれていることは確かめません(3.1.1)。page-title-presentはページにタイトルがあることを確かめますが、それがページを説明していることは確かめません(2.4.2)。そうした達成基準はcantTellになり、理由コードはcomposite.rollup.cantTell.partialCheckです。HTMLレポートでは「一部のみ確認」と表示され、残りは人が確認します。ルールがすべてをチェックする達成基準はsrc/coverage/complete-applicability.js(COMPLETE_CHECK)にそれぞれの理由とともに列挙されますが、まだ1つもありません。どの達成基準にも、ルールからは見えないことが含まれています。代替テキスト、ラベル、見出し、リンクのテキストが正しい内容を伝えているか、スクリプトが描画・変更するコンテンツがそれを満たしているか、といったことです。そのため、1.14ではWCAGの複合ルールがpassになることはありません。
同じように、達成基準がnotApplicableになるのは、その達成基準が適用されるすべてのケースをルールが見つけられる場合だけです。2.4.4と2.4.9ではリンク、2.4.3と2.4.7ではフォーカス可能な要素、2.5.3ではラベルの付いたコンポーネント、4.1.1ではマークアップがそれに当たります。フォームのフィールド(1.3.5、3.3.2)とポインターのターゲット(2.5.8)はこの一覧にありません。ARIAのロールで作ったフィールド、autocompleteのないフィールド、スクリプトによってだけターゲットになる要素は、それらのルールが何も見つけなかったページにもありえるからです。それ以外の達成基準では、ルールがすべて何も見つけなくても、わかるのはルールが探すケースがないことだけです。遅延の後にリダイレクトするスクリプトは<meta http-equiv="refresh">のない制限時間(2.2.1)ですし、ほかの言語の一節(3.1.2)にlangが付いているとは限りません。WCAG-EMの用語では、「存在しない」と言うにはコンテンツがありうる場所をすべて確認している必要があります。そのため、そうした達成基準はcantTellになり、理由コードはcomposite.rollup.cantTell.partialCoverageです。
つまり、複合ルールのfailは本物の決定論的な違反であり、構成ルールがすべてpassだった複合ルールのcantTell(partialCheck)は、その達成基準についての自動チェックがすべて問題なく、残りは人が確認するものだということを示します。それだけでは適合を主張するものではありません。ファセットの表(coverage-report.md)は、各達成基準のどの部分が自動でカバーされ、どの部分を人が確認するかを示します。これらは「チェックは問題なし、確認が必要」「チェックする対象が見つからない」として表示し、passとして表示しないでください。
複合ルールのdata.details.contributors配列(出力スキーマを参照)には、すべてのアトミックルールとそれぞれの判定結果が並びます。複合ルールをブラックボックスとして扱うのではなく、どのファセットがfail/cantTellの原因になったのかを正確に確認するのに使ってください。
実行されたカスタムルール(engineOptions.customRules)やパックのルールは、meta.wcagScまたはmeta.normativeMappingsのWCAGのエントリーを通じて、組み込みルールと同じように、対応する各達成基準の複合ルールに算入されます。不合格になったものは複合ルールを不合格にします。カスタムルールは組み込みルールのように仕様と照合されていないため、複合ルールは貢献したルールの中でカスタムルールにcustom: trueを付け、data.details.customChecksIdsに列挙します。組み込みルールの上書きは、それ自身の対応付けに従って算入されます。その達成基準に対応していれば組み込みルールの代わりに算入され、どの達成基準にも対応していなければどこにも算入されません(警告が出ます)。
達成基準ではなくルールを指定したスキャン(includeRuleIds: ['link-name-present']のようなIDで、WCAGのタグではないタグで、optInRulesで、またはベストプラクティスとして指定)でも、それらのルールがチェックする達成基準の複合ルールが得られます。wcag244のような達成基準のタグを指定した場合は、その達成基準だけが報告されます(エンジンオプションを参照)。1.12より前は、そうしたスキャンは複合ルールを1つも報告しませんでした。判定は上の表で決まります。不合格のルールがあればその達成基準は不合格になり、スキャンが選ばなかったルールは実行されなかった構成ルールとして扱われるので、その達成基準はよくてもcantTell、つまり一部だけチェックしたものになります(reasonCodeはcomposite.rollup.cantTell.notSelectedChildで、そうした構成ルールにはそれぞれnotSelected: trueが付きます。HTMLレポートでは「一部のみ確認」と表示されます)。
適合レベル(A/AA/AAA)を対象にする
runOnly.wcagにWCAGのバージョンとレベルを指定します。
// WCAG 2.2 Level A and AA:runDomRulesInPage(url, null, {}, { wcag: { version: '2.2', level: 'AA' } });// The same, and the best-practice rules, which test no WCAG criterion:runDomRulesInPage(url, null, {}, { wcag: { version: '2.2', level: 'AA' }, bestPractices: true });
目標を指定すると、そのバージョンで有効な、そのレベル以下のすべての達成基準のルールが、各達成基準がそのバージョンで持つレベル(@surea11y/core/wcagのWCAG_CRITERIA)に従って実行されます。そのため、4.1.1「構文解析」は2.0と2.1の目標には含まれ、2.2の目標には含まれません。目標は、実行のWCAGバージョンと、報告する集約のレベルも設定します。WCAGの目標で選ぶを参照してください。
WCAG_CRITERIAの各エントリーには、W3CがWCAG 2.2でその達成基準に付けているid(non-text-content)もあります。ルールが持つWCAGの対応付けはすべて、2.1と2.2について、そのバージョンの勧告にある達成基準の節(url)と、そのUnderstanding文書(understandingUrl)にリンクします。ルール自身にヘルプリンクがない場合、レポーターはUnderstanding文書をヘルプリンクとして使います。
タグで指定する
同じ目標はレベルのタグでも指定できます。runOnly.tags(またはengineOptions.tags.include)に渡します。
// WCAG 2.0 A and AA. Both tags are required: they are not cumulative.runDomRulesInPage(url, null, {}, { tags: ['wcag2a', 'wcag2aa'] });
レベルのタグは入れ子になっていません。ここで最も間違えやすい点です。ルールには、対応する達成基準ごとにレベルのタグが1つ付くだけで、それ以上は付きません。AAの達成基準だけに対応するルールにはwcag2aaが付き、wcag2aは付きません。そのため{ tags: ['wcag2aa'] }だけを指定すると、2.0のA+AAを対象にする105件のルールではなく、2.0のAAの達成基準に対応する11件のルールだけが実行されます。対象にするレベルはすべて列挙するか、この問題がないrunOnly.wcagを使ってください。engineOptions.profile(wcag22-aa、en301549-v4.1.1、en301549-v3.2.1、section508)に任せることもできます。適合性プロファイルを参照してください。
WCAGのバージョンについても同じです。2.1や2.2で追加された達成基準には、追加されたバージョンのタグしか付きません。そのため、適合の対象全体はタグの集合の和になります。バージョンごとの既成のタグセット(WCAG 2.2で追加ではなく削除された1つの達成基準を含む)はエンジンオプションのガイドを参照してください。
削除された達成基準は自動的に処理されます。どの実行でも、対象とするWCAGのバージョン(engineOptions.wcagVersion、なければ実行が適用するパックのプロファイルのwcagVersion、それもなければrunOnly.wcagのバージョン、それもなければバージョンのタグから導かれるもの、それもなければ2.2)が決まり、engine.wcagVersionとして報告されます。2.2を対象にした場合、SC 4.1.1「構文解析」だけに対応するルールはfailを報告できません。ルールは実行されて検出箇所を報告しますが、判定結果はcantTellになり、その読み替えを説明するwcagVersionScopeフィールドが付きます。1.14からは、2.0や2.1を対象にした場合も同じで、wcagVersionScope.by: "errata"が付きます。W3Cによるこれらのバージョンの2023年の正誤表は、HTMLやXMLのコンテンツについて4.1.1を常に満たされているものとみなしているからです。そのため、WCAGのスキャンが4.1.1で失敗することはありません。4.1.1で不合格になるのは、その文言を残している規格のプロファイル、つまりen301549-v3.2.1のようにkeepsParsing: trueを宣言したプロファイルだけです。
アトミックルールとは違い、複合ルールは累積的に絞り込まれます。ランナーはengineOptions.wcagLevel、なければrunOnly.wcagのレベル、またはtagsで指定された最も高いレベルを読み取り、それより上のレベルの複合ルールをすべて除外します。そのため['wcag2a', 'wcag2aa']を指定すると、AAAだけの達成基準についてはrulesResultsのエントリーが返りません。目標ではなくチェックを指定するスキャンで設定するのはwcagLevel('A'、'AA'、'AAA')です。選んだルールはすべて実行され、報告する達成基準だけがそのレベルまでに絞られます。そのため、contrast-computableを'AA'でスキャンすると、AAAの1.4.6ではなく1.4.3が報告されます。実行はこれをengine.wcagLevelとして記録します。正確な優先順位が必要なら、src/core/dom-runner.jsのtargetWcagLevelOf/isAllowedByTargetLevelを参照してください。
tagsを完全に省略すると(デフォルト)、すべてのレベルのすべてのルールが実行され、複合ルールの除外も行われません。
EN 301 549
EN 301 549は、プロファイル、mappingsのキー、@surea11y/core/en301549とともに専用のパック@surea11y/pack-en301549に移り、core 2.0.0で削除されます。それまではすべて従来どおり動作し、プロセスごとに1回console.warnを出します。移行するには、パックをengineOptions.packsに渡します。スキャンは同じ名前をパックから取り、表のエクスポートは@surea11y/pack-en301549/publicにあります。coreが規格を非推奨にする手順を参照してください。
EN 301 549の第9章は、WCAGのレベルAとAAの達成基準を、9.に達成基準の番号を続けた条項として再掲しています。WCAG 1.4.3は条項9.1.4.3です。スキャンが要求した場合、アトミックルールと複合ルールのすべての結果は、meta.normativeMappingsのWCAGのエントリーの後に、各達成基準の条項を、その達成基準を含む規格のバージョンごとに1つずつ持ちます。スキャンはengineOptions.mappings: ['en301549'](両方のバージョン)や['en301549:V3.2.1'](1つ)で要求するか、EN 301 549のプロファイルを対象にして要求します。プロファイルはその名前のバージョンを追加します。デフォルトでは、結果はWCAGだけを示します(ほかの規格を参照)。
| バージョン | 基になったもの | もう一方との違い |
|---|---|---|
| V3.2.1 (2021-03) | WCAG 2.1 A・AA、50の達成基準 | 9.4.1.1構文解析を含む |
| V4.1.1 (2026-09) | WCAG 2.2 A・AA、55の達成基準 | 2.4.11、2.5.7、2.5.8、3.2.6、3.3.7、3.3.8を追加。9.4.1.1は無効 |
条項の各エントリーは、再掲している達成基準もwcagSc: ["1.4.3"]として示します。そのため、達成基準ごとにまとめたビュー(JUnitのスイート、HTMLレポートの集約)は、複数の達成基準に対応するルールでも正しい達成基準の下に置けます。AAAの達成基準には、どちらのバージョンにも条項がありません。カタログも同じオプションで条項を示します。getChecksCatalog({ mappings: ['en301549'] })では各エントリーのnormativeMappingsに、getRulesCatalog({ mappings: ['en301549'] })が返す複合ルールのエントリーではmeta.standardMappingsに入るので、スキャンを実行しなくても達成基準の条項を読み取れます。
この表は@surea11y/core/en301549として公開されています。あるバージョンが求める達成基準のうちスキャンが網羅しなかったものなど、逆方向の見方が必要なツール向けです。
const { EN301549_CLAUSES, en301549ClausesForSc } = require('@surea11y/core/en301549');Object.keys(EN301549_CLAUSES['V3.2.1']); // the 50 WCAG 2.1 A and AA criteria V3.2.1 restatesen301549ClausesForSc('2.5.8'); // [{ version: 'V4.1.1', clause: '9.2.5.8', title: 'Target size (minimum)' }]
これは公開された2つの文書の対応関係であり、適合の主張ではありません。結果に付いた条項は、そのWCAGの達成基準がEN 301 549のどの要件に当たるかを示すだけです。特定の法律がどのバージョンを求めるかは、エンジンの範囲外です。
EN 301 549は、ランナー、ルールカタログ、レポート生成のすべてが読む規格の登録簿の1つのエントリーです。独自の判定を持つ規格(自身のルールが検査する要件を持つもの。たとえばRGAAのような国の規格)や組織のチェックリストは、パックとして提供されます。パックはコアの外にある独自のパッケージで、スキャンはengineOptions.packsでそれを受け取り、そのエントリーはそのスキャンの間だけ登録簿に加わり、独自のプロファイル、ルール、集約を持ちます。カスタムルールは自身のnormativeMappingsで任意の規格を指定できますが、プロファイル、mappingsの切り替え、オプトインルール、集約、SARIF・JUnit・HTMLレポートでの表示を得られるのは、登録された規格(コアの規格と、そのスキャンのパックの規格)だけです。規格をコア自体に組み込む方法は、GitHub上のコントリビューター向けガイドであるWCAG_CONFORMANCE.mdを参照してください。
このエンジンにはわからないこと
WCAGへの完全な適合を証明できる自動ツールはありません。このツールも同じです。これはsurea11yに固有の制限ではなく、WCAGそのものに内在するものです。達成基準のうち無視できない割合が、人間の判断(代替テキストが単にあるだけでなく正確か、エラーメッセージが理解できるか)や、このエンジンの静的なDOMスキャンというアーキテクチャではまったくできない動的なテスト(キーボードトラップの検出、拡大時の実際のレイアウト/リフロー)を必要とします。何が対象外で、なぜそうなのかの明示的な全一覧は既知の制限事項を参照してください。
そのうえでsurea11yが正直に提供できるのは、次のことです。
- コアの組み込みルールは、対象にした規格とバージョンにおける決定論的で規範上の違反に対してだけ
failを報告し、確信が持てないときはcantTellを報告するように作られています。これは設計上の目標であり、保証ではありません。ルールがなお誤りうる例外的なケースは既知の制限事項に挙げてあり、各検出結果の検出箇所は何に基づいて判断したかを示し、ルールカタログは各ルールの確度を示します。カスタムルール、パックのルール、上書きがどれだけ正確かは、それがどう書かれたかによります。デフォルトではその規格はWCAGです。ほかの規格だけが求める要件のルールはオプトインで、デフォルトではその規格を対象にしたときだけ実行されます(オプトインルールを参照)。engineOptions.optInRulesは、スキャンのプロファイルやフィルターをなくさずにオプトインルールを有効にします。optInRules: 'all'を指定し、プロファイルもフィルターもない場合、実行はすべてのルールを対象にし、特定の規格を対象にしません。タグのリストを指定した場合は、そのオプトインのグループだけが有効になります。engine.optInRulesは、ルールが実行されたタグを列挙します。WCAGのプロファイルのもとでは、有効にしたルールはそのタグがプロファイルに一致するときだけ実行され、実行は引き続きWCAGを対象にします。そのため、コアのルールのうち、WCAGを対象にしたスキャンが実行するのは、対象の達成基準のルールと、追加した場合のベストプラクティスのルールだけです。ベストプラクティスのルールは達成基準を示しませんが、見つけたもの自体がバリアである場合はfailを報告することがあります。空の見出し(empty-heading)がその例で、WCAGの複合ルールはそれを算入しません。オプトインのタグを持たないカスタムルール、上書き、パックのルールは、選択に含まれれば実行され、WCAGが求めていない独自のチェックについてfailを報告することがあります。 - すべての
cantTellは、不確かさを握りつぶしたものではなく、人間によるレビューが必要だという明示的なフラグです。 - ルールが一部しかチェックしない達成基準を、複合ルールが
passにすることはありません。ルールがすべてpassでもcantTell(composite.rollup.cantTell.partialCheck)になるので、どのルールもチェックしない部分についてpassが黙っていることはありません。 - ファセットのカバレッジ表を見れば、どの達成基準のどの部分に自動カバレッジがまったくないか、つまり人に残されているのは何かが正確にわかります。
対象レベルのすべての達成基準で複合ルールにfailがなければ、それはそのレベルで自動化できるチェックがすべて問題なかったという意味です。適合性のうち自動化できる部分を正確に述べたものであって、WCAG自体が求める手動レビューの代わりではありません。