Skip to content

Commit 9f95ae6

Browse files
committed
Integrate with Active Support instrumentation
Capybara Accessibility Audit integrates with [Active Support's instrumentation][] through publishing [ActiveSupport::Notifications][]. `audit.capybara_accessibility_audit` notification --- Subscribe to `audit.capybara_accessibility_audit` notifications emitted when an accessibility audit is automatically conducted. In addition to the metadata provided by default (like `name`, `duration`, and `allocations`, etc.), the `payload` includes additional information: | Payload | Type | Description | | ------------- | ---------------------------------- | ----------- | | method | Symbol | The Capybara method that triggered the audit | options | [ActiveSupport::OrderedOptions][] | The audit's configuration | test | [ActionDispatch::SystemTestCase][] | The test case that triggered the audit > [!NOTE] > The `audit.capybara_accessibility_audit` notifications are only published when > an audit is conducted automatically. No `audit.capybara_accessibility_audit` > notifications will be published when `assert_no_accessibility_violations` is > invoked directly. [Active Support's instrumentation]: https://guides.rubyonrails.org/active_support_instrumentation.html [ActiveSupport::Notifications]: https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html [ActiveSupport::OrderedOptions]: https://api.rubyonrails.org/classes/ActiveSupport/OrderedOptions.html [ActionDispatch::SystemTestCase]: https://api.rubyonrails.org/classes/ActionDispatch/SystemTestCase.html
1 parent 51c1f0a commit 9f95ae6

File tree

9 files changed

+104
-5
lines changed

9 files changed

+104
-5
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ end
1818
gem "rails", rails_constraint
1919
gem "rspec-rails"
2020

21+
gem "minitest", "< 6"
2122
gem "puma"
2223
gem "standard", "~> 1.12"
2324
gem "capybara-playwright-driver", require: "capybara/playwright"

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,35 @@ end
6060
[aXe]: https://www.deque.com/axe/
6161
[axe-core-rspec]: https://github.com/dequelabs/axe-core-gems/blob/develop/packages/axe-core-rspec/README.md#matcher
6262

63+
## Active Support instrumentation
64+
65+
Capybara Accessibility Audit integrates with [Active Support's
66+
instrumentation][] through publishing [ActiveSupport::Notifications][].
67+
68+
### `audit.capybara_accessibility_audit` notification
69+
70+
Subscribe to `audit.capybara_accessibility_audit` notifications emitted when an
71+
accessibility audit is automatically conducted. In addition to the
72+
metadata provided by default (like `name`, `duration`, and `allocations`, etc.),
73+
the `payload` includes additional information:
74+
75+
| Payload | Type | Description |
76+
| ------------- | ---------------------------------- | ----------- |
77+
| method | Symbol | The Capybara method that triggered the audit
78+
| options | [ActiveSupport::OrderedOptions][] | The audit's configuration
79+
| test | [ActionDispatch::SystemTestCase][] | The test case that triggered the audit
80+
81+
> [!NOTE]
82+
> The `audit.capybara_accessibility_audit` notifications are only published when
83+
> an audit is conducted automatically. No `audit.capybara_accessibility_audit`
84+
> notifications will be published when `assert_no_accessibility_violations` is
85+
> invoked directly.
86+
87+
[Active Support's instrumentation]: https://guides.rubyonrails.org/active_support_instrumentation.html
88+
[ActiveSupport::Notifications]: https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html
89+
[ActiveSupport::OrderedOptions]: https://api.rubyonrails.org/classes/ActiveSupport/OrderedOptions.html
90+
[ActionDispatch::SystemTestCase]: https://api.rubyonrails.org/classes/ActionDispatch/SystemTestCase.html
91+
6392
## Frequently Asked Questions
6493

6594
My application already exists, automated accessibility audits are uncovering violations left and right. Do I have to fix them all at once?

lib/capybara_accessibility_audit/adapter.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ def initialize(test)
88

99
def audit!(method)
1010
if accessibility_audit_enabled && method.in?(accessibility_audit_after_methods) && javascript_enabled?
11-
assert_no_accessibility_violations(**accessibility_audit_options)
11+
ActiveSupport::Notifications.instrument "audit.capybara_accessibility_audit" do |payload|
12+
payload[:method] = method
13+
payload[:options] = accessibility_audit_options
14+
payload[:test] = @test
15+
16+
assert_no_accessibility_violations(**accessibility_audit_options)
17+
end
1218
end
1319
end
1420

lib/capybara_accessibility_audit/audit_system_test_extensions.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ def skip_accessibility_violations(value, &block)
8787
accessibility_audit_options.skipping = skipping
8888
end
8989

90-
def assert_no_accessibility_violations(auditor: @accessibility_audit_auditor, **options)
90+
def assert_no_accessibility_violations(auditor: accessibility_audit_options.auditor, **options)
91+
options = options.with_defaults(accessibility_audit_options.except(:auditor))
9192
options.assert_valid_keys(
9293
:according_to,
9394
:checking,

lib/capybara_accessibility_audit/engine.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class Engine < ::Rails::Engine
2424
auditor_class = app.config.capybara_accessibility_audit.auditor
2525
reporter_class = app.config.capybara_accessibility_audit.reporter
2626

27-
@accessibility_audit_auditor = auditor_class.new(page, reporter_class.new(self))
27+
accessibility_audit_options.auditor = auditor_class.new(page, reporter_class.new(self))
2828
end
2929
end
3030
end
@@ -45,7 +45,7 @@ class Engine < ::Rails::Engine
4545
auditor_class = app.config.capybara_accessibility_audit.auditor
4646
reporter_class = app.config.capybara_accessibility_audit.reporter
4747

48-
@accessibility_audit_auditor = auditor_class.new(page, reporter_class.new(self))
48+
accessibility_audit_options.auditor = auditor_class.new(page, reporter_class.new(self))
4949
end
5050

5151
config.before(type: :system, &configure)

test/application_system_test_case.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Capybara.javascript_driver = ENV.fetch("DRIVER", "selenium_chrome_headless").to_sym
44

55
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
6-
driven_by Capybara.javascript_driver, screen_size: [1400, 1400], options: {js_errors: true}
6+
driven_by Capybara.javascript_driver, screen_size: [1400, 1400], options: {js_errors: true, timeout: 10}
77

88
def assert_rule_violation(rule = nil, with: rule, without: nil, &block)
99
exception = assert_raises(Minitest::Assertion, &block)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
module ActiveSupport::Testing
2+
unless defined?(NotificationAssertions)
3+
module NotificationAssertions
4+
def assert_notification(pattern, payload = nil, &block)
5+
notifications = capture_notifications(pattern, &block)
6+
assert_not_empty(notifications, "No #{pattern} notifications were found")
7+
8+
return notifications.first if payload.nil?
9+
10+
notification = notifications.find { |notification| notification.payload.slice(*payload.keys) == payload }
11+
assert_not_nil(notification, "No #{pattern} notification with payload #{payload} was found")
12+
13+
notification
14+
end
15+
16+
def assert_notifications_count(pattern, count, &block)
17+
actual_count = capture_notifications(pattern, &block).count
18+
assert_equal(count, actual_count, "Expected #{count} instead of #{actual_count} notifications for #{pattern}")
19+
end
20+
21+
def assert_no_notifications(pattern = nil, &block)
22+
notifications = capture_notifications(pattern, &block)
23+
error_message = if pattern
24+
"Expected no notifications for #{pattern} but found #{notifications.size}"
25+
else
26+
"Expected no notifications but found #{notifications.size}"
27+
end
28+
assert_empty(notifications, error_message)
29+
end
30+
31+
def capture_notifications(pattern = nil, &block)
32+
notifications = []
33+
ActiveSupport::Notifications.subscribed(->(n) { notifications << n }, pattern, &block)
34+
notifications
35+
end
36+
end
37+
38+
ActiveSupport.on_load(:active_support_test_case) { include NotificationAssertions }
39+
end
40+
end

test/system/audit_assertions_test.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@ class AuditAssertionsTest < ApplicationSystemTestCase
8080
accept_prompt("Hello?") { click_button "Open prompt" }
8181
dismiss_prompt("Hello?") { click_button "Open prompt" }
8282
end
83+
84+
test "publishes audit.capybara_accessibility_audit notifications" do
85+
visit_audit, click_on_audit = capture_notifications "audit.capybara_accessibility_audit" do
86+
visit violations_path
87+
88+
assert_rule_violation "label: Form elements must have labels" do
89+
click_on "Violate rule: label"
90+
end
91+
end
92+
93+
assert_kind_of CapybaraAccessibilityAudit::AxeAuditor, visit_audit.payload.dig(:options, :auditor)
94+
assert_equal self, click_on_audit.payload[:test]
95+
assert_equal :visit, visit_audit.payload[:method]
96+
assert_equal accessibility_audit_options, visit_audit.payload[:options]
97+
98+
assert_kind_of CapybaraAccessibilityAudit::AxeAuditor, click_on_audit.payload.dig(:options, :auditor)
99+
assert_equal self, click_on_audit.payload[:test]
100+
assert_equal :click_on, click_on_audit.payload[:method]
101+
assert_equal accessibility_audit_options, click_on_audit.payload[:options]
102+
end
83103
end
84104

85105
class DisablingAuditAssertionsTest < ApplicationSystemTestCase

test/test_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33

44
require_relative "../test/dummy/config/environment"
55
require "rails/test_help"
6+
7+
Rails.root.glob("../../test/support/**/*.rb").each { |file| require file }

0 commit comments

Comments
 (0)