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

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