EARLレポート

@surea11y/core/earlは、スキャン結果をJSON-LD形式のEARL 1.0として出力します。EARLは「このツールがこの対象をテストし、この結果を得た」ことを表すためにW3Cが公開している語彙で、ACT Rulesコミュニティグループが実装レポートとして受け付ける形式でもあります。

const { renderEarlReport } = require('@surea11y/core/earl');
 
const report = renderEarlReport(result, {
assertor: { name: 'surea11y', version: '1.14.1' },
mode: 'earl:automatic'
});
 
fs.writeFileSync('earl.jsonld', JSON.stringify(report, null, 2));

用途

読み手は2種類いて、同じドキュメントを別々の理由で必要としています。

実装レポートは、ACT Rulesコミュニティグループのテストケースに対してこのエンジンがどう振る舞うかを伝えるものです。これによって、エンジンがほかの実装と並んで掲載されます。掲載は推奨を意味せず、W3Cがデータを検証するわけでもありません。正確な言い方は「ACT実装として掲載されている」であり、「W3C認定」ではありません。このエンジン自身のレポートはhttps://surea11y.github.io/act-report/act-report.jsonldで公開しており、scripts/act-report.jsで再生成しています。--runner=chromiumは、ブラウザーのバインディングがエンジンを実行するのと同じようにChromiumでのスキャンから同じレポートを生成し、アサーターの名前は「SureA11y in a browser」、レポートの説明にはChromiumのバージョンが入ります。

利用者にとっては、データ交換用の形式になります。自動スキャンと手動の監査、あるいは複数のエンジンのように、複数の情報源の結果を組み合わせる必要があるとき、アクセシビリティ関連のツールが読み込むのがEARLです。どのアサーションにも、誰がどのように判定したかが記録されているからです。どこかに提出するかどうかにかかわらず、持っておく価値があります。

ほかのレポーターとの違い

SARIFレポートとHTMLレポートはどちらも違反だけを扱います。failとcantTellの検出箇所を報告し、それ以外は捨てます。EARLはその逆です。実行されたすべてのルールがアサーションになり、passもinapplicableも含まれます。実装レポートは、エンジンがあらゆる箇所で何を判定したかについての主張だからです。適用対象が見つからずに何も報告しなかったルールも、ノイズではなく証拠です。それによって読み手は、「このエンジンはチェックしたが、チェック対象がなかった」のか「このエンジンはそのルールをそもそも実装していない」のかを区別できます。各アサーションは、1つの対象に対する1つのルールについてのものです。ルールのpassedは、そのルールが対応するWCAGの達成基準についての判定ではありません。ほとんどの達成基準には、どのルールも扱わない部分があります(WCAG適合性との対応を参照)。

構造

グラフはアサーションを平らに並べたリストではなく、対象(subject)ごとにまとめられています。

{
"@context": "https://www.w3.org/WAI/content-assets/wcag-act-rules/earl-context.json",
"@graph": [
{
"@type": "TestSubject",
"source": "https://example.test/",
"assertions": [
{
"@type": "Assertion",
"test": { "title": "img-alt-present", "isPartOf": ["WCAG2:non-text-content"] },
"result": { "outcome": "earl:failed" },
"assertedBy": {
"@type": "Assertor",
"name": "surea11y",
"release": { "@type": "Version", "revision": "1.14.1" }
},
"mode": "earl:automatic"
}
]
}
]
}
  • sourceはスキャンしたURLです。結果にURLがない場合はabout:blankになります。URLのない結果が複数ある場合は、すべてを1つにまとめるのではなく、それぞれをリスト内の位置(1から数えます)によるabout:blank#result-<n>という独立した対象にします。
  • test.titleはエンジン自身のルールIDです。ACTの用語では、ルールとは実装が実行した手順のことで、ルールIDが指すのはまさにそれです。
  • test.isPartOfは、そのルールが対応する達成基準をWCAG2:<criterion-id>の形で列挙します。対象は、standardがWCAGであるか存在せず(SARIFと同じ)、typeを持たないnormativeMappingsの各エントリーです。どの達成基準にも対応しないルールでは、このフィールド自体を省略します。空のリストを出すと、「意図的にどれにも対応させていない」ではなく「対応するものが見つからなかった」と読めてしまうためです。エンジンの自動ルールでこれに当たるのはaria-allowed-role、empty-heading、landmark-role-name-presentです。そうしたルールもfailになることはあります(empty-headingは名前のない見出しをfailにします)。その場合のアサーションはisPartOfのないearl:failedで、どのWCAG達成基準も主張しないルールが不合格になったことを示します。
  • isPartOfはスキャンのWCAGのレベルとバージョンに従います。それらによってスキャンがrulesResultsから外した達成基準は列挙されません。たとえば、engineOptions.wcagLevel: 'AA'のもとでの1.4.6(AAA)や、WCAG 2.2でincludeRuleIds: ['duplicate-id']を実行したときの、2.2で削除された4.1.1です。すべての達成基準が外されたルールにはisPartOfがありません。アサーション自体はルールについてのものなので残ります。スキャンが報告する達成基準は列挙されます。レベルより上でもIDで指定された集約や、4.1.1を選んだ2.2のスキャンでの4.1.1です。engine.wcagLevelもengine.wcagVersionも記録していない結果では、すべての達成基準が列挙されます。
  • assertedByは、assertor: nullを渡さない限り常に出力されます。デフォルトはsurea11yと、結果が持つエンジンのバージョンです。modeは、指定した場合にだけ出力されます。

達成基準のIDは、その達成基準自身のタイトルから導き出します(Non-text Content→non-text-content)。normativeMappingsには、typeを持つUnderstanding文書への参照と、自身のstandardを名乗るほかの規格のエントリー(EN 301 549の条項、パックの要件やチェックリスト項目)も含まれます。どちらも達成基準と同じrequirementの番号を持つことがあります。達成基準として読み取るのは、standardがWCAGであるか存在せず、typeを持たないエントリーだけです。適合レベルは必要ありません。isPartOfはWCAGの達成基準だけを列挙し、ほかの規格のエントリーは含めません。

コンパクトな結果(engineOptions.output.detail: 'findings'。出力スキーマを参照)は、それが表す完全な結果として読まれるので、同じように報告されます。

判定結果

エンジンEARL
passearl:passed
failearl:failed
cantTellearl:cantTell
notApplicableearl:inapplicable

earl:untestedに対応するものはありません。実行されなかったルールにはアサーションの元になる結果がないため、untestedのアサーションを出すのではなく、アサーション自体を出しません。

cantTellを報告しても、ACT実装としての評価が下がることはありません。ACT自身の一貫性のルールでは、自動化された実装が一部の例(すべてではありません)で「判断できない」と報告しても、一貫性があるものとして扱われます。部分的に一貫した実装に許されないのは誤検出です。つまり、ルール上passになるべき例や適用対象外の例をfailにすることです。注視すべきはこの点で、これはこのレポーターではなくルールの性質です。直近のフル実行の時点で、エンジンは対応する60件のルールについてW3Cが公開している957件の例で誤検出ゼロです。詳しくはACT_RULE_MAPPING.mdを参照してください。この数字をいつまでも鵜呑みにせず、最新の値はscripts/act-testcase-check.jsを再実行して確認してください。

複数の結果を1つのレポートに

多数のページを対象にしたレポートがごく普通なので、renderEarlReportは単一の結果と同じように配列も受け取ります。

renderEarlReport([homeResult, checkoutResult, searchResult], { assertor });

runa11yCoreAcrossFramesによるフレームをまたぐ結果は、単独でも配列の中でも、そのまま受け取ります。応答した各フレームが1つの結果として加わり、それぞれのURLを持つ対象になります。最上位より下のフレームで、URLから区別できないもの(about:srcdocやabout:blankである場合や、結果の別のフレームが同じURLを持つ場合)は、そのURLとパス(SARIFとJUnitがそのフレームを示すのに使うセレクター)で名前が付けられます。パスの各セレクターはエンコードされ、/でつながれます:about:srcdoc#frame=%23pay、about:srcdoc#frame=%23widget/%23inner。セレクターを持たないバインディングのフレームのように、パスまで同じ場合は、結果のフレームの中での順番(1から)が加わります:about:srcdoc#frame=iframe&n=3。nullや文字列など、スキャン結果ではないものを渡すとTypeErrorを投げます。

URLが同じ結果は1つの対象にまとめられます。同じページを異なるengineOptionsでスキャンしても、表しているのは1つのリソースであり、コンテキスト上も同じsourceを持つ2つの対象を表現する手段がないからです。同じURLの同じルールについて2つの結果がアサーションを持つ場合は、悪いほうの判定結果が残ります。順にearl:failed、earl:cantTell、earl:passed、earl:inapplicableで、入力をどの順序にしても不合格が隠れることはありません。同じページを2つのビューポート幅でスキャンした場合も同じです。EARLにはページがレンダリングされた条件を表す語彙がないため、複数の幅から作ったレポートには、各ルールについて、いずれかの幅での不合格が残ります。幅そのものが重要なら、幅ごとに1つのレポートを生成してください。

出力は決定論的です。対象はsource順、アサーションはルールID順に並び、同じ入力であれば順序を問わずバイト単位で同一の出力になります。唯一の例外はURLのない結果で、リスト内の位置(about:blank#result-2)で区別されます。順序に左右されたくない場合は、各結果にURLを持たせてください。だからこそ、2つのエンジンバージョン間の差分に意味が生まれます。

オプション

オプション意味
assertor{ name, version }。nameのデフォルトはsurea11yです。versionはassertorのreleaseとして出力され、すべての結果が同じengine.versionを持つ場合はその値がデフォルトになります。異なるリリースの結果が混在する場合は、指定しない限りreleaseは付きません。assertedByを丸ごと省くにはnullを渡します。
mode'earl:automatic'などのEARLのテストモードです。指定しない場合は省略されます。

関連情報