ベースライン/許可リスト

厳格なCIゲート(「failの判定結果が1つでもあればビルドを失敗させる」)は、完璧ではない既存サイトを初めてスキャンするチームにとって導入の障害になります。既存の違反をすべて直すまで、何もリリースできないからです。ベースラインを使えば、チームは「このN件は既知なのでゲートの対象にしない。ただし本当に新しい違反が出たらその時点で失敗させる」と指定できます。

# Once: record every current fail occurrence, does not fail the build.
$ surea11y scan ./dist/index.html --write-baseline baseline.json
# Commit baseline.json to git.
# From then on, in CI: only a NEW (not-yet-baselined) fail occurrence gates the build.
$ surea11y scan ./dist/index.html --baseline baseline.json

ベースラインファイルはgitにコミットする前提です。そうすることで、アクセシビリティの負債を追加で受け入れることが、明示的でレビュー可能な判断になります。新しい違反は件数の静かな変化ではなく、PRでのファイル差分の新しい行として現れます。

役に立つ場面

  • 既存サイトにスキャンを導入するとき。上で挙げた、そもそもの動機となったケースです。成熟したサイトを初めてスキャンすると、既存の違反が数百件見つかることがあります。それらを一度ベースラインに記録すれば、「まずすべて直す」ことを求めずに、すぐにゲートを有効にできます。
  • 減っていく様子が見える段階的な修正。違反をまとめて修正し、定期的に--write-baselineでベースラインを再生成します。再生成のたびにエントリーが消え、gitの差分は小さくなっていきます。ファイルそのものが進捗の記録になり、一群の問題を直したPRでは、何が片付いたのかが正確にわかります。
  • 自分では管理できないサードパーティーやベンダーのコンテンツ。ページに埋め込まれたウィジェット(チャットバブル、広告枠、決済用のiframe)に、既知で自分では直せない違反がある場合です。その特定の検出箇所だけをベースラインに記録するほうが、excludeSelectorsでサブツリー全体を除外する(エンジンオプションのガイドを参照)よりも正確です。その周辺のほかの場所、自分のコードの中も含めて新たに入り込んだ問題は、通常どおりゲートの対象になります。
  • 大規模なリファクタリング中のリグレッション防止。移行の途中では、一部のルールが一時的に失敗するのもやむを得ない場合があります。その失敗はすでに把握されていて、多数のPRにわたって修正が進んでいるものです。進行中の既知の違反をベースラインに記録しておけば、ゲートを完全に切ったり、全PRをそれで止めたりせずに、同じ期間にほかのPRが持ち込んだ無関係な新しいリグレッションを検出できます。
  • 新しいルールや、より厳しいルールの段階的な導入。コードベースがまだ満たしていないルール(またはWCAGのレベル全体)を有効にする場合です。既存の不足をベースラインに記録してすぐにゲートを有効にし、残りの課題は別途片付けながら、そのルールへの新たな違反を防ぎます。コードベースが準拠するまでルールの有効化を待つ必要はありません。

照合の仕組み

検出箇所の構造には、ページ上の位置に左右されない完全な識別子になるものはありません(出力スキーマを参照)。selectorとstructuralPathはどちらもDOM内での要素の位置から導かれるため、指摘された要素自体はまったく変わっていなくても、ページのほかの場所で無関係なマークアップが変わるとずれることがあります。

そこで、ベースラインのエントリーの識別子は次のようにしています。

ruleId + reasonCode + html

reasonCodeはoccurrence.data.details.reasonCodeにあるルール固有のコード(ルールが設定しない場合、または空でない文字列以外を設定した場合は"DEFAULT")、htmlは検出箇所のouterHTMLのスニペットです。位置ではなく内容に基づくので、指摘された要素自体のマークアップが変わらない限り、要素がページ内で移動しても(並べ替え、無関係な兄弟要素の追加や削除)一致し続けます。<html>や<body>で報告される、ページそのものについての検出結果(html-lang-attr-present、bypass-blocks-present、landmark-one-main)では、スニペットは開始タグだけなので、ページの内容を編集しても新しい検出結果にはなりません。スニペットは、各タグの属性を名前順に、クラス名を並べ替えたうえで比較されます。そのため、前回のビルドがsrc class="a b"と書いたところを今回のビルドがclass="b a" srcと書いても一致します。比較は照合の時点で行われるので、この変更より前に書かれたベースラインファイルも引き続き一致します。selectorもベースラインファイルに記録されますが、差分を読む人のための手がかりにすぎず、照合には一切使いません。

既知の制限事項:要素自体のマークアップに動的な内容(タイムスタンプ、ライブカウンター、ランダムに生成されるID)が含まれる場合、htmlのスニペットが毎回異なるため、2回のスキャンの間で一致することはありません。お使いのページがこれに当てはまる場合、その特定のルールや要素についてはベースラインの仕組みは役に立ちません。後述の「代替手段」を参照してください。

照合では、有無だけでなく検出箇所の数も数えます。ページに、ruleId+reasonCode+htmlがバイト単位で同一になる要素が3つあり(同じ壊れたコンポーネントが3回繰り返されている場合など)、ベースラインにはそのうち1つしか記録されていなければ、新しいスキャンは3つすべてを既知とはせず、既知1件、新規2件と報告します。

フレーム横断の結果(runa11yCoreAcrossFrames)でフレーム内にある検出結果は、フレームでも区別されます。そのエントリーにはフレームのパス、つまりそのフレームに至るフレーム要素のセレクター("frame": ["#checkout"])が記録され、そのパスのフレームにある検出結果とだけ一致します。ページとフレームにある同じ壊れたコンポーネントは、2つの検出結果です。frameのないエントリーはページ自身のものなので、単一ドキュメントのスキャンから書き出したベースラインは引き続きページと一致します。

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

新しいスキャンのどれとも一致しないベースラインのエントリーはstale(古い)として報告されます。その違反が修正されたのかもしれませんし、要素のマークアップが変わったのかもしれません。あるいは、スキャンがそこに届かなかった(別のビューポート、範囲、ルールのセット)のかもしれません。これは情報提供のためだけのもので、ビルドを止めることはありません。staleのエントリーを確認したうえで、定期的に--write-baselineでベースラインを再生成して整理してください。

不適合の可能性が高い検出結果

ベースラインが記録するのは不合格です。判定のつかない検出結果は不合格ではありませんが、1.14からは、そのうちのいくつかが不合格の方向に傾きます。ページの手がかりが、証明はできないものの不合格を示しているということです(そのlean。出力スキーマを参照)。それらでビルドを止める場合(JUnitとSARIFのレポートのleanFailAs)、不合格と同じように、ページにすでにあるものを受け入れる方法が必要です。ベースラインを作るときに指定してください。

const { buildBaselineEntries } = require('@surea11y/core/baseline');
 
const entries = buildBaselineEntries(result, { leanFail: true });

不合格の方向に傾き、leanFailAsのもとでビルドを止める検出結果(@surea11y/coreのleaningFailures(result))には、それぞれ不合格の後に"lean": "fail"の付いたエントリーが作られます。

{ "ruleId": "heading-order", "reasonCode": "HEADING_ORDER_SKIPPED_LEVEL", "selector": "html > body > main > h3", "html": "<h3>Details</h3>", "lean": "fail" }

このエントリーは、同じ識別子を持つ、不合格の方向に傾く検出結果とだけ一致します。通常のエントリーは不合格とだけ一致します。leanFailAsのもとでは、JUnitは一致した検出結果でビルドを止めず(その<skipped>のメッセージは、既知の不合格の可能性が高いものとして数えます)、SARIFはそれをwarningのままにします。leanFailAsがなければ、これらのエントリーは何も変えません。matchBaselineは、staleのものも含めてこれらを集計から外します。ただし{ leanFail: true }を渡した場合は、leanFail({ total, knownCount, newCount, newOccurrences, staleCount })で照合します。--write-baselineが書き込むのは不合格だけです。

leanはマイナーリリースで追加されるので、アップグレードによって、ベースラインにない不合格の可能性が高いものが現れることがあります。アップグレードの後はleanFail: trueでベースラインを作り直し、差分を確認してください。leanをどう選び、どう計測しているかは、GitHub上のLEANS.mdに書かれています。

アップグレード

検出結果の識別子には理由コードが含まれ、マイナーリリースで検出結果に新しい理由コードが付くことがあります。理由コードを付けていなかったルール(DEFAULT)が付けるようになる場合や、1つのコードが原因ごとに2つに分かれる場合です(GitHub上のAPI_STABILITY.mdの検出結果の識別子を参照)。検出結果は同じで、アップグレード前に書いたベースラインも引き続き一致します。移動はそれぞれsrc/baseline-moves.json(ルール、古いコード、新しいコード)に記録され、古いコードのエントリーは、識別子の残りの部分(ルール、マークアップ、フレーム)が同じなら、新しいコードの検出結果と一致します。matchBaselineも、SARIFとJUnitのレポートのbaselineEntriesオプションも、これを読み取ります。エントリーはまず検出結果自身のコードで探されるので、まったく同じ識別子を持つ別の検出結果があるときに、移動した検出結果に割り当てられることはありません。

1.14では、1.13から次のコードが移動します。

ルール古いコード新しいコード
img-alt-present, embed-text-alternative-present, object-text-alternative-present, svg-image-text-alternative-present, svg-text-alternative-present, video-poster-text-alternative-presentDEFAULTTEXT_ALTERNATIVE_MISSING
area-alt-presentDEFAULTTEXT_ALTERNATIVE_MISSING, TEXT_ALTERNATIVE_EMPTY
canvas-text-alternative-presentDEFAULTTEXT_ALTERNATIVE_MISSING, ROLE_IMG_NAME_MISSING
area-alt-quality, canvas-text-alternative-quality, embed-text-alternative-quality, input-image-alt-quality, object-text-alternative-qualityDEFAULTTEXT_ALTERNATIVE_REVIEW
img-alt-quality, svg-text-alternative-qualityDEFAULTTEXT_ALTERNATIVE_REVIEW, IMAGE_ONLY_CONTROL_CONTENT
img-alt-decorativeEXCLUDED_ELEMENT_REVIEWIMAGE_ONLY_CONTROL_CONTENT
input-image-alt-decorativeDEFAULTNAME_FROM_ELSEWHERE_REVIEW
manual-reviewDEFAULTKEYBOARD_REVIEW
list-children-validLIST_INVALID_CHILDLIST_ITEM_ROLE_OVERRIDDEN
listitem-parent-validLISTITEM_INVALID_PARENTLIST_ROLE_OVERRIDDEN
aria-prohibited-childrenARIA_PROHIBITED_CHILD_ROLELESSARIA_PROHIBITED_CHILD
duplicate-idDUPLICATE_IDDUPLICATE_ID_IN_SHADOW_TREE
valid-langELEMENT_LANG_INVALIDDEFAULT_LANG_INVALID
p-as-headingBOLD_LARGE_PARAGRAPH, BOLD_LARGE_DIVHEADING_LIKE_NUMBER, HEADING_LIKE_LINK

この表が対象にするのは1つのマイナーリリースです。移動は、それが入ったリリースから次のマイナーリリースまで記録され、次のマイナーリリースで削除されます。そのため、1.13で書いたベースラインは1.14.xでは引き続き一致しますが、1.15より前に再生成する必要があります。

アップグレードの後に--write-baseline(またはbuildBaselineEntries)でベースラインを再生成すると、すべての検出結果が現在のコードで書き込まれ、表は不要になります。ただし、アップグレードで新たに報告されるようになった検出結果もすべて受け入れてしまうので、差分を確認してください。新しいものを受け入れずにコードだけを更新するには、@surea11y/core/baselineのmigrateBaselineEntriesでファイルを書き換えます。

const { migrateBaselineEntries } = require('@surea11y/core/baseline');
 
const file = JSON.parse(fs.readFileSync('baseline.json', 'utf8'));
// With the scan's result, an entry for a split code takes the code its finding has now.
file.entries = migrateBaselineEntries(file, result);
fs.writeFileSync('baseline.json', JSON.stringify(file, null, 2) + '\n');

この関数は、移動したエントリーを新しいコードにし、それ以外のエントリーはそのままにしたコピーを返します。結果を渡さない場合は、検出結果がなくてもわかる移動だけを書き換えます。ルールがもう出さず、1つの新しいコードに移ったもの(img-alt-presentのDEFAULT)がそれに当たり、分割は対象外です。

アップグレードで判定結果が変わった検出結果は別の話です。それまで確認を求めていたものを不合格にするようになったルールや、それまで不合格にしていたものについて確認を求めるようになったルールは、ベースラインになかった検出結果を出したり、あった検出結果を出さなくなったりします。その理由は変更履歴に書かれています。

複数のビューポート幅でのスキャン

ベースラインのエントリーはビューポートを記録せず、照合にも使いません。同じ要素の同じ欠陥は、どの幅でも一致します。これは、検出結果がレイアウトに左右されないページに適しています。text-spacing-content-lossやtarget-size-minimumのようにレイアウトに依存するルールは、ある幅ではfailになり、別の幅ではならないことがあります。そのため、複数の幅でスキャンするページでは、幅ごとに1つのベースラインを用意してください(a11y-baseline-390.json、a11y-baseline-1280.json)。

すべての幅から書き出した1つのベースラインでも、ゲートは正しく機能します。検出結果が新規になるのは、どのエントリーにも一致しない場合だけだからです。ただしその場合、各実行がほかの幅の検出結果をstale(古い)として報告するため、staleの件数から何が修正されたかを読み取れなくなります。

ファイル形式

{
"version": 1,
"generatedAt": "2026-07-30T12:00:00.000Z",
"entries": [
{ "ruleId": "img-alt-present", "reasonCode": "TEXT_ALTERNATIVE_MISSING", "selector": "html > body > img", "html": "<img src=\"logo.png\">" }
]
}

--baseline <path>は、{ version: 1, entries: [...] }の形でないファイルを、古い形式や別の形式だと推測せず、終了コード2の明確なエラーで拒否します。matchBaselineと、SARIFおよびJUnitのレポーターのbaselineEntriesオプションは、保存したままのファイル(それをJSON.parseしたもの)か、そのentriesリストを受け取ります。

--jsonとの併用

--baselineまたは--write-baselineを--jsonと一緒に使うと、出力されるオブジェクトには通常のエンジンの結果と並んでbaselineキーが追加されます(これはCLIの出力だけのもので、出力スキーマで説明しているエンジン自体の結果の契約には含まれません)。

  • --write-baseline:{ mode: "write", path, entries: <number written> }
  • --baseline:{ mode: "check", totalFail, knownCount, newCount, staleCount, newOccurrences: [...] }

代替手段:2回のスキャンの差分を自分で取る

お使いのページがこのモデルに合わない場合(指摘される要素自体に動的な内容が多い場合)は、2回分の--jsonの完全な出力の差分を自分で取る方法がいつでも使えます。INTEGRATION.mdを参照してください。識別や照合のロジックを完全に制御できる代わりに、それを自分で書く必要があります。