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/registry.ex
defmodule Excessibility.TelemetryCapture.Registry do
@moduledoc """
Auto-discovers enrichers and analyzers at compile time.
Built-in enrichers are discovered from `lib/telemetry_capture/enrichers/` and must
implement the `Excessibility.TelemetryCapture.Enricher` behaviour.
Built-in analyzers are discovered from `lib/telemetry_capture/analyzers/` and must
implement the `Excessibility.TelemetryCapture.Analyzer` behaviour.
## Custom Plugins
Users can register custom enrichers and analyzers via application config:
# config/test.exs
config :excessibility,
custom_enrichers: [MyApp.CustomEnricher],
custom_analyzers: [MyApp.CustomAnalyzer]
Custom modules must implement the appropriate behaviour.
## Usage
# Get all enrichers (built-in + custom)
Registry.discover_enrichers()
# Get analyzers that run by default
Registry.get_default_analyzers()
# Get specific analyzer by name
Registry.get_analyzer(:memory)
"""
@enricher_behaviour Excessibility.TelemetryCapture.Enricher
@analyzer_behaviour Excessibility.TelemetryCapture.Analyzer
# Compile-time discovery helper functions
file_to_module = fn path, subdir ->
module_name =
path
|> Path.basename(".ex")
|> Macro.camelize()
subdir_name = Macro.camelize(subdir)
Module.concat([Excessibility.TelemetryCapture, subdir_name, module_name])
end
implements_behaviour? = fn module, behaviour ->
case Code.ensure_compiled(module) do
{:module, _} ->
behaviours = module.__info__(:attributes)[:behaviour] || []
behaviour in behaviours
{:error, _} ->
false
end
end
discover_modules = fn subdir, behaviour ->
base_path = Path.join([__DIR__, subdir])
base_path
|> Path.join("*.ex")
|> Path.wildcard()
|> Enum.map(&file_to_module.(&1, subdir))
|> Enum.filter(&implements_behaviour?.(&1, behaviour))
|> Enum.sort_by(& &1.name())
end
# Built-in plugins discovered at compile time
@builtin_enrichers discover_modules.("enrichers", @enricher_behaviour)
@builtin_analyzers discover_modules.("analyzers", @analyzer_behaviour)
@doc """
Returns all registered enrichers (built-in + custom).
Enrichers run automatically during timeline building.
Custom enrichers can be configured via `:custom_enrichers` in app config.
"""
def discover_enrichers do
custom = Application.get_env(:excessibility, :custom_enrichers, [])
valid_custom = Enum.filter(custom, &valid_enricher?/1)
merge_plugins(@builtin_enrichers, valid_custom)
end
@doc """
Returns all registered analyzers (built-in + custom).
Custom analyzers can be configured via `:custom_analyzers` in app config.
"""
def discover_analyzers do
custom = Application.get_env(:excessibility, :custom_analyzers, [])
valid_custom = Enum.filter(custom, &valid_analyzer?/1)
merge_plugins(@builtin_analyzers, valid_custom)
end
@doc """
Returns analyzers that are enabled by default.
These run unless explicitly disabled via --no-analyze flag.
"""
def get_default_analyzers do
Enum.filter(discover_analyzers(), & &1.default_enabled?())
end
@doc """
Returns all analyzers regardless of default_enabled? status.
"""
def get_all_analyzers, do: discover_analyzers()
@doc """
Finds analyzer by name.
Returns nil if not found.
"""
def get_analyzer(name) do
Enum.find(discover_analyzers(), fn analyzer ->
analyzer.name() == name
end)
end
@doc """
Finds enricher by name.
Returns nil if not found.
"""
def get_enricher(name) do
Enum.find(discover_enrichers(), fn enricher ->
enricher.name() == name
end)
end
@doc """
Resolves which enrichers are needed for a set of analyzer names.
Returns deduplicated list of enricher names.
"""
def resolve_enrichers(analyzer_names) do
alias Excessibility.TelemetryCapture.Analyzer
analyzer_names
|> Enum.map(&get_analyzer/1)
|> Enum.reject(&is_nil/1)
|> Enum.flat_map(&Analyzer.get_required_enrichers/1)
|> Enum.uniq()
end
# Validates that a module implements the Enricher behaviour
defp valid_enricher?(module) do
case Code.ensure_compiled(module) do
{:module, _} ->
behaviours = module.__info__(:attributes)[:behaviour] || []
@enricher_behaviour in behaviours
{:error, _} ->
false
end
end
# Validates that a module implements the Analyzer behaviour
defp valid_analyzer?(module) do
case Code.ensure_compiled(module) do
{:module, _} ->
behaviours = module.__info__(:attributes)[:behaviour] || []
@analyzer_behaviour in behaviours
{:error, _} ->
false
end
end
# Merges built-in and custom plugins, sorted by name, no duplicates
defp merge_plugins(builtin, custom) do
(builtin ++ custom)
|> Enum.uniq()
|> Enum.sort_by(& &1.name())
end
end