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