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

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