axe-core-api 4.1.0 → 4.2.0.pre.edcd659

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: 247006d763d38bfd0b7d9d3f0857df83b6750b01b77a0680d6f05d6806705a62
4
- data.tar.gz: 5e104b3c04ce8675b6eff39a728ec9270d20f60e6fff58d367a26b804ce722c8
3
+ metadata.gz: e3964599a863f53179b8b189e6fa60e4d220240fe362c7b3aa6263bb4686b366
4
+ data.tar.gz: '0558215cb31d486cd8383118e63c0a1c29de40c4aabdbc5c29f8c791aa86037e'
5
5
  SHA512:
6
- metadata.gz: 9a43d1b471a5e69b755903c071a8b999db5d1b1b7c6995a9864165ccb6f0f1f4e9557b7b68d954ab4c2d866a1532c484f0500f11b19ffa4fd238eff28a741375
7
- data.tar.gz: a0504ff01fbdd8ad292a68ebc9314ded4e6abe6d663a0d81e9b971f5e4f07bd1da16a54ad8bf823d4dfcaad4945cb617141c6478188583e8b6dcd6df9682c209
6
+ metadata.gz: 06de18b3a8137ca0f65cec3e9c9c72637854ce5339df2ce2569460bf9cb50c603e703631333204db45ff0b50266b7dcc27ed1812627cd9120d1906c1bf040993
7
+ data.tar.gz: 47f7aeb22a09792f63b4be4f2429be0ef0f03a8c06aaed5e33cd94b80c9d25822841ca9c8d5bc0963e1e5d1c21b7c0ccf027e14bbcac7c0485aedd78656d5718
@@ -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,170 @@ 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
+ axe_finish_run page, partial_results
41
+ }
42
+ Audit.new to_js, Results.new(results)
33
43
  end
34
44
 
35
45
  private
36
46
 
37
47
  def audit(page)
38
- yield page.execute_async_script "#{METHOD_NAME}.apply(#{Core::JS_NAME}, arguments)", *js_args
48
+ script = <<-JS
49
+ var callback = arguments[arguments.length - 1];
50
+ var context = arguments[0] || document;
51
+ var options = arguments[1] || {};
52
+ #{METHOD_NAME}(context, options).then(callback);
53
+ JS
54
+ page.execute_async_script_fixed script, *js_args
55
+ end
56
+
57
+ def switch_to_frame_by_handle(page, handle)
58
+ page = get_selenium page
59
+ page.switch_to.frame handle
60
+ end
61
+
62
+ def switch_to_parent_frame(page)
63
+ page = get_selenium page
64
+ page.switch_to.parent_frame
65
+ end
66
+
67
+ def within_about_blank_context(page)
68
+ driver = get_selenium page
69
+
70
+ driver.execute_script("window.open('about:blank'), '_blank'")
71
+ driver.switch_to.window page.window_handles[-1]
72
+ driver.get "about:blank"
73
+
74
+ ret = yield page
75
+
76
+ driver.switch_to.window page.window_handles[-1]
77
+ driver.close
78
+ driver.switch_to.window @original_window
79
+
80
+ ret
81
+ end
82
+ def window_handle(page)
83
+ page = get_selenium page
84
+
85
+ return page.window_handle if page.respond_to?("window_handle")
86
+ page.current_window_handle
87
+ end
88
+
89
+ def run_partial_recursive(page, context, lib, top_level = false)
90
+ begin
91
+ if not top_level
92
+ begin
93
+ Common::Loader.new(page, lib).load_top_level Axe::Configuration.instance.jslib
94
+ rescue
95
+ return [nil]
96
+ end
97
+
98
+ end
99
+
100
+ frame_contexts = get_frame_context_script page
101
+ if frame_contexts.respond_to?("key?") and frame_contexts.key?("errorMessage")
102
+ throw frame_contexts if top_level
103
+ return [nil]
104
+ end
105
+
106
+ res = axe_run_partial page, context
107
+ if res.key?("errorMessage")
108
+ throw res if top_level
109
+ return [nil]
110
+ else
111
+ results = [res]
112
+ end
113
+
114
+ for frame_context in frame_contexts
115
+ frame_selector = frame_context["frameSelector"]
116
+ frame_context = frame_context["frameContext"]
117
+ frame = axe_shadow_select page, frame_selector
118
+ switch_to_frame_by_handle page, frame
119
+ res = run_partial_recursive page, frame_context, lib
120
+ results += res
121
+ end
122
+
123
+ ensure
124
+ switch_to_parent_frame page if not top_level
125
+ end
126
+ return results
127
+ end
128
+
129
+ def axe_finish_run(page, partial_results)
130
+ script = <<-JS
131
+ const partialResults = arguments[0];
132
+ return axe.finishRun(partialResults);
133
+ JS
134
+ page.execute_script_fixed script, partial_results
135
+ end
136
+
137
+ def axe_shadow_select(page, frame_selector)
138
+ script = <<-JS
139
+ const frameSelector = arguments[0];
140
+ return axe.utils.shadowSelect(frameSelector);
141
+ JS
142
+ page.execute_script_fixed script, frame_selector
143
+ end
144
+
145
+ def axe_run_partial(page, context)
146
+ script = <<-JS
147
+ const context = arguments[0];
148
+ const options = arguments[1];
149
+ const cb = arguments[arguments.length - 1];
150
+ try {
151
+ const ret = window.axe.runPartial(context, options);
152
+ cb(ret);
153
+ } catch (err) {
154
+ const ret = {
155
+ violations: [],
156
+ passes: [],
157
+ url: '',
158
+ timestamp: new Date().toString(),
159
+ errorMessage: err.message
160
+ };
161
+ cb(ret);
162
+ }
163
+ JS
164
+ page.execute_async_script_fixed script, context, @options
165
+ end
166
+
167
+ def get_frame_context_script(page)
168
+ script = <<-JS
169
+ const context = arguments[0];
170
+ try {
171
+ return window.axe.utils.getFrameContexts(context);
172
+ } catch (err) {
173
+ return {
174
+ violations: [],
175
+ passes: [],
176
+ url: '',
177
+ timestamp: new Date().toString(),
178
+ errorMessage: err.message
179
+ };
180
+ }
181
+ JS
182
+ page.execute_script_fixed script, @context
183
+ end
184
+
185
+ def get_selenium(page)
186
+ page = page.driver if page.respond_to?("driver")
187
+ page = page.browser if page.respond_to?("browser") and not page.browser.is_a?(::Symbol)
188
+ page
39
189
  end
40
190
 
41
191
  def js_args
42
192
  [@context, @options]
43
- .reject(&:empty?)
44
- .map(&:to_json)
193
+ .map(&:to_h)
45
194
  end
46
195
 
47
196
  def to_js
@@ -26,7 +26,7 @@ module Axe
26
26
  # init
27
27
  def initialize
28
28
  @page = :page
29
- @skip_iframes = :skip_iframes
29
+ @skip_iframes = nil
30
30
  @jslib_path = get_root + "/node_modules/axe-core/axe.min.js"
31
31
  end
32
32
 
data/lib/axe/core.rb CHANGED
@@ -14,13 +14,28 @@ module Axe
14
14
  end
15
15
 
16
16
  def call(callable)
17
- callable.call(@page)
17
+ if has_run_partial?
18
+ callable.analyze_post_43x @page, self
19
+ else
20
+ callable.call @page
21
+ end
18
22
  end
19
23
 
20
24
  private
21
25
 
22
26
  def load_axe_core(source)
23
- Common::Loader.new(@page, self).call(source) unless already_loaded?
27
+ return if already_loaded?
28
+ loader = Common::Loader.new(@page, self)
29
+ loader.load_top_level source
30
+ return if has_run_partial?
31
+
32
+ loader.call source
33
+ end
34
+
35
+ def has_run_partial?
36
+ @page.evaluate_script <<-JS
37
+ typeof window.axe.runPartial === 'function'
38
+ JS
24
39
  end
25
40
 
26
41
  def already_loaded?
@@ -31,6 +46,7 @@ module Axe
31
46
  end
32
47
 
33
48
  def wrap_driver(driver)
49
+ driver = driver.driver if driver.respond_to? :driver
34
50
  ::WebDriverScriptAdapter::QuerySelectorAdapter.wrap(
35
51
  ::WebDriverScriptAdapter::FrameAdapter.wrap(
36
52
  ::WebDriverScriptAdapter::ExecuteAsyncScriptAdapter.wrap(
data/lib/loader.rb CHANGED
@@ -6,10 +6,18 @@ 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
+ @page.execute_script "axe.configure({ allowedOrigins: ['<unsafe_all_origins>'] });"
13
21
  Common::Hooks.run_after_load @lib
14
22
  load_into_iframes(source) unless Axe::Configuration.instance.skip_iframes
15
23
  end
@@ -18,7 +26,7 @@ module Common
18
26
 
19
27
  def load_into_iframes(source)
20
28
  @page.find_frames.each do |iframe|
21
- @page.within_frame(iframe) { call source }
29
+ @page.within_frame(iframe) { call source, false }
22
30
  end
23
31
  end
24
32
  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