東京都府中市、九段下のWEB制作会社Maromaroのブログです

2023.10.10

松橋一誠

複数ページのアクセシビリティチェックの結果をCSV出力

こんにちは、Maromaroの松橋です。

今回は、複数ページのアクセシビリティチェックを行う手順をご紹介します!

アクセシビリティチェックは、個別のページであれば、Chromeの拡張があるので、気軽にチェックすることができます。

axe DevTools – Web Accessibility Testing

しかし、数千ページを一気にチェックするとなると、この方法では厳しいです。

そのような時に、Puppeteeraxe-core といいうNode.jsのライブラリを利用して、一気にアクセシビリティチェックを行うと便利です。

手順やアイディアについては、以下の記事が非常にわかりやすかったため、これを参考にしつつ、自分仕様にいくつか改修して進めました。
ウェブアクセシビリティ検証ツール「axe」を用いた自動テスト実行スクリプト

アクセシビリティチェックの手順

作業時のnode.jsのバージョンは18.12.1、npmは8.19.2でした。

❯ node -v

v18.12.1
❯ npm -v
8.19.2

必要モジュール(Puppeteer、axe-core、axe-reports)をインストールしていきます。

❯  npm install puppeteer --no-save

❯ npm install @axe-core/puppeteer
❯ npm install axe-reports

axe-test.js を下記の内容で作成します。

// 必要なモジュールをインポート
const { AxePuppeteer } = require('@axe-core/puppeteer');
const puppeteer = require('puppeteer');
const AXE_LOCALE_JA = require('axe-core/locales/ja.json');
const AxeReports = require('axe-reports');
const fs = require('fs');

// 設定オブジェクトの定義
const config = {
	locale: AXE_LOCALE_JA
}

// URLリストの取得と整形
let urls_list = fs.readFileSync("./urls.txt", "utf-8");
urls_list = urls_list.replace(/\r?\n/g, ',');
urls_list = urls_list.split(',');

// 空文字列をフィルタリングして有効なURLのみを取得
const urls = urls_list.filter(url => url.trim() !== "");

// CSVレポートのヘッダー行を作成
AxeReports.createCsvReportHeaderRow();

// 50件ずつURLを処理する関数
async function process_urls(start_index, browser) {
	for (let i = start_index; i < start_index + 50 && i < urls.length; i++) {
		const url = urls[i];
		const page = await browser.newPage();
		await page.setBypassCSP(true);

		try {
			await Promise.all([
				page.setDefaultNavigationTimeout(0),
				page.waitForNavigation({waitUntil: ['load', 'networkidle2']}),
				page.goto(url)
			]);
		} catch (error) {
			console.error(`Navigation error on ${url}:`, error.message);
			fs.appendFileSync('output.csv', `"${url}","チェックに失敗しました: ${error.message}"\n`);
			await page.close();
			continue;
		}

		try {
			// アクセシビリティチェックとタイムアウトをraceさせる
			await Promise.race([
				new AxePuppeteer(page).configure(config).withTags(['wcag2a', 'wcag2aa', 'bestpractice']).analyze().then(results => {
					AxeReports.createCsvReportRow(results);
				}),
				new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout after 30 seconds')), 30000))
			]);
		} catch (error) {
			console.error(`Accessibility check failed on ${url}:`, error.message);
			fs.appendFileSync('output.csv', `"${url}","アクセシビリティチェックに失敗しました: ${error.message}"\n`);
		}

		await page.close();
	}
}
// アクセシビリティチェックの実行
(async () => {
  const browser = await puppeteer.launch({
    headless: "new",
    timeout: 0,  // 追加: ページナビゲーションのタイムアウトを無効にする
    protocolTimeout: 60000  // 追加: プロトコルタイムアウトを60秒に設定
  });

	// 全てのURLを50件ずつ処理
	for (let i = 0; i < urls.length; i += 50) {
		await process_urls(i, browser);
	}

	await browser.close();
})();

アクセシビリティチェックの基準は下記の部分で指定の基準を設定しましょう。

withTags(['wcag2a', 'wcag2aa', 'bestpractice']) 

チェック開始時、ページチェックが途中で止まったり、チェックが進まないという時があり、、、下記の対策をコードに反映しております。

・PCのメモリ対策で50件ずつ チェックを実施
数千件もチェックするのでメモリを沢山利用してしまうのかな、、、と推定し、50件ずつにしてみてます。
ただ、大部分の止まってしまう原因は次に上げる問題だったので、ページチェックが止まってしまう時に、本当に効果があるかは未検証です(すみません!)

・ページの読み込みが30秒超えた時はタイムアウトとする
ページの読み込みが止まってしまうページは、私の場合は、ブラウザのコンソールにJavaScriptのエラーがあったのが原因でした。。。汗
なので、30秒経過したら別途対応としてoutput.csvに追加するようにしています。

さいごに、チェックを開始する時は、下記のコマンドでcsvファイルに結果が流し込まれていきます。

❯  node axe-test.js => result.csv

まとめ

私の場合、今回のチェックは改修を含めて2時間程度かかりました。しかし、チェックがスムーズに進めば、数千件のチェックは1時間もかからないと感じます。

非常に便利だったため、複数ページを一気にアクセシビリティチェックしたい際には、この方法をおすすめします。

以上、Maromaroの松橋でした。