Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ group :development, :test do
gem 'pry'
gem 'pundit-matchers', '~> 3.1', '>= 3.1.2'
gem 'brakeman', require: false
gem 'rails_accessibility_testing', '~> 1.6'
gem 'axe-core-capybara', '~> 4.0'
end

group :development, :staging do
Expand Down
28 changes: 28 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ GEM
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
axe-core-api (4.11.1)
dumb_delegator
ostruct
virtus
axe-core-capybara (4.11.1)
axe-core-api (= 4.11.1)
dumb_delegator
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
base64 (0.3.0)
bcrypt (3.1.20)
benchmark (0.4.1)
Expand All @@ -106,6 +117,8 @@ GEM
childprocess (5.1.0)
logger (~> 1.5)
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
concurrent-ruby (1.3.5)
connection_pool (2.5.4)
crass (1.0.6)
Expand All @@ -115,6 +128,8 @@ GEM
debug (1.9.2)
irb (~> 1.10)
reline (>= 0.3.8)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
devise (4.9.4)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
Expand All @@ -127,6 +142,7 @@ GEM
dotenv (= 3.2.0)
railties (>= 6.1)
drb (2.2.3)
dumb_delegator (1.1.0)
erubi (1.13.1)
factory_bot (6.5.0)
activesupport (>= 5.0.0)
Expand All @@ -146,6 +162,7 @@ GEM
hashie (5.0.0)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
image_processing (1.13.0)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
Expand Down Expand Up @@ -235,6 +252,7 @@ GEM
omniauth (~> 2.1)
ruby-saml (~> 1.18)
orm_adapter (0.5.0)
ostruct (0.6.3)
pg (1.5.9)
pry (0.15.0)
coderay (~> 1.1)
Expand Down Expand Up @@ -285,6 +303,9 @@ GEM
rails-html-sanitizer (1.6.2)
loofah (~> 2.21)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails_accessibility_testing (1.6.0)
axe-core-capybara (~> 4.0)
nokogiri (>= 1.10)
railties (7.1.5.2)
actionpack (= 7.1.5.2)
activesupport (= 7.1.5.2)
Expand Down Expand Up @@ -350,12 +371,17 @@ GEM
railties (>= 6.0.0)
stringio (3.1.2)
thor (1.4.0)
thread_safe (0.3.6)
timeout (0.4.3)
turbo-rails (2.0.11)
actionpack (>= 6.0.0)
railties (>= 6.0.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
virtus (2.0.0)
axiom-types (~> 0.1)
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.1)
Expand Down Expand Up @@ -385,6 +411,7 @@ PLATFORMS

DEPENDENCIES
annotate (~> 3.2)
axe-core-capybara (~> 4.0)
bootsnap
brakeman
capybara (~> 3.40)
Expand All @@ -409,6 +436,7 @@ DEPENDENCIES
pundit
pundit-matchers (~> 3.1, >= 3.1.2)
rails (~> 7.1.3, >= 7.1.3.2)
rails_accessibility_testing (~> 1.6)
redis (>= 4.0.1)
repost
rspec-rails
Expand Down
113 changes: 113 additions & 0 deletions config/accessibility.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Rails A11y Configuration
#
# This file configures accessibility checks for your Rails application.
# See https://rayraycodes.github.io/rails-accessibility-testing/ for full documentation.

# Global enable/disable flag for all accessibility checks
# Set to false to completely disable all accessibility checks (manual and automatic)
# When false, check_comprehensive_accessibility and automatic checks will be skipped
# Default: false
# (Set to false to allow other RSpec tests to pass in GitHub Actions CI even if accessibility tests fail.
# When true, any failing accessibility tests will cause the entire CI pipeline to fail.)
# Set to true to run accessibility checks manually: rspec spec/accessibility/all_pages_accessibility_spec.rb
accessibility_enabled: false

# WCAG compliance level (A, AA, AAA)
wcag_level: AA

# Summary configuration
# Control how accessibility test summaries are displayed
summary:
# Show summary at end of test suite (true/false)
show_summary: true

# Show only errors in summary, hide warnings (true/false)
errors_only: false

# Show fix suggestions in error messages (true/false)
# When false, only shows error type and location
show_fixes: true

# Ignore warnings completely - only show errors (true/false)
# When true, warnings are filtered out and not displayed or counted
ignore_warnings: false

# Scanning strategy
# 'paths' - Scan by visiting routes/paths (default)
# 'view_files' - Scan by finding view files and visiting their routes
scan_strategy: 'view_files'

# Static scanner configuration
# Controls behavior of the static file scanner (a11y_static_scanner)
static_scanner:
# Only scan files that have changed since last scan (true/false)
# When true, uses file modification times to track changes
# When false, scans all files on every run
scan_changed_only: true

# Check interval in seconds when running continuously
# Scanner checks for file changes every N seconds
check_interval: 3

# Force full scan on startup (true/false)
# When true, scans all files on first run regardless of changes
# When false, only scans changed files from the start
full_scan_on_startup: true

# System specs configuration
# Controls behavior of accessibility checks in RSpec system specs
system_specs:
# Automatically run accessibility checks after each system spec (true/false)
# When true, checks run automatically after each `visit` in system specs
# When false, checks only run when explicitly called (e.g., check_comprehensive_accessibility)
# Default: false (set to true if you want automatic checks)
# Can be overridden per-profile (see profile sections below)
auto_run: false

# Global check configuration
# Set to false to disable a check globally
checks:
form_labels: true
image_alt_text: true
interactive_elements: true
heading_hierarchy: true
keyboard_accessibility: true
aria_landmarks: true
form_errors: true
table_structure: true
duplicate_ids: true
skip_links: true
color_contrast: false # Disabled by default (requires JS evaluation)

# Profile-specific configurations
# Override global settings for different environments

development:
checks:
color_contrast: false # Skip in dev for speed
# Add other dev-specific overrides here
# system_specs:
# auto_run: true # Override global system_specs.auto_run for development

test:
checks:
# Test environment uses global settings by default
# Add test-specific overrides here
# system_specs:
# auto_run: true # Override global system_specs.auto_run for test

ci:
checks:
color_contrast: true # Full checks in CI
# Add CI-specific overrides here
# system_specs:
# auto_run: true # Override global system_specs.auto_run for CI

# Ignored rules with reasons
# Use this to temporarily ignore specific rules while fixing issues
# Always include a reason and plan to fix
ignored_rules:
# - rule: form_labels
# reason: "Legacy form, scheduled for refactor in Q2"
# comment: "Will be fixed in PR #123"

26 changes: 26 additions & 0 deletions config/initializers/rails_a11y.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

# Rails A11y Configuration
#
# Configure accessibility testing behavior for your Rails application.
#
# @see https://rayraycodes.github.io/rails-accessibility-testing/ for documentation

# Only configure if the gem is available (not in production)
if defined?(RailsAccessibilityTesting)
RailsAccessibilityTesting.configure do |config|
# Automatically run checks after system specs
# Set to false to disable automatic checks
config.auto_run_checks = false

# Logger for accessibility check output
# Set to nil to use default logger
# config.logger = Rails.logger

# Configuration file path (relative to Rails.root)
# config.config_path = 'config/accessibility.yml'

# Default profile to use (development, test, ci)
# config.default_profile = :test
end
end
124 changes: 124 additions & 0 deletions spec/accessibility/all_pages_accessibility_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
require 'rails_helper'

RSpec.describe 'All Pages Accessibility', type: :accessibility do
# Test all view files for accessibility using static file scanning
# Generated automatically by rails_a11y:install generator

# Helper method to get all view files (non-partials)
def self.get_all_view_files
return [] unless defined?(Rails) && Rails.root

view_dir = Rails.root.join('app', 'views')
return [] unless File.directory?(view_dir)

extensions = %w[erb haml slim]
view_files = []

extensions.each do |ext|
# Find all HTML view files (exclude partials that start with _)
pattern = File.join(view_dir, '**', "*.html.#{ext}")
Dir.glob(pattern).each do |file|
# Skip partials (files starting with _)
next if File.basename(file).start_with?('_')
# Skip layout files
next if file.include?('/layouts/')
view_files << file
end
end

view_files.sort
end

# Format errors with file locations and line numbers
def format_static_errors(errors, warnings)
return "" if errors.empty? && warnings.empty?

output = []

# Group errors and warnings by file
errors_by_file = errors.group_by { |e| e[:file] }
warnings_by_file = warnings.group_by { |w| w[:file] }

# Show errors first
if errors.any?
output << "\n" + "="*70
output << "❌ #{errors.length} error#{'s' if errors.length != 1} found"
output << ""
format_issues_by_file(errors_by_file, output, 'error')
output << ""
output << "="*70
end

# Show warnings if any
if warnings.any?
output << "\n" + "="*70
output << "⚠️ #{warnings.length} warning#{'s' if warnings.length != 1} found"
output << "="*70
output << ""

format_issues_by_file(warnings_by_file, output, 'warning')

output << ""
output << "="*70
end

output.join("\n")
end

def format_issues_by_file(issues_by_file, output, issue_type)
issues_by_file.each_with_index do |(file_path, file_issues), file_index|
output << "" if file_index > 0

output << "📝 #{file_path} (#{file_issues.length} #{issue_type}#{'s' if file_issues.length != 1})"

file_issues.each do |issue|
issue_line = " • #{issue[:type]}"

# Add line number if available
if issue[:line]
issue_line += " [Line #{issue[:line]}]"
end

# Add element identifier
if issue[:element][:id].present?
issue_line += " [id: #{issue[:element][:id]}]"
elsif issue[:element][:href].present?
href_display = issue[:element][:href].length > 30 ? "#{issue[:element][:href][0..27]}..." : issue[:element][:href]
issue_line += " [href: #{href_display}]"
elsif issue[:element][:src].present?
src_display = issue[:element][:src].length > 30 ? "#{issue[:element][:src][0..27]}..." : issue[:element][:src]
issue_line += " [src: #{src_display}]"
end

output << issue_line
end
end
end

# Scan all view files statically
view_files = get_all_view_files

if view_files.empty?
it "no view files found to scan" do
skip "No view files found in app/views"
end
else
view_files.each do |view_file|
it "scans #{view_file} for accessibility issues" do
require 'rails_accessibility_testing/static_file_scanner'

scanner = RailsAccessibilityTesting::StaticFileScanner.new(view_file)
result = scanner.scan

errors = result[:errors] || []
warnings = result[:warnings] || []

if errors.any? || warnings.any?
expect(errors).to be_empty, format_static_errors(errors, warnings)
else
puts "\n✅ #{view_file}: No errors found"
end
end
end
end
end
1 change: 1 addition & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
require 'rails_accessibility_testing'
# Add additional requires below this line. Rails is not loaded until this point!

# Requires supporting ruby files with custom matchers and macros, etc, in
Expand Down