webアクセシビリティ診断を行う自動ツールを作成する
※当記事は2024年3月に執筆した記事で、情報は当時のものになります。
はじめに
はじめまして、webサイトの構築・運用を行っているとうめと申します。
皆さんが運用しているwebサイトはWCAGの基準に達しているでしょうか?
国によってはwebアクセシビリティを確保しなかったことによる訴訟問題にも発生しています。※1
しかし、構築時の要件に入っていなかったり、運用していくうちに基準を下回ってたりと見直す必要が出てくるかもしれません。
もちろん、アクセシビリティ診断ツールを利用するのもよいですが、対象となるページがたくさんある場合、1ページずつ実行していくのは大変な作業となります。
そこで、今回は複数ページでもすべて自動で診断し、結果を出力てくれる
ツールの作り方について紹介します。
開発手順
※node.jsが利用できる前提で説明しています。
利用できない方は各自インストールを行ってください。
利用する技術
- node.js
- @axe-core/puppeteer
- csv-stringify
- puppeteer
仕様
今回は以下の仕様をもとにツールを開発していきます。
- アクセシビリティ診断はAxe-coreを利用する。
- PCとSPそれぞれ診断を行う。
- URLが1行ずつ記載されたテキストファイルを読み込む。
- ルール違反となっている要素をCSV形式で一覧化する。
開発環境の用意
開発用のフォルダを用意し、その中でコマンドプロンプトを起動して以下のコマンドを実行してください。
$ npm init -y
$ npm i @axe-core/puppeteer csv-stringify puppeteer
同階層にJavaScriptファイルを作成してください。
ファイル名は何でもよいですが、今回は「start.js」とします。
$ touch start.js
処理の実装
作成したファイルに処理を追加していきます。
まずは必要なモジュールを呼び出します。
const { AxePuppeteer } = require("@axe-core/puppeteer");
const puppeteer = require("puppeteer");
const fs = require("fs");
const stringify = require("csv-stringify/sync");
次にテスト結果を日本語で出力するための設定を定義します。
const AXE_LOCALE_JA = require("axe-core/locales/ja.json");
const config = {
locale: AXE_LOCALE_JA,
};
今回はテキストファイルに診断対象のURLを記述しそれを読み込む仕様
にするため、ファイル読み込みおよび配列に変換の処理を追加します。
読み込むファイルは url.txt とし、その中に1行ずつURLが記載されているとします。
let urlList = fs.readFileSync("./url.txt", "utf-8").replaceAll(/\r/g, '').split("\n");
urlList = urlList.filter(url => url != '');
Axe-coreの診断結果は複雑な形式で帰ってくるため、それを整形する関数を作成します。
指摘事項1件につき、以下の情報をまとめるための関数を作成します。
- URL
- 指摘事項のID(例:color-contrast-enhanced)
- 深刻度(例:serious)
- タグ(例:wcag2aaa,ACT)
- 指摘されている事項の詳細URL
- 指摘事項のメッセージ
- 指摘対象のHTML
function insertData(url, element, node, datas) {
let data = {
url: url,
ViolationType: datas['id'],
impact: datas['impact'],
tag: element["tags"].join(),
help: element["helpUrl"],
message: datas["message"],
htmlElement: node["html"],
}
data["domElement"] = '';
if (node["target"].length > 0) {
for (let i = 1; i <= node["target"].length; i++) {
if (node["target"].length === i) {
data["domElement"] += node["target"][i - 1] + "\n";
} else {
data["domElement"] += node["target"][i - 1];
}
}
}
return data;
}
次に診断結果をCSV形式で出力するために整形する関数を作成します。
function convertCSV(results, filename) {
let csvArray = [];
let allData = [];
results.forEach((raw) => {
let inapplicable = raw["data"]["inapplicable"];
let passes = raw["data"]["passes"];
let incomplete = raw["data"]["incomplete"];
let violations = raw["data"]["violations"];
allData.push(inapplicable, violations);
allData.forEach((data) => {
data.forEach((element) => {
element["nodes"].forEach((node) => {
if (node["any"].length > 0) {
node["any"].forEach(d => {
csvArray.push(insertData(raw["url"], element, node, d));
});
} else if (node["all"].length > 0) {
node["all"].forEach(d => {
csvArray.push(insertData(raw["url"], element, node, d));
});
} else if (node["none"].length > 0) {
node["none"].forEach(d => {
csvArray.push(insertData(raw["url"], element, node, d));
});
} else {
return;
}
});
});
});
});
const csvString = stringify.stringify(csvArray, {
header: true,
});
fs.writeFileSync(filename, csvString);
}
最後に、実際に診断を行う処理を作成します。
アクセシビリティ検証はPCとSPの2パターン行いますが、書き出し結果を分けるためにそれぞれを関数化していきます。
まずはPCページの診断を行う処理の作成です。
どのアクセシビリティ基準で診断するか、withTagsメソッドに配列形式で
タグを指定することが可能です。
今回は特に制限する必要がないためすべて指定しておきます。
async function axe_PC() {
const urls = urlList;
let resultArray = [];
const browser = await puppeteer.launch();
const page = await browser.newPage();
for (let i = 0; i < urls.length; i++) {
const url = urls[i];
await page.setBypassCSP(true);
// ページを読み込む。
await Promise.all([
page.setDefaultNavigationTimeout(0),
page.waitForNavigation({ waitUntil: ["load", "networkidle2"] }),
page.goto(`${url}`),
]);
const results = await new AxePuppeteer(page)
.configure(config)
.withTags([
"wcag2a",
"wcag2aa",
"wcag2aaa",
"wcag21a",
"wcag21aa",
"wcag22aa",
"wcag2a-obsolete",
"wcag***",
"ACT",
"section508",
"section508.*.*",
"TTv5",
"TT*.*",
"EN-301-549",
"EN-9.*",
"cat.*"
])
.analyze();
resultArray.push({
data: results,
url: url,
});
}
convertCSV(resultArray, "result_pc.csv");
await page.close();
await browser.close();
}
次にSPで診断する処理を作成します。
エミュレーターの指定外はすべて同じです。
async function axe_SP() {
const urls = urlList;
let resultArray = [];
const browser = await puppeteer.launch();
const page = await browser.newPage();
// デバイスのエミュレートをする場合は、下記を適用する。
await page.emulate(puppeteer.KnownDevices["iPhone 8"]);
for (let i = 0; i < urls.length; i++) {
const url = urls[i];
await page.setBypassCSP(true);
// ページを読み込む。
await Promise.all([
page.setDefaultNavigationTimeout(0),
page.waitForNavigation({ waitUntil: ["load", "networkidle2"] }),
page.goto(`${url}`),
]);
const results = await new AxePuppeteer(page)
.configure(config)
.withTags([
"wcag2a",
"wcag2aa",
"wcag2aaa",
"wcag21a",
"wcag21aa",
"wcag22aa",
"wcag2a-obsolete",
"wcag***",
"ACT",
"section508",
"section508.*.*",
"TTv5",
"TT*.*",
"EN-301-549",
"EN-9.*",
"cat.*"
])
.analyze();
resultArray.push({
data: results,
url: url,
});
}
convertCSV(resultArray, "result_sp.csv");
await page.close();
await browser.close();
}
最後に、上記で作成した二つの関数を呼び出します。
これでプログラムの実装は終了です。
axe_PC();
axe_SP();
URLリストの作成
診断を行いたいURLはurl.txtに1行ずつ記入して下さい。
記入が終わったら、start.jsと同階層に保存してください。
記入例
https://example.com/dir1.html
https://example.com/dir2.html
https://example.com/dir3.html
...
実行
コマンドラインでstart.jsを実行するだけです。
$ node start.js
実行に成功すると、同階層に「result_pc.csv」と「result_sp.csv」が生成されます。
書き出されたCSVをスプレッドシートに取り込むとこのような表示になります。
今後の展開
これで修正すべき点が明確になりました。
ただ、ログインが必要なページには対応していないので、
それをクリアできるよう改良したいと思います。
未来の自分、頑張ってください。