Finding DOM element by data-role in Phoenix LiveView testing
Tags: phoenix, liveview, css, javascript
Published: 2021-04-24 01:03:47

Today I put together one technique I find useful for writing test. That is to use [data attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*) for finding DOM elements in test. The same idea can apply to JavaScripting, but I focus mainly on [Phoenix LiveView](https://github.com/phoenixframework/phoenix_live_view) testing here because that is the one I have been working on.

## TL;DR

As of writing, I prefer to take advantage of [data attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes) over using commonly used [Id selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors). Particularly, I love the flexibility and explicitness of the custom `data-role` attribute.

Given that we have this HTML document

```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
```

In test code, we can find a given DOM element by `data-role`.

```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
```

In case we want to pinpoint a specific list item for example, we could use both `data-role` and `data-id` attributes.

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

[Phoenix.LiveViewTest](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html) is so awesome that it allows us to test our dynamic pages easily without the need of a headless browser.

## Alternative approaches

Of course, we can use [other CSS selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) as well. Here are some commonly-used selectors.

- [Id selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors)
- [Class selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors)
- [`data-test-id`](https://kentcdodds.com/blog/making-your-ui-tests-resilient-to-change)
- [ARIA roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques)

The `data-test-id` is similar to `data-role` but the difference is to me `data-test-id` is less descriptive because it is test specific as opposed to `data-role` defining semantics of the DOM element itself.

While `data-role` is just a custom [data attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*), [ARIA roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques) are [more formal standard or specifications](https://www.w3.org/standards/webdesign/accessibility) and as of writing, [details of many roles are to be determined](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques).

## Advantages of using data attributes

To me, some advantages of using data attributes are:

- minimize the risk of accidentally breaking test code when styling pages
- enable us to describe what the element is flexibly
- make the DOM self explanatory

## Disadvantages of using data attributes

I am aware of some disadvantages, but they are no big deal.

- a little verbose
- many examples out there might use DOM Id or class for finding an element

That's it!