ある仕事のReactプロジェクトでJest + React Testing Libraryを使いコンポーネントのUIテストを書いていて、なぜかWAI-ARIA (Web Accessibility Initiative - Accessible Rich Internet Applications)を学ぶことになりました。😅
長年Webアプリを開発していますが、プログラマーなのでHTMLには知らない世界がありますね。
WAI-ARIAとはなにか
ここでは、WAI-ARIA (Web Accessibility Initiative - Accessible Rich Internet Applications)に付いての解説はしませんが、簡単に説明します。詳しくはMDNのアクセシビリティや、ネット上の情報を読んでください。
現在Webにはあらゆる情報が集まっているので、目の不自由な方も読み上げ機能を使って利用しています。
HTMLタグは<buton>
や<a href="...">
などのように役割を持ったタグがあり、読み上げ機能はそれらを判断して音声で伝えています。しかし以下の例はJavaScriptを使い、<span>
タグ本来のコンテンツ表示用タグではなく<button>
として使っています。
<span onClick="gotoDetailPage()" class="・・・">詳細</span>
一部のUIフレームワーク等で見かけますよね、本来は<button>
タグを使うべきですが、色々な理由から現実のWebでは使われていますよね。
そこでWAI-ARIAではロール(role=
)属性を付ける事を推奨しています。
<span role="button" onClick="gotoDetailPage()" class="・・・">詳細</span>
role="button"
属性があれば読み上げ機能はここがボタンだと判断できます。
さらに、WAI-ARIAプロパティとしてaria-required="true"
なども定義されています、aria-required
の意味は想像通りフォームの必須項目です。
今回の記事ではaria-label
が出てきますが、これはタグのラベルとして扱われます。
なぜWAI-ARIA ?
React Testing Libraryを使いコンポーネントのUIテストを書いているわけですが、以前書いたTailwind CSSの良さが少しわかった気がしたのジャンケンアプリのUIテストを書くと以下のようになります。
React Testing Libraryに付いては別途ブログを書きたいと思うので、簡単に説明すると
- ① コンピューターの手は乱数(Math.random()関数)で発生しているのでモックで0.8を戻すように置き換え
- ②
<App>
コンポーネントを描画 - ③
チョキ
という文字の書かれているHTML要素をクリック - ④
cell
=<td>
要素内の文字列を取得 - ⑤ 上の結果の検証、
cells[0]
は時間なのでチェックしないようにしています
import { render, screen, fireEvent } from '@testing-library/react';
import App from './App';
describe('App', () => {
test('チョキ ボタンを押すと対戦結果が表示される', () => {
global.Math.random = jest.fn(() => 0.9); // ← ①
render(<App/>); // ← ②
fireEvent.click(screen.getByText("チョキ")); // ← ③
const cells = screen.queryAllByRole("cell").map(e => e.textContent); // ← ④
expect(cells.slice(1)).toEqual(['チョキ', 'パー', '勝ち']); // ← ⑤
});
});
React Testing Libraryでは画面(DOM)上の要素の選択には、画面に表示されている文字列で選択するgetByText()
や要素のロールで選択するgetByRole()
などが主流で従来のUIテストツールにあったCSS セレクターベースの選択は主流ではないようです(実際にはできますが)。
過去にUIテストは何度か書いていますが、CSSセレクターベースの指定はUI変更の影響を受けやすく、ちょっとした画面変更でもテストが通らなくなる、あまり良くない方法だと思います。
サンプルアプリのUIをかえてみましょう
このジャンケンアプリのボタンを画像(アイコン)に変えてみましょう。
ボタン上の文字が無くなったので、テストコードは③が動かなくなります。そこでアプリのWAI-ARIA的に良いコード書き変えてみましょう。
App.tsx
const JyankenBox: React.FC<JyankenBoxProps> = ({ actionPon }) => {
return (
<div className="w-[230px] mx-auto flex">
<Button onClick={() => actionPon(Te.Guu)} ariaLabel="グー"><img src="/images/guu.png" /></Button>
<Button className="mx-5" onClick={() => actionPon(Te.Choki)} ariaLabel="チョキ">
<img src="/images/choki.png" />
</Button>
<Button onClick={() => actionPon(Te.Paa)} ariaLabel="パー"><img src="/images/paa.png" /></Button>
</div>
);
};
App.test.tsx
- ①
getByRole()
にはname:
オプションでaria-label
を指定できます
他の変更はありません。
import App from './App';
describe('App', () => {
test('チョキ ボタンを押すと対戦結果が表示される', () => {
global.Math.random = jest.fn(() => 0.9);
render(<App/>);
fireEvent.click(screen.getByRole("button", {name: "チョキ"})); // ← ①
const cells = screen.queryAllByRole("cell").map(e => e.textContent);
expect(cells.slice(1)).toEqual(['チョキ', 'パー', '勝ち'])
});
});
サンプルアプリのUIを戻してみましょう
何らかの理由によって、ボタンは文字ベースに戻す事になったとしましょう。
App.tsx
注)<Button>
はReactコンポーネントなので、aria-label
の指定はコンポーネント内で行っても良いですね。
const JyankenBox: React.FC<JyankenBoxProps> = ({ actionPon }) => {
return (
<div className="w-[230px] mx-auto flex">
<Button onClick={() => actionPon(Te.Guu)} ariaLabel="グー">グー</Button>
<Button className="mx-5" onClick={() => actionPon(Te.Choki)} ariaLabel="チョキ">
チョキ
</Button>
<Button onClick={() => actionPon(Te.Paa)} ariaLabel="パー">パー</Button>
</div>
);
};
この変更を行っても、テストコードの変更する必要はありませまん! すばらし。😊
まとめ
以前から、ときどきaria-
という属性は目にしてきましたが無視してました、ごめんなさい。😅
今回WAI-ARIAを学んだ事で、利用者に優しいアプリは、テストコードにも優しい事を知りました。今後アプリを作る際にはWAI-ARIAを意識して行こうと思います。