capybara-accessible 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|