capybara-accessible 0.2.0 → 0.2.1
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 +7 -0
- data/README.md +13 -3
- data/capybara-accessible.gemspec +4 -2
- data/lib/capybara/accessible.rb +102 -3
- data/lib/capybara/accessible/auditor.rb +99 -54
- data/lib/capybara/accessible/driver_extensions.rb +6 -0
- data/lib/capybara/accessible/element.rb +1 -14
- data/lib/capybara/accessible/version.rb +1 -1
- data/lib/vendor/google/accessibility-developer-tools/axs_testing.js +61 -41
- data/spec/poltergeist_driver_spec.rb +56 -0
- data/spec/{driver_spec.rb → selenium_driver_spec.rb} +2 -2
- data/spec/webkit_driver_spec.rb +71 -0
- metadata +59 -42
- data/lib/capybara/accessible/driver.rb +0 -16
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bc474ce3ecde90316cd8f6693ec63070c2e7c134
|
4
|
+
data.tar.gz: 80e58047c93f55023767eecc37d46b6487f64391
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bb2173ca56885f1cb684ceafb54438e0059f1224e93fca36974592c64a2935c639926b5fcf8a9885af54228662445a5e2678e6c4594a4e96644afbefa06bb45b
|
7
|
+
data.tar.gz: 0f18d822bca546abd31efddaa2775a622c2a1a0b8dee4caf2831a1197bc2698668f1b6f8543556242a977577fe93f9686e878317f2203c4e423ce3c2e9383a22
|
data/README.md
CHANGED
@@ -31,14 +31,24 @@ Add `gem 'capybara-accessible'` to your application's Gemfile and run `bundle` o
|
|
31
31
|
|
32
32
|
## Usage
|
33
33
|
|
34
|
-
You can use capybara-accessible as a drop-in replacement for Rack::Test, Selenium
|
34
|
+
You can use capybara-accessible as a drop-in replacement for Rack::Test, Selenium, capybara-webkit, or Poltergeist drivers for Capybara.
|
35
35
|
Simply set the driver in `spec/spec_helper.rb` or `features/support/env.rb`:
|
36
36
|
|
37
37
|
require 'capybara/rspec'
|
38
38
|
require 'capybara/accessible'
|
39
39
|
|
40
|
-
|
41
|
-
Capybara.
|
40
|
+
# For selenium integration
|
41
|
+
Capybara.default_driver = :accessible_selenium
|
42
|
+
Capybara.javascript_driver = :accessible_selenium
|
43
|
+
|
44
|
+
# For capybara webkit integration
|
45
|
+
Capybara.default_driver = :accessible_webkit
|
46
|
+
Capybara.javascript_driver = :accessible_webkit
|
47
|
+
|
48
|
+
# For poltergeist integration
|
49
|
+
Capybara.default_driver = :accessible_poltergeist
|
50
|
+
Capybara.javascript_driver = :accessible_poltergeist
|
51
|
+
|
42
52
|
|
43
53
|
We suggest that you use [pry-rescue with pry-stack_explorer](https://github.com/ConradIrwin/pry-rescue)
|
44
54
|
to debug the accessibility failures in the DOM. pry-rescue will open a debugging session at the first exception,
|
data/capybara-accessible.gemspec
CHANGED
@@ -19,8 +19,10 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency("capybara", "~> 2.0")
|
22
|
-
|
23
|
-
|
22
|
+
|
23
|
+
spec.add_development_dependency "selenium-webdriver"
|
24
|
+
spec.add_development_dependency "capybara-webkit"
|
25
|
+
spec.add_development_dependency "poltergeist"
|
24
26
|
spec.add_development_dependency "rake"
|
25
27
|
spec.add_development_dependency "rspec"
|
26
28
|
spec.add_development_dependency "pry"
|
data/lib/capybara/accessible.rb
CHANGED
@@ -1,16 +1,115 @@
|
|
1
1
|
require 'capybara'
|
2
2
|
require 'capybara/accessible/auditor'
|
3
3
|
require 'capybara/accessible/element'
|
4
|
+
require 'capybara/accessible/driver_extensions'
|
4
5
|
require "capybara/accessible/version"
|
5
6
|
require "capybara/accessible/railtie" if defined?(Rails)
|
6
7
|
|
7
8
|
module Capybara
|
8
9
|
module Accessible
|
10
|
+
class SeleniumDriverAdapter
|
11
|
+
def modal_dialog_present?(driver)
|
12
|
+
begin
|
13
|
+
driver.browser.switch_to.alert
|
14
|
+
true
|
15
|
+
rescue ::Selenium::WebDriver::Error::NoAlertOpenError, ::NoMethodError
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def failures_script
|
21
|
+
"return axs.Audit.auditResults(results).getErrors();"
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_report_script
|
25
|
+
"return axs.Audit.createReport(results);"
|
26
|
+
end
|
27
|
+
|
28
|
+
def run_javascript(driver, script)
|
29
|
+
driver.execute_script(script)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class WebkitDriverAdapter
|
34
|
+
def modal_dialog_present?(driver)
|
35
|
+
driver.alert_messages.any?
|
36
|
+
end
|
37
|
+
|
38
|
+
def failures_script
|
39
|
+
"axs.Audit.auditResults(results).getErrors();"
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_report_script
|
43
|
+
"axs.Audit.createReport(results);"
|
44
|
+
end
|
45
|
+
|
46
|
+
def run_javascript(driver, script)
|
47
|
+
driver.evaluate_script(script)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class PoltergeistDriverAdapter
|
52
|
+
def modal_dialog_present?(driver)
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
def failures_script
|
57
|
+
"return axs.Audit.auditResults(results).getErrors()"
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_report_script
|
61
|
+
"return axs.Audit.createReport(results)"
|
62
|
+
end
|
63
|
+
|
64
|
+
def run_javascript(driver, script)
|
65
|
+
# Have to wrap in an anonymous function because of https://github.com/jonleighton/poltergeist/issues/198
|
66
|
+
driver.evaluate_script %{ (function() {#{script}})() }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class << self
|
71
|
+
def skip_audit
|
72
|
+
Capybara::Accessible::Auditor.disable
|
73
|
+
yield
|
74
|
+
ensure
|
75
|
+
Capybara::Accessible::Auditor.enable
|
76
|
+
end
|
77
|
+
|
78
|
+
def driver_adapter
|
79
|
+
@driver_adapter
|
80
|
+
end
|
81
|
+
|
82
|
+
def setup(driver, adaptor)
|
83
|
+
@driver_adapter = adaptor
|
84
|
+
driver.extend(Capybara::Accessible::DriverExtensions)
|
85
|
+
driver
|
86
|
+
end
|
87
|
+
end
|
9
88
|
end
|
10
89
|
end
|
11
90
|
|
12
|
-
require "capybara/accessible/driver"
|
13
|
-
|
14
91
|
Capybara.register_driver :accessible do |app|
|
15
|
-
|
92
|
+
puts "DEPRECATED: Please register this driver as 'accessible_selenium'"
|
93
|
+
driver = Capybara::Selenium::Driver.new(app)
|
94
|
+
adaptor = Capybara::Accessible::SeleniumDriverAdapter.new
|
95
|
+
Capybara::Accessible.setup(driver, adaptor)
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
Capybara.register_driver :accessible_selenium do |app|
|
100
|
+
driver = Capybara::Selenium::Driver.new(app)
|
101
|
+
adaptor = Capybara::Accessible::SeleniumDriverAdapter.new
|
102
|
+
Capybara::Accessible.setup(driver, adaptor)
|
103
|
+
end
|
104
|
+
|
105
|
+
Capybara.register_driver :accessible_webkit do |app|
|
106
|
+
driver = Capybara::Webkit::Driver.new(app)
|
107
|
+
adaptor = Capybara::Accessible::WebkitDriverAdapter.new
|
108
|
+
Capybara::Accessible.setup(driver, adaptor)
|
109
|
+
end
|
110
|
+
|
111
|
+
Capybara.register_driver :accessible_poltergeist do |app|
|
112
|
+
driver = Capybara::Poltergeist::Driver.new(app)
|
113
|
+
adaptor = Capybara::Accessible::PoltergeistDriverAdapter.new
|
114
|
+
Capybara::Accessible.setup(driver, adaptor)
|
16
115
|
end
|
@@ -1,57 +1,102 @@
|
|
1
1
|
module Capybara::Accessible
|
2
2
|
class InaccessibleError < Capybara::CapybaraError; end
|
3
3
|
|
4
|
-
class
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@disabled = false
|
10
|
-
end
|
11
|
-
end
|
4
|
+
class Auditor
|
5
|
+
class Node < self
|
6
|
+
def initialize(session)
|
7
|
+
@driver = session.driver
|
8
|
+
end
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
def audit!
|
11
|
+
if modal_dialog_present?
|
12
|
+
puts "Skipping accessibility audit: Modal dialog present"
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
private
|
19
|
+
def modal_dialog_present?
|
20
|
+
Capybara::Accessible.driver_adapter.modal_dialog_present?(driver)
|
21
|
+
end
|
20
22
|
end
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
+
class Driver < self
|
25
|
+
def initialize(driver)
|
26
|
+
@driver = driver
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
30
|
+
class <<self
|
31
|
+
def exclusions=(rules)
|
32
|
+
@exclusions = rules
|
33
|
+
end
|
34
|
+
|
35
|
+
def exclusions
|
36
|
+
@exclusions ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
def log_level=(level)
|
40
|
+
@log_level = level
|
41
|
+
end
|
42
|
+
|
43
|
+
def log_level
|
44
|
+
@log_level ||= :error
|
45
|
+
end
|
46
|
+
|
47
|
+
def severe_rules=(rules)
|
48
|
+
@severe_rules = rules
|
49
|
+
end
|
50
|
+
|
51
|
+
def severe_rules
|
52
|
+
@severe_rules ||= []
|
53
|
+
end
|
54
|
+
|
55
|
+
def disable
|
56
|
+
@disabled = true
|
57
|
+
end
|
58
|
+
|
59
|
+
def enable
|
60
|
+
@disabled = false
|
61
|
+
end
|
29
62
|
|
30
|
-
|
31
|
-
|
63
|
+
def disabled?
|
64
|
+
@disabled
|
65
|
+
end
|
32
66
|
end
|
33
67
|
|
34
|
-
def
|
35
|
-
|
68
|
+
def audit!
|
69
|
+
return if Auditor.disabled?
|
70
|
+
|
71
|
+
if failures?
|
72
|
+
log_level_response[Capybara::Accessible::Auditor.log_level].call(failure_messages)
|
73
|
+
end
|
36
74
|
end
|
37
75
|
|
38
|
-
|
39
|
-
|
76
|
+
private
|
77
|
+
attr_reader :driver
|
78
|
+
|
79
|
+
def log_level_response
|
80
|
+
@log_level_response ||= {
|
81
|
+
warn: ->(messages) { puts messages },
|
82
|
+
error: ->(messages) { raise Capybara::Accessible::InaccessibleError, failure_messages }
|
83
|
+
}
|
40
84
|
end
|
41
85
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
run_script("#{perform_audit_script} return axs.Audit.auditResults(results).getErrors();")
|
47
|
-
end
|
86
|
+
def failures?
|
87
|
+
failures = run_script(perform_audit_script + driver_adaptor.failures_script)
|
88
|
+
|
89
|
+
Array(failures).any?
|
48
90
|
end
|
49
91
|
|
50
92
|
def failure_messages
|
51
|
-
|
93
|
+
result = run_script(perform_audit_script + driver_adaptor.create_report_script)
|
94
|
+
"Found at #{page_url} \n\n#{result}"
|
52
95
|
end
|
53
96
|
|
54
|
-
|
97
|
+
def audit_rules
|
98
|
+
File.read(File.expand_path("../../../vendor/google/accessibility-developer-tools/axs_testing.js", __FILE__))
|
99
|
+
end
|
55
100
|
|
56
101
|
def perform_audit_script
|
57
102
|
<<-JAVASCRIPT
|
@@ -81,34 +126,34 @@ module Capybara::Accessible
|
|
81
126
|
|
82
127
|
def mapping
|
83
128
|
@mapping ||= {
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
129
|
+
'AX_ARIA_01' => 'badAriaRole',
|
130
|
+
'AX_ARIA_02' => 'nonExistentAriaLabelledbyElement',
|
131
|
+
'AX_ARIA_03' => 'requiredAriaAttributeMissing',
|
132
|
+
'AX_ARIA_04' => 'badAriaAttributeValue',
|
133
|
+
'AX_TEXT_01' => 'controlsWithoutLabel',
|
134
|
+
'AX_TEXT_02' => 'imagesWithoutAltText',
|
135
|
+
'AX_TITLE_01' => 'pageWithoutTitle',
|
136
|
+
'AX_IMAGE_01' => 'elementsWithMeaningfulBackgroundImage',
|
137
|
+
'AX_FOCUS_01' => 'focusableElementNotVisibleAndNotAriaHidden',
|
138
|
+
'AX_FOCUS_02' => 'unfocusableElementsWithOnClick',
|
139
|
+
'AX_COLOR_01' => 'lowContrastElements',
|
140
|
+
'AX_VIDEO_01' => 'videoWithoutCaptions',
|
141
|
+
'AX_AUDIO_01' => 'audioWithoutControls'
|
142
|
+
# 'AX_TITLE_01' => 'linkWithUnclearPurpose', # This has a duplicate name
|
143
|
+
# 'AX_ARIA_05' => '', # This has no rule associated with it
|
99
144
|
}
|
100
145
|
end
|
101
146
|
|
102
147
|
def page_url
|
103
|
-
|
148
|
+
driver.current_url
|
104
149
|
end
|
105
150
|
|
106
151
|
def run_script(script)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
152
|
+
driver_adaptor.run_javascript(driver, script)
|
153
|
+
end
|
154
|
+
|
155
|
+
def driver_adaptor
|
156
|
+
Capybara::Accessible.driver_adapter
|
112
157
|
end
|
113
158
|
end
|
114
159
|
end
|
@@ -1,22 +1,9 @@
|
|
1
1
|
module Capybara
|
2
2
|
module Node
|
3
3
|
class Element < Base
|
4
|
-
include Capybara::Accessible::Auditor
|
5
|
-
|
6
4
|
def click
|
7
5
|
synchronize { base.click }
|
8
|
-
|
9
|
-
@session.driver.browser.switch_to.alert
|
10
|
-
puts "Skipping accessibility audit: Modal dialog present"
|
11
|
-
rescue ::Selenium::WebDriver::Error::NoAlertOpenError, ::NoMethodError
|
12
|
-
if Capybara.current_driver == :accessible && audit_failures.any?
|
13
|
-
if Capybara::Accessible::Auditor.log_level == :warn
|
14
|
-
puts failure_messages
|
15
|
-
else
|
16
|
-
raise Capybara::Accessible::InaccessibleError, failure_messages
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
6
|
+
Capybara::Accessible::Auditor::Node.new(@session).audit!
|
20
7
|
end
|
21
8
|
end
|
22
9
|
end
|
@@ -13,7 +13,7 @@
|
|
13
13
|
* See the License for the specific language governing permissions and
|
14
14
|
* limitations under the License.
|
15
15
|
*
|
16
|
-
* Generated from http://github.com/GoogleChrome/accessibility-developer-tools/tree/
|
16
|
+
* Generated from http://github.com/GoogleChrome/accessibility-developer-tools/tree/80f7c9ac87569c23429a7b07de1af980679c9a34
|
17
17
|
*
|
18
18
|
* See project README for build steps.
|
19
19
|
*/
|
@@ -543,9 +543,29 @@ axs.utils.elementHasZeroArea = function(a) {
|
|
543
543
|
return a.right - a.left && b ? !1 : !0
|
544
544
|
};
|
545
545
|
axs.utils.elementIsOutsideScrollArea = function(a) {
|
546
|
-
a = a.
|
547
|
-
|
548
|
-
|
546
|
+
for(var b = a.parentElement, c = a.ownerDocument.defaultView;b != c.document.body;) {
|
547
|
+
if(axs.utils.isClippedBy(a, b)) {
|
548
|
+
return!0
|
549
|
+
}
|
550
|
+
if(axs.utils.canScrollTo(a, b) && !axs.utils.elementIsOutsideScrollArea(b)) {
|
551
|
+
return!1
|
552
|
+
}
|
553
|
+
b = b.parentElement
|
554
|
+
}
|
555
|
+
return!axs.utils.canScrollTo(a, c.document.body)
|
556
|
+
};
|
557
|
+
axs.utils.canScrollTo = function(a, b) {
|
558
|
+
var c = a.getBoundingClientRect(), d = b.getBoundingClientRect(), e = d.top, f = d.left, g = e - b.scrollTop, e = e - b.scrollTop + b.scrollHeight, h = f - b.scrollLeft + b.scrollWidth;
|
559
|
+
if(c.right < f - b.scrollLeft || c.bottom < g || c.left > h || c.top > e) {
|
560
|
+
return!1
|
561
|
+
}
|
562
|
+
f = a.ownerDocument.defaultView;
|
563
|
+
g = f.getComputedStyle(b);
|
564
|
+
return c.left > d.right || c.top > d.bottom ? "scroll" == g.overflow || "auto" == g.overflow || b instanceof f.HTMLBodyElement : !0
|
565
|
+
};
|
566
|
+
axs.utils.isClippedBy = function(a, b) {
|
567
|
+
var c = a.getBoundingClientRect(), d = b.getBoundingClientRect(), e = d.top - b.scrollTop, f = d.left - b.scrollLeft, g = a.ownerDocument.defaultView.getComputedStyle(b);
|
568
|
+
return(c.right < d.left || c.bottom < d.top || c.left > d.right || c.top > d.bottom) && "hidden" == g.overflow ? !0 : c.right < f || c.bottom < e ? "visible" != g.overflow : !1
|
549
569
|
};
|
550
570
|
axs.utils.isAncestor = function(a, b) {
|
551
571
|
return null == b ? !1 : b === a ? !0 : axs.utils.isAncestor(a, b.parentNode)
|
@@ -1006,7 +1026,7 @@ axs.properties.getFocusProperties = function(a) {
|
|
1006
1026
|
d && (h.transparent = !0);
|
1007
1027
|
e && (h.zeroArea = !0);
|
1008
1028
|
f && (h.outsideScrollArea = !0);
|
1009
|
-
0 < g.length && (h.overlappingElements = g);
|
1029
|
+
g && 0 < g.length && (h.overlappingElements = g);
|
1010
1030
|
d = {value:c, valid:c};
|
1011
1031
|
c && (d.reason = axs.properties.getHiddenReason(a));
|
1012
1032
|
h.hidden = d;
|
@@ -1060,62 +1080,58 @@ axs.properties.getContrastRatioProperties = function(a) {
|
|
1060
1080
|
(c = axs.utils.suggestColors(d, e, a, c)) && Object.keys(c).length && (b.suggestedColors = c);
|
1061
1081
|
return b
|
1062
1082
|
};
|
1063
|
-
axs.properties.findTextAlternatives = function(a, b, c) {
|
1064
|
-
var
|
1083
|
+
axs.properties.findTextAlternatives = function(a, b, c, d) {
|
1084
|
+
var e = c || !1;
|
1065
1085
|
c = axs.utils.asElement(a);
|
1066
|
-
if(!c || !d && axs.utils.isElementOrAncestorHidden(c)) {
|
1086
|
+
if(!c || !e && !d && axs.utils.isElementOrAncestorHidden(c)) {
|
1067
1087
|
return null
|
1068
1088
|
}
|
1069
1089
|
if(a.nodeType == Node.TEXT_NODE) {
|
1070
1090
|
return c = {type:"text"}, c.text = a.textContent, c.lastWord = axs.properties.getLastWord(c.text), b.content = c, a.textContent
|
1071
1091
|
}
|
1072
1092
|
a = null;
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
b.ariaLabel = e
|
1080
|
-
}
|
1081
|
-
c.hasAttribute("role") && "presentation" == c.getAttribute("role") || (a = axs.properties.getTextFromHostLanguageAttributes(c, b, a, d));
|
1082
|
-
if(d && axs.utils.elementIsHtmlControl(c)) {
|
1083
|
-
e = c.ownerDocument.defaultView;
|
1084
|
-
if(c instanceof e.HTMLInputElement) {
|
1093
|
+
e || (a = axs.properties.getTextFromAriaLabelledby(c, b));
|
1094
|
+
c.hasAttribute("aria-label") && (d = {type:"text"}, d.text = c.getAttribute("aria-label"), d.lastWord = axs.properties.getLastWord(d.text), a ? d.unused = !0 : e && axs.utils.elementIsHtmlControl(c) || (a = d.text), b.ariaLabel = d);
|
1095
|
+
c.hasAttribute("role") && "presentation" == c.getAttribute("role") || (a = axs.properties.getTextFromHostLanguageAttributes(c, b, a, e));
|
1096
|
+
if(e && axs.utils.elementIsHtmlControl(c)) {
|
1097
|
+
d = c.ownerDocument.defaultView;
|
1098
|
+
if(c instanceof d.HTMLInputElement) {
|
1085
1099
|
var f = c;
|
1086
1100
|
"text" == f.type && f.value && 0 < f.value.length && (b.controlValue = {text:f.value});
|
1087
1101
|
"range" == f.type && (b.controlValue = {text:f.value})
|
1088
1102
|
}
|
1089
|
-
c instanceof
|
1090
|
-
b.controlValue && (
|
1103
|
+
c instanceof d.HTMLSelectElement && (b.controlValue = {text:c.value});
|
1104
|
+
b.controlValue && (d = b.controlValue, a ? d.unused = !0 : a = d.text)
|
1091
1105
|
}
|
1092
|
-
if(
|
1093
|
-
|
1094
|
-
"textbox" ==
|
1095
|
-
if("slider" ==
|
1106
|
+
if(e && axs.utils.elementIsAriaWidget(c)) {
|
1107
|
+
e = c.getAttribute("role");
|
1108
|
+
"textbox" == e && c.textContent && 0 < c.textContent.length && (b.controlValue = {text:c.textContent});
|
1109
|
+
if("slider" == e || "spinbutton" == e) {
|
1096
1110
|
c.hasAttribute("aria-valuetext") ? b.controlValue = {text:c.getAttribute("aria-valuetext")} : c.hasAttribute("aria-valuenow") && (b.controlValue = {value:c.getAttribute("aria-valuenow"), text:"" + c.getAttribute("aria-valuenow")})
|
1097
1111
|
}
|
1098
|
-
if("menu" ==
|
1099
|
-
|
1100
|
-
|
1112
|
+
if("menu" == e) {
|
1113
|
+
var g = c.querySelectorAll("[role=menuitemcheckbox], [role=menuitemradio]");
|
1114
|
+
d = [];
|
1115
|
+
for(f = 0;f < g.length;f++) {
|
1116
|
+
"true" == g[f].getAttribute("aria-checked") && d.push(g[f])
|
1101
1117
|
}
|
1102
|
-
if(0 <
|
1118
|
+
if(0 < d.length) {
|
1103
1119
|
g = "";
|
1104
|
-
for(f = 0;f <
|
1105
|
-
g += axs.properties.findTextAlternatives(
|
1120
|
+
for(f = 0;f < d.length;f++) {
|
1121
|
+
g += axs.properties.findTextAlternatives(d[f], {}, !0), f < d.length - 1 && (g += ", ")
|
1106
1122
|
}
|
1107
1123
|
b.controlValue = {text:g}
|
1108
1124
|
}
|
1109
1125
|
}
|
1110
|
-
if("combobox" ==
|
1126
|
+
if("combobox" == e || "select" == e) {
|
1111
1127
|
b.controlValue = {text:"TODO"}
|
1112
1128
|
}
|
1113
|
-
b.controlValue && (
|
1129
|
+
b.controlValue && (d = b.controlValue, a ? d.unused = !0 : a = d.text)
|
1114
1130
|
}
|
1115
|
-
|
1116
|
-
c.hasAttribute("role") && (
|
1117
|
-
(
|
1118
|
-
c.hasAttribute("title") && (
|
1131
|
+
d = !0;
|
1132
|
+
c.hasAttribute("role") && (e = c.getAttribute("role"), (e = axs.constants.ARIA_ROLES[e]) && (!e.namefrom || 0 > e.namefrom.indexOf("contents")) && (d = !1));
|
1133
|
+
(e = axs.properties.getTextFromDescendantContent(c)) && d && (d = {type:"text"}, d.text = e, d.lastWord = axs.properties.getLastWord(d.text), a ? d.unused = !0 : a = e, b.content = d);
|
1134
|
+
c.hasAttribute("title") && (e = {type:"string", valid:!0}, e.text = c.getAttribute("title"), e.lastWord = axs.properties.getLastWord(e.lastWord), a ? e.unused = !0 : a = e.text, b.title = e);
|
1119
1135
|
return 0 == Object.keys(b).length && null == a ? null : a
|
1120
1136
|
};
|
1121
1137
|
axs.properties.getTextFromDescendantContent = function(a) {
|
@@ -1196,7 +1212,7 @@ axs.properties.getLastWord = function(a) {
|
|
1196
1212
|
};
|
1197
1213
|
axs.properties.getTextProperties = function(a) {
|
1198
1214
|
var b = {};
|
1199
|
-
a = axs.properties.findTextAlternatives(a, b);
|
1215
|
+
a = axs.properties.findTextAlternatives(a, b, !1, !0);
|
1200
1216
|
if(0 == Object.keys(b).length) {
|
1201
1217
|
if(!a) {
|
1202
1218
|
return null
|
@@ -1540,7 +1556,11 @@ axs.AuditRule.specs.focusableElementNotVisibleAndNotAriaHidden = {name:"focusabl
|
|
1540
1556
|
}
|
1541
1557
|
return!0
|
1542
1558
|
}, test:function(a) {
|
1543
|
-
|
1559
|
+
if(axs.utils.isElementOrAncestorHidden(a)) {
|
1560
|
+
return!1
|
1561
|
+
}
|
1562
|
+
a.focus();
|
1563
|
+
return!axs.utils.elementIsVisible(a)
|
1544
1564
|
}, code:"AX_FOCUS_01"};
|
1545
1565
|
axs.AuditRule.specs.imagesWithoutAltText = {name:"imagesWithoutAltText", heading:"Images should have an alt attribute", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_text_02--images-should-have-an-alt-attribute-unless-they-have-an-aria-role-of-presentation", severity:axs.constants.Severity.WARNING, relevantElementMatcher:function(a) {
|
1546
1566
|
return axs.browserUtils.matchSelector(a, "img") && !axs.utils.isElementOrAncestorHidden(a)
|
@@ -1618,7 +1638,7 @@ axs.AuditRule.specs.requiredAriaAttributeMissing = {name:"requiredAriaAttributeM
|
|
1618
1638
|
axs.AuditRule.specs.unfocusableElementsWithOnClick = {name:"unfocusableElementsWithOnClick", heading:"Elements with onclick handlers must be focusable", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_focus_02--elements-with-onclick-handlers-must-be-focusable", severity:axs.constants.Severity.WARNING, opt_requiresConsoleAPI:!0, relevantElementMatcher:function(a) {
|
1619
1639
|
return a instanceof a.ownerDocument.defaultView.HTMLBodyElement || axs.utils.isElementOrAncestorHidden(a) ? !1 : "click" in getEventListeners(a) ? !0 : !1
|
1620
1640
|
}, test:function(a) {
|
1621
|
-
return!a.hasAttribute("tabindex") && !axs.utils.isElementImplicitlyFocusable(a)
|
1641
|
+
return!a.hasAttribute("tabindex") && !axs.utils.isElementImplicitlyFocusable(a) && !a.disabled
|
1622
1642
|
}, code:"AX_FOCUS_02"};
|
1623
1643
|
axs.AuditRule.specs.videoWithoutCaptions = {name:"videoWithoutCaptions", heading:"Video elements should use <track> elements to provide captions", url:"https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules#-ax_video_01--video-elements-should-use-track-elements-to-provide-captions", severity:axs.constants.Severity.WARNING, relevantElementMatcher:function(a) {
|
1624
1644
|
return axs.browserUtils.matchSelector(a, "video")
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Using poltergeist driver" do
|
4
|
+
require "capybara/poltergeist"
|
5
|
+
before do
|
6
|
+
@session = Capybara::Session.new(:accessible_poltergeist, AccessibleApp)
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'a page without accessibility errors' do
|
10
|
+
it 'does not raise an exception on audit failures' do
|
11
|
+
expect { @session.visit('/accessible') }.to_not raise_error
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'a page with inaccessible elements' do
|
16
|
+
it 'raises an exception on visiting the page' do
|
17
|
+
expect { @session.visit('/inaccessible') }.to raise_error(Capybara::Accessible::InaccessibleError)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'raises an exception when visiting the page via a link' do
|
21
|
+
@session.visit('/accessible')
|
22
|
+
expect { @session.click_link('inaccessible') }.to raise_error(Capybara::Accessible::InaccessibleError)
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'with configuration that excludes rules' do
|
26
|
+
before do
|
27
|
+
Capybara::Accessible::Auditor.exclusions = ['AX_TEXT_01']
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'does not raise an error on an excluded rule' do
|
31
|
+
expect { @session.visit('/excluded') }.to_not raise_error
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'a page with a javascript popup' do
|
36
|
+
it 'does not raise an exception' do
|
37
|
+
@session.visit('/alert')
|
38
|
+
expect { @session.click_link('Alert!') }.to_not raise_error
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'with severity set to severe' do
|
43
|
+
before do
|
44
|
+
Capybara::Accessible::Auditor.severe_rules = ['AX_TEXT_02']
|
45
|
+
end
|
46
|
+
|
47
|
+
after do
|
48
|
+
Capybara::Accessible::Auditor.severe_rules = []
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'raises an exception on the image without alt text' do
|
52
|
+
expect { @session.visit('/severe') }.to raise_error(Capybara::Accessible::InaccessibleError)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe
|
3
|
+
describe "Using selenium driver" do
|
4
4
|
before do
|
5
|
-
@session = Capybara::Session.new(:
|
5
|
+
@session = Capybara::Session.new(:accessible_selenium, AccessibleApp)
|
6
6
|
end
|
7
7
|
|
8
8
|
context 'a page without accessibility errors' do
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Using webkit driver" do
|
4
|
+
require "capybara-webkit"
|
5
|
+
before do
|
6
|
+
@session = Capybara::Session.new(:accessible_webkit, AccessibleApp)
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'a page without accessibility errors' do
|
10
|
+
it 'does not raise an exception on audit failures' do
|
11
|
+
expect { @session.visit('/accessible') }.to_not raise_error
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'a page with inaccessible elements' do
|
16
|
+
it 'raises an exception on visiting the page' do
|
17
|
+
expect { @session.visit('/inaccessible') }.to raise_error(Capybara::Accessible::InaccessibleError)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'raises an exception when visiting the page via a link' do
|
21
|
+
@session.visit('/accessible')
|
22
|
+
expect { @session.click_link('inaccessible') }.to raise_error(Capybara::Accessible::InaccessibleError)
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'with configuration that excludes rules' do
|
26
|
+
before do
|
27
|
+
Capybara::Accessible::Auditor.exclusions = ['AX_TEXT_01']
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'does not raise an error on an excluded rule' do
|
31
|
+
expect { @session.visit('/excluded') }.to_not raise_error
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with log level set to warn' do
|
36
|
+
before do
|
37
|
+
Capybara::Accessible::Auditor.log_level = :warn
|
38
|
+
end
|
39
|
+
|
40
|
+
after do
|
41
|
+
Capybara::Accessible::Auditor.log_level = :error
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'puts to stdout and does not raise an error' do
|
45
|
+
$stdout.should_receive(:puts)
|
46
|
+
expect { @session.visit('/inaccessible') }.to_not raise_error
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'a page with a javascript popup' do
|
51
|
+
it 'does not raise an exception' do
|
52
|
+
@session.visit('/alert')
|
53
|
+
expect { @session.click_link('Alert!') }.to_not raise_error
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'with severity set to severe' do
|
58
|
+
before do
|
59
|
+
Capybara::Accessible::Auditor.severe_rules = ['AX_TEXT_02']
|
60
|
+
end
|
61
|
+
|
62
|
+
after do
|
63
|
+
Capybara::Accessible::Auditor.severe_rules = []
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'raises an exception on the image without alt text' do
|
67
|
+
expect { @session.visit('/severe') }.to raise_error(Capybara::Accessible::InaccessibleError)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
metadata
CHANGED
@@ -1,126 +1,139 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: capybara-accessible
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Case Commons
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2014-
|
11
|
+
date: 2014-06-19 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: capybara
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- - ~>
|
17
|
+
- - "~>"
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '2.0'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- - ~>
|
24
|
+
- - "~>"
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '2.0'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: selenium-webdriver
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - ">="
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: '0'
|
38
|
-
type: :
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: capybara-webkit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: poltergeist
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
39
63
|
prerelease: false
|
40
64
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
65
|
requirements:
|
43
|
-
- -
|
66
|
+
- - ">="
|
44
67
|
- !ruby/object:Gem::Version
|
45
68
|
version: '0'
|
46
69
|
- !ruby/object:Gem::Dependency
|
47
70
|
name: rake
|
48
71
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
72
|
requirements:
|
51
|
-
- -
|
73
|
+
- - ">="
|
52
74
|
- !ruby/object:Gem::Version
|
53
75
|
version: '0'
|
54
76
|
type: :development
|
55
77
|
prerelease: false
|
56
78
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
79
|
requirements:
|
59
|
-
- -
|
80
|
+
- - ">="
|
60
81
|
- !ruby/object:Gem::Version
|
61
82
|
version: '0'
|
62
83
|
- !ruby/object:Gem::Dependency
|
63
84
|
name: rspec
|
64
85
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
86
|
requirements:
|
67
|
-
- -
|
87
|
+
- - ">="
|
68
88
|
- !ruby/object:Gem::Version
|
69
89
|
version: '0'
|
70
90
|
type: :development
|
71
91
|
prerelease: false
|
72
92
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
93
|
requirements:
|
75
|
-
- -
|
94
|
+
- - ">="
|
76
95
|
- !ruby/object:Gem::Version
|
77
96
|
version: '0'
|
78
97
|
- !ruby/object:Gem::Dependency
|
79
98
|
name: pry
|
80
99
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
100
|
requirements:
|
83
|
-
- -
|
101
|
+
- - ">="
|
84
102
|
- !ruby/object:Gem::Version
|
85
103
|
version: '0'
|
86
104
|
type: :development
|
87
105
|
prerelease: false
|
88
106
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
107
|
requirements:
|
91
|
-
- -
|
108
|
+
- - ">="
|
92
109
|
- !ruby/object:Gem::Version
|
93
110
|
version: '0'
|
94
111
|
- !ruby/object:Gem::Dependency
|
95
112
|
name: tddium
|
96
113
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
114
|
requirements:
|
99
|
-
- -
|
115
|
+
- - ">="
|
100
116
|
- !ruby/object:Gem::Version
|
101
117
|
version: '0'
|
102
118
|
type: :development
|
103
119
|
prerelease: false
|
104
120
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
121
|
requirements:
|
107
|
-
- -
|
122
|
+
- - ">="
|
108
123
|
- !ruby/object:Gem::Version
|
109
124
|
version: '0'
|
110
125
|
- !ruby/object:Gem::Dependency
|
111
126
|
name: sinatra
|
112
127
|
requirement: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
128
|
requirements:
|
115
|
-
- -
|
129
|
+
- - ">="
|
116
130
|
- !ruby/object:Gem::Version
|
117
131
|
version: '0'
|
118
132
|
type: :development
|
119
133
|
prerelease: false
|
120
134
|
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
none: false
|
122
135
|
requirements:
|
123
|
-
- -
|
136
|
+
- - ">="
|
124
137
|
- !ruby/object:Gem::Version
|
125
138
|
version: '0'
|
126
139
|
description: Capybara extension and webdriver for automated accessibility testing
|
@@ -130,7 +143,7 @@ executables: []
|
|
130
143
|
extensions: []
|
131
144
|
extra_rdoc_files: []
|
132
145
|
files:
|
133
|
-
- .gitignore
|
146
|
+
- ".gitignore"
|
134
147
|
- Gemfile
|
135
148
|
- LICENSE.txt
|
136
149
|
- README.md
|
@@ -138,44 +151,48 @@ files:
|
|
138
151
|
- capybara-accessible.gemspec
|
139
152
|
- lib/capybara/accessible.rb
|
140
153
|
- lib/capybara/accessible/auditor.rb
|
141
|
-
- lib/capybara/accessible/
|
154
|
+
- lib/capybara/accessible/driver_extensions.rb
|
142
155
|
- lib/capybara/accessible/element.rb
|
143
156
|
- lib/capybara/accessible/railtie.rb
|
144
157
|
- lib/capybara/accessible/tasks.rb
|
145
158
|
- lib/capybara/accessible/version.rb
|
146
159
|
- lib/vendor/google/accessibility-developer-tools/axs_testing.js
|
147
160
|
- spec/accessible_app.rb
|
148
|
-
- spec/
|
161
|
+
- spec/poltergeist_driver_spec.rb
|
162
|
+
- spec/selenium_driver_spec.rb
|
149
163
|
- spec/spec_helper.rb
|
164
|
+
- spec/webkit_driver_spec.rb
|
150
165
|
- tddium.yml
|
151
166
|
- travis.yml
|
152
167
|
homepage: https://github.com/Casecommons/capybara-accessible
|
153
168
|
licenses:
|
154
169
|
- MIT
|
170
|
+
metadata: {}
|
155
171
|
post_install_message:
|
156
172
|
rdoc_options: []
|
157
173
|
require_paths:
|
158
174
|
- lib
|
159
175
|
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
-
none: false
|
161
176
|
requirements:
|
162
|
-
- -
|
177
|
+
- - ">="
|
163
178
|
- !ruby/object:Gem::Version
|
164
179
|
version: '0'
|
165
180
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
166
|
-
none: false
|
167
181
|
requirements:
|
168
|
-
- -
|
182
|
+
- - ">="
|
169
183
|
- !ruby/object:Gem::Version
|
170
184
|
version: '0'
|
171
185
|
requirements: []
|
172
186
|
rubyforge_project:
|
173
|
-
rubygems_version:
|
187
|
+
rubygems_version: 2.2.2
|
174
188
|
signing_key:
|
175
|
-
specification_version:
|
189
|
+
specification_version: 4
|
176
190
|
summary: A Selenium based webdriver and Capybara extension that runs Google Accessibility
|
177
191
|
Developer Tools auditing assertions on page visits.
|
178
192
|
test_files:
|
179
193
|
- spec/accessible_app.rb
|
180
|
-
- spec/
|
194
|
+
- spec/poltergeist_driver_spec.rb
|
195
|
+
- spec/selenium_driver_spec.rb
|
181
196
|
- spec/spec_helper.rb
|
197
|
+
- spec/webkit_driver_spec.rb
|
198
|
+
has_rdoc:
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module Capybara::Accessible
|
2
|
-
class Driver < Capybara::Selenium::Driver
|
3
|
-
include Capybara::Accessible::Auditor
|
4
|
-
|
5
|
-
def visit(path)
|
6
|
-
super
|
7
|
-
if Capybara.current_driver == :accessible && audit_failures.any?
|
8
|
-
if Capybara::Accessible::Auditor.log_level == :warn
|
9
|
-
puts failure_messages
|
10
|
-
else
|
11
|
-
raise Capybara::Accessible::InaccessibleError, failure_messages
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|