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 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