Web UIテストでカスタムデータ属性の用いてDOM要素を指定

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/82804/c6c7b9f4-335f-207f-5909-9bfa0423ab99.png)

最近、Web UIのテストで[カスタムデータ属性](https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/data-*)で[DOM](https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model)要素を指定するやり方が気に入ってます。それについてまとめてみようと思います。

この手法はWebのテストのみならず、JavaScriptのスクリプトでも適用できるのですが、今は[Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view)のテストの勉強中なのでWeb UIテストの文脈で書いてます。

- [autoracex #24 (2021/04/24 00:00〜)](https://autoracex.connpass.com/event/211239/)での成果です。
- [English edition](https://dev.to/mnishiguchi/finding-dom-element-by-data-role-in-phoenix-liveview-testing-1i7f)

https://autoracex.connpass.com/

https://join.slack.com/t/elixirjp/shared_invite/zt-ae8m5bad-WW69GH1w4iuafm1tKNgd~w

## TL;DR

今書いている時点で、一般的によく使われる[ID セレクター](https://developer.mozilla.org/ja/docs/Web/CSS/ID_selectors)より、個人的に[データ属性](https://developer.mozilla.org/ja/docs/Learn/HTML/Howto/Use_data_attributes)を使用してDOM要素を取得するのが好みです。特に`data-role`というカスタム属性を使うことにより、アプリ全体で一貫した規約的なものができ、かつ柔軟にその要素の役割を明示することにもなると考えています。

仮にこういうHTML文書があったとします。

```html
defmodule MnishiguchiWeb.AlchemistsLive do
  use MnishiguchiWeb, :live_view

  alias Mnishiguchi.Alchemists

  @impl Phoenix.LiveView
  def mount(_params, _session, socket) do
    {:ok, assign(socket, alchemists: Alchemists.list_alchemists())}
  end

  @impl Phoenix.LiveView
  def render(assigns) do
    ~L"""
    <div class="row">
      <%= for alchemist <- @alchemists do %>
        <div class="card mb-3" data-role="alchemist-card" data-id="<%= alchemist.id %>">
          <div class="card-body">
            <h5 class="card-title"><%= alchemist.name %></h5>
          </div>
        <div>
      <% end %>
    </div>
    """
  end
end
```

テストコードでは`data-role`カスタム属性を用いてDOM要素を見つけられます。

```elixir
defmodule MnishiguchiWeb.AlchemistsLiveTest do
  use MnishiguchiWeb.ConnCase, async: true

  import Phoenix.LiveViewTest

  @path "/alchemists"

  test "displays alchemists", %{conn: conn} do
    _alchemist1 = create_alchemist(name: "Taro Yamada")

    {:ok, view, _disconnected_html} = live(conn, @path)

    assert has_alchemist_card?(view, "Taro Yamada")
    refute has_alchemist_card?(view, "Jiro Yamada")
  end

  defp has_alchemist_card?(view, name) do
    has_element?(view, "[data-role=alchemist-card]", name)
  end
end
```

IDで特定したいのであれば、`data-role`と`data-id`との2つのカスタム属性を用いてDOM要素を使用する手もあります。

```elixir
  defp has_alchemist_card?(view, id, name) do
    has_element?(view, "[data-role=alchemist-card][data-id=#{id}]", name)
  end
```

DOM要素のテキストにこだわらないのであれば、こういうので良い場合があるかもしれません。

```elixir
  defp has_alchemist_card?(view) do
    has_element?(view, "[data-role=alchemist-card]")
  end
```

[Phoenix.LiveViewTest](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html)は、すばらしくawesomeです。

[has_element?/3](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#has_element?/3)等の[便利な関数](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#functions)を用いて、ヘッドレスブラウザーなしで動的コンテンツをテストできます。

[open_browser/2](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#open_browser/2)でブラウザーを開いて今どんな状態なのかを確認することもできます。

最高に快適です。

## その他のアプローチ

もちろん、世の中には他にもいろんなDOM要素を指定する[CSS セレクター](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Selectors)があります。

よく使われるものの中にはこれらがあります。

- [ID セレクター](https://developer.mozilla.org/ja/docs/Web/CSS/ID_selectors)
- [クラスセレクター](https://developer.mozilla.org/ja/docs/Web/CSS/Class_selectors)
- [ARIAロール](https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/ARIA_Techniques)
- [`data-test-id`](https://kentcdodds.com/blog/making-your-ui-tests-resilient-to-change)

`data-test-id`は`data-role`に似ているのですが、明示的にテスト用属性なので少し意味合いが違います。テスト用途に限定せずにその要素のアプリでの振る舞いについて示す方が良いのではと考えています。

[ARIAロール](https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/ARIA_Techniques)が使えるところはARIAロールを積極的に使うべきなのでしょう。Phoenixのデフォルトのフラッシュメッセージには`role="alert"`が付与されています。

```html
<main role="main" class="container my-4">
  <p class="alert alert-info" role="alert"
    phx-click="lv:clear-flash"
    phx-value-key="info"><%= live_flash(@flash, :info) %></p>

  <p class="alert alert-danger" role="alert"
    phx-click="lv:clear-flash"
    phx-value-key="error"><%= live_flash(@flash, :error) %></p>

  <%= @inner_content %>
</main>
```

ですので、テストコードでそれが利用できます。

```elixir
assert has_element?(view, "[role=alert]", ~s[No item matching "-1"])
```

しかしながら、ARIAロールは[正式な仕様](https://www.w3.org/standards/webdesign/accessibility)のようなので自由に好きなようにかけるものではありませんし、現時点では[まだ多くのロールの仕様がきまってない？](https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/ARIA_Techniques)ようです。それが難点です。

## [カスタムデータ属性](https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/data-*)を使用するメリット

- UIのスタイリング目的でHTMLタグ、CSSクラスやIDをイジっているときにテストが壊れるリスクの軽減
- DOM要素の役割を柔軟に表現できる
- 実は、属性セレクターには、いくつか便利な[パターンマッチング構文](https://developer.mozilla.org/ja/docs/Web/CSS/Attribute_selectors#syntax)があります

## [カスタムデータ属性](https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/data-*)を使用するデメリット

- 若干コードが冗長
- 一般的には[ID セレクター](https://developer.mozilla.org/ja/docs/Web/CSS/ID_selectors)の方がよく使われている

以上！

https://autoracex.connpass.com/

https://join.slack.com/t/elixirjp/shared_invite/zt-ae8m5bad-WW69GH1w4iuafm1tKNgd~w

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html

https://developer.mozilla.org/ja/docs/Web/CSS/Class_selectors

https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Selectors

https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/data-*

https://developer.mozilla.org/ja/docs/Web/CSS/Attribute_selectors#syntax

https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA

https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/ARIA_Techniques

https://kentcdodds.com/blog/making-your-ui-tests-resilient-to-change

https://online.pragmaticstudio.com/courses/liveview-pro/steps/43