Cypressで使う
Cypressは、テスト対象のアプリと並んでブラウザー自体の中でテストを実行するJavaScriptのE2Eテストフレームワークです。surea11yは@surea11y/cypressバインディングで連携します。
$ npm install @surea11y/core @surea11y/cypress cypress
cypressはpeerDependenciesのエントリーです。ふつうにCypressのspecを書くときと同じく、型とランタイムを解決するにはCypressを自分でインストールしておく必要があります。
// cypress/e2e/my-page.cy.jsconst { A11yCoreBuilder } = require('@surea11y/cypress');it('has no accessibility violations', () => {cy.visit('https://example.com/');new A11yCoreBuilder().include('#main') // optional -- call multiple times for multi-region scans.exclude('.cookie-banner') // optional.withWcag('2.2', 'AA') // the rules for WCAG 2.2's A and AA criteria.disableRules(['meta-refresh-no-exceptions']).analyze().then((results) => {const fails = results.checksResults.filter((r) => r.outcome === 'fail');expect(fails).to.have.length(0);});});
.withWcag(version, level)は適合性の目標で選択します。WCAG 2.2のレベルAとAAの達成基準のルールを、各達成基準がそのバージョンで持つレベルに従って実行します。.withBestPractices()は、WCAGの達成基準を検査しないregionなどのルールを追加します。単独で使うと、それらのルールだけを実行します。
パックはNodeで準備され、Cypressのテストはブラウザーで実行されます。そのため、Cypressの.withPacks()は各パックのモジュール名(パッケージ名、またはプロジェクトのルートからのパス)を受け取り、バインディングのプラグイン@surea11y/cypress/pluginがNodeでパックを準備します。setupNodeEventsからsurea11y(on, config)の戻り値を返して登録してください。.options({ profile })でパックのプロファイルを選びます。実行されなかったパックは'pack-skipped'のスキャンギャップとして報告されます。
ドライバーを使うほかのバインディングと違い、コンストラクターに{ page }/{ browser }/{ driver }の引数はありません。Cypressにはそのようなハンドルがないからです。cyはすべてのspecファイルで暗黙的に使え、それ自体がドライバーです。analyze()が返すのはPromiseではなくCypressのchainableなので、awaitしないでください。例のように.then()を呼び出します。
Webコンポーネント。ElementInternalsをプライベートフィールドに保持し、それを通じて名前や状態、ロールを自分に設定するコンポーネントは、ページのどのスクリプトからも読み取れません。そのため既定では、スキャンはそのようなコンポーネントを判定せずに確認を求め(cantTell)、その方法だけで設定されたロールはまったくチェックされません。コンポーネントライブラリーを作ったりテストしたりしているなら、ページ自身のスクリプトが実行される前に、バインディングのオプトインのinternalsフックをインストールしてください。そうすれば、それらのコンポーネントもほかのコントロールと同じように判定されます。
// cypress/support/e2e.jsimport { installInternalsHook } from '@surea11y/cypress';installInternalsHook();
サポートファイルで一度呼び出せば、Cypressのwindow:before:loadを通じてすべての訪問で実行されます。1回の訪問だけなら、cy.visit()に{ onBeforeLoad: installInternalsHook }として渡します。アプリのウィンドウのevalで実行されます。Cypressは既定でアプリのContent Security Policyを取り除くので、これで動作します。アプリ内のフレームには適用されません。
このフックはテストや監査の実行のためのものです。記録した内容をページのどのスクリプトからも読める場所に保持するため、オプトインになっています。フックがない場合、スキャンはそのようなコンポーネントについて確認を求めたときに、一度だけそのことを知らせます。詳しくはREADMEのinternalsフックの節を参照してください。
クリックハンドラーとライブリージョン。core 1.14から、オプトインのフックがさらに2つあります。多くのページはクリックハンドラーをスクリプトから追加しますが、後からそれを一覧にできるページのスクリプトはありません。そのため、スキャンはクリックリスナーを持つ<div role="button">がキーボードでは何もしないことを見抜けません。リスナーフックがあれば、そのようなボタンは失敗します(CLICK_HANDLER_NO_KEY_LISTENER)。ライブリージョンフックは、ページのライブリージョンに何が与えられたかを記録します。スキャンはテキストが入った状態で追加された、スクリーンリーダーが読み上げないことの多いステータスメッセージについて確認を求め、announcements()はページが読み上げ用に提供した内容を返します。これで、操作の結果がユーザーに伝えられたかをテストで確かめられます。internalsフックと同じように、ページ自身のスクリプトが実行される前にインストールしてください。
// cypress/support/e2e.jsimport { installListenerHook, installLiveRegionHook } from '@surea11y/cypress';installListenerHook();installLiveRegionHook();// in a speccy.get('#save').click();A11yCoreBuilder.announcements().then((said) => {expect(said.map((a) => a.text)).to.include('Saved');});
internalsフックと同じく、サポートファイルですべての訪問をカバーでき、適用されるのはアプリ自身のウィンドウだけで、その中のフレームには適用されません。announcements()はアプリのウィンドウを読み、.analyze()と同じくchainableを返します。
internalsフックと同じく、どちらもテストや監査の実行のためのものです。記録した内容はページのどのスクリプトからも読めます。announcements()はページが読み上げ用に提供した内容で、スクリーンリーダーが実際に読み上げた内容ではありません。ライブリージョンフックがなければnullです。どちらかのフックがない場合、そのフックなら判断できた検出結果がフックなしで判定されたときにgetScanNotes()がそのことを示します。詳しくはREADMEのリスナーフックとライブリージョンフックの節を参照してください。
ダークモード、スマートフォンの幅、重大度によるゲート。.conditions([{ colorScheme: 'dark' }, { width: 320 }])は、ページをそのままの状態でスキャンしたあと、条件ごとにもう一度スキャンし、1つの結果を返します。ルールの判定結果はすべてのスキャンのうち最も悪いものになり、各検出結果にはそれが見つかった条件が示されます。条件ごとに完全なスキャンを行います。また、.frames(true)とはまだ組み合わせられません。幅はcy.viewport()で設定します。カラースキームにはChromium系のブラウザー(Chrome、Edge、Electron)が必要で、Firefoxでは.analyze()がスキャンの前に失敗します。getGatingFailures(results, { failOnSeverity: 'serious' })は、その重大度以上で失敗したルールを返します。これにより、ほかの問題は報告し続けながら、まず重大な問題と致命的な問題でテストを止められます。formatFailures()も同じオプションを受け取ります。
傾きと確認手順のある問い。core 1.14から、cantTellの検出結果は理由とともにfailまたはpassのどちらかに傾くことがあり、人が答えるための手順であるhowToCheckを持ちます。formatFailures()は失敗の直後に不適合の可能性が高い問いを並べ、{ howToCheck: true }で手順も出力します。不適合の可能性が高いものでもテストを失敗させるには、coreのleaningFailures(results)をゲートに加えてください。Cypressでは手順はプラグインから届きます。setupNodeEventsはsurea11y(on, config)の戻り値を返す必要があります。返さない場合、結果に手順は含まれませんが、何も失敗しません。
A11yCoreBuilderにはほかにも、実行するルールを変えずに報告する達成基準をWCAGのレベルまでに絞る.wcagLevel()、タグのルールに名前を挙げたルールを加えて実行する.includeMode('or')、スキャンが何をチェックするかを実行前に一覧にし、何も実行しない選択が問題のないスキャンに見えないようにする.plannedChecks()と.plannedCriteria()、CSSを読むルールのために、ページ自身が読めない別オリジンのスタイルシートを.analyze()が取得しないようにする.fetchStyleSheets(false)、ページがどのようにスキャンされたかを示すgetScanNotes(results)があります。それぞれの説明はバインディングのREADMEにあります。
パッケージのリポジトリにある実行可能なサンプルも参照してください。
次のステップ:実行するルールを選ぶ、スキャンをCIに組み込む、結果のスキーマ全体を確認する。