Swing the A11y Axe as an Elixir Playwright

Accessibility testing without Wallaby.

While working on the PhoenixTest Playwright driver, I was worried I might lose a11y_audit along the way.

This wonderful library wraps axe-core, a JavaScript accessibility testing engine, for ExUnit. Usage with Wallaby and Hound is documented. And then there is the ominous sounding section "For other environments":

get_audit_result =
  fn ->
    execute_script(A11yAudit.JS.axe_core())
    axe_result_map = execute_script(A11yAudit.JS.await_audit_results())
    A11yAudit.Results.from_json(axe_result_map)
  end

A11yAudit.Assertions.assert_no_violations(get_audit_result.())

And now for Playwright

So mainly, we need the ability to execute some JavaScript in the browser. Since Playwright exposes this functionality via the SDKs, it should be possible with a custom Elixir wrapper also.

And it is! Here are two short snippets showing how:

defmodule MyTest do
  use PhoenixTest.Playwright.Case, async: true

  alias PhoenixTest.Playwright.Frame

  test "is accessible", %{conn: conn} do
    conn
    |> visit("/")
    |> unwrap(&assert_a11y/1)
  end

  defp assert_a11y(%{frame_id: frame_id}) do
    Frame.evaluate(frame_id, A11yAudit.JS.axe_core())

    frame_id
    |> Frame.evaluate("axe.run()")
    |> A11yAudit.Results.from_json()
    |> A11yAudit.Assertions.assert_no_violations()
  end
end

Notice that we use axe.run() instead of A11yAudit.JS.await_audit_results(). Playwright automatically awaits the Promise returned by axe.run(), but doesn't support top-level awaits as used by await_audit_results().

And that's it!