Packages
Library for accessibility snapshot testing in Phoenix applications using axe-core and Playwright.
Current section
Files
Jump to
Current section
Files
lib/telemetry_capture/analyzers/accessibility_correlation.ex
defmodule Excessibility.TelemetryCapture.Analyzers.AccessibilityCorrelation do
@moduledoc """
Correlates state changes with accessibility implications.
Identifies events that may need accessibility considerations:
- Modal/dialog opening (focus management)
- Dynamic content updates (live regions)
- Error messages (ARIA announcements)
- List updates (screen reader notifications)
Not enabled by default - run with `--analyze=accessibility_correlation`.
## Output
%{
findings: [
%{
severity: :info,
message: "Event handle_event:open_modal changes 'modal_open' - modal state is accessibility-sensitive",
events: [2],
metadata: %{
field: :modal_open,
type: :modal,
recommendations: ["Focus management needed - trap focus in modal"]
}
}
],
stats: %{a11y_concerns: 1}
}
"""
@behaviour Excessibility.TelemetryCapture.Analyzer
@a11y_sensitive_patterns [
{:modal, ~w(modal dialog drawer sheet popup overlay), "Focus management needed - trap focus in modal"},
{:error, ~w(error alert warning message notification), "Use role=\"alert\" or aria-live for announcements"},
{:loading, ~w(loading spinner pending), "Announce loading state to screen readers"},
{:list, ~w(items list results data rows), "Consider aria-live=\"polite\" for list updates"}
]
def name, do: :accessibility_correlation
def default_enabled?, do: false
def analyze(%{timeline: []}, _opts) do
%{findings: [], stats: %{a11y_concerns: 0}}
end
def analyze(%{timeline: timeline}, _opts) do
findings =
Enum.flat_map(timeline, &check_event/1)
%{
findings: findings,
stats: %{a11y_concerns: length(findings)}
}
end
defp check_event(%{changes: nil}), do: []
defp check_event(%{changes: changes}) when map_size(changes) == 0, do: []
defp check_event(%{changes: changes, sequence: seq, event: event}) do
Enum.flat_map(changes, fn {field, _value} -> check_field_a11y(field, seq, event) end)
end
defp check_event(_), do: []
defp check_field_a11y(field, seq, event) do
field_str = field |> to_string() |> String.downcase()
@a11y_sensitive_patterns
|> Enum.filter(fn {_type, patterns, _rec} ->
Enum.any?(patterns, &String.contains?(field_str, &1))
end)
|> Enum.map(fn {type, _patterns, recommendation} ->
%{
severity: :info,
message: "Event #{event} changes '#{field}' - #{type} state is accessibility-sensitive",
events: [seq],
metadata: %{
field: field,
type: type,
recommendations: [recommendation]
}
}
end)
end
end