defmodule Mix.Tasks.Excessibility do
@shortdoc "Runs pally against generated snapshots"
@moduledoc "Library to aid in testing your application for WCAG compliance automatically using Pa11y and Wallaby."
use Mix.Task
@requirements ["app.config"]
@assets_task Application.compile_env(:excessibility, :assets_task, "assets.deploy")
@pally_path Application.compile_env(
:excessibility,
:pa11y_path,
"assets/node_modules/pa11y/bin/pa11y.js"
)
@output_path Application.compile_env(
:excessibility,
:excessibility_output_path,
"test/excessibility"
)
@snapshots_path "#{@output_path}/html_snapshots"
@ex_assets_path "#{@output_path}/assets"
@max_failures Application.compile_env(
:excessibility,
:max_failures,
0
)
@max_percentage_failures Application.compile_env(
:excessibility,
:max_percentage_failures,
0
)
@impl Mix.Task
def run(_args) do
Mix.Task.run(@assets_task)
File.mkdir_p("#{@ex_assets_path}/css/")
File.mkdir_p("#{@ex_assets_path}/js/")
File.mkdir_p("#{@snapshots_path}/")
spinner_pid = spawn_link(fn -> spinner() end)
@snapshots_path
|> File.ls!()
|> filter_dirs()
|> run_pa11y()
|> print_results()
|> exit_status(@max_failures, @max_percentage_failures)
Process.exit(spinner_pid, :normal)
end
defp filter_dirs(list_of_files) do
Enum.filter(list_of_files, fn file -> String.contains?(file, ".html") end)
end
defp run_pa11y(list_of_files) do
list_of_files
|> Enum.map(fn file ->
file_path = "#{File.cwd!()}/" <> "#{@snapshots_path}/" <> file
pally = "#{File.cwd!()}/#{@pally_path}"
System.cmd("sh", ["-c", "#{pally} #{file_path}"])
end)
|> Enum.sort(fn {_res_one, status_one}, {_res_two, status_two} ->
status_one < status_two
end)
end
defp exit_status(results, max_failures, max_percentage_failures) do
total_tests = length(results)
failures = Enum.filter(results, fn {_, status} -> status != 0 end)
num_failures = length(failures)
failure_percentage = num_failures / total_tests * 100
if num_failures <= max_failures || failure_percentage <= max_percentage_failures do
System.stop(0)
else
if Mix.env() !== :test do
System.halt(1)
end
end
end
defp print_results(results) do
Enum.each(results, fn {result, _status} -> IO.puts(result) end)
results
end
defp spinner do
spinner_chars = ["|", "/", "-", "\\"]
spinner_index = 0
IO.write("Testing for a11y violations, please wait:")
IO.puts("\e[B")
loop(spinner_chars, spinner_index)
end
defp loop(spinner_chars, spinner_index) do
IO.ANSI.clear_line()
IO.write("#{Enum.at(spinner_chars, spinner_index)}")
# Adjust the sleep duration as needed
:timer.sleep(300)
clear_line()
next_index =
if spinner_index == 3 do
0
else
spinner_index + 1
end
loop(spinner_chars, next_index)
end
defp clear_line do
IO.write("\e[2K")
IO.write("\r")
end
end