rails_accessibility_testing 1.5.2 → 1.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/ARCHITECTURE.md +376 -1
  3. data/CHANGELOG.md +76 -0
  4. data/GUIDES/getting_started.md +40 -5
  5. data/GUIDES/system_specs_for_accessibility.md +12 -4
  6. data/README.md +52 -8
  7. data/docs_site/Gemfile.lock +89 -0
  8. data/docs_site/_config.yml +9 -0
  9. data/docs_site/_includes/header.html +1 -0
  10. data/docs_site/_layouts/default.html +754 -15
  11. data/docs_site/architecture.md +533 -0
  12. data/docs_site/index.md +2 -1
  13. data/exe/a11y_live_scanner +10 -39
  14. data/exe/a11y_static_scanner +333 -0
  15. data/lib/generators/rails_a11y/install/install_generator.rb +19 -30
  16. data/lib/generators/rails_a11y/install/templates/accessibility.yml.erb +39 -0
  17. data/lib/generators/rails_a11y/install/templates/all_pages_accessibility_spec.rb.erb +132 -45
  18. data/lib/rails_accessibility_testing/accessibility_helper.rb +131 -126
  19. data/lib/rails_accessibility_testing/checks/base_check.rb +14 -5
  20. data/lib/rails_accessibility_testing/checks/form_errors_check.rb +1 -1
  21. data/lib/rails_accessibility_testing/checks/form_labels_check.rb +6 -4
  22. data/lib/rails_accessibility_testing/checks/heading_check.rb +7 -15
  23. data/lib/rails_accessibility_testing/checks/image_alt_text_check.rb +1 -1
  24. data/lib/rails_accessibility_testing/checks/interactive_elements_check.rb +12 -8
  25. data/lib/rails_accessibility_testing/config/yaml_loader.rb +20 -0
  26. data/lib/rails_accessibility_testing/erb_extractor.rb +141 -0
  27. data/lib/rails_accessibility_testing/error_message_builder.rb +11 -6
  28. data/lib/rails_accessibility_testing/file_change_tracker.rb +95 -0
  29. data/lib/rails_accessibility_testing/line_number_finder.rb +61 -0
  30. data/lib/rails_accessibility_testing/rspec_integration.rb +74 -33
  31. data/lib/rails_accessibility_testing/shared_examples.rb +2 -0
  32. data/lib/rails_accessibility_testing/static_file_scanner.rb +80 -0
  33. data/lib/rails_accessibility_testing/static_page_adapter.rb +116 -0
  34. data/lib/rails_accessibility_testing/static_scanning.rb +61 -0
  35. data/lib/rails_accessibility_testing/version.rb +3 -1
  36. data/lib/rails_accessibility_testing/violation_converter.rb +80 -0
  37. data/lib/rails_accessibility_testing.rb +9 -1
  38. metadata +26 -2
@@ -213,16 +213,16 @@ module RailsAccessibilityTesting
213
213
  @accessibility_warnings = []
214
214
  @in_comprehensive_check = true
215
215
 
216
- # Show page being checked
216
+ # Show page being checked - simplified header
217
217
  page_path = safe_page_path || 'current page'
218
218
  page_url = safe_page_url || 'current URL'
219
+ view_file = determine_view_file(page_path, page_url, {})
220
+
219
221
  puts "\n" + "="*70
220
- puts "🔍 Scanning page for accessibility issues..."
221
- puts "="*70
222
- puts "📍 Page: #{page_path}"
223
- puts "🔗 URL: #{page_url}"
222
+ puts "🔍 Scanning: #{view_file || page_path}"
224
223
  puts "="*70
225
- puts ""
224
+ print " Running checks"
225
+ $stdout.flush
226
226
 
227
227
  # Use RuleEngine to run checks from checks/ folder
228
228
  begin
@@ -234,23 +234,26 @@ module RailsAccessibilityTesting
234
234
  path: safe_page_path
235
235
  }
236
236
 
237
- # Progress callback for real-time feedback
237
+ # Progress callback for real-time feedback - show dots only
238
238
  progress_callback = lambda do |check_number, total_checks, check_name, status, data = nil|
239
239
  case status
240
240
  when :start
241
- print " [#{check_number}/#{total_checks}] Checking #{check_name}... "
241
+ print "."
242
242
  $stdout.flush
243
243
  when :passed
244
- puts "✓"
244
+ # Already printed dot, no need to print anything
245
245
  when :found_issues
246
- puts "✗ Found #{data} issue#{'s' if data != 1}"
246
+ # Already printed dot, no need to print anything
247
247
  when :error
248
- puts "⚠ Error: #{data}"
248
+ # Already printed dot, no need to print anything
249
249
  end
250
250
  end
251
251
 
252
252
  violations = engine.check(page, context: context, progress_callback: progress_callback)
253
253
 
254
+ # Print newline after dots
255
+ puts ""
256
+
254
257
  # Convert violations to our error/warning format
255
258
  violations.each do |violation|
256
259
  element_context = violation.element_context || {}
@@ -306,33 +309,60 @@ module RailsAccessibilityTesting
306
309
  puts error_output
307
310
  $stdout.flush # Flush immediately to ensure errors are visible
308
311
 
309
- # Show warnings after errors (if any)
310
- if @accessibility_warnings.any?
312
+ # Check config for errors_only flag
313
+ errors_only = false
314
+ begin
315
+ require 'rails_accessibility_testing/config/yaml_loader'
316
+ profile = defined?(Rails) && Rails.env.test? ? :test : :development
317
+ config = RailsAccessibilityTesting::Config::YamlLoader.load(profile: profile)
318
+ summary_config = config['summary'] || {}
319
+ errors_only = summary_config.fetch('errors_only', false)
320
+ rescue StandardError
321
+ # Use default if config can't be loaded
322
+ end
323
+
324
+ # Check if we're in live scanner mode (detect by checking caller)
325
+ is_live_scanner = caller.any? { |line| line.include?('a11y_live_scanner') }
326
+
327
+ # Show warnings after errors (if any) - but skip if errors_only or live scanner
328
+ if @accessibility_warnings.any? && !is_live_scanner && !errors_only
311
329
  warning_output = format_all_warnings(@accessibility_warnings)
312
330
  puts warning_output
313
331
  $stdout.flush
314
332
  end
315
333
 
316
- # Summary - make it very clear
317
- puts "\n" + "="*70
318
- puts "📊 SUMMARY: Found #{error_count} ERROR#{'S' if error_count != 1}"
319
- puts " #{warning_count} warning#{'s' if warning_count != 1}" if warning_count > 0
320
- puts "="*70
334
+ # Compact summary
335
+ puts "📊 #{error_count} error#{'s' if error_count != 1}"
336
+ puts " #{warning_count} warning#{'s' if warning_count != 1}" if warning_count > 0 && !is_live_scanner && !errors_only
321
337
  $stdout.flush
322
338
 
323
339
  # Raise to fail the test (errors already formatted above)
324
340
  # Include error and warning counts in message so they can be extracted even if exception is caught
325
341
  raise "ACCESSIBILITY ERRORS FOUND: #{error_count} error(s), #{warning_count} warning(s) - see details above"
326
342
  elsif @accessibility_warnings.any?
327
- # Only warnings, no errors - show warnings and indicate test passed with warnings
328
- puts format_all_warnings(@accessibility_warnings)
329
- puts "\n" + "="*70
330
- puts "📊 SUMMARY: Test passed with #{@accessibility_warnings.length} warning#{'s' if @accessibility_warnings.length != 1}"
331
- puts " ✓ #{timestamp}"
332
- puts "="*70
333
- puts "\n✅ Accessibility checks completed with warnings (test passed, but please address warnings above)"
334
- puts " 📄 Page: #{page_context_info[:path] || 'current page'}"
335
- puts " 📝 View: #{page_context_info[:view_file] || 'unknown'}"
343
+ # Check config for errors_only flag
344
+ errors_only = false
345
+ begin
346
+ require 'rails_accessibility_testing/config/yaml_loader'
347
+ profile = defined?(Rails) && Rails.env.test? ? :test : :development
348
+ config = RailsAccessibilityTesting::Config::YamlLoader.load(profile: profile)
349
+ summary_config = config['summary'] || {}
350
+ errors_only = summary_config.fetch('errors_only', false)
351
+ rescue StandardError
352
+ # Use default if config can't be loaded
353
+ end
354
+
355
+ # Check if we're in live scanner mode - skip warnings in live scanner
356
+ is_live_scanner = caller.any? { |line| line.include?('a11y_live_scanner') }
357
+
358
+ if !is_live_scanner && !errors_only
359
+ # Only warnings, no errors - show warnings and indicate test passed with warnings
360
+ puts format_all_warnings(@accessibility_warnings)
361
+ puts "📊 #{@accessibility_warnings.length} warning#{'s' if @accessibility_warnings.length != 1}"
362
+ else
363
+ # Live scanner or errors_only - just show success
364
+ puts "✅ No errors"
365
+ end
336
366
  else
337
367
  # All checks passed with no errors and no warnings - show success message
338
368
  puts "📊 SUMMARY: All checks passed!"
@@ -396,141 +426,103 @@ module RailsAccessibilityTesting
396
426
  }
397
427
  end
398
428
 
399
- # Format all collected errors with summary at top and details at bottom
429
+ # Format all collected errors - compact format grouped by file
400
430
  def format_all_errors(errors)
401
431
  return "" if errors.empty?
402
432
 
403
- timestamp = format_timestamp_for_terminal
433
+ # Group errors by view file
434
+ errors_by_file = errors.group_by { |e| e[:page_context][:view_file] || e[:page_context][:path] || 'unknown' }
404
435
 
405
436
  output = []
406
437
  output << "\n" + "="*70
407
- output << "❌ ACCESSIBILITY ERRORS FOUND: #{errors.length} issue(s) #{timestamp}"
438
+ output << "❌ #{errors.length} error#{'s' if errors.length != 1} found"
408
439
  output << "="*70
409
440
  output << ""
410
- output << "📋 SUMMARY OF ISSUES:"
411
- output << ""
412
441
 
413
- # Summary list at top
414
- errors.each_with_index do |error, index|
415
- error_type = error[:error_type]
416
- page_context = error[:page_context]
417
- element_context = error[:element_context]
442
+ # Show errors grouped by file with clear spacing
443
+ errors_by_file.each_with_index do |(file_path, file_errors), file_index|
444
+ # Add spacing between files (except first)
445
+ output << "" if file_index > 0
418
446
 
419
- # Build summary line - prioritize view file
420
- summary = " #{index + 1}. #{error_type}"
447
+ # File header
448
+ output << "📝 #{file_path} (#{file_errors.length} error#{'s' if file_errors.length != 1})"
421
449
 
422
- # Add view file prominently first
423
- if page_context[:view_file]
424
- summary += "\n 📝 File: #{page_context[:view_file]}"
425
- end
426
-
427
- # Add element identifier if available
428
- if element_context[:id].present?
429
- summary += "\n 🔍 Element: [id: #{element_context[:id]}]"
430
- elsif element_context[:href].present?
431
- href_display = element_context[:href].length > 40 ? "#{element_context[:href][0..37]}..." : element_context[:href]
432
- summary += "\n 🔍 Element: [href: #{href_display}]"
433
- elsif element_context[:src].present?
434
- src_display = element_context[:src].length > 40 ? "#{element_context[:src][0..37]}..." : element_context[:src]
435
- summary += "\n 🔍 Element: [src: #{src_display}]"
436
- end
437
-
438
- # Add path as fallback if no view file
439
- if !page_context[:view_file] && page_context[:path]
440
- summary += "\n 🔗 Path: #{page_context[:path]}"
450
+ # List errors for this file
451
+ file_errors.each_with_index do |error, index|
452
+ error_type = error[:error_type]
453
+ element_context = error[:element_context]
454
+
455
+ # Compact error line
456
+ error_line = " • #{error_type}"
457
+
458
+ # Add element identifier if available (compact)
459
+ if element_context[:id].present?
460
+ error_line += " [id: #{element_context[:id]}]"
461
+ elsif element_context[:href].present?
462
+ href_display = element_context[:href].length > 30 ? "#{element_context[:href][0..27]}..." : element_context[:href]
463
+ error_line += " [href: #{href_display}]"
464
+ elsif element_context[:src].present?
465
+ src_display = element_context[:src].length > 30 ? "#{element_context[:src][0..27]}..." : element_context[:src]
466
+ error_line += " [src: #{src_display}]"
467
+ end
468
+
469
+ output << error_line
441
470
  end
442
-
443
- output << summary
444
- end
445
-
446
- output << ""
447
- output << "="*70
448
- output << "📝 DETAILED ERROR DESCRIPTIONS:"
449
- output << "="*70
450
- output << ""
451
-
452
- # Detailed descriptions at bottom
453
- errors.each_with_index do |error, index|
454
- output << "\n" + "-"*70
455
- output << "ERROR #{index + 1} of #{errors.length}:"
456
- output << "-"*70
457
- output << error[:message]
458
471
  end
459
472
 
460
473
  output << ""
461
474
  output << "="*70
462
- output << "💡 Fix all issues above, then re-run the accessibility checks"
463
- output << "="*70
464
475
  output << ""
465
476
 
466
477
  output.join("\n")
467
478
  end
468
479
 
469
- # Format all collected warnings with summary at top and details at bottom (same format as errors)
480
+ # Format all collected warnings - compact format grouped by file
470
481
  def format_all_warnings(warnings)
471
482
  return "" if warnings.empty?
472
483
 
473
- timestamp = format_timestamp_for_terminal
484
+ # Group warnings by view file
485
+ warnings_by_file = warnings.group_by { |w| w[:page_context][:view_file] || w[:page_context][:path] || 'unknown' }
474
486
 
475
487
  output = []
476
488
  output << "\n" + "="*70
477
- output << "⚠️ ACCESSIBILITY WARNINGS FOUND: #{warnings.length} warning(s) #{timestamp}"
489
+ output << "⚠️ #{warnings.length} warning#{'s' if warnings.length != 1} found"
478
490
  output << "="*70
479
491
  output << ""
480
- output << "📋 SUMMARY OF WARNINGS:"
481
- output << ""
482
492
 
483
- # Summary list at top
484
- warnings.each_with_index do |warning, index|
485
- warning_type = warning[:warning_type]
486
- page_context = warning[:page_context]
487
- element_context = warning[:element_context]
488
-
489
- # Build summary line - prioritize view file
490
- summary = " #{index + 1}. #{warning_type}"
493
+ # Show warnings grouped by file with clear spacing
494
+ warnings_by_file.each_with_index do |(file_path, file_warnings), file_index|
495
+ # Add spacing between files (except first)
496
+ output << "" if file_index > 0
491
497
 
492
- # Add view file prominently first
493
- if page_context[:view_file]
494
- summary += "\n 📝 File: #{page_context[:view_file]}"
495
- end
496
-
497
- # Add element identifier if available
498
- if element_context[:id].present?
499
- summary += "\n 🔍 Element: [id: #{element_context[:id]}]"
500
- elsif element_context[:href].present?
501
- href_display = element_context[:href].length > 40 ? "#{element_context[:href][0..37]}..." : element_context[:href]
502
- summary += "\n 🔍 Element: [href: #{href_display}]"
503
- elsif element_context[:src].present?
504
- src_display = element_context[:src].length > 40 ? "#{element_context[:src][0..37]}..." : element_context[:src]
505
- summary += "\n 🔍 Element: [src: #{src_display}]"
506
- end
498
+ # File header
499
+ output << "📝 #{file_path} (#{file_warnings.length} warning#{'s' if file_warnings.length != 1})"
507
500
 
508
- # Add path as fallback if no view file
509
- if !page_context[:view_file] && page_context[:path]
510
- summary += "\n 🔗 Path: #{page_context[:path]}"
501
+ # List warnings for this file
502
+ file_warnings.each_with_index do |warning, index|
503
+ warning_type = warning[:warning_type]
504
+ element_context = warning[:element_context]
505
+
506
+ # Compact warning line
507
+ warning_line = " • #{warning_type}"
508
+
509
+ # Add element identifier if available (compact)
510
+ if element_context[:id].present?
511
+ warning_line += " [id: #{element_context[:id]}]"
512
+ elsif element_context[:href].present?
513
+ href_display = element_context[:href].length > 30 ? "#{element_context[:href][0..27]}..." : element_context[:href]
514
+ warning_line += " [href: #{href_display}]"
515
+ elsif element_context[:src].present?
516
+ src_display = element_context[:src].length > 30 ? "#{element_context[:src][0..27]}..." : element_context[:src]
517
+ warning_line += " [src: #{src_display}]"
518
+ end
519
+
520
+ output << warning_line
511
521
  end
512
-
513
- output << summary
514
- end
515
-
516
- output << ""
517
- output << "="*70
518
- output << "📝 DETAILED WARNING DESCRIPTIONS:"
519
- output << "="*70
520
- output << ""
521
-
522
- # Detailed descriptions at bottom
523
- warnings.each_with_index do |warning, index|
524
- output << "\n" + "-"*70
525
- output << "WARNING #{index + 1} of #{warnings.length}:"
526
- output << "-"*70
527
- output << warning[:message]
528
522
  end
529
523
 
530
524
  output << ""
531
525
  output << "="*70
532
- output << "💡 Consider addressing these warnings to improve accessibility"
533
- output << "="*70
534
526
  output << ""
535
527
 
536
528
  output.join("\n")
@@ -542,10 +534,23 @@ module RailsAccessibilityTesting
542
534
  # @param page_context [Hash] Context about the page being tested
543
535
  # @return [String] Formatted error message
544
536
  def build_error_message(error_type, element_context, page_context)
537
+ # Check config for show_fixes setting
538
+ show_fixes = true
539
+ begin
540
+ require 'rails_accessibility_testing/config/yaml_loader'
541
+ profile = defined?(Rails) && Rails.env.test? ? :test : :development
542
+ config = RailsAccessibilityTesting::Config::YamlLoader.load(profile: profile)
543
+ summary_config = config['summary'] || {}
544
+ show_fixes = summary_config.fetch('show_fixes', true)
545
+ rescue StandardError
546
+ # Use default if config can't be loaded
547
+ end
548
+
545
549
  RailsAccessibilityTesting::ErrorMessageBuilder.build(
546
550
  error_type: error_type,
547
551
  element_context: element_context,
548
- page_context: page_context
552
+ page_context: page_context,
553
+ show_fixes: show_fixes
549
554
  )
550
555
  end
551
556
 
@@ -80,11 +80,20 @@ module RailsAccessibilityTesting
80
80
  # @param element_context [Hash] Optional element context to help find partials
81
81
  # @return [Hash]
82
82
  def page_context(element_context = nil)
83
- {
84
- url: safe_page_url,
85
- path: safe_page_path,
86
- view_file: determine_view_file(element_context)
87
- }
83
+ # For static scanning, use view_file from context if available
84
+ if @context && @context[:view_file]
85
+ {
86
+ url: nil,
87
+ path: nil,
88
+ view_file: @context[:view_file]
89
+ }
90
+ else
91
+ {
92
+ url: safe_page_url,
93
+ path: safe_page_path,
94
+ view_file: determine_view_file(element_context)
95
+ }
96
+ end
88
97
  end
89
98
 
90
99
  # Get element context from Capybara element
@@ -17,7 +17,7 @@ module RailsAccessibilityTesting
17
17
 
18
18
  page.all('.field_with_errors input, .field_with_errors textarea, .field_with_errors select, .is-invalid, [aria-invalid="true"]').each do |input|
19
19
  id = input[:id]
20
- next if id.blank?
20
+ next if id.nil? || id.to_s.strip.empty?
21
21
 
22
22
  has_error_message = page.has_css?("[aria-describedby*='#{id}'], .field_with_errors label[for='#{id}'] + .error, .field_with_errors label[for='#{id}'] + .invalid-feedback", wait: false)
23
23
 
@@ -18,13 +18,15 @@ module RailsAccessibilityTesting
18
18
 
19
19
  page.all('input[type="text"], input[type="email"], input[type="password"], input[type="number"], input[type="tel"], input[type="url"], input[type="search"], input[type="date"], input[type="time"], input[type="datetime-local"], textarea, select').each do |input|
20
20
  id = input[:id]
21
- next if id.blank?
21
+ next if id.nil? || id.to_s.strip.empty?
22
22
 
23
23
  has_label = page.has_css?("label[for='#{id}']", wait: false)
24
- aria_label = input[:"aria-label"].present?
25
- aria_labelledby = input[:"aria-labelledby"].present?
24
+ aria_label = input[:"aria-label"]
25
+ aria_labelledby = input[:"aria-labelledby"]
26
+ has_aria_label = aria_label && !aria_label.to_s.strip.empty?
27
+ has_aria_labelledby = aria_labelledby && !aria_labelledby.to_s.strip.empty?
26
28
 
27
- unless has_label || aria_label || aria_labelledby
29
+ unless has_label || has_aria_label || has_aria_labelledby
28
30
  element_ctx = element_context(input)
29
31
  element_ctx[:input_type] = input[:type] || input.tag_name
30
32
 
@@ -95,22 +95,14 @@ module RailsAccessibilityTesting
95
95
  end
96
96
 
97
97
  # Check 5: Headings with only images (no alt text or empty alt) (WCAG 4.1.2)
98
+ # Note: For static scanning, we check if heading text is empty
99
+ # Full image checking within headings requires more complex DOM traversal
100
+ # This is a simplified check - full implementation would require xpath queries
98
101
  headings.each do |heading|
99
- # Check if heading contains only images
100
- images = heading.all('img', visible: true)
101
- if images.any? && heading.text.strip.empty?
102
- # Check if all images have alt text
103
- images_without_alt = images.select { |img| img[:alt].nil? || img[:alt].strip.empty? }
104
- if images_without_alt.any?
105
- element_ctx = element_context(heading)
106
- violations << violation(
107
- message: "Heading contains images without alt text - headings must have accessible text",
108
- element_context: element_ctx,
109
- wcag_reference: "4.1.2",
110
- remediation: "Add alt text to images in the heading, or add visible text alongside the images:\n\n<h1><img src=\"image.jpg\" alt=\"Descriptive text\">Heading Text</h1>"
111
- )
112
- end
113
- end
102
+ heading_text = heading.text.strip
103
+ # If heading has no text, it might be image-only (but we can't easily check images within heading in static mode)
104
+ # This check is primarily for dynamic scanning where we can traverse the DOM
105
+ # For static scanning, we rely on the empty heading check above
114
106
  end
115
107
 
116
108
  # Check 6: Headings used only for styling (WCAG 2.4.6)
@@ -31,7 +31,7 @@ module RailsAccessibilityTesting
31
31
  wcag_reference: "1.1.1",
32
32
  remediation: generate_remediation(element_ctx)
33
33
  )
34
- elsif alt_value.blank? && has_alt_attribute
34
+ elsif (alt_value.nil? || alt_value.to_s.strip.empty?) && has_alt_attribute
35
35
  # Image has alt attribute but it's empty - warn about this
36
36
  # Empty alt is valid for decorative images, but we should check if it's actually decorative
37
37
  element_ctx = element_context(img)
@@ -18,22 +18,26 @@ module RailsAccessibilityTesting
18
18
  page.all('button, a[href], [role="button"], [role="link"]').each do |element|
19
19
  next unless element.visible?
20
20
 
21
- text = element.text.strip
21
+ text = element.text.to_s.strip
22
22
  aria_label = element[:"aria-label"]
23
23
  aria_labelledby = element[:"aria-labelledby"]
24
24
  title = element[:title]
25
25
 
26
26
  # Check if element contains an image with alt text (common pattern for logo links)
27
27
  has_image_with_alt = false
28
- if text.blank?
29
- images = element.all('img', visible: :all)
30
- has_image_with_alt = images.any? do |img|
31
- alt = img[:alt]
32
- alt.present? && !alt.strip.empty?
33
- end
28
+ if text.empty?
29
+ # For static scanning, we can't easily check images within elements
30
+ # This check works better in dynamic scanning with Capybara
31
+ # For now, skip image checking in static mode
32
+ has_image_with_alt = false
34
33
  end
35
34
 
36
- if text.blank? && aria_label.blank? && aria_labelledby.blank? && title.blank? && !has_image_with_alt
35
+ text_empty = text.empty?
36
+ aria_label_empty = aria_label.nil? || aria_label.to_s.strip.empty?
37
+ aria_labelledby_empty = aria_labelledby.nil? || aria_labelledby.to_s.strip.empty?
38
+ title_empty = title.nil? || title.to_s.strip.empty?
39
+
40
+ if text_empty && aria_label_empty && aria_labelledby_empty && title_empty && !has_image_with_alt
37
41
  element_ctx = element_context(element)
38
42
  tag = element.tag_name
39
43
 
@@ -68,8 +68,21 @@ module RailsAccessibilityTesting
68
68
 
69
69
  merged_checks = checks.merge(profile_checks)
70
70
 
71
+ # Deep merge summary configuration
72
+ base_summary = base_config['summary'] || {}
73
+ profile_summary = profile_config['summary'] || {}
74
+ merged_summary = base_summary.merge(profile_summary)
75
+
76
+ # Merge static_scanner config
77
+ base_static_scanner = base_config['static_scanner'] || {}
78
+ profile_static_scanner = profile_config['static_scanner'] || {}
79
+ merged_static_scanner = base_static_scanner.merge(profile_static_scanner)
80
+
71
81
  base_config.merge(
72
82
  'checks' => merged_checks,
83
+ 'summary' => merged_summary,
84
+ 'scan_strategy' => profile_config['scan_strategy'] || base_config['scan_strategy'] || 'paths',
85
+ 'static_scanner' => merged_static_scanner,
73
86
  'profile' => profile.to_s,
74
87
  'ignored_rules' => parse_ignored_rules(parsed, profile)
75
88
  )
@@ -105,6 +118,13 @@ module RailsAccessibilityTesting
105
118
  {
106
119
  'wcag_level' => 'AA',
107
120
  'checks' => default_checks,
121
+ 'summary' => {
122
+ 'show_summary' => true,
123
+ 'errors_only' => false,
124
+ 'show_fixes' => true,
125
+ 'ignore_warnings' => false
126
+ },
127
+ 'scan_strategy' => 'paths',
108
128
  'ignored_rules' => [],
109
129
  'profile' => 'test'
110
130
  }