パック
パックは、ルール、コアのルールのバリアント、規格やチェックリストと、それらのプロファイルやメッセージを、コアをフォークもビルドもせずに、それ自身のパッケージとして@surea11y/coreに提供します。RGAAのような国の規格もパックにでき、組織のアクセシビリティ方針もパックにできます。スキャンはengineOptions.packsでパックを受け取り、コア自身のルールや規格と同じように実行します。Nodeでもブラウザーでも、CLI、バインディング、test-matchersのどこからでも動作し、結果、集約、レポートの項目はコアのものと同じ形式になります。パックには@surea11y/core 1.11.1以降が必要です。
パックの種類
| 手元にあるもの | 種類 | 含むもの |
|---|---|---|
| 組織のアクセシビリティ方針:「WCAG 2.2 AAに、これらのルールを加え、あのルールを除き、これらの項目をレポートに載せる」 | チェックリスト | コアのルールと独自のルールを選ぶプロファイルと、チェックリストの項目となる集約。 |
| 独自の要件を持つ規格(RGAAのような国の規格や、業界の規格):番号の付いた要件があり、それぞれをいくつかのルールが検査する | 規格 | 要件の表、各要件を検査するルール、要件ごとの結果、プロファイル。 |
| プロファイルなしで、すべてのスキャンに追加したいいくつかのルール | ルールのみ | ルールと、コアのルールのバリアントや上書き。チェックリストから始めて、そのプロファイルと集約を削除してください。 |
パックのルールとプロファイルは、コアのどのルールでも使えます。チェックリストや規格の大部分は、選んでまとめたコアのルールに、コアにない少数のルールを加えたものです。このページの例では、すべてのバインディングがexamples/フォルダーに同梱している小さなパックacme-policy-pack.jsを使います。独自ルール1つ、チェックリストのプロファイル(WCAG 2.2のAとAA、region、独自ルール)、チェックリストの項目2つを持ちます。そのソースは後述します。
EN 301 549のパック
@surea11y/pack-en301549は、SureA11yファミリーの、すぐにインストールできる規格パックです。EN 301 549の第9章(Web)を、V3.2.1とV4.1.1で扱います。バージョンごとのプロファイルen301549-v4.1.1とen301549-v3.2.1は条項ごとに結果を1つ報告し、mappings: ['en301549']は、WCAGのスキャンを含むあらゆるスキャンの結果に条項を示します。@surea11y/core 1.13.0以降が必要です。
core 1.14.0に合わせた1.1.0では、各条項を、それが再掲するWCAGの達成基準をコアが判定するのと同じように判定します。そのため、まだ合格する条項はありません。ルールがすべて合格した条項は、残りを人が確認するよう、その達成基準と同じくcantTellになります(WCAG適合性との対応を参照)。en301549-v3.2.1プロファイルは4.1.1「構文解析」を維持するため、そこでは重複したidは引き続き9.4.1.1を不合格にします。また、9.1.4.5、9.3.3.1、9.4.1.3に最初のルールが加わりました(image-of-text、invalid-field-error-message、live-region-announcements)。core 1.13では、1.0系と同じように動作します。
// npm install @surea11y/core @surea11y/pack-en301549const en301549 = require('@surea11y/pack-en301549');runDomRulesInPage(url, null, { packs: [en301549], profile: 'en301549-v4.1.1' });runDomRulesInPage(url, null, { packs: [en301549], profile: 'wcag22-aa', mappings: ['en301549:V4.1.1'] });
独自の規格を作るときの実例でもあります。ルールは持ちません。第9章はWCAGを再掲したものなので、各条項はその達成基準に対するコアのルールで確認します。プロファイルはそれらのルールを選び(tags、mappedRules: true)、規格の各バージョンを判定するWCAGのバージョンを示し(wcagVersion)、V3.2.1では4.1.1「構文解析」を維持します(keepsParsing)。条項はWCAGの達成基準を1対1で再掲しています(applicability: 'wcag')。ルールの対応表はインストールされたコアのカタログから導くため、コアが追加したルールもパックを変えずに対象になります。WCAGにない要件を持つ規格も同じ形で、独自のルールを加えます。ソースは短いので、後述のパックを書くと合わせて読む価値があります。
コアは1.13.0までEN 301 549を内蔵していましたが、1.13.0でこのパックに置き換える形で非推奨にしました(WCAG適合性との対応を参照)。ほかのプロファイルと同じく、パックのプロファイルが決めるのは何を実行するかです。適合を表明するには、引き続き人がすべての条項を確認する必要があります。
パックを使う
Nodeから
const { runDomRulesInPage, getChecksCatalog } = require('@surea11y/core');const acmePolicy = require('./acme-policy-pack');const result = runDomRulesInPage(url, null, { packs: [acmePolicy], profile: 'acme-web-2026' }, null);result.engine.packs; // ['@acme/a11y-policy@1.0.0']getChecksCatalog({ packs: [acmePolicy] }); // core's rules and the pack's
パックは1回だけ準備され、同じパックのオブジェクトでの次のスキャンのために保持されます。そのため、すべてのページに同じパックを渡すクロールでは、準備は1回で済みます。definePackはパックのオブジェクトを凍結します。変更できないものとして扱い、スキャン間では同じオブジェクトを再利用してください。パックを渡すとスキャンに登録されますが、そのルールが選択されるわけではありません。パックの名前空間のタグを持つパックのルールはオプトインです。パックのプロファイルを使わない場合、IDかそのタグで選択されたときだけ実行されます(名前を参照)。このタグを持たないパックのルールはコアのルールと同じように選択されるため、パックを渡したデフォルトのスキャンで実行されます。ベストプラクティスも実行するには、プロファイルと並べてrunOnly: { bestPractices: true }を追加してください。
ブラウザーのバインディングで
Playwright、Puppeteer、Selenium、WebdriverIOは、.withPacks()でパックのオブジェクトを受け取り、スキャンの前にページに、また.frames(true)ではすべてのフレームに登録します。
const { A11yCoreBuilder, getScanGaps } = require('@surea11y/playwright');const acmePolicy = require('./acme-policy-pack');const results = await new A11yCoreBuilder({ page }).withPacks(acmePolicy).options({ profile: 'acme-web-2026' }).analyze();results.engine.packs; // ['@acme/a11y-policy@1.0.0']// A checklist's items, each one result, beside core's WCAG rollups:results.rulesResults.filter((r) => r.meta && r.meta.standard === 'Acme web policy');
複数のパックでスキャンするには、配列を渡すか、.withPacks()をもう一度呼び出してください。バインディングは.withPacks()を共通して持ちますが、各ビルダーはそれぞれのドライバーのオブジェクト({ page }、{ driver }、{ browser })を受け取り、それぞれのセットアップが必要です。Playwright、Puppeteer、Selenium、WebdriverIOを参照してください。
Cypressで
パックはNodeで準備されますが、Cypressのspecはブラウザーで実行されます。そのためCypressでは、.withPacks()は各パックのモジュール名(パッケージ名、またはプロジェクトのルートからのパス)を受け取り、バインディングのプラグインがNodeでパックを準備します。プラグインは1回だけ登録してください。
// cypress.config.jsconst { defineConfig } = require('cypress');const surea11y = require('@surea11y/cypress/plugin');module.exports = defineConfig({e2e: {setupNodeEvents(on, config) {surea11y(on, config);}}});
new A11yCoreBuilder().withPacks('./examples/acme-policy-pack.js') // or a package name, '@acme/a11y-policy'.options({ profile: 'acme-web-2026' }).analyze().then((results) => {expect(results.engine.packs).to.deep.equal(['@acme/a11y-policy@1.0.0']);});
.analyze()は、パックを登録するスクリプトをcy.taskを通じてプラグインに求め、それをページで、また.frames(true)では同一オリジンのすべてのフレームで実行します。プラグインは実行が終わるまでパックを保持するため、パックのファイルへの変更は、次のcypress runか、cypress openを再起動した後に反映されます。ほかのバインディングが受け取るようなパックのオブジェクトを渡すと、ブラウザーでは準備できないため、.withPacks()で例外が投げられます。
test-matchersで
@surea11y/test-matchersはNodeでスキャンするため、パックはオブジェクトそのものとして渡します。
const acmePolicy = require('./acme-policy-pack');const policy = { packs: [acmePolicy], profile: 'acme-web-2026' };test("a page that breaks the pack's house rule fails the assertion", () => {document.body.innerHTML = '<main><h1>Welcome</h1><video autoplay src="intro.mp4"></video></main>';expect(document).not.toHaveNoA11yViolations(policy);});
すべてのテストが同じパックのオブジェクトを渡すよう、パックのオブジェクトは1か所(セットアップファイルやモジュール)にまとめてください。実行されなかったパックは、アサーションを失敗させます。test-matchersを参照してください。
CLIから
$ surea11y scan ./dist/index.html --pack ./acme-policy-pack.js --profile acme-web-2026$ surea11y scan ./dist/index.html --pack @acme/a11y-policy --profile acme-web-2026
--packは、作業ディレクトリのプロジェクトから解決されるパッケージ名か、パスを受け取り、複数のパックには繰り返して指定します。パックは@surea11y/core/packを読み込みます。これは、プロジェクトに@surea11y/cliをインストールすると、CLIと並んでインストールされます。CLIを参照してください。
独自のページで
ブラウザー拡張機能や独自のpage.evaluateでは、コアのブラウザー用バンドルの後に@surea11y/core/packのpackScript([acmePolicy])を注入するか、buildBrowserBundle({ packs: [acmePolicy] })で作った1つのファイルを配布し、パックをname@versionとして指定してください。スタンドアロンを参照してください。スクリプトをファイルとして読み込むページには、buildBrowserBundleで作る1つのファイルが最適です。パックのスクリプトは、それを書き出したコアでしか動作しません。
別のコアのバンドルを持つページはパックをスキップし、skippedPacksが2つのバージョンを示します。そのため、そのファイルはコアかパックのリリースごとに作り直してください。
スキャンが報告するもの
engine.packsは実行された各パックをname@versionの形で示し、engine.profileは適用されたプロファイルを示します。- パックのルールはコアのルールと同じように
checksResultsに現れ、そのIDはパックの名前空間で始まります(acme-no-autoplay)。WCAGの達成基準に対応するパックのルールは、その達成基準の集約に算入されます。ただし、パックのオプトインの名前空間のタグを持つルールは除きます。それらのルールはWCAGが求めていないことをチェックするため、パック自身の集約にだけ算入されます。それでもそのwcagScは、レポーターのために、検査するWCAGの達成基準を示します。ほかの規格(すべてのルールをWCAGの達成基準で対応付けるEN 301 549)がそれらを自身のものとすることはありません。上書きは、置き換えるコアのルールの代わりに、それ自身の対応が示す集約に算入されます。 - チェックリストの項目や規格の要件は
rulesResultsの集約で、チェックリストや規格の名前をmeta.standardとして持ちます。HTMLレポートはそれらを独自のセクションに表示し、SARIFは各ルールに、それが属する項目のタグ(acme-images)を付け、JUnitはそれらをプロパティとして列挙します。結果のstandardsがチェックリストを示すため、保存した結果はどこでレンダリングしても同じになります。 - プロファイルの
severityは、それが変更する結果に表示され、ルール自身の重大度はruleSeverityとして保持されます。
パックが実行されない場合
coreの範囲がインストールされているコアを含まないパック、妥当でないパック、コアと両立しないパック(名前空間の外にあるルールID、コアが持つ辞書のキー、存在しないルールのバリアント)はスキップされます。スキャンはそのパックなしで実行され、警告を出し、skippedPacksに{ name, reason }として列挙します。strictOptionsのもとでは、engineOptions.packsのすべてのエラーと同じくcode: 'INVALID_ENGINE_OPTIONS'付きのエラーを投げます。互いに衝突する2つのパック(両方が定義する辞書のキー、1つのキーを持つ2つの規格)はエラーを投げます。どちらかを選ぶ方法は、どれも渡された順序に左右されてしまうからです。ページ内では、engineOptions.packsで指定されたのに、どのpackScriptの呼び出しも登録していないパックや、別のバージョンのコアで登録されたパックが、同じように列挙されます。そのため、パックを追加した目的のチェックがないままスキャンが終わることもあります。パックに依存するツールは、スキャンを完了したものとして扱う前にskippedPacksを確認してください。
どのツールも、スキップされたパックを目に見える失敗に変えます。
- ブラウザーのバインディングは、
.withPacks()を呼び出したときに各パックを確認し、コアが実行できないパックがあればスキャンの前に例外を投げます。それでもページが実行しなかったパックは、getScanGaps(results)の'pack-skipped'のスキャンギャップになり、.analyze()がそれについて警告するため、スキャンギャップを検証するテストは失敗します。 - test-matchersは、パックと理由を示してアサーションを失敗させます。
- CLIは、見つからない、または読み込めないパックや、エンジンがスキップするパックに対して、レポートもベースラインも書き出さずに終了コード
2で終了します。
パックは@surea11y/coreおよびそれを実行するパッケージと並べてインストールし、3つが同じコアを共有するようにしてください。
パックを書く
例から始める
パックは、@surea11y/core/packのdefinePackで作るオブジェクトです。これは、バインディングが同梱している例のacme-policy-pack.jsです。
'use strict';// A small pack: one house rule, a checklist profile and two checklist items.// A real pack is a package of its own; `npx surea11y-pack new <folder>` writes one.const { definePack, wcagTags } = require('@surea11y/core/pack');module.exports = definePack({name: '@acme/a11y-policy',title: 'Acme web policy',version: '1.0.0',namespace: 'acme',core: '^1.11.1',rules: [{id: 'acme-no-autoplay',meta: {title: 'Videos do not play sound on their own',description: 'A video that starts with sound talks over a screen reader.',tags: ['acme'],defaultSeverity: 'serious'},runInPage(ctx) {// defaultMuted reflects the muted attribute where muted isn't set from it yet (jsdom).const loud = [...ctx.document.querySelectorAll('video[autoplay]')].filter((v) => !(v.muted || v.defaultMuted));return loud.length? {outcome: 'fail',occurrences: loud.map((v) =>ctx.helpers.reportOccurrence(v, { summary: 'This video plays with sound as the page opens.', hint: 'Remove autoplay, or add muted.' }))}: { outcome: 'pass', occurrences: [] };}}],profiles: {'acme-web-2026': { tags: wcagTags('2.2', ['A', 'AA']), rules: ['region', 'acme-no-autoplay'] }},rollups: [{ id: 'acme-images', title: 'Images have a text alternative', checksIds: ['img-alt-present'] },{ id: 'acme-media', title: 'Media stays quiet until asked', checksIds: ['acme-no-autoplay'] }]});
そのプロファイルacme-web-2026は、コアのWCAG 2.2のAとAAのルール、どのWCAGのタグでも選択されないベストプラクティスのルールregion、そして独自ルールを実行します。2つの集約はチェックリストの項目で、それぞれがコアまたはパックのルールを1つの結果にまとめます。
規格の場合は、EN 301 549のパックが、公開済みの完全な例です。
surea11y-pack newで作る
独自のパックを作るには、生成したパッケージから始めてください。
$ npx -p @surea11y/core surea11y-pack new acme-a11y --name @acme/a11y-pack# or, for a standard:$ npx -p @surea11y/core surea11y-pack new city-standard --name @city/a11y-standard --kind standard --title "City Standard"$ cd acme-a11y$ npm install$ npx playwright install chromium # once, for npm run docs$ npm test && npm run lint && npm run docs
パックは生成した時点で動作します。テストは合格し、ルールはlintを通り、例はルールと一致します。surea11y-pack newは、何かを書き出す前にスキャンと同じようにパックを確認し、パックを無効にする名前、名前空間、タイトル(WCAGというタイトルは、その規格をコアの規格の1つとして示してしまいます)を、理由を示して拒否します。そこから少しずつ変更し、その都度npm testを実行してください。READMEが各ファイルを順に説明しています。
| ファイル | 含むもの |
|---|---|
index.js | @surea11y/core/packのdefinePack()で書いたパックです。 |
rules/automatic/<ns>-link-text-specific.js | コアのヘルパーを使って判定するルールです。 |
rules/automatic/<ns>-contrast-enhanced.js | 7:1で判定する、コアのcontrast-minimumのバリアントです。 |
rules/manual/<ns>-new-window-review.js | 人によるレビューが必要な手動ルールです。理由コードと、人がそれに答えるための手順を持ちます(meta.reasonCodes、meta.review)。 |
i18n/en.json | メッセージです。 |
tests/rules/*.test.js、tests/profiles.test.js | ルールごとのテストと、各プロファイルが実行するもののテストです。 |
docs/RULE_EXAMPLES.md | 各ルールの判定結果の例です。 |
eslint.config.js | ルールに対するコアのlintルールです。 |
requirements.js、rule-map.js | 規格の場合のみ:要件と、それぞれを検査するルールです。 |
package.json | privateで、@surea11y/coreを、それを書き出したバージョン(^1.14.1)でpeer依存関係に、eslint、jsdom、Playwrightを開発用の依存関係に持ちます。 |
README.md、.gitignore | これらのファイルについてのパック自身のガイドと、gitが対象外にするものです。 |
名前
- パッケージ名とバージョンは、npmのパッケージ名と完全なバージョン(
1.0.0)です。スキャンはそれらをengine.packsで報告します。 - 名前空間(
namespace: 'acme')は、パックのIDをコアやほかのパックのIDと区別します。すべてのルール、集約、プロファイルのIDは、名前空間と-で始まります。コアのルールIDの接頭辞になる名前(img-alt-presentに対するimgのように、img、aria、link)や、wcagで始まる名前は使えず、1つのスキャンにある2つのパックが同じ名前空間を持つことはできません。名前空間は、パック自身のルールをオプトインにするタグでもあります。このタグを持つルールは、パックのいずれかのプロファイルのもとか、IDか、タグ(optInRules: ['acme'])で選択されたときだけ実行されます。そのため、デフォルトでは、WCAGを対象とするスキャンでは実行されません。optInRulesでこのタグを有効にしても、スキャンのプロファイルやフィルターはなくなりません。WCAGのプロファイルのもとでは、有効にしたルールは、そのタグがプロファイルにも一致するときだけ実行されます。パックを使うすべてのスキャンで実行すべきルールには、このタグを付けないでください。そうすると、そのルールはコアのルールと同じように選択され、タグが一致すれば、WCAGを対象とするスキャンでも実行されて不合格を報告することがあります。 coreは、パックが動作するコアのバージョンの範囲('^1.11.1')で、npmと同じように範囲として読まれます。新しいパックは1.11.1から始めてください。1.11.0のパッケージには、Nodeでパックに必要なファイルが含まれていませんでした。範囲外のコアでのスキャンはパックをスキップし、理由をskippedPacksに示します。surea11y-pack newは、パックを生成したバージョン(^1.14.1)を書き出します。1.14で追加されたもの(meta.review、reasonCodesやfindingsの行、meta.contributions、applicability、partial、keepsParsing)をパックが使う場合は、1.14から始まる範囲のままにしてください。titleは結果やレポートでのチェックリストの呼び名、descriptionはパックが何をチェックするかを述べる1文で、利用者にパックを選ばせるツールのためのものです(describePacksがそれを返します)。
プロファイル
プロファイルは名前の付いた選択で、engineOptions.profileで実行します。ルールは、そのプロファイルのtagsのいずれかを持つか、IDがrulesにあり、excludeにない場合に、そのもとで実行されます。パックの名前空間のタグを持つパックのルールは、パックのすべてのプロファイルのもとで実行されます。
profiles: {'acme-policy': {tags: wcagTags('2.2', ['A', 'AA']), // core's WCAG 2.2 A and AA rulesrules: ['region', 'heading-order'], // and these, by idexclude: { rules: ['contrast-minimum'] }, // but not this oneseverity: { 'img-alt-present': 'critical' } // and this one is critical here},'acme-quick': {tags: [],rules: ['img-alt-present', 'link-name-present', 'page-title-present']}}
| フィールド | 働き |
|---|---|
tags | これらのタグのいずれかを持つルールです。@surea11y/core/packのwcagTags(version, levels)はコアのWCAGのタグを返します(wcagTags('2.1')は2.0と2.1の、AとAAです)。 |
rules | コアまたはパックのルールをIDで指定します。WCAGのタグでは選択されないベストプラクティスのルールや、tags: []と組み合わせて、パック自身のルールのほかにプロファイルが実行するコアのルールを指定します。 |
exclude | { rules: [...] }はルールをスキャンから除外します。{ criteria: ['2.5.8'] }はそれらの達成基準を免除します。その集約は除かれ、すべての達成基準が免除されたルールも除かれます。プロファイルが残す達成基準も検査するルールは引き続き実行されます。これはスキャンがチェックする範囲を狭めるもので、WCAGの要件は変わりません。 |
severity | { ruleId: level }:そのプロファイルでの別の重大度です。結果にはそれが表示され、ルール自身の重大度はruleSeverityとして保持されます。ベースラインと検出結果のIDは変わりません。 |
wcagVersion | '2.0'、'2.1'、'2.2'のいずれかで、プロファイルのスキャンを判定するWCAGのバージョン(engine.wcagVersion)です。指定しない場合はtagsのWCAGのタグがバージョンを示し、WCAGのタグがないプロファイルは2.2で判定されます。スキャン自身のengineOptions.wcagVersionが引き続き優先されます。コア1.12.0以降です。 |
keepsParsing | EN 301 549 V3.2.1のように、規格が4.1.1「構文解析」を文言どおりに求める場合はtrueです。そのとき、2.0や2.1を対象にしても、その不合格は不合格のままです。これがないプロファイルでは、WCAG 2.0と2.1に対する2023年の正誤表がHTMLとXMLについて4.1.1を満たされているものとするため、duplicate-idは代わりに確認を求めます(4.1.1を削除した2.2では、プロファイルの指定にかかわらず確認を求めます)。数えるのはプロファイル自身のフィールドだけで、コアのプロファイルの代わりになるパックのプロファイルはそれを引き継ぎません。engineOptions.mappingsは判定結果を変えないため、4.1.1を維持する規格で判定するスキャンは、その規格のプロファイルを実行します。core 1.14.0以降です。 |
version、mappedRules | 規格のプロファイルの場合のみ:対象とする規格のバージョンと、そのバージョンで規格が対応付けるすべてのルールを実行するためのmappedRules: trueです。 |
規格が求めるものだけを実行するプロファイル。tags: [<ns>]とmappedRules: trueを持つ規格のプロファイルは、パック自身のルールと、その対応表が要件に結び付けるコアのルールだけを実行します。規格が求めないことを検査するルールはなく、結果にWCAGの達成基準は含まれません。ただし、WCAGのタグがないため、その規格がどのWCAGのバージョンに基づくかを示すものがなく、実行は2.2で判定されます。2.1に基づく規格ではこれは誤りです。実行は、2.5.8のように規格にない2.2の達成基準を報告します。バージョンを指定してください。規格が今も4.1.1「構文解析」を求める場合は、keepsParsing: trueも指定してください。生成されたindex.jsは、それをrequirements.jsから読みます。そこでは規格の各バージョンが、基づくWCAGのバージョンを示しています({ version: '1.0', wcagVersion: '2.2' })。
'city-1.0': {version: '1.0',tags: ['city'], // the pack's own rulesmappedRules: true, // and the core rules its map links to a requirementwcagVersion: wcagVersionOf('1.0') // judged by the WCAG version requirements.js gives for 1.0}
コアのルールとそのID、タグは、ルールのカタログにあります。
チェックリストの項目と要件
チェックリストの項目は、そのrollupsです。{ id, title, checksIds }の形で、それぞれがルールを1つの結果にまとめます。項目は、ルールのいずれかが不合格なら不合格になり、すべてが合格すれば合格します。不合格のルールがなく、いずれかがcantTellを返した場合、項目もcantTellになり、人によるレビューが必要です。パックが持つのは、profilesとrollups(チェックリスト)か、standardのどちらか一方で、両方は持ちません。
規格の要件は、ジェネレーターが書き出す2つの表から来ます。requirements.js(バージョンごとに、各要件の番号、文言、WCAGの達成基準)とrule-map.js(各ルールがどの要件を検査するか、そしてその理由)です。@surea11y/core/packのruleMappedStandard()は、それらを規格に変換します。要件ごとの集約、各ルールの結果が示す要件、そしてパックを準備するときの両方の表の確認です。
検出結果を要件に振り分ける
rule-map.jsのルールの行は、reasonCodes: { [code]: { requirements, outcome } }で、ある理由コードの検出結果を別の要件に振り分けられます。requirements(デフォルトは行のもの)はその検出結果を数える要件で、[]ならどこにも数えません。outcome: 'cantTell'は、そこで不合格の検出結果を確認の問いにします。要件の集約は、その要件に数える検出結果でルールを判定するため、規格は、WCAGが不合格にする原因について確認を求めたり、2つの原因を2つのテストに振り分けたりできます。
'list-children-valid': {requirements: ['9.3.1'],reasonCodes: { LIST_ITEM_ROLE_OVERRIDDEN: { requirements: ['9.3.1', '9.3.2'], outcome: 'cantTell' } }}
行は、検出結果が記録している内容で振り分けることもできます。findings: [{ when, requirements, outcome }]のwhenは、検出結果のdata.detailsのフィールドが等しくなければならない値を、ドット区切りのパスで示します。検出結果は、最初に一致したエントリーに従い、なければ理由コード、それもなければ行に従います。そのため、規格はあるルールの不合格を、理由コードが同じでも、テキストの大きさごとのテストに振り分けられます。
'contrast-minimum': {requirements: ['3.1', '3.2'],findings: [{ when: { 'typography.isLargeText': false }, requirements: ['3.1'] },{ when: { 'typography.isLargeText': true }, requirements: ['3.2'] }]}
比べるのは、ルールが安定して保つフィールドだけにしてください。すべてのルールのreasonCodeと、GitHub上のコアのAPIの安定性ポリシーが挙げる少数のフィールド(contrast-minimumのコントラスト比、しきい値、文字組みなど)です。変わったりなくなったりしたフィールドは何にも一致せず、検出結果は理由コードか行に従います。各ルールの理由コードは、GitHub上のコアのルールカタログの「What a finding reports」に一覧があります。
要件の判定方法を変えるフィールドがさらに2つあります。EN 301 549の第9章のように、要件がWCAGの達成基準を1対1で再掲する規格は、ruleMappedStandardにapplicability: 'wcag'を渡します。すると要件の集約は、その達成基準のWCAGの集約がそうなる場合にだけnotApplicableやpassになり、1つのレポートの中で両者が一致します。達成基準を再掲してはいないものの、コアのルールでは完全には検査できない要件は、要件の表でpartial: trueとします。すると、その集約は、ルールがすべて合格した場合も、すべて何も見つけなかった場合もcantTellになります。ルールが検査したのはその一部だけだからです。独自の集約を書く規格は、各集約のmetaで両方を示し、meta.contributionsで検出結果を振り分けます。形はpack.d.tsのStandardRollupが示し、GitHub上のコアのパックのガイドが順を追って説明しています。どれもcore 1.14.0が必要です。
ルール
ルールはコアのルールモジュールと同じ形です。{ id, meta, runInPage }に、一部の場合にだけ適用されるときはapplicabilityを加え、同じctxを受け取ります。runInPage(ctx)はページ内で実行されるため、ctxだけを使い、その外の変数やモジュールは決して使いません。また、読み取りしか行いません。パックのルールはコアのルールの後に実行され、渡されるヘルパーとオプションは読み取り専用です。読み取り専用は決まりであって、サンドボックスではありません。ルールのコードがそれ以上のことをするのを止めるものはないため、信頼できるパックだけを読み込んでください(GitHub上のコアのセキュリティポリシーを参照)。コードがほとんど、またはまったく要らない近道が3つあります。バリアントはコアのルールを別の設定で実行し({ id, from: 'contrast-minimum', config: { normalTextRatio: 7 }, meta }。しきい値は0より大きい有限の数、選択肢は列挙された値のいずれかを取り、definePackはそれ以外を拒否します)、上書きはコアのルールを置き換え(overridesに列挙し、結果のoverriddenBuiltinIdsにも示されます)、そのままのコアのルールはプロファイルや集約にIDを書くだけで済みます。ルールの書き方は、GitHub上のコアのルール作成ガイドにあります。
GitHub上のコアのルール設定の一覧は、コアのルールがバリアントに提供するすべての設定を、デフォルト値、取りうる値、変わる内容とともに示します。getChecksCatalog()は同じものを各ルールのvariantSettingsとして返します。別の規格における要件の版や、WCAGが評価者に委ねる選択は、こうしてコアのコードの上で実行されます。core 1.14から、contrast-minimumはロゴのように見えるテキスト(logoText)、より高いコントラストに切り替えるスイッチがあるページ(contrastSwitch)、セミボールドのテキスト(semiBold)についての選択肢を、valid-langは、RGAAのようにISO 639のあらゆる言語コードを認める規格のためのcodes: 'iso639'を提供します。
人がcantTellを確認する方法
検出結果を未判定のまま残すことがあるルールは、コアのルールと同じように、人がそれにどう答えるかを理由コードごとに示せます(出力スキーマの「確認方法」を参照)。検出結果が示す要素から始まるいくつかの手順と、合格の場合と不合格の場合にそれぞれ何が見えるかです。
meta.review = {GENERIC_LINK_NAME: {steps: ['Find the link the report names.', 'Read the text around it.'],passWhen: 'The text around the link says where it goes.',failWhen: 'Nothing near the link says where it goes, which fails WCAG 2.4.4.'}};
definePackは形を確認します。理由コードごとに1〜6の手順と、空でないpassWhenとfailWhenが必要です。meta.reasonCodesで理由コードを宣言したルール(['GENERIC_LINK_NAME'])は、そこで宣言したかrunInPageで使うコードにだけ手順を示せるため、書き間違えたコードは拒否されます。この確認のために宣言する価値があります。手順はスキャンのロケールにかかわらず英語で、ページには入りません。engineOptions.packsを指定したrunDomRulesInPageは結果に手順を追加し、ページから得た結果にはaddHowToCheck(result, { packs })が追加します。バリアントはコアのルールの翻訳済みの手順を持つため、自分の手順は要りません。上書きは、カスタムルールと同じく手順を持ちません。core 1.14.0以降です。
メッセージと言語
ルールが表示する各テキストはキーを持ち、ロケールごとにi18n/<locale>.jsonに置かれ、パックのdictionariesに列挙されます。engineOptions.locale: 'fr'でのスキャンはフランス語のテキストを表示し、パックに辞書がないロケールでは英語が表示されます。パックのキーは名前空間で始まるパック独自のもので、パックがコアの同梱するロケールのコアのメッセージを変更することはできません。
テスト、lint、ドキュメント
- テストには、コア自身のルールのテストが使う
@surea11y/core/testingを使います。runa11yCoreOnHtml(html, { engineOptions })はHTMLをjsdomでスキャンし、assertRule(result, ruleId, outcome)はルールを確認します。パックがある場合、スキャンはパックのオブジェクトからと、ページが持つ形の両方でパックを実行するため、関数の外の変数を読むルールはテストで失敗します。開発用の依存関係としてjsdomが必要です。 - 確認:
definePackは問題をそれぞれ示して例外を投げるため、パック自身のテストが失敗します。checkPack(pack)は例外を投げずに同じ一覧を返し、インストールされたコアのバージョンも確認します。どちらも、スキャンと同じように、規格が持つ表の確認(validate)をコアとパックのルールに対して実行するため、wcagVersionのないバージョンや、表にない要件に振り分けた検出結果は、すべてのスキャンでパックがスキップされる代わりに、そこで拒否されます。core 1.14.0以降です。 - lint(
npm run lint、@surea11y/core/eslint-plugin)は、パックのルールに、コアのルールと同じlintルールを適用します。 - ドキュメント(
npm run docs、surea11y-pack docs)は、パックのdocs/RULE_CATALOG.mdを書き出し、そのdocs/RULE_EXAMPLES.mdのすべての例をChromiumで実行します(CHROMIUM_EXECUTABLE_PATHで別のChromiumビルドを指定できます)。--checkは、どちらかが古くなっていれば失敗するので、パックのCIで使えます。
公開する
生成されたパッケージはprivateです。ライセンスを選び、package.jsonでlicenseを設定してprivateを削除し、npm publishしてください。@surea11y/coreはpeer dependencyのままにし、範囲はcoreに合わせます。パックが土台にするものはセマンティックバージョニングの対象です。コア1.11向けに書いたパックは、それ以降の1.xのバージョンでも動作し続けます。パックの利用者にとって、ルールのIDとその理由コードはベースラインが保持するものです。名前の変更や削除は、パックのメジャーバージョンでだけ行ってください。
空のフォルダーから公開済みのパックまでの完全なガイドは、GitHub上のコアのPACKS.mdにあり、すべてのフィールドはENGINE_OPTIONS.mdのパックのリファレンスにあります。