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 to with a custom Elixir wrapper also.
And it is! Here are two short snippets showing how:
defmodule MyTest do
use PhoenixTest.Case, playwright: true
test "is accessible", %{session: session} do
session
|> visit(~p"/")
|> assert_accessible()
end
defp assert_accessible(session) do
run_audit =
fn ->
Playwright.Frame.evaluate(session.frame_id, A11yAudit.JS.axe_core())
Playwright.Frame.evaluate(session.frame_id, "axe.run()")
end
A11yAudit.Assertions.assert_no_violations(run_audit.())
session
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 await
s
as used by await_audit_results()
.
defmodule Playwright.Frame do
def evaluate(frame_id, js, opts \\ []) do
params =
opts
|> Enum.into(%{expression: js, isFunction: false, arg: nil})
|> Map.update!(:arg, &Serialization.serialize/1)
[guid: frame_id, method: :evaluateExpression, params: params]
|> Connection.post()
|> unwrap_response(& &1.result.value)
|> Serialization.deserialize()
end
end
The Connection
module is explained in my previous post on using Plawright in Elixir.
The serialization is somewhat cryptic at first, but a straight-forward implementation can be taken from playwright-elixir.
And that's it!