axe-core-api 4.0.0 → 4.2.0.pre.33afee3

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
  SHA256:
3
- metadata.gz: 9643d81e17129aeceeeae359d0bff29ca30fea8fe9291bf35081caee537eec42
4
- data.tar.gz: 58e7bd8b82a0a1a17d047a3417a22e82b794ada5b62447b83acf2a5689a12a9f
3
+ metadata.gz: f912af3af630db04fffaf7bf951e439d8d50b302446cb531928a453d317f55c5
4
+ data.tar.gz: c144064037bd09230de05f6a25c55846dc92742ad126be45d6023db8b2298406
5
5
  SHA512:
6
- metadata.gz: 1926fe5633260d63b6b4f21a5cd5e8387135a7b46767f7796abe8dd354f6c6da996aab61c70b4f700b28d5e7ed892a3d0b2860b9479a0b5baff76d0d50c6e744
7
- data.tar.gz: 5452fd2f97449a985d9eaa9f448c665048aaab8b5f7f7900e5171a78811a2fafb5b217804990963f9d50908ef956f72134b61ee29ee015e0398046f4ace38696
6
+ metadata.gz: 330231cec5eb39c3dbdb18acc05a69d0c19051da826a2753f51364d120445731113eb8fabb3af801294817a6b536ee28083a2086b0f13e1df28490404e39ba82
7
+ data.tar.gz: b84595ee0edd9b02e241248eb47c429d4ae5ae651961f20e08454d79bca303c58042520a2993c1fa124d3ca43e4c096efc5394d1d8d8f5ebe10d2d0c37a280cd
@@ -16,13 +16,19 @@ module Axe
16
16
  @exclusion.concat selectors.map { |s| Array(Selector.new s) }
17
17
  end
18
18
 
19
+ def to_h
20
+ to_hash
21
+ end
19
22
  def to_hash
20
- { include: @inclusion, exclude: @exclusion }
21
- .reject { |k, v| v.empty? }
23
+ return { exclude: @exclusion } if @inclusion.empty?
24
+ h = {}
25
+ h["include"] = @inclusion unless @inclusion.empty?
26
+ h["exclude"] = @exclusion unless @exclusion.empty?
27
+ h
22
28
  end
23
29
 
24
- def to_json
25
- to_hash.to_json
30
+ def to_json(options = nil)
31
+ to_hash.to_json options
26
32
  end
27
33
 
28
34
  def empty?
@@ -14,12 +14,16 @@ module Axe
14
14
  @custom = {}
15
15
  end
16
16
 
17
+ def to_h
18
+ to_hash
19
+ end
20
+
17
21
  def to_hash
18
22
  @rules.to_hash.merge(@custom)
19
23
  end
20
24
 
21
- def to_json
22
- to_hash.to_json
25
+ def to_json(options = nil)
26
+ to_hash.to_json options
23
27
  end
24
28
 
25
29
  def empty?
@@ -10,6 +10,10 @@ module Axe
10
10
  attribute :incomplete, ::Array[Rule]
11
11
  attribute :passes, ::Array[Rule]
12
12
  attribute :timestamp
13
+ attribute :testEngine
14
+ attribute :testEnvironment
15
+ attribute :testRunner
16
+ attribute :toolOptions
13
17
  attribute :url, ::String
14
18
  attribute :violations, ::Array[Rule]
15
19
  end
@@ -28,12 +32,18 @@ module Axe
28
32
  inapplicable: inapplicable.map(&:to_h),
29
33
  incomplete: incomplete.map(&:to_h),
30
34
  passes: passes.map(&:to_h),
35
+ testEngine: testEngine,
31
36
  timestamp: timestamp,
32
37
  url: url,
33
38
  violations: violations.map(&:to_h),
34
39
  }
35
40
  end
36
41
 
42
+ def timestamp=(ts)
43
+ timestamp = ts
44
+ end
45
+
46
+
37
47
  private
38
48
 
39
49
  def violation_count_message
data/lib/axe/api/run.rb CHANGED
@@ -27,21 +27,182 @@ module Axe
27
27
  end
28
28
 
29
29
  def call(page)
30
- audit page do |results|
31
- Audit.new to_js, Results.new(results)
32
- end
30
+ results = audit page
31
+ Audit.new to_js, Results.new(results)
32
+ end
33
+
34
+ def analyze_post_43x(page, lib)
35
+ @original_window = window_handle page
36
+ partial_results = run_partial_recursive(page, @context, lib, true)
37
+ throw partial_results if partial_results.respond_to?("key?") and partial_results.key?("errorMessage")
38
+ results = within_about_blank_context(page) { |page|
39
+ Common::Loader.new(page, lib).load_top_level Axe::Configuration.instance.jslib
40
+ begin
41
+ axe_finish_run page, partial_results
42
+ rescue
43
+ raise StandardError.new "axe.finishRun failed. Please check out https://github.com/dequelabs/axe-core-gems/error-handling.md`"
44
+ end
45
+ }
46
+ Audit.new to_js, Results.new(results)
33
47
  end
34
48
 
35
49
  private
36
50
 
37
51
  def audit(page)
38
- yield page.execute_async_script "#{METHOD_NAME}.apply(#{Core::JS_NAME}, arguments)", *js_args
52
+ script = <<-JS
53
+ var callback = arguments[arguments.length - 1];
54
+ var context = arguments[0] || document;
55
+ var options = arguments[1] || {};
56
+ #{METHOD_NAME}(context, options).then(callback);
57
+ JS
58
+ page.execute_async_script_fixed script, *js_args
59
+ end
60
+
61
+ def switch_to_frame_by_handle(page, handle)
62
+ page = get_selenium page
63
+ page.switch_to.frame handle
64
+ end
65
+
66
+ def switch_to_parent_frame(page)
67
+ page = get_selenium page
68
+ page.switch_to.parent_frame
69
+ end
70
+
71
+ def within_about_blank_context(page)
72
+ driver = get_selenium page
73
+
74
+ num_handles = page.window_handles.length
75
+ begin
76
+ driver.execute_script("window.open('about:blank'), '_blank'")
77
+ if num_handles == page.window_handles.length
78
+ raise StandardError.new "Could not open new window. Please make sure that you have popup blockers disabled."
79
+ end
80
+ driver.switch_to.window page.window_handles[-1]
81
+ rescue
82
+ raise StandardError.new "switchToWindow failed. Are you using updated browser drivers?"
83
+ end
84
+ driver.get "about:blank"
85
+
86
+ ret = yield page
87
+
88
+ driver.switch_to.window page.window_handles[-1]
89
+ driver.close
90
+ driver.switch_to.window @original_window
91
+
92
+ ret
93
+ end
94
+ def window_handle(page)
95
+ page = get_selenium page
96
+
97
+ return page.window_handle if page.respond_to?("window_handle")
98
+ page.current_window_handle
99
+ end
100
+
101
+ def run_partial_recursive(page, context, lib, top_level = false)
102
+ begin
103
+ if not top_level
104
+ begin
105
+ Common::Loader.new(page, lib).load_top_level Axe::Configuration.instance.jslib
106
+ rescue
107
+ return [nil]
108
+ end
109
+
110
+ end
111
+
112
+ frame_contexts = get_frame_context_script page
113
+ if frame_contexts.respond_to?("key?") and frame_contexts.key?("errorMessage")
114
+ throw frame_contexts if top_level
115
+ return [nil]
116
+ end
117
+
118
+ res = axe_run_partial page, context
119
+ if res.key?("errorMessage")
120
+ throw res if top_level
121
+ return [nil]
122
+ else
123
+ results = [res]
124
+ end
125
+
126
+ for frame_context in frame_contexts
127
+ frame_selector = frame_context["frameSelector"]
128
+ frame_context = frame_context["frameContext"]
129
+ frame = axe_shadow_select page, frame_selector
130
+ switch_to_frame_by_handle page, frame
131
+ res = run_partial_recursive page, frame_context, lib
132
+ results += res
133
+ end
134
+
135
+ ensure
136
+ switch_to_parent_frame page if not top_level
137
+ end
138
+ return results
139
+ end
140
+
141
+ def axe_finish_run(page, partial_results)
142
+ script = <<-JS
143
+ const partialResults = arguments[0];
144
+ return axe.finishRun(partialResults);
145
+ JS
146
+ page.execute_script_fixed script, partial_results
147
+ end
148
+
149
+ def axe_shadow_select(page, frame_selector)
150
+ script = <<-JS
151
+ const frameSelector = arguments[0];
152
+ return axe.utils.shadowSelect(frameSelector);
153
+ JS
154
+ page.execute_script_fixed script, frame_selector
155
+ end
156
+
157
+ def axe_run_partial(page, context)
158
+ script = <<-JS
159
+ const context = arguments[0];
160
+ const options = arguments[1];
161
+ const cb = arguments[arguments.length - 1];
162
+ try {
163
+ const ret = window.axe.runPartial(context, options);
164
+ cb(ret);
165
+ } catch (err) {
166
+ const ret = {
167
+ violations: [],
168
+ passes: [],
169
+ url: '',
170
+ timestamp: new Date().toString(),
171
+ errorMessage: err.message
172
+ };
173
+ cb(ret);
174
+ }
175
+ JS
176
+ page.execute_async_script_fixed script, context, @options
177
+ end
178
+
179
+ def get_frame_context_script(page)
180
+ script = <<-JS
181
+ const context = arguments[0];
182
+ try {
183
+ return window.axe.utils.getFrameContexts(context);
184
+ } catch (err) {
185
+ return {
186
+ violations: [],
187
+ passes: [],
188
+ url: '',
189
+ timestamp: new Date().toString(),
190
+ errorMessage: err.message
191
+ };
192
+ }
193
+ JS
194
+ page.execute_script_fixed script, @context
195
+ end
196
+
197
+ def get_selenium(page)
198
+ page = page.driver if page.respond_to?("driver")
199
+ page = page.browser if page.respond_to?("browser") and not page.browser.is_a?(::Symbol)
200
+ page
39
201
  end
40
202
 
41
203
  def js_args
42
204
  [@context, @options]
43
- .reject(&:empty?)
44
- .map(&:to_json)
205
+ .map(&:to_h)
45
206
  end
46
207
 
47
208
  def to_js
@@ -14,7 +14,8 @@ module Axe
14
14
  attr_writer :jslib
15
15
  attr_accessor :page,
16
16
  :jslib_path,
17
- :skip_iframes
17
+ :skip_iframes,
18
+ :legacy_mode
18
19
  def_delegators ::WebDriverScriptAdapter,
19
20
  :async_results_identifier,
20
21
  :async_results_identifier=,
@@ -26,7 +27,7 @@ module Axe
26
27
  # init
27
28
  def initialize
28
29
  @page = :page
29
- @skip_iframes = :skip_iframes
30
+ @skip_iframes = nil
30
31
  @jslib_path = get_root + "/node_modules/axe-core/axe.min.js"
31
32
  end
32
33
 
data/lib/axe/core.rb CHANGED
@@ -14,13 +14,36 @@ module Axe
14
14
  end
15
15
 
16
16
  def call(callable)
17
- callable.call(@page)
17
+ if use_run_partial
18
+ callable.analyze_post_43x @page, self
19
+ else
20
+ callable.call @page
21
+ end
22
+ end
23
+
24
+ def call_verbatim(callable)
25
+ callable.call @page
18
26
  end
19
27
 
20
28
  private
21
29
 
30
+ def use_run_partial
31
+ has_run_partial? and not Axe::Configuration.instance.legacy_mode
32
+ end
33
+
22
34
  def load_axe_core(source)
23
- Common::Loader.new(@page, self).call(source) unless already_loaded?
35
+ return if already_loaded?
36
+ loader = Common::Loader.new(@page, self)
37
+ loader.load_top_level source
38
+ return if use_run_partial
39
+
40
+ loader.call source
41
+ end
42
+
43
+ def has_run_partial?
44
+ @page.evaluate_script <<-JS
45
+ typeof window.axe.runPartial === 'function'
46
+ JS
24
47
  end
25
48
 
26
49
  def already_loaded?
@@ -31,6 +54,7 @@ module Axe
31
54
  end
32
55
 
33
56
  def wrap_driver(driver)
57
+ driver = driver.driver if driver.respond_to? :driver
34
58
  ::WebDriverScriptAdapter::QuerySelectorAdapter.wrap(
35
59
  ::WebDriverScriptAdapter::FrameAdapter.wrap(
36
60
  ::WebDriverScriptAdapter::ExecuteAsyncScriptAdapter.wrap(
data/lib/loader.rb CHANGED
@@ -6,19 +6,33 @@ module Common
6
6
  def initialize(page, lib)
7
7
  @page = page
8
8
  @lib = lib
9
+ @loaded_top_level = false
9
10
  end
10
11
 
11
- def call(source)
12
+ def load_top_level(source)
12
13
  @page.execute_script source
14
+ @loaded_top_level = true
15
+ Common::Hooks.run_after_load @lib
16
+ end
17
+
18
+ def call(source, is_top_level = true)
19
+ @page.execute_script source unless (@loaded_top_level and is_top_level)
20
+ set_allowed_origins
13
21
  Common::Hooks.run_after_load @lib
14
22
  load_into_iframes(source) unless Axe::Configuration.instance.skip_iframes
15
23
  end
16
24
 
17
25
  private
18
26
 
27
+ def set_allowed_origins
28
+ allowed_origins = "<unsafe_all_origins>"
29
+ allowed_origins = "<same_origin>" if Axe::Configuration.instance.legacy_mode
30
+ @page.execute_script "axe.configure({ allowedOrigins: ['#{allowed_origins}'] });"
31
+ end
32
+
19
33
  def load_into_iframes(source)
20
34
  @page.find_frames.each do |iframe|
21
- @page.within_frame(iframe) { call source }
35
+ @page.within_frame(iframe) { call source, false }
22
36
  end
23
37
  end
24
38
  end
@@ -8,7 +8,7 @@ module WebDriverScriptAdapter
8
8
  def self.wrap(driver)
9
9
  raise WebDriverError, "WebDriver must respond to #execute_script" unless driver.respond_to? :execute_script
10
10
 
11
- driver.respond_to?(:evaluate_script) ? driver : new(driver)
11
+ driver.respond_to?(:evaluate_script) ? ExecEvalScriptAdapter2.new(driver) : new(driver)
12
12
  end
13
13
 
14
14
  # executes script without returning result
@@ -21,6 +21,19 @@ module WebDriverScriptAdapter
21
21
  def evaluate_script(script)
22
22
  __getobj__.execute_script "return #{script}"
23
23
  end
24
+
25
+ def execute_script_fixed(script, *args)
26
+ page = __getobj__
27
+ page.execute_script(script, *args)
28
+ end
29
+ end
30
+ class ExecEvalScriptAdapter2 < ::DumbDelegator
31
+ def execute_script_fixed(script, *args)
32
+ page = __getobj__
33
+ page = page.driver if page.respond_to?("driver")
34
+ page = page.browser if page.respond_to?("browser") and not page.browser.is_a?(::Symbol)
35
+ page.execute_script(script, *args)
36
+ end
24
37
  end
25
38
 
26
39
  class WebDriverError < TypeError; end
@@ -3,6 +3,11 @@ require "securerandom"
3
3
  require "timeout"
4
4
  require_relative "./exec_eval_script_adapter"
5
5
 
6
+ def get_selenium(page)
7
+ page = page.driver if page.respond_to?("driver")
8
+ page = page.browser if page.respond_to?("browser") and not page.browser.is_a?(Symbol)
9
+ page
10
+ end
6
11
  module WebDriverScriptAdapter
7
12
  class << self
8
13
  attr_accessor :async_results_identifier,
@@ -76,7 +81,7 @@ module WebDriverScriptAdapter
76
81
 
77
82
  class ExecuteAsyncScriptAdapter < ::DumbDelegator
78
83
  def self.wrap(driver)
79
- new ExecEvalScriptAdapter.wrap driver
84
+ new driver
80
85
  end
81
86
 
82
87
  def execute_async_script(script, *args)
@@ -84,6 +89,13 @@ module WebDriverScriptAdapter
84
89
  execute_script ScriptWriter.async_wrapper(script, *args, ScriptWriter.callback(results))
85
90
  Patiently.wait_until { evaluate_script results }
86
91
  end
92
+
93
+ def execute_async_script_fixed(script, *args)
94
+ page = __getobj__
95
+ page = page.driver if page.respond_to?("driver")
96
+ page = page.browser if page.respond_to?("browser") and not page.browser.is_a?(::Symbol)
97
+ page.execute_async_script(script, *args)
98
+ end
87
99
  end
88
100
 
89
101
  configure do |c|
@@ -3,7 +3,7 @@ require "dumb_delegator"
3
3
  module WebDriverScriptAdapter
4
4
  class FrameAdapter < ::DumbDelegator
5
5
  def self.wrap(driver)
6
- if driver.respond_to?(:within_frame)
6
+ if driver.respond_to?(:find_css)
7
7
  CapybaraAdapter.new driver
8
8
  elsif !driver.respond_to?(:switch_to)
9
9
  WatirAdapter.new driver
@@ -16,20 +16,19 @@ module WebDriverScriptAdapter
16
16
 
17
17
  private
18
18
 
19
- class CapybaraAdapter < ::DumbDelegator
20
- def find_frames
21
- all(:css, "iframe")
19
+ class WatirAdapter < ::DumbDelegator
20
+ def initialize(driver)
21
+ super(driver)
22
+ @driver = driver
22
23
  end
23
- end
24
24
 
25
- class WatirAdapter < ::DumbDelegator
26
25
  # delegate to Watir's Selenium #driver
27
26
  def within_frame(frame, &block)
28
- SeleniumAdapter.instance_method(:within_frame).bind(FrameAdapter.wrap driver).call(frame, &block)
27
+ SeleniumAdapter.instance_method(:within_frame).bind(FrameAdapter.wrap @driver).call(frame, &block)
29
28
  end
30
29
 
31
30
  def find_frames
32
- driver.find_elements(:css, "iframe")
31
+ find_elements(:css, "iframe")
33
32
  end
34
33
  end
35
34
 
@@ -57,6 +56,41 @@ module WebDriverScriptAdapter
57
56
  end
58
57
  end
59
58
 
59
+ class CapybaraAdapter < ::DumbDelegator
60
+ def initialize(driver)
61
+ super(driver)
62
+ @driver = driver
63
+ end
64
+
65
+ def within_frame(frame)
66
+ # Patch the `Symbol` class to respond to the :native method.
67
+ # Will be fixed in https://github.com/teamcapybara/capybara/pull/2462
68
+ (:parent).class.define_method(:native) do
69
+ nil
70
+ end
71
+ switch_to_frame frame
72
+ yield
73
+ ensure
74
+ begin
75
+ switch_to_frame :parent
76
+ rescue => e
77
+ if /switchToParentFrame|frame\/parent/.match(e.message)
78
+ ::Kernel.warn "WARNING:
79
+ This browser only supports first-level iframes.
80
+ Second-level iframes and beyond will not be audited.
81
+ To skip auditing all iframes,
82
+ set Axe::Configuration#skip_iframes=true"
83
+ end
84
+ switch_to_frame :top
85
+ end
86
+ end
87
+
88
+ def find_frames
89
+ find_css("iframe")
90
+ end
91
+ end
92
+
93
+
60
94
  # Selenium Webdriver < 2.43 doesnt support moving back to the parent
61
95
  class ParentlessFrameAdapter < ::DumbDelegator
62
96
  # storage of frame stack (for reverting to parent) taken from Capybara