rails_accessibility_testing 1.2.0 ā 1.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -0
- data/README.md +8 -2
- data/lib/rails_accessibility_testing/accessibility_helper.rb +6 -0
- data/lib/rails_accessibility_testing/checks/image_alt_text_check.rb +15 -0
- data/lib/rails_accessibility_testing/cli/command.rb +104 -19
- data/lib/rails_accessibility_testing/config/yaml_loader.rb +3 -1
- data/lib/rails_accessibility_testing/configuration.rb +3 -1
- data/lib/rails_accessibility_testing/engine/rule_engine.rb +3 -1
- data/lib/rails_accessibility_testing/rspec_integration.rb +6 -0
- data/lib/rails_accessibility_testing/version.rb +1 -1
- data/lib/rails_accessibility_testing.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 916b677bef5f630731d65df12bdd430949d70876ec0c5a9c047e15353084ae7b
|
|
4
|
+
data.tar.gz: 4960a239874be87bc96a74ba7804cfc3213ae0ea62a4b9a367d437fed337e0d0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f0d0e645d4d2929da2b0d2461459510759dab172192e7564be8dd2e57f2771ff86144f4fb8ece7fcbd04351ba2cc0ee08964ea7a0e07d65485171718e7608a5c
|
|
7
|
+
data.tar.gz: d8678b98bd3978104dd2158ff04f3494ff291d690b75ecee4bc29121eb76d283fd378a1cb6f763f14904060a1e8cc9e39288dcc4ca7f8ab8420253fcbc1273e1
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2024-12-XX
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- CLI reports now use ErrorMessageBuilder for detailed, formatted error messages
|
|
12
|
+
- CLI reports include comprehensive remediation steps, element details, and WCAG references
|
|
13
|
+
- Better empty alt text detection (checks both Capybara attributes and JavaScript getAttribute)
|
|
14
|
+
- Improved server port detection with better error handling and timeout management
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- CLI default profile changed to `:development` for faster checks (color contrast disabled by default)
|
|
18
|
+
- Improved server wait logic with longer retry times (up to 20 seconds) and better port re-detection
|
|
19
|
+
- Report generation now skips when no URLs are checked (cleaner output when server isn't ready)
|
|
20
|
+
- Port detection now prioritizes common Rails ports (3000, 3001, 4000, 5000) and excludes problematic ports
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Fixed logger accessor compatibility issue - logger access is now optional to work with older gem versions
|
|
24
|
+
- Fixed CLI connection issues by improving port detection and server readiness checks
|
|
25
|
+
- Fixed CLI showing empty reports when server isn't ready - now shows informative message instead
|
|
26
|
+
- Improved error handling for connection timeouts and connection refused errors
|
|
27
|
+
- Better handling of interrupt signals during server wait operations
|
|
28
|
+
|
|
29
|
+
### Improved
|
|
30
|
+
- CLI error messages are now more detailed and actionable with specific remediation steps
|
|
31
|
+
- Server detection is more reliable with improved timeout handling and error recovery
|
|
32
|
+
- Better user experience when running in Procfile.dev with automatic retries
|
|
33
|
+
|
|
8
34
|
## [1.2.0] - 2024-12-XX
|
|
9
35
|
|
|
10
36
|
### Changed
|
|
@@ -111,6 +137,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
111
137
|
- Compatible with RSpec Rails 6.0+
|
|
112
138
|
- Modular architecture with rule engine and check definitions
|
|
113
139
|
|
|
140
|
+
[1.3.0]: https://github.com/rayraycodes/rails-accessibility-testing/releases/tag/v1.3.0
|
|
114
141
|
[1.2.0]: https://github.com/rayraycodes/rails-accessibility-testing/releases/tag/v1.2.0
|
|
115
142
|
[1.1.6]: https://github.com/rayraycodes/rails-accessibility-testing/releases/tag/v1.1.6
|
|
116
143
|
[1.1.5]: https://github.com/rayraycodes/rails-accessibility-testing/releases/tag/v1.1.5
|
data/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
**The RSpec + RuboCop of accessibility for Rails. Catch WCAG violations before they reach production.**
|
|
9
9
|
|
|
10
|
-
**Current Version:** 1.
|
|
10
|
+
**Current Version:** 1.2.0
|
|
11
11
|
|
|
12
12
|
š **[š Full Documentation](https://rayraycodes.github.io/rails-accessibility-testing/)** | [š» GitHub](https://github.com/rayraycodes/rails-accessibility-testing) | [š RubyGems](https://rubygems.org/gems/rails_accessibility_testing)
|
|
13
13
|
|
|
@@ -40,6 +40,8 @@ Add to your `Gemfile`:
|
|
|
40
40
|
group :development, :test do
|
|
41
41
|
gem 'rails_accessibility_testing'
|
|
42
42
|
gem 'axe-core-capybara', '~> 4.0'
|
|
43
|
+
# Your existing Capybara, selenium-webdriver, webdrivers gems
|
|
44
|
+
# The gem has minimal dependencies - you control your own driver setup
|
|
43
45
|
end
|
|
44
46
|
```
|
|
45
47
|
|
|
@@ -301,9 +303,13 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed architecture documentation.
|
|
|
301
303
|
- Ruby 3.0+ (3.1+ recommended)
|
|
302
304
|
- Rails 6.0+ (7.1+ recommended)
|
|
303
305
|
- RSpec Rails 6.0+ (for RSpec) or Minitest (for Minitest)
|
|
304
|
-
- Capybara 3.0+
|
|
306
|
+
- Capybara 3.0+ (provided by your project)
|
|
307
|
+
- selenium-webdriver 4.0+ (provided by your project, for system specs)
|
|
308
|
+
- webdrivers (optional, provided by your project, for automatic driver management)
|
|
305
309
|
- Chrome/Chromium browser
|
|
306
310
|
|
|
311
|
+
**Note:** As of version 1.2.0, the gem has minimal dependencies. You provide and configure Capybara, selenium-webdriver, and webdrivers in your own Gemfile, giving you full control over your test driver setup.
|
|
312
|
+
|
|
307
313
|
## š¤ Contributing
|
|
308
314
|
|
|
309
315
|
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
@@ -83,6 +83,9 @@ module AccessibilityHelper
|
|
|
83
83
|
# If we collected any errors and this was called directly (not from comprehensive), raise them
|
|
84
84
|
if @accessibility_errors.any? && !@in_comprehensive_check
|
|
85
85
|
raise format_all_errors(@accessibility_errors)
|
|
86
|
+
elsif @accessibility_errors.empty? && !@in_comprehensive_check
|
|
87
|
+
# Show success message when all checks pass
|
|
88
|
+
puts "\nā
All basic accessibility checks passed! (5 checks: form labels, images, interactive elements, headings, keyboard)"
|
|
86
89
|
end
|
|
87
90
|
end
|
|
88
91
|
|
|
@@ -103,6 +106,9 @@ module AccessibilityHelper
|
|
|
103
106
|
# If we collected any errors, raise them all together
|
|
104
107
|
if @accessibility_errors.any?
|
|
105
108
|
raise format_all_errors(@accessibility_errors)
|
|
109
|
+
else
|
|
110
|
+
# Show success message when all checks pass
|
|
111
|
+
puts "\nā
All comprehensive accessibility checks passed! (11 checks: form labels, images, interactive elements, headings, keyboard, ARIA landmarks, form errors, table structure, duplicate IDs, skip links, color contrast)"
|
|
106
112
|
end
|
|
107
113
|
end
|
|
108
114
|
|
|
@@ -17,6 +17,10 @@ module RailsAccessibilityTesting
|
|
|
17
17
|
|
|
18
18
|
page.all('img', visible: :all).each do |img|
|
|
19
19
|
has_alt_attribute = page.evaluate_script("arguments[0].hasAttribute('alt')", img.native)
|
|
20
|
+
# Get alt value - might be nil, empty string, or actual text
|
|
21
|
+
alt_value = img[:alt] || ""
|
|
22
|
+
# Also check via JavaScript to be sure
|
|
23
|
+
alt_value_js = page.evaluate_script("arguments[0].getAttribute('alt')", img.native) || ""
|
|
20
24
|
|
|
21
25
|
if has_alt_attribute == false
|
|
22
26
|
element_ctx = element_context(img)
|
|
@@ -27,6 +31,17 @@ module RailsAccessibilityTesting
|
|
|
27
31
|
wcag_reference: "1.1.1",
|
|
28
32
|
remediation: generate_remediation(element_ctx)
|
|
29
33
|
)
|
|
34
|
+
elsif (alt_value.blank? || alt_value_js.blank?) && has_alt_attribute
|
|
35
|
+
# Image has alt attribute but it's empty - warn about this
|
|
36
|
+
# Empty alt is valid for decorative images, but we should check if it's actually decorative
|
|
37
|
+
element_ctx = element_context(img)
|
|
38
|
+
|
|
39
|
+
violations << violation(
|
|
40
|
+
message: "Image has empty alt attribute - ensure this image is purely decorative. If it conveys information, add descriptive alt text.",
|
|
41
|
+
element_context: element_ctx,
|
|
42
|
+
wcag_reference: "1.1.1",
|
|
43
|
+
remediation: generate_remediation(element_ctx)
|
|
44
|
+
)
|
|
30
45
|
end
|
|
31
46
|
end
|
|
32
47
|
|
|
@@ -53,7 +53,7 @@ module RailsAccessibilityTesting
|
|
|
53
53
|
|
|
54
54
|
def parse_options(argv)
|
|
55
55
|
options = {
|
|
56
|
-
profile: :
|
|
56
|
+
profile: :development, # Use development profile by default (faster, no color contrast)
|
|
57
57
|
format: :human,
|
|
58
58
|
output: nil,
|
|
59
59
|
debug: false
|
|
@@ -135,12 +135,44 @@ module RailsAccessibilityTesting
|
|
|
135
135
|
url = normalize_url(target)
|
|
136
136
|
|
|
137
137
|
# Wait for server to be ready (with retries)
|
|
138
|
-
|
|
138
|
+
# If wait fails, try to re-detect port and update URL
|
|
139
|
+
if url.match?(/\Ahttps?:\/\//)
|
|
140
|
+
# First, try to detect the port (might not be ready yet)
|
|
141
|
+
uri = URI.parse(url)
|
|
142
|
+
detected_port = detect_server_port
|
|
143
|
+
|
|
144
|
+
# If we detected a different port, update the URL
|
|
145
|
+
if detected_port != uri.port.to_s
|
|
146
|
+
url = "#{uri.scheme}://#{uri.host}:#{detected_port}#{uri.path}"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Now wait for server to be ready
|
|
150
|
+
server_ready = wait_for_server(url, max_retries: 20, retry_delay: 1)
|
|
151
|
+
|
|
152
|
+
# If still not ready, try re-detecting port one more time
|
|
153
|
+
unless server_ready
|
|
154
|
+
new_port = detect_server_port
|
|
155
|
+
if new_port != uri.port.to_s
|
|
156
|
+
url = "#{uri.scheme}://#{uri.host}:#{new_port}#{uri.path}"
|
|
157
|
+
server_ready = wait_for_server(url, max_retries: 20, retry_delay: 1)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# If still not ready, skip this check and try next time
|
|
161
|
+
unless server_ready
|
|
162
|
+
# Server still starting - this is normal, will retry automatically
|
|
163
|
+
$stderr.puts "Waiting for server to start... (will retry automatically)"
|
|
164
|
+
next
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
139
168
|
|
|
140
169
|
Capybara.visit(url)
|
|
141
170
|
violations = engine.check(Capybara.current_session, context: { url: url })
|
|
142
171
|
all_violations.concat(violations)
|
|
143
172
|
checked_urls << { url: url, violations: violations.count }
|
|
173
|
+
rescue Interrupt
|
|
174
|
+
# Handle interrupt gracefully - exit the loop
|
|
175
|
+
break
|
|
144
176
|
rescue StandardError => e
|
|
145
177
|
$stderr.puts "Error checking #{target}: #{e.message}"
|
|
146
178
|
end
|
|
@@ -180,8 +212,8 @@ module RailsAccessibilityTesting
|
|
|
180
212
|
return target if target.match?(/\Ahttps?:\/\//)
|
|
181
213
|
|
|
182
214
|
# If it's a path and we're using Selenium, construct a full URL
|
|
183
|
-
#
|
|
184
|
-
port =
|
|
215
|
+
# Try to detect the actual port, or use environment variables, or default to 3000
|
|
216
|
+
port = detect_server_port
|
|
185
217
|
base_url = ENV['RAILS_URL'] || "http://localhost:#{port}"
|
|
186
218
|
|
|
187
219
|
# Ensure path starts with /
|
|
@@ -189,7 +221,42 @@ module RailsAccessibilityTesting
|
|
|
189
221
|
"#{base_url}#{path}"
|
|
190
222
|
end
|
|
191
223
|
|
|
192
|
-
def
|
|
224
|
+
def detect_server_port
|
|
225
|
+
# Check environment variables first
|
|
226
|
+
return ENV['PORT'] if ENV['PORT']
|
|
227
|
+
return ENV['RAILS_PORT'] if ENV['RAILS_PORT']
|
|
228
|
+
|
|
229
|
+
# Try to detect port from Rails server - check common Rails ports
|
|
230
|
+
# Check in order: 3000 (most common), then others
|
|
231
|
+
# Prioritize 3000 first as it's the Rails default
|
|
232
|
+
common_ports = [3000, 3001, 4000, 5000]
|
|
233
|
+
|
|
234
|
+
common_ports.each do |port|
|
|
235
|
+
begin
|
|
236
|
+
require 'net/http'
|
|
237
|
+
http = Net::HTTP.new('localhost', port)
|
|
238
|
+
http.open_timeout = 1
|
|
239
|
+
http.read_timeout = 1
|
|
240
|
+
# Try to get a response - check if it looks like a Rails server
|
|
241
|
+
response = http.head('/')
|
|
242
|
+
# Accept 2xx, 3xx, or 4xx responses (server is responding)
|
|
243
|
+
# Reject 5xx as it might be a proxy or error
|
|
244
|
+
if response.code.to_i < 500
|
|
245
|
+
# Additional check: Rails servers usually have certain headers
|
|
246
|
+
# But for now, any HTTP response on these common ports is likely Rails
|
|
247
|
+
return port.to_s
|
|
248
|
+
end
|
|
249
|
+
rescue Errno::ECONNREFUSED, Net::OpenTimeout, Net::ReadTimeout, SocketError, Interrupt, Errno::EHOSTUNREACH, Errno::ETIMEDOUT
|
|
250
|
+
# Port not available or interrupted, try next
|
|
251
|
+
next
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Default to 3000 if nothing found
|
|
256
|
+
'3000'
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def wait_for_server(url, max_retries: 15, retry_delay: 1)
|
|
193
260
|
require 'net/http'
|
|
194
261
|
require 'uri'
|
|
195
262
|
|
|
@@ -199,22 +266,31 @@ module RailsAccessibilityTesting
|
|
|
199
266
|
max_retries.times do |attempt|
|
|
200
267
|
begin
|
|
201
268
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
202
|
-
http.open_timeout =
|
|
203
|
-
http.read_timeout =
|
|
269
|
+
http.open_timeout = 2
|
|
270
|
+
http.read_timeout = 2
|
|
204
271
|
response = http.head('/')
|
|
205
|
-
return if response.code.to_i < 500 # Server is responding
|
|
206
|
-
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Net::OpenTimeout, Net::ReadTimeout, SocketError
|
|
272
|
+
return true if response.code.to_i < 500 # Server is responding
|
|
273
|
+
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Net::OpenTimeout, Net::ReadTimeout, SocketError, Errno::EHOSTUNREACH
|
|
207
274
|
# Server not ready yet
|
|
208
275
|
if attempt < max_retries - 1
|
|
209
|
-
|
|
276
|
+
begin
|
|
277
|
+
sleep(retry_delay)
|
|
278
|
+
rescue Interrupt
|
|
279
|
+
# Handle interrupt gracefully - return false to indicate failure
|
|
280
|
+
return false
|
|
281
|
+
end
|
|
210
282
|
next
|
|
211
283
|
else
|
|
212
|
-
# Last attempt failed
|
|
213
|
-
|
|
214
|
-
return
|
|
284
|
+
# Last attempt failed
|
|
285
|
+
return false
|
|
215
286
|
end
|
|
287
|
+
rescue Interrupt
|
|
288
|
+
# Handle interrupt gracefully
|
|
289
|
+
return false
|
|
216
290
|
end
|
|
217
291
|
end
|
|
292
|
+
|
|
293
|
+
false
|
|
218
294
|
end
|
|
219
295
|
|
|
220
296
|
def resolve_routes(routes)
|
|
@@ -230,6 +306,11 @@ module RailsAccessibilityTesting
|
|
|
230
306
|
end
|
|
231
307
|
|
|
232
308
|
def generate_report(results, options)
|
|
309
|
+
# Don't generate report if no URLs were checked (server not ready)
|
|
310
|
+
if results[:summary][:urls_checked] == 0
|
|
311
|
+
return
|
|
312
|
+
end
|
|
313
|
+
|
|
233
314
|
output = case options[:format]
|
|
234
315
|
when :json
|
|
235
316
|
generate_json_report(results)
|
|
@@ -262,11 +343,14 @@ module RailsAccessibilityTesting
|
|
|
262
343
|
output << ""
|
|
263
344
|
|
|
264
345
|
results[:violations].each_with_index do |violation, index|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
346
|
+
# Use ErrorMessageBuilder for detailed formatted messages
|
|
347
|
+
detailed_message = ErrorMessageBuilder.build(
|
|
348
|
+
error_type: violation.message,
|
|
349
|
+
element_context: violation.element_context || {},
|
|
350
|
+
page_context: violation.page_context || {}
|
|
351
|
+
)
|
|
352
|
+
output << detailed_message
|
|
353
|
+
output << "" if index < results[:violations].count - 1 # Add spacing between violations
|
|
270
354
|
end
|
|
271
355
|
else
|
|
272
356
|
output << "ā
No accessibility violations found!"
|
|
@@ -300,7 +384,8 @@ module RailsAccessibilityTesting
|
|
|
300
384
|
-v, --version Show version
|
|
301
385
|
|
|
302
386
|
Examples:
|
|
303
|
-
rails_a11y
|
|
387
|
+
rails_a11y /home /about
|
|
388
|
+
rails_a11y /
|
|
304
389
|
rails_a11y --urls https://example.com
|
|
305
390
|
rails_a11y --routes home_path about_path --format json --output report.json
|
|
306
391
|
HELP
|
|
@@ -38,7 +38,9 @@ module RailsAccessibilityTesting
|
|
|
38
38
|
|
|
39
39
|
merge_profile_config(parsed, profile)
|
|
40
40
|
rescue StandardError => e
|
|
41
|
-
RailsAccessibilityTesting.config.
|
|
41
|
+
if defined?(RailsAccessibilityTesting) && RailsAccessibilityTesting.config.respond_to?(:logger) && RailsAccessibilityTesting.config.logger
|
|
42
|
+
RailsAccessibilityTesting.config.logger.warn("Failed to load config: #{e.message}")
|
|
43
|
+
end
|
|
42
44
|
default_config
|
|
43
45
|
end
|
|
44
46
|
|
|
@@ -9,11 +9,13 @@ module RailsAccessibilityTesting
|
|
|
9
9
|
# end
|
|
10
10
|
#
|
|
11
11
|
# @attr [Boolean] auto_run_checks Whether to automatically run checks after system specs
|
|
12
|
+
# @attr [Logger, nil] logger Optional logger for accessibility check output
|
|
12
13
|
class Configuration
|
|
13
|
-
attr_accessor :auto_run_checks
|
|
14
|
+
attr_accessor :auto_run_checks, :logger
|
|
14
15
|
|
|
15
16
|
def initialize
|
|
16
17
|
@auto_run_checks = true
|
|
18
|
+
@logger = nil
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
|
|
@@ -38,7 +38,9 @@ module RailsAccessibilityTesting
|
|
|
38
38
|
@violation_collector.add(violations) if violations.any?
|
|
39
39
|
rescue StandardError => e
|
|
40
40
|
# Log but don't fail - one check error shouldn't stop others
|
|
41
|
-
RailsAccessibilityTesting.config.
|
|
41
|
+
if defined?(RailsAccessibilityTesting) && RailsAccessibilityTesting.config.respond_to?(:logger) && RailsAccessibilityTesting.config.logger
|
|
42
|
+
RailsAccessibilityTesting.config.logger.error("Check #{check_class.rule_name} failed: #{e.message}")
|
|
43
|
+
end
|
|
42
44
|
end
|
|
43
45
|
end
|
|
44
46
|
|
|
@@ -48,6 +48,12 @@ module RailsAccessibilityTesting
|
|
|
48
48
|
# Run comprehensive accessibility checks
|
|
49
49
|
instance = example.example_group_instance
|
|
50
50
|
instance.check_comprehensive_accessibility
|
|
51
|
+
|
|
52
|
+
# Show success message if checks passed (no exception was raised)
|
|
53
|
+
unless example.exception
|
|
54
|
+
$stdout.puts "\nā
All comprehensive accessibility checks passed! (11 checks)"
|
|
55
|
+
$stdout.flush
|
|
56
|
+
end
|
|
51
57
|
rescue StandardError => e
|
|
52
58
|
example.set_exception(e)
|
|
53
59
|
end
|