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 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 or capybara-webkit drivers for Capybara.
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
- Capybara.default_driver = :accessible
41
- Capybara.javascript_driver = :accessible
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,
@@ -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
- spec.add_dependency("selenium-webdriver")
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"
@@ -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
- Capybara::Accessible::Driver.new(app)
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 << self
5
- def skip_audit
6
- @disabled = true
7
- yield
8
- ensure
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
- module Auditor
14
- def self.exclusions=(rules)
15
- @@exclusions = rules
16
- end
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
- def self.exclusions
19
- @@exclusions ||= []
18
+ private
19
+ def modal_dialog_present?
20
+ Capybara::Accessible.driver_adapter.modal_dialog_present?(driver)
21
+ end
20
22
  end
21
23
 
22
- def self.log_level=(level)
23
- @@log_level= level
24
+ class Driver < self
25
+ def initialize(driver)
26
+ @driver = driver
27
+ end
24
28
  end
25
29
 
26
- def self.log_level
27
- @@log_level ||= :error
28
- end
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
- def self.severe_rules=(rules)
31
- @@severe_rules = rules
63
+ def disabled?
64
+ @disabled
65
+ end
32
66
  end
33
67
 
34
- def self.severe_rules
35
- @@severe_rules ||= []
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
- def audit_rules
39
- File.read(File.expand_path("../../../vendor/google/accessibility-developer-tools/axs_testing.js", __FILE__))
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 audit_failures
43
- if Capybara::Accessible.instance_variable_get(:@disabled)
44
- []
45
- else
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
- "Found at #{page_url} \n\n" + run_script("#{perform_audit_script} return axs.Audit.createReport(results)")
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
- private
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
- 'AX_ARIA_01' => 'badAriaRole',
85
- 'AX_ARIA_02' => 'nonExistentAriaLabelledbyElement',
86
- 'AX_ARIA_03' => 'requiredAriaAttributeMissing',
87
- 'AX_ARIA_04' => 'badAriaAttributeValue',
88
- 'AX_TEXT_01' => 'controlsWithoutLabel',
89
- 'AX_TEXT_02' => 'imagesWithoutAltText',
90
- 'AX_TITLE_01' => 'pageWithoutTitle',
91
- 'AX_IMAGE_01' => 'elementsWithMeaningfulBackgroundImage',
92
- 'AX_FOCUS_01' => 'focusableElementNotVisibleAndNotAriaHidden',
93
- 'AX_FOCUS_02' => 'unfocusableElementsWithOnClick',
94
- 'AX_COLOR_01' => 'lowContrastElements',
95
- 'AX_VIDEO_01' => 'videoWithoutCaptions',
96
- 'AX_AUDIO_01' => 'audioWithoutControls'
97
- # 'AX_TITLE_01' => 'linkWithUnclearPurpose', # This has a duplicate name
98
- # 'AX_ARIA_05' => '', # This has no rule associated with it
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
- @session ? @session.driver.current_url : current_url
148
+ driver.current_url
104
149
  end
105
150
 
106
151
  def run_script(script)
107
- if @session
108
- @session.driver.execute_script(script)
109
- else
110
- execute_script(script)
111
- end
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
@@ -0,0 +1,6 @@
1
+ module Capybara::Accessible::DriverExtensions
2
+ def visit(path)
3
+ super
4
+ Capybara::Accessible::Auditor::Driver.new(self).audit!
5
+ end
6
+ 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
- begin
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
@@ -1,5 +1,5 @@
1
1
  module Capybara
2
2
  module Accessible
3
- VERSION = "0.2.0"
3
+ VERSION = "0.2.1"
4
4
  end
5
5
  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/6e426d3e4e38b611bc013c14c2ade77674397627
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.getBoundingClientRect();
547
- var b = document.body.scrollWidth, c = document.body.scrollTop, d = document.body.scrollLeft;
548
- return a.top >= document.body.scrollHeight || a.bottom <= -c || a.left >= b || a.right <= -d ? !0 : !1
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 d = c || !1;
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
- d || (a = axs.properties.getTextFromAriaLabelledby(c, b));
1074
- if(c.hasAttribute("aria-label")) {
1075
- var e = {type:"text"};
1076
- e.text = c.getAttribute("aria-label");
1077
- e.lastWord = axs.properties.getLastWord(e.text);
1078
- a ? e.unused = !0 : d && axs.utils.elementIsHtmlControl(c) || (a = e.text);
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 e.HTMLSelectElement && (b.controlValue = {text:c.value});
1090
- b.controlValue && (e = b.controlValue, a ? e.unused = !0 : a = e.text)
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(d && axs.utils.elementIsAriaWidget(c)) {
1093
- d = c.getAttribute("role");
1094
- "textbox" == d && c.textContent && 0 < c.textContent.length && (b.controlValue = {text:c.textContent});
1095
- if("slider" == d || "spinbutton" == d) {
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" == d) {
1099
- for(var g = c.querySelectorAll("[role=menuitemcheckbox], [role=menuitemradio]"), e = [], f = 0;f < g.length;f++) {
1100
- "true" == g[f].getAttribute("aria-checked") && e.push(g[f])
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 < e.length) {
1118
+ if(0 < d.length) {
1103
1119
  g = "";
1104
- for(f = 0;f < e.length;f++) {
1105
- g += axs.properties.findTextAlternatives(e[f], {}, !0), f < e.length - 1 && (g += ", ")
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" == d || "select" == d) {
1126
+ if("combobox" == e || "select" == e) {
1111
1127
  b.controlValue = {text:"TODO"}
1112
1128
  }
1113
- b.controlValue && (e = b.controlValue, a ? e.unused = !0 : a = e.text)
1129
+ b.controlValue && (d = b.controlValue, a ? d.unused = !0 : a = d.text)
1114
1130
  }
1115
- e = !0;
1116
- c.hasAttribute("role") && (d = c.getAttribute("role"), (d = axs.constants.ARIA_ROLES[d]) && (!d.namefrom || 0 > d.namefrom.indexOf("contents")) && (e = !1));
1117
- (d = axs.properties.getTextFromDescendantContent(c)) && e && (e = {type:"text"}, e.text = d, e.lastWord = axs.properties.getLastWord(e.text), a ? e.unused = !0 : a = d, b.content = e);
1118
- c.hasAttribute("title") && (d = {type:"string", valid:!0}, d.text = c.getAttribute("title"), d.lastWord = axs.properties.getLastWord(d.lastWord), a ? d.unused = !0 : a = d.text, b.title = d);
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
- return axs.utils.isElementOrAncestorHidden(a) ? !1 : !axs.utils.elementIsVisible(a)
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 Capybara::Accessible::Driver do
3
+ describe "Using selenium driver" do
4
4
  before do
5
- @session = Capybara::Session.new(:accessible, AccessibleApp)
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.0
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-01-23 00:00:00.000000000 Z
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: :runtime
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/driver.rb
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/driver_spec.rb
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: 1.8.27
187
+ rubygems_version: 2.2.2
174
188
  signing_key:
175
- specification_version: 3
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/driver_spec.rb
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