axe-matchers 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bdf81bdb1412cef3a81c282f9ca50e00e45a5ea2
4
- data.tar.gz: caf2305fffda49e5b17cfddbb2df32ca4d74e452
3
+ metadata.gz: 88bbc1b711577affa1153852e892ed18fc531265
4
+ data.tar.gz: d22ece7e3486a010e178fc94398a14c50a79290c
5
5
  SHA512:
6
- metadata.gz: bfe6802522f3e4c9cc3beed6df69b07ec53d35075afed103293913ee885d328e84f2a4d8401b349b32455d984f13ea9f3f5dff56f9a78ee42394fe1e4cce31de
7
- data.tar.gz: 43c4b5dbbc063725cd4dd7c6f47a86b7b2097081d2d7fd3c9c2ec86ec7949e4a5a27e1e4f53f8345ce51c9e0f72e96cf33baa1041439af1f04e3e6ee4a310cf5
6
+ metadata.gz: ca6f9503838d38f9eeb918e5d9571f0ea487e68ac9e9ee868f24e0fd5fe52b041bf61c89450dead8e75ece88fe8a2e15da9c4037a46de3a83e78664db2899238
7
+ data.tar.gz: fce3a5257299ebf5f9d674c410d19b6979766435e8ec473788ad5fa257c3fc4947f35050beec8d4773a0685d764c702aff48690e640b89a0920e03cff668d376
data/README.md CHANGED
@@ -1,26 +1,32 @@
1
- # Installation
1
+ # axe-matchers
2
+
3
+ Automated accessibility testing powered by aXe.
2
4
 
3
- ## with Bundler
5
+ Provides Cucumber step definitions and RSpec matchers for auditing accessibility.
6
+
7
+ # Installation
4
8
 
5
- add to your `Gemfile`:
9
+ Add this line to your application's Gemfile:
6
10
 
7
11
  ``` ruby
8
12
  gem 'axe-matchers'
9
13
  ```
10
14
 
11
- and then run
15
+ And then execute:
12
16
 
13
17
  ``` sh
14
- bundle install
18
+ $ bundle install
15
19
  ```
16
20
 
17
- ## manually (without Bundler)
21
+ Or install it yourself as:
18
22
 
19
23
  ``` sh
20
- gem install axe-matchers
24
+ $ gem install axe-matchers
21
25
  ```
22
26
 
23
- # Cucumber Configuration
27
+ # Cucumber
28
+
29
+ ## Configuration
24
30
 
25
31
  1. Require step definitions: in `features/support/env.rb` or similar.
26
32
 
@@ -46,21 +52,21 @@ gem install axe-matchers
46
52
  end
47
53
  ```
48
54
 
49
- # Accessibility Steps
55
+ ## Built-In Accessibility Cucumber Steps
50
56
 
51
57
  To construct an axe accessibility Cucumber step, begin with the base step, and append any clauses necessary. All of the following clauses may be mixed and matched; however, they must appear in the specified order:
52
58
 
53
59
  `Then the page should be accessible [including] [excluding] [according-to] [checking-rules/checking-only-rules] [skipping-rules]`
54
60
 
55
- ## Base Step
61
+ ### Base Step
56
62
 
57
63
  ``` gherkin
58
64
  Then the page should be accessible
59
65
  ```
60
66
 
61
- The base step is the core component of the step. It is a complete step on its own and will verify the currently loaded page is accessible using the default configuration of [axe.a11yCheck](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#api-name-axea11ycheck) (the entire document is checked using the default rules).
67
+ The base step is the core component of the step. It is a complete step on its own and will verify the currently loaded page is accessible using the default configuration of [axe.a11yCheck][a11ycheck] (the entire document is checked using the default rules).
62
68
 
63
- ## Inclusion clause
69
+ ### Inclusion clause
64
70
 
65
71
  ``` gherkin
66
72
  Then the page should be accessible within "#selector"
@@ -68,9 +74,9 @@ Then the page should be accessible within "#selector"
68
74
 
69
75
  The inclusion clause (`within "#selector"`) specifies which elements of the page should be checked. A valid CSS selector must be provided, and is surrounded in double quotes. Compound selectors may be used to select multiple elements. e.g. `within "#header, .footer"`
70
76
 
71
- Additional [context parameter documentation](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#a-context-parameter)
77
+ *see additional [context parameter documentation][context-param]*
72
78
 
73
- ## Exclusion clause
79
+ ### Exclusion clause
74
80
 
75
81
  ``` gherkin
76
82
  Then the page should be accessible excluding "#selector"
@@ -78,7 +84,7 @@ Then the page should be accessible excluding "#selector"
78
84
 
79
85
  The exclusion clause (`excluding "#selector"`) specifies which elements of the document should be ignored. A valid CSS selector must be provided, and is surrounded in double quotes. Compound selectors may be used to select multiple elements. e.g. `excluding "#widget, .ad"`
80
86
 
81
- Additional [context parameter documentation](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#a-context-parameter)
87
+ *see additional [context parameter documentation][context-param]*
82
88
 
83
89
  If desired, a semicolon (`;`) or the word `but` may be used to separate the exclusion clause from the inclusion clause (if present).
84
90
 
@@ -87,7 +93,7 @@ Then the page should be accessible within "main"; excluding "aside"
87
93
  Then the page should be accessible within "main" but excluding "aside"
88
94
  ```
89
95
 
90
- ## Accessibility Standard (Tag) clause
96
+ ### Accessibility Standard (Tag) clause
91
97
 
92
98
  ``` gherkin
93
99
  Then the page should be accessible according to: tag-name
@@ -95,7 +101,7 @@ Then the page should be accessible according to: tag-name
95
101
 
96
102
  The tag clause specifies which accessibility standard (or standards) should be used to check the page. The accessibility standards are specified by name (tag). Multiple standards can be specified when comma-separated. e.g. `according to: wcag2a, section508`
97
103
 
98
- The acceptable [tag names are documented](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#b-options-parameter) as well as a [complete listing of rules](https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md) that correspond to each tag/standard.
104
+ The acceptable [tag names are documented][options-param] as well as a [complete listing of rules][rules] that correspond to each tag.
99
105
 
100
106
  If desired, a semicolon (`;`) may be used to separate the tag clause from the preceding clause.
101
107
 
@@ -103,13 +109,15 @@ If desired, a semicolon (`;`) may be used to separate the tag clause from the pr
103
109
  Then the page should be accessible within "#header"; according to: best-practice
104
110
  ```
105
111
 
106
- ## Checking Rules clause
112
+ ### Checking Rules clause
107
113
 
108
114
  ``` gherkin
109
115
  Then the page should be accessible checking: ruleId
110
116
  ```
111
117
 
112
- The checking-rules clause specifies which *additional* rules to run (in addition to the specified tags, if any, or the default ruleset). The rules are specified by comma-separated rule IDs. (see [rules documentation](https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md) for a list of valid rule IDs)
118
+ The checking-rules clause specifies which *additional* rules to run (in addition to the specified tags, if any, or the default ruleset). The rules are specified by comma-separated rule IDs.
119
+
120
+ *see [rules documentation][rules] for a list of valid rule IDs*
113
121
 
114
122
  If desired, a semicolon (`;`) or the word `and` may be used to separate the checking-rules clause from the preceding clause.
115
123
 
@@ -118,7 +126,7 @@ Then the page should be accessible according to: wcag2a; checking: color-contras
118
126
  Then the page should be accessible according to: wcag2a and checking: color-contrast
119
127
  ```
120
128
 
121
- ### Exclusive Rules clause
129
+ #### Exclusive Rules clause
122
130
 
123
131
  ``` gherkin
124
132
  Then the page should be accessible checking only: ruleId
@@ -126,13 +134,15 @@ Then the page should be accessible checking only: ruleId
126
134
 
127
135
  This clause is not really a separate clause. But rather, by adding the word `only` to the checking-rules clause, the meaning of the step can be changed. As described above, by default the checking-rules clause specifies *additional* rules to run. If the word `only` is used, then *only* the specified rules are checked.
128
136
 
129
- ## Skipping Rules clause
137
+ ### Skipping Rules clause
130
138
 
131
139
  ``` gherkin
132
140
  Then the page should be accessible skipping: ruleId
133
141
  ```
134
142
 
135
- The skipping-rules clause specifies which rules to skip. This allows an accessibility standard to be provided (via the tag clause) while ignoring a particular rule. The rules are specified by comma-separated rule IDs. (see [rules documentation](https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md) for a list of valid rule IDs)
143
+ The skipping-rules clause specifies which rules to skip. This allows an accessibility standard to be provided (via the tag clause) while ignoring a particular rule. The rules are specified by comma-separated rule IDs.
144
+
145
+ *see [rules documentation][rules] for a list of valid rule IDs*
136
146
 
137
147
  If desired, a semicolon (`;`) or the word `but` may be used to separate the skipping-rules clause from the preceding clause.
138
148
 
@@ -141,7 +151,7 @@ Then the page should be accessible according to: wcag2a; skipping: accesskeys
141
151
  Then the page should be accessible according to: wcag2a but skipping: accesskeys
142
152
  ```
143
153
 
144
- # Examples
154
+ ## Examples
145
155
 
146
156
  ``` gherkin
147
157
  Then the page should be accessible within "main, header" but excluding "footer"
@@ -152,3 +162,27 @@ Then the page should be accessible checking only: document-title, label
152
162
 
153
163
  Then the page should be accessible according to: best-practice and checking: aria-roles, definition-list
154
164
  ```
165
+
166
+ # WebDrivers
167
+
168
+ axe-matchers supports Capybara, Selenium, and Watir webdrivers; each tested with Firefox, Chrome, Safari, and PhantomJS. Additionally, capybara-webkit and poltergeist are supported.
169
+
170
+ *__Notes:__*
171
+
172
+ - Auditing IFrames is not suppored in Poltergeist < 1.8.0. Upgrade to 1.8.0+ or set `skip_iframes=true` in `Axe.configure`
173
+ - Chrome requires [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/) (tested with 2.21)
174
+ - Safari requires [SafariDriver](https://code.google.com/p/selenium/wiki/SafariDriver) (tested with 2.48)
175
+
176
+
177
+
178
+ [inclusion-clause]: #inclusion-clause
179
+ [exclusion-clause]: #exclusion-clause
180
+ [tag-clause]: #accessibility-standard-tag-clause
181
+ [rules-clause]: #checking-rules-clause
182
+ [exclusive-rules-clause]: #exclusive-rules-clause
183
+ [skipping-rules-clause]: #skipping-rules-clause
184
+
185
+ [a11ycheck]: https://github.com/dequelabs/axe-core/blob/master/doc/API.md#api-name-axea11ycheck
186
+ [context-param]: https://github.com/dequelabs/axe-core/blob/master/doc/API.md#a-context-parameter
187
+ [options-param]: https://github.com/dequelabs/axe-core/blob/master/doc/API.md#b-options-parameter
188
+ [rules]: https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md
data/lib/axe.rb CHANGED
@@ -1,17 +1,8 @@
1
- require 'forwardable'
2
1
  require 'axe/configuration'
2
+ require 'axe/version'
3
3
 
4
4
  module Axe
5
- class << self
6
- extend Forwardable
7
- def_delegator :configuration, :page_from
8
-
9
- def configuration
10
- @configuration ||= Configuration.new
11
- end
12
-
13
- def configure
14
- yield configuration if block_given?
15
- end
5
+ def self.configure
6
+ yield Configuration.instance if block_given?
16
7
  end
17
8
  end
@@ -1,5 +1,4 @@
1
1
  module Axe
2
2
  module API
3
- LIBRARY_IDENTIFIER = "axe"
4
3
  end
5
4
  end
@@ -8,12 +8,13 @@ require 'axe/api/audit'
8
8
  require 'axe/api/context'
9
9
  require 'axe/api/options'
10
10
  require 'axe/api/results'
11
- require 'axe/javascript_library'
11
+ require 'axe/core'
12
12
 
13
13
  module Axe
14
14
  module API
15
15
  class A11yCheck
16
- METHOD_NAME = "#{LIBRARY_IDENTIFIER}.a11yCheck"
16
+ JS_NAME = "a11yCheck"
17
+ METHOD_NAME = "#{Core::JS_NAME}.#{JS_NAME}"
17
18
 
18
19
  extend Forwardable
19
20
  def_delegators :@context, :within, :excluding
@@ -28,7 +29,6 @@ module Axe
28
29
  end
29
30
 
30
31
  def call(page)
31
- inject_axe_lib page
32
32
  audit page do |results|
33
33
  Audit.new to_js, Results.new(results)
34
34
  end
@@ -36,12 +36,8 @@ module Axe
36
36
 
37
37
  private
38
38
 
39
- def inject_axe_lib(page)
40
- JavaScriptLibrary.new.inject_into page
41
- end
42
-
43
39
  def audit(page)
44
- yield page.execute_async_script "#{METHOD_NAME}.apply(#{LIBRARY_IDENTIFIER}, arguments)", @context.to_json, @options.to_json
40
+ yield page.execute_async_script "#{METHOD_NAME}.apply(#{Core::JS_NAME}, arguments)", @context.to_json, @options.to_json
45
41
  end
46
42
 
47
43
  def to_js
@@ -1,42 +1,56 @@
1
1
  require 'forwardable'
2
+ require 'pathname'
3
+ require 'rubygems'
4
+ require 'singleton'
5
+ require 'yaml'
6
+
7
+ require 'axe/hooks'
2
8
  require 'webdriver_script_adapter/execute_async_script_adapter'
3
9
 
4
10
  module Axe
5
11
  class Configuration
12
+ include Singleton
13
+ include Hooks
6
14
  extend Forwardable
7
15
 
8
- attr_accessor :page
16
+ attr_writer :jslib
17
+ attr_accessor :page, :jslib_path, :skip_iframes
18
+
9
19
  def_delegators ::WebDriverScriptAdapter,
10
20
  :async_results_identifier, :async_results_identifier=,
11
21
  :max_wait_time, :max_wait_time=,
12
22
  :wait_interval, :wait_interval=
13
23
 
14
- def page_from(world)
15
- page_from_eval(world) ||
16
- page ||
17
- default_page_from(world) ||
18
- from_ivar(:@page, world) ||
19
- from_ivar(:@browser, world) ||
20
- from_ivar(:@driver, world) ||
21
- from_ivar(:@webdriver, world) ||
22
- NullWebDriver.new
24
+ def initialize
25
+ @page = :page
26
+ @jslib_path = gem_root + '/node_modules/axe-core/axe.min.js'
23
27
  end
24
28
 
25
- private
26
-
27
- def page_from_eval(world)
28
- world.instance_eval "#{page}" if page.is_a?(String) || page.is_a?(Symbol)
29
+ def jslib
30
+ @jslib ||= Pathname.new(@jslib_path).read
29
31
  end
30
32
 
31
- def default_page_from(world)
32
- world.page if world.respond_to? :page
33
+ class << self
34
+ def from_yaml(path="config/axe.yml")
35
+ file = Pathname.new(path)
36
+ from_hash(YAML.load_file(file)) if file.exist?
37
+ instance
38
+ end
39
+
40
+ def from_hash(attributes)
41
+ attributes.each do |k, v|
42
+ instance.__send__("#{k}=", v)
43
+ end
44
+ instance
45
+ end
33
46
  end
34
47
 
35
- def from_ivar(ivar, world)
36
- self.page = ivar
37
- page_from_eval(world)
48
+ private
49
+
50
+ def gem_root
51
+ Gem::Specification.find_by_name('axe-matchers').gem_dir
38
52
  end
39
53
  end
40
-
41
- class NullWebDriver; end
42
54
  end
55
+
56
+ Axe::Configuration.from_yaml
@@ -0,0 +1,46 @@
1
+ require 'webdriver_script_adapter/execute_async_script_adapter'
2
+ require 'webdriver_script_adapter/frame_adapter'
3
+ require 'webdriver_script_adapter/query_selector_adapter'
4
+
5
+ require 'axe/configuration'
6
+ require 'axe/loader'
7
+
8
+ module Axe
9
+ class Core
10
+ JS_NAME = "axe"
11
+
12
+ def initialize(page)
13
+ @page = wrap_driver page
14
+ load_axe_core Axe::Configuration.instance.jslib
15
+ end
16
+
17
+ def call(callable)
18
+ callable.call(@page)
19
+ end
20
+
21
+ private
22
+
23
+ def load_axe_core(source)
24
+ Loader.new(@page, self).call(source) unless already_loaded?
25
+ end
26
+
27
+ def already_loaded?
28
+ @page.evaluate_script <<-JS
29
+ window.#{JS_NAME} &&
30
+ typeof #{JS_NAME}.a11yCheck === 'function'
31
+ JS
32
+ end
33
+
34
+ def wrap_driver(driver)
35
+ ::WebDriverScriptAdapter::QuerySelectorAdapter.wrap(
36
+ ::WebDriverScriptAdapter::FrameAdapter.wrap(
37
+ ::WebDriverScriptAdapter::ExecuteAsyncScriptAdapter.wrap(
38
+ ::WebDriverScriptAdapter::ExecEvalScriptAdapter.wrap(
39
+ driver
40
+ )
41
+ )
42
+ )
43
+ )
44
+ end
45
+ end
46
+ end
@@ -1,6 +1,6 @@
1
1
  require 'yaml'
2
2
 
3
- require 'axe'
3
+ require 'axe/finds_page'
4
4
  require 'axe/matchers'
5
5
  require 'axe/expectation'
6
6
 
@@ -41,7 +41,7 @@ module Axe
41
41
  $/x
42
42
 
43
43
  def self.create_for(world)
44
- new Axe.page_from world
44
+ new(FindsPage.in(world).page)
45
45
  end
46
46
 
47
47
  def initialize(page)
@@ -50,12 +50,12 @@ module Axe
50
50
 
51
51
  def assert_accessibility(negate=false, inclusion="", exclusion="", tags="", run_only=false, run_rules="", skip_rules="", options=nil)
52
52
  is_accessible = Axe::Matchers::BeAccessible.new.tap do |a|
53
- a.within *selector(inclusion)
54
- a.excluding *selector(exclusion)
55
- a.according_to *split(tags)
56
- a.checking *split(run_rules) unless run_only
57
- a.checking_only *split(run_rules) if run_only
58
- a.skipping *split(skip_rules)
53
+ a.within(*selector(inclusion))
54
+ a.excluding(*selector(exclusion))
55
+ a.according_to(*split(tags))
56
+ a.checking(*split(run_rules)) unless run_only
57
+ a.checking_only(*split(run_rules)) if run_only
58
+ a.skipping(*split(skip_rules))
59
59
  a.with_options to_hash(options)
60
60
  end
61
61
 
@@ -0,0 +1,55 @@
1
+ require 'axe/configuration'
2
+
3
+ module Axe
4
+ class FindsPage
5
+ WEBDRIVER_NAMES = [ :page, :browser, :driver, :webdriver ]
6
+
7
+ class << self
8
+ alias :in :new
9
+ end
10
+
11
+ def initialize(world)
12
+ @world = world
13
+ end
14
+
15
+ def page
16
+ from_configuration || implicit or raise "A page/browser/webdriver must be configured"
17
+ end
18
+
19
+ private
20
+
21
+ def configuration
22
+ Axe::Configuration.instance
23
+ end
24
+
25
+ def from_configuration
26
+ if configuration.page.is_a?(String) || configuration.page.is_a?(Symbol)
27
+ from_world(configuration.page)
28
+ else
29
+ configuration.page
30
+ end
31
+ end
32
+
33
+ def implicit
34
+ WEBDRIVER_NAMES.map(&method(:from_world)).drop_while(&:nil?).first
35
+ end
36
+
37
+ def from_world(name)
38
+ via_method(name) || via_ivar(name)
39
+ end
40
+
41
+ def via_method(name)
42
+ @world.__send__(name) if @world.respond_to?(name)
43
+ end
44
+
45
+ def via_ivar(name)
46
+ name = ensure_ivar_format(name)
47
+ @world.instance_variable_get(name) if @world.instance_variables.include?(name)
48
+ end
49
+
50
+ def ensure_ivar_format(name)
51
+ # ensure leading '@'
52
+ name.to_s.sub(/^([^@])/, '@\1').to_sym
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,32 @@
1
+ module Axe
2
+ module Hooks
3
+ HOOKS = [ :after_load ]
4
+
5
+ HOOKS.each do |hook_name|
6
+ # define instance-level registration method per hook
7
+ define_method hook_name do |callable=nil, &block|
8
+ callable ||= block
9
+ Hooks.callbacks.fetch(hook_name) << callable if callable
10
+ end
11
+
12
+ # define singleton-level run_* method per hook
13
+ define_singleton_method "run_#{hook_name}" do |*args|
14
+ callbacks.fetch(hook_name).each do |callback|
15
+ callback.call(*args)
16
+ end
17
+ end
18
+ end
19
+
20
+ # beware, the callbacks hash is a single shared instance tied to this module
21
+ def self.callbacks
22
+ @callbacks ||= initialize_callbacks_array_per_hook
23
+ end
24
+
25
+ private
26
+
27
+ def self.initialize_callbacks_array_per_hook
28
+ Hash[ HOOKS.map{|name| [name, []]} ]
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ require 'axe/configuration'
2
+ require 'axe/hooks'
3
+
4
+ module Axe
5
+ class Loader
6
+ def initialize(page, lib)
7
+ @page = page
8
+ @lib = lib
9
+ end
10
+
11
+ def call(source)
12
+ @page.execute_script source
13
+ Axe::Hooks.run_after_load @lib
14
+ load_into_iframes(source) unless Axe::Configuration.instance.skip_iframes
15
+ end
16
+
17
+ private
18
+
19
+ def load_into_iframes(source)
20
+ iframes.each do |iframe|
21
+ @page.within_frame(iframe) { call source }
22
+ end
23
+ end
24
+
25
+ def iframes
26
+ @page.find_elements(:tag_name, "iframe")
27
+ end
28
+ end
29
+ end
@@ -1,6 +1,6 @@
1
1
  require 'forwardable'
2
2
  require 'chain_mail/chainable'
3
- require 'axe/page'
3
+ require 'axe/core'
4
4
  require 'axe/api/a11y_check'
5
5
 
6
6
  module Axe
@@ -18,8 +18,7 @@ module Axe
18
18
  end
19
19
 
20
20
  def matches?(page)
21
- @audit = @a11y_check.call Page.new page
22
-
21
+ @audit = Core.new(page).call @a11y_check
23
22
  @audit.passed?
24
23
  end
25
24
  end
@@ -1,3 +1,29 @@
1
1
  module Axe
2
- VERSION = "1.1.1"
2
+ module VERSION
3
+ MAJOR=1
4
+ MINOR=2
5
+ PATCH=0
6
+ PRE=nil
7
+ BUILD=nil
8
+
9
+ class << self
10
+ def to_s
11
+ [MAJOR, MINOR, PATCH].join(".") + pre + build
12
+ end
13
+
14
+ private
15
+
16
+ def pre
17
+ empty?(PRE) ? "" : "-#{PRE}"
18
+ end
19
+
20
+ def build
21
+ empty?(BUILD) ? "" : "+#{BUILD}"
22
+ end
23
+
24
+ def empty?(v)
25
+ v.nil? || v.empty?
26
+ end
27
+ end
28
+ end
3
29
  end
@@ -0,0 +1,63 @@
1
+ require 'dumb_delegator'
2
+
3
+ module WebDriverScriptAdapter
4
+ class FrameAdapter < ::DumbDelegator
5
+
6
+ def self.wrap(driver)
7
+ if driver.respond_to?(:within_frame)
8
+ driver #capybara already supports within_frame
9
+ elsif !driver.respond_to?(:switch_to)
10
+ WatirAdapter.new driver
11
+ elsif driver.switch_to.respond_to?(:parent_frame)
12
+ new driver # add within_frame to selenium
13
+ else
14
+ ParentlessFrameAdapter.new driver # old selenium doesn't support parent_frame
15
+ end
16
+ end
17
+
18
+ def within_frame(frame)
19
+ switch_to.frame(frame)
20
+ yield
21
+ ensure
22
+ begin
23
+ switch_to.parent_frame
24
+ rescue => e
25
+ if /switchToParentFrame|frame\/parent/.match(e.message)
26
+ ::Kernel.warn "WARNING: This browser only supports first-level iframes. Second-level iframes and beyond will not be audited. To skip auditing all iframes, set Axe::Configuration#skip_iframes=true"
27
+ end
28
+ switch_to.default_content
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ class WatirAdapter < ::DumbDelegator
35
+ # delegate to Watir's Selenium #driver
36
+ def within_frame(frame, &block)
37
+ FrameAdapter.instance_method(:within_frame).bind(FrameAdapter.wrap driver).call(frame.wd, &block)
38
+ end
39
+ end
40
+
41
+ # Selenium Webdriver < 2.43 doesnt support moving back to the parent
42
+ class ParentlessFrameAdapter < ::DumbDelegator
43
+
44
+ # storage of frame stack (for reverting to parent) taken from Capybara
45
+ # : https://github.com/jnicklas/capybara/blob/2.6.2/lib/capybara/selenium/driver.rb#L117-L147
46
+ #
47
+ # There doesnt appear to be any way in Selenium Webdriver < 2.43 to move back to a parent frame
48
+ # other than going back to the root and then reiterating down
49
+ def within_frame(frame)
50
+ @frame_stack[window_handle] ||= []
51
+ @frame_stack[window_handle] << frame
52
+
53
+ switch_to.frame(frame)
54
+ yield
55
+ ensure
56
+ @frame_stack[window_handle].pop
57
+ switch_to.default_content
58
+ @frame_stack[window_handle].each { |f| switch_to.frame(f) }
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,18 @@
1
+ require 'dumb_delegator'
2
+
3
+ module WebDriverScriptAdapter
4
+ class QuerySelectorAdapter < ::DumbDelegator
5
+
6
+ def self.wrap(driver)
7
+ # capybara: all(<tag>) but also seems to support all(:tag_name, <tag>)
8
+ # watir: elements(:tag_name); also supports #iframes
9
+ # selenium: find_elements(:tag_name, <tag>); aliased as all
10
+
11
+ driver.respond_to?(:find_elements) ? driver : new(driver)
12
+ end
13
+
14
+ def find_elements(*args)
15
+ respond_to?(:elements) ? elements(*args) : all(*args)
16
+ end
17
+ end
18
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: axe-matchers
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Deque Systems
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-09-04 00:00:00.000000000 Z
12
+ date: 2016-03-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: dumb_delegator
@@ -67,6 +67,20 @@ dependencies:
67
67
  - - ~>
68
68
  - !ruby/object:Gem::Version
69
69
  version: '1.3'
70
+ - !ruby/object:Gem::Dependency
71
+ name: pry
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
70
84
  - !ruby/object:Gem::Dependency
71
85
  name: rake
72
86
  requirement: !ruby/object:Gem::Requirement
@@ -171,14 +185,14 @@ dependencies:
171
185
  requirements:
172
186
  - - ~>
173
187
  - !ruby/object:Gem::Version
174
- version: '1.6'
188
+ version: '1.8'
175
189
  type: :development
176
190
  prerelease: false
177
191
  version_requirements: !ruby/object:Gem::Requirement
178
192
  requirements:
179
193
  - - ~>
180
194
  - !ruby/object:Gem::Version
181
- version: '1.6'
195
+ version: '1.8'
182
196
  - !ruby/object:Gem::Dependency
183
197
  name: selenium-webdriver
184
198
  requirement: !ruby/object:Gem::Requirement
@@ -232,21 +246,25 @@ files:
232
246
  - lib/axe/api/value_object.rb
233
247
  - lib/axe/api.rb
234
248
  - lib/axe/configuration.rb
249
+ - lib/axe/core.rb
235
250
  - lib/axe/cucumber/step.rb
236
251
  - lib/axe/cucumber/step_definitions.rb
237
252
  - lib/axe/cucumber.rb
238
253
  - lib/axe/dsl.rb
239
254
  - lib/axe/expectation.rb
240
- - lib/axe/javascript_library.rb
255
+ - lib/axe/finds_page.rb
256
+ - lib/axe/hooks.rb
257
+ - lib/axe/loader.rb
241
258
  - lib/axe/matchers/be_accessible.rb
242
259
  - lib/axe/matchers.rb
243
- - lib/axe/page.rb
244
260
  - lib/axe/rspec.rb
245
261
  - lib/axe/version.rb
246
262
  - lib/axe.rb
247
263
  - lib/chain_mail/chainable.rb
248
264
  - lib/webdriver_script_adapter/exec_eval_script_adapter.rb
249
265
  - lib/webdriver_script_adapter/execute_async_script_adapter.rb
266
+ - lib/webdriver_script_adapter/frame_adapter.rb
267
+ - lib/webdriver_script_adapter/query_selector_adapter.rb
250
268
  - node_modules/axe-core/axe.min.js
251
269
  - LICENSE
252
270
  - README.md
@@ -1,26 +0,0 @@
1
- require 'pathname'
2
- require 'rubygems'
3
-
4
- module Axe
5
- class JavaScriptLibrary
6
-
7
- def inject_into(page)
8
- page.execute_script source
9
- end
10
-
11
- def source
12
- axe_lib.read
13
- end
14
-
15
- private
16
-
17
- def axe_lib
18
- gem_root + 'node_modules/axe-core/axe.min.js'
19
- end
20
-
21
- def gem_root
22
- Pathname.new Gem::Specification.find_by_name('axe-matchers').gem_dir
23
- end
24
-
25
- end
26
- end
@@ -1,26 +0,0 @@
1
- require 'forwardable'
2
- require 'webdriver_script_adapter/exec_eval_script_adapter'
3
- require 'webdriver_script_adapter/execute_async_script_adapter'
4
-
5
- module Axe
6
- class Page
7
- extend Forwardable
8
- def_delegators :@driver, :execute_script, :execute_async_script
9
-
10
- def initialize(driver)
11
- @driver = wrap_exec_async wrap_exec_eval driver
12
- end
13
-
14
- private
15
-
16
- # ensure driver has #execute_async_script
17
- def wrap_exec_async(driver)
18
- ::WebDriverScriptAdapter::ExecuteAsyncScriptAdapter.wrap driver
19
- end
20
-
21
- # ensure driver has #execute_script and #evaluate_script
22
- def wrap_exec_eval(driver)
23
- ::WebDriverScriptAdapter::ExecEvalScriptAdapter.wrap driver
24
- end
25
- end
26
- end