Karmaとmochaとaxeでアクセシビリティチェックを行う

アクセシビリティ・エンジニア 黒澤

はじめに

突然ですが、皆さんには次のような経験はないでしょうか。

  • 最初は正しく動作していたはずなのに、開発している間にいつのまにか動作しなくなっていた
  • Pull Requestなどを使ったレビューを実施しているものの、レビューで議論したい内容とは関係ない問題(インデントが揃っていない、命名規則に沿ってない、などなど)が多い

そのような場合、機械(プログラム)でチェックできる部分は機械(プログラム)に任せる、例えば自動テストを導入する、ことが一般的になってきたと感じています。前述の悩みはアクセシビリティでも同じです。先ほどの例をアクセシビリティに置き換えると次のようになるでしょう。

  • 最初はアクセシビリティの問題がなかったはずなのに、開発している間にいつのまにかアクセシビリティの問題が作りこまれていた
  • Pull Requestなどを使ったレビューを実施しているものの、レビューで議論したい内容とは関係ない問題(HTMLやARIAの構造が明白におかしい、代替テキストを指定できる構造になっていない、などなど)が多い

そこで、この記事ではNode.jsで開発しているプロジェクトの単体テストでアクセシビリティチェックを行う方法を簡単に紹介します。具体的には、テストランナーにKarma、テストフレームワークにmocha、アクセシビリティチェックにaxe(The Accessibility Engine)を用います。

axeはDeque Systemsが開発しているオープンソースのアクセシビリティチェックライブラリです。JavaScriptで書かれており、モダンなブラウザー(Internet Explorer 9以上など)とPhantomJSで動作します。axeは、FirefoxOS用のUIコンポーネントの自動テスト((test-utils))などでも使われています。また、axeをGoogle ChromeやMozilla Firefoxの開発者ツールに統合する拡張機能も公開されています(Google Chrome用拡張機能Mozilla Firefox用拡張機能)。

axeでアクセシビリティチェック

Karmaとmochaのインストール方法や設定方法はこの記事では解説しませんが、karma+mocha+power-assertでDOM操作を含むユニットテストをES6で書くなどが参考になります(なお、この記事のサンプルコードはES5.1です)。いずれにせよ、Node.jsとnpmがインストールされている環境で、少なくとも次のような操作が必要でしょう。

npm i --save-dev karma
`npm bin`/karma init
# Frameworkで少なくともmochaを選ぶ

次に、package.jsonを編集してnpm testでKarmaを呼び出すようにします。

{
  ...
  "scripts": {
    "test": "karma start"
  },
  ...
}

そして、axeをインストールします。

npm i --save-dev axe-core

インストールしたaxe-coreパッケージに含まれるaxe.min.js(もしくはaxe.js)をテスト対象のHTMLに埋め込みます。なお、axe-coreはCommonJSモジュールとしては特殊でvar axe = require('axe-core');のような使い方は通常しません。

module.exports = function(config) {
    // ...
    files: [
        path.join('node_modules', 'axe-core', 'axe.min.js'),
        // ...
    ],
    // ...
}

すると、テストページのグローバルスコープにaxeオブジェクトが見えるようになります。個々のテストではaxeオブジェクトのメソッドを呼び出してアクセシビリティチェックを行います。

では、簡単なテストを書いてみます(test/sample.spec.js)。

var assert = require('assert');

describe('サンプル', function() {
    before(function() {
        // fixutreの読み込みなど
        // 問題のあるHTMLをテストページに追加
        var html = '<div><a href="about:unicorn"><img src="about:unicorn"></a></div>';
        document.body.innerHTML += html;
    });

    it('アクセシビリティの問題がないこと', function(done) {
        // axeでチェック
        axe.a11yCheck(document.body, null, function(result) {
            // 問題がないことの確認
            assert.ok(result.violations.length === 0,
                      '機械的にチェックできるアクセシビリティの問題はないはず:');
            // テスト終了
            done();
        });
    });
});

axeのチェック結果はコールバック関数に渡され(result)、問題があればviolations配列に格納されています。このテストでは、violations配列は空である、つまり問題が見つからない、ことをテストしています。

それでは、実際にテストを実行します。

npm test

すると、次のようなエラーが画面に表示されました(Windows 7のGoogle Chrome 47で実行した場合)。

Chrome 47.0.2526 (Windows 7 0.0.0) サンプル 機械的にチェックできるアクセシビリティの問題がないこと FAILED
        Uncaught AssertionError: 機械的にチェックできるアクセシビリティの問題はないはず: (...)

axeによって問題があることが報告されています。とはいえ、実際には問題の有無だけではなく、どういった問題が起こったのかも確認する必要がでてくるでしょう。ここでは発見された問題をエラーメッセージに追加してみます。

var assert = require('assert');

describe('サンプル', function() {
    before(function() {
        // fixutreの読み込みなど
        // 問題のあるHTMLをテストページに追加
        var html = '<div><a href="about:unicorn"><img src="about:unicorn"></a></div>';
        document.body.innerHTML += html;
    });

    it('アクセシビリティの問題がないこと', function(done) {
        // axeでチェック
        axe.a11yCheck(document.body, null, function(result) {
            // 各問題をまとめる
            var messages = [];
            var violations = result.violations;
            violations.forEach(function(violation) {
                messages.push('\n');
                // 問題の影響度合い
                messages.push('[' + violation.impact + '] ');
                // 問題の概要
                messages.push(violation.description + '\n');
                violation.nodes.forEach(function(node, index) {
                    // 問題発生要素を指し示すCSSセレクター
                    messages.push('CSSセレクター: ' + node.target.join(',') + '\n');
                    // 問題発生要素のHTML
                    messages.push('HTML: ' + node.html.substring(0, 128) + '\n');
                });
            });
            // 問題がないことの確認
            assert.ok(violations.length === 0,
                      '機械的にチェックできるアクセシビリティの問題はないはず:' + messages.join(''));
            // テスト終了
            done();
        });
    });
});

すると、画面に問題の影響度合い、概要、問題発生要素を表すCSSセレクター、問題発生要素のHTMLの一部が表示されます。記事執筆段階のaxeでは次の内容が出力されます。

Uncaught AssertionError: 機械的にチェックできるアクセシビリティの問題はないはず:
[critical] Ensures <img> elements have alternate text or a role of none or presentation
CSSセレクター: body > div:nth-of-type(2) > a > img
HTML: <img src="about:unicorn">

[critical] Ensures links have discernible text
CSSセレクター: body > div:nth-of-type(2) > a
HTML: <a href="about:unicorn"><img src="about:unicorn"></a>

まとめ

アクセシビリティチェックを単体テストに組み込むことは、意外と簡単に始められることがおわかりいただけると思います。皆さんもぜひ始めてみてください。