Seleniumで使う

Selenium WebDriverは、あらゆる言語から実際のブラウザーを操作するための昔からの標準です。クロスブラウザーの自動化ツールとして最も定着しており、E2Eテストによく使われます。surea11yは@surea11y/seleniumバインディングで連携します。

$ npm install @surea11y/core @surea11y/selenium selenium-webdriver

selenium-webdriverにはSelenium Managerが同梱されており、初めてドライバーを作成するときに、対応するchromedriverを自動でダウンロードして管理します。Seleniumサーバーを別に用意する必要も、ドライバーを手動でインストールする必要もありません(Chrome自体は、あらかじめマシンにインストールしておいてください)。

const { Builder, Browser } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const { A11yCoreBuilder } = require('@surea11y/selenium');
 
const options = new chrome.Options().addArguments('--headless=new');
const driver = await new Builder().forBrowser(Browser.CHROME).setChromeOptions(options).build();
await driver.get('https://example.com/');
 
const results = await new A11yCoreBuilder({ driver })
.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();
 
console.log(results.checksResults.filter(r => r.outcome === 'fail'));
await driver.quit();

.withWcag(version, level)は適合性の目標で選択します。WCAG 2.2のレベルAとAAの達成基準のルールを、各達成基準がそのバージョンで持つレベルに従って実行します。.withBestPractices()は、WCAGの達成基準を検査しないregionなどのルールを追加します。単独で使うと、それらのルールだけを実行します。

.withPacks()はスキャンの前にページへパックを登録し、.options({ profile })でそのプロファイルを選びます。実行されなかったパックは'pack-skipped'のスキャンギャップとして報告されるため、getScanGaps(results)を検証するテストは、パックなしで合格せずに失敗します。

Puppeteer/Playwrightが{ page }を受け取るのに対し、このビルダーは{ driver }(SeleniumのWebDriver)を受け取ります。生成時の違いはこの1点だけで、それ以降はすべて同じです。

Webコンポーネント。ElementInternalsをプライベートフィールドに保持し、それを通じて名前や状態、ロールを自分に設定するコンポーネントは、ページのどのスクリプトからも読み取れません。そのため既定では、スキャンはそのようなコンポーネントを判定せずに確認を求め(cantTell)、その方法だけで設定されたロールはまったくチェックされません。コンポーネントライブラリーを作ったりテストしたりしているなら、ページ自身のスクリプトが実行される前に、バインディングのオプトインのinternalsフックをインストールしてください。そうすれば、それらのコンポーネントもほかのコントロールと同じように判定されます。

const options = new chrome.Options().enableBidi(); // the webSocketUrl: true capability
const driver = await new Builder().forBrowser(Browser.CHROME).setChromeOptions(options).build();
 
await A11yCoreBuilder.installInternalsHook(driver); // 'bidi' (or 'cdp' without BiDi on Chrome)
await driver.get('https://example.com/');

driver.get()の前に呼び出してください。従来のWebDriverではページのスクリプトより前にスクリプトを実行できないため、フックはセッションがWebDriver BiDiに対応していればそれを使い(Chrome、Edge、Firefoxで、上の例のようにドライバーの作成時にBiDiを有効にした場合)、そうでなければChromeとEdgeでDevToolsプロトコルを使います。戻り値は'bidi'か'cdp'です。Safari、またはBiDiなしのFirefoxでの従来のセッションでは、呼び出しは拒否されます。

このフックはテストや監査の実行のためのものです。記録した内容をページのどのスクリプトからも読める場所に保持するため、オプトインになっています。フックがない場合、スキャンはそのようなコンポーネントについて確認を求めたときに、一度だけそのことを知らせます。詳しくはREADMEのinternalsフックの節を参照してください。

クリックハンドラーとライブリージョン。core 1.14から、オプトインのフックがさらに2つあります。多くのページはクリックハンドラーをスクリプトから追加しますが、後からそれを一覧にできるページのスクリプトはありません。そのため、スキャンはクリックリスナーを持つ<div role="button">がキーボードでは何もしないことを見抜けません。リスナーフックがあれば、そのようなボタンは失敗します(CLICK_HANDLER_NO_KEY_LISTENER)。ライブリージョンフックは、ページのライブリージョンに何が与えられたかを記録します。スキャンはテキストが入った状態で追加された、スクリーンリーダーが読み上げないことの多いステータスメッセージについて確認を求め、announcements()はページが読み上げ用に提供した内容を返します。これで、操作の結果がユーザーに伝えられたかをテストで確かめられます。internalsフックと同じように、ページ自身のスクリプトが実行される前にインストールしてください。

await A11yCoreBuilder.installListenerHook(driver); // before driver.get()
await A11yCoreBuilder.installLiveRegionHook(driver);
await driver.get('https://example.com/settings');
await driver.findElement(By.css('#save')).click();
 
const said = await A11yCoreBuilder.announcements(driver);
// [{ text: 'Saved', politeness: 'polite', ..., selector: '#status' }]

どちらもinternalsフックと同じようにdriver.get()の前にインストールします。セッションにWebDriver BiDiがあればそれを使い、なければChromeとEdgeでDevToolsプロトコルを使います。それぞれ'bidi'または'cdp'を返します。Byはselenium-webdriverから取得します。

internalsフックと同じく、どちらもテストや監査の実行のためのものです。記録した内容はページのどのスクリプトからも読めます。announcements()はページが読み上げ用に提供した内容で、スクリーンリーダーが実際に読み上げた内容ではありません。ライブリージョンフックがなければnullです。どちらかのフックがない場合、そのフックなら判断できた検出結果がフックなしで判定されたときにgetScanNotes()がそのことを示します。詳しくはREADMEのリスナーフックとライブリージョンフックの節を参照してください。

ダークモード、スマートフォンの幅、重大度によるゲート。.conditions([{ colorScheme: 'dark' }, { width: 320 }])は、ページをそのままの状態でスキャンしたあと、条件ごとにもう一度スキャンし、1つの結果を返します。ルールの判定結果はすべてのスキャンのうち最も悪いものになり、各検出結果にはそれが見つかった条件が示されます。条件ごとに完全なスキャンを行います。また、.frames(true)とはまだ組み合わせられません。ChromeとEdgeではどちらもDevToolsプロトコルで設定します。ほかのブラウザーでは幅はウィンドウのサイズを変え、カラースキームを指定すると.analyze()がスキャンの前に拒否されます。getGatingFailures(results, { failOnSeverity: 'serious' })は、その重大度以上で失敗したルールを返します。これにより、ほかの問題は報告し続けながら、まず重大な問題と致命的な問題でテストを止められます。formatFailures()も同じオプションを受け取ります。

傾きと確認手順のある問い。core 1.14から、cantTellの検出結果は理由とともにfailまたはpassのどちらかに傾くことがあり、人が答えるための手順であるhowToCheckを持ちます。formatFailures()は失敗の直後に不適合の可能性が高い問いを並べ、{ howToCheck: true }で手順も出力します。不適合の可能性が高いものでもテストを失敗させるには、coreのleaningFailures(results)をゲートに加えてください。

A11yCoreBuilderにはほかにも、実行するルールを変えずに報告する達成基準をWCAGのレベルまでに絞る.wcagLevel()、タグのルールに名前を挙げたルールを加えて実行する.includeMode('or')、スキャンが何をチェックするかを実行前に一覧にし、何も実行しない選択が問題のないスキャンに見えないようにする.plannedChecks()と.plannedCriteria()、スキャンギャップの警告を自分のログに送る.logger()、CSSを読むルールのために、ページ自身が読めない別オリジンのスタイルシートを.analyze()が取得しないようにする.fetchStyleSheets(false)、ページがどのようにスキャンされたかを示すgetScanNotes(results)があります。それぞれの説明はバインディングのREADMEにあります。

パッケージのリポジトリにある実行可能なサンプルも参照してください。

次のステップ:実行するルールを選ぶ、スキャンをCIに組み込む、結果のスキーマ全体を確認する。