EY-Office ブログ

今さらながらHTMLのroleやara-属性を知った

ある仕事のReactプロジェクトでJest + React Testing Libraryを使いコンポーネントのUIテストを書いていて、なぜかWAI-ARIA (Web Accessibility Initiative - Accessible Rich Internet Applications)を学ぶことになりました。😅

長年Webアプリを開発していますが、プログラマーなのでHTMLには知らない世界がありますね。

WAI-ARIA

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を意識して行こうと思います。

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ