axe-core-api 2.6.1.pre.0f0b25b
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 +7 -0
- data/LICENSE +362 -0
- data/README.md +5 -0
- data/lib/axe/api/a11y_check.rb +69 -0
- data/lib/axe/api/audit.rb +24 -0
- data/lib/axe/api/context.rb +35 -0
- data/lib/axe/api/options.rb +32 -0
- data/lib/axe/api/results/check.rb +32 -0
- data/lib/axe/api/results/checked_node.rb +51 -0
- data/lib/axe/api/results/node.rb +35 -0
- data/lib/axe/api/results/rule.rb +58 -0
- data/lib/axe/api/results.rb +48 -0
- data/lib/axe/api/rules.rb +37 -0
- data/lib/axe/api/run.rb +53 -0
- data/lib/axe/api/selector.rb +18 -0
- data/lib/axe/api/value_object.rb +9 -0
- data/lib/axe/configuration.rb +44 -0
- data/lib/axe/core.rb +45 -0
- data/lib/axe/dsl.rb +15 -0
- data/lib/axe/expectation.rb +33 -0
- data/lib/axe/finds_page.rb +55 -0
- data/lib/axe/matchers/be_accessible.rb +36 -0
- data/lib/chain_mail/chainable.rb +19 -0
- data/lib/hooks.rb +31 -0
- data/lib/loader.rb +25 -0
- data/lib/webdriver_script_adapter/exec_eval_script_adapter.rb +27 -0
- data/lib/webdriver_script_adapter/execute_async_script_adapter.rb +94 -0
- data/lib/webdriver_script_adapter/frame_adapter.rb +84 -0
- data/lib/webdriver_script_adapter/query_selector_adapter.rb +17 -0
- data/node_modules/axe-core/axe.min.js +12 -0
- metadata +200 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative "../value_object"
|
2
|
+
require_relative "./checked_node"
|
3
|
+
|
4
|
+
module Axe
|
5
|
+
module API
|
6
|
+
class Results
|
7
|
+
class Rule < ValueObject
|
8
|
+
values do
|
9
|
+
attribute :id, ::Symbol
|
10
|
+
attribute :description, ::String
|
11
|
+
attribute :help, ::String
|
12
|
+
attribute :helpUrl, ::String
|
13
|
+
attribute :impact, ::Symbol
|
14
|
+
attribute :tags, ::Array[::Symbol]
|
15
|
+
attribute :nodes, ::Array[CheckedNode]
|
16
|
+
end
|
17
|
+
|
18
|
+
def failure_messages(index)
|
19
|
+
[
|
20
|
+
title_message(index + 1),
|
21
|
+
*[
|
22
|
+
helpUrl,
|
23
|
+
node_count_message,
|
24
|
+
"",
|
25
|
+
nodes.reject { |n| n.nil? }.map(&:failure_messages).map { |n| n.push("") }.flatten.map(&indent),
|
26
|
+
].flatten.map(&indent),
|
27
|
+
]
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_h
|
31
|
+
{
|
32
|
+
description: description,
|
33
|
+
help: help,
|
34
|
+
helpUrl: helpUrl,
|
35
|
+
id: id,
|
36
|
+
impact: impact,
|
37
|
+
nodes: nodes.map(&:to_h),
|
38
|
+
tags: tags,
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def indent
|
45
|
+
->(line) { line.prepend(" " * 4) unless line.nil? }
|
46
|
+
end
|
47
|
+
|
48
|
+
def title_message(count)
|
49
|
+
"#{count}) #{id}: #{help} (#{impact})"
|
50
|
+
end
|
51
|
+
|
52
|
+
def node_count_message
|
53
|
+
"The following #{nodes.length} #{nodes.length == 1 ? "node" : "nodes"} violate this rule:"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative "./value_object"
|
2
|
+
|
3
|
+
module Axe
|
4
|
+
module API
|
5
|
+
class Results < ValueObject
|
6
|
+
require_relative "./results/rule"
|
7
|
+
|
8
|
+
values do
|
9
|
+
attribute :inapplicable, ::Array[Rule]
|
10
|
+
attribute :incomplete, ::Array[Rule]
|
11
|
+
attribute :passes, ::Array[Rule]
|
12
|
+
attribute :timestamp
|
13
|
+
attribute :url, ::String
|
14
|
+
attribute :violations, ::Array[Rule]
|
15
|
+
end
|
16
|
+
|
17
|
+
def failure_message
|
18
|
+
[
|
19
|
+
"",
|
20
|
+
violation_count_message,
|
21
|
+
"",
|
22
|
+
violations_failure_messages,
|
23
|
+
].flatten.join("\n")
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_h
|
27
|
+
{
|
28
|
+
inapplicable: inapplicable.map(&:to_h),
|
29
|
+
incomplete: incomplete.map(&:to_h),
|
30
|
+
passes: passes.map(&:to_h),
|
31
|
+
timestamp: timestamp,
|
32
|
+
url: url,
|
33
|
+
violations: violations.map(&:to_h),
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def violation_count_message
|
40
|
+
"Found #{violations.count} accessibility #{violations.count == 1 ? "violation" : "violations"}:"
|
41
|
+
end
|
42
|
+
|
43
|
+
def violations_failure_messages
|
44
|
+
violations.each_with_index.map(&:failure_messages)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Axe
|
2
|
+
module API
|
3
|
+
class Rules
|
4
|
+
def initialize
|
5
|
+
@tags = []
|
6
|
+
@included = []
|
7
|
+
@excluded = []
|
8
|
+
@exclusive = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def according_to(*tags)
|
12
|
+
@tags.concat tags.flatten
|
13
|
+
end
|
14
|
+
|
15
|
+
def checking(*rules)
|
16
|
+
@included.concat rules.flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
def checking_only(*rules)
|
20
|
+
@exclusive.concat rules.flatten
|
21
|
+
end
|
22
|
+
|
23
|
+
def skipping(*rules)
|
24
|
+
@excluded.concat rules.flatten
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
{}.tap do |options|
|
29
|
+
# TODO warn that tags + exclusive-rules are incompatible
|
30
|
+
options.merge! runOnly: { type: :tag, values: @tags } unless @tags.empty?
|
31
|
+
options.merge! runOnly: { type: :rule, values: @exclusive } unless @exclusive.empty?
|
32
|
+
options.merge! rules: Hash[@included.product([enabled: true]) + @excluded.product([enabled: false])] unless @included.empty? && @excluded.empty?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/axe/api/run.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
require_relative "../../chain_mail/chainable"
|
5
|
+
require_relative "./audit"
|
6
|
+
require_relative "./context"
|
7
|
+
require_relative "./options"
|
8
|
+
require_relative "./results"
|
9
|
+
require_relative "../core"
|
10
|
+
|
11
|
+
module Axe
|
12
|
+
module API
|
13
|
+
class Run
|
14
|
+
JS_NAME = "run"
|
15
|
+
METHOD_NAME = "#{Core::JS_NAME}.#{JS_NAME}"
|
16
|
+
|
17
|
+
extend Forwardable
|
18
|
+
def_delegators :@context, :within, :excluding
|
19
|
+
def_delegators :@options, :according_to, :checking, :checking_only, :skipping, :with_options
|
20
|
+
|
21
|
+
extend ChainMail::Chainable
|
22
|
+
chainable :within, :excluding, :according_to, :checking, :checking_only, :skipping, :with_options
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@context = Context.new
|
26
|
+
@options = Options.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(page)
|
30
|
+
audit page do |results|
|
31
|
+
Audit.new to_js, Results.new(results)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def audit(page)
|
38
|
+
yield page.execute_async_script "#{METHOD_NAME}.apply(#{Core::JS_NAME}, arguments)", *js_args
|
39
|
+
end
|
40
|
+
|
41
|
+
def js_args
|
42
|
+
[@context, @options]
|
43
|
+
.reject(&:empty?)
|
44
|
+
.map(&:to_json)
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_js
|
48
|
+
str_args = (js_args + ["callback"]).join(", ")
|
49
|
+
"#{METHOD_NAME}(#{str_args});"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Axe
|
2
|
+
module API
|
3
|
+
class Selector
|
4
|
+
def initialize(s)
|
5
|
+
@selector = case s
|
6
|
+
when Array then s
|
7
|
+
when String, Symbol then [String(s)]
|
8
|
+
when Hash then Selector.new(s[:selector]).to_a.unshift s[:iframe]
|
9
|
+
else Selector.new(s.selector).to_a.unshift s.iframe
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_a
|
14
|
+
@selector
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "singleton"
|
2
|
+
require "forwardable"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
require_relative "../hooks"
|
6
|
+
require_relative "../webdriver_script_adapter/execute_async_script_adapter"
|
7
|
+
|
8
|
+
module Axe
|
9
|
+
class Configuration
|
10
|
+
include Singleton
|
11
|
+
include Common::Hooks
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
attr_writer :jslib
|
15
|
+
attr_accessor :page,
|
16
|
+
:jslib_path,
|
17
|
+
:skip_iframes
|
18
|
+
def_delegators ::WebDriverScriptAdapter,
|
19
|
+
:async_results_identifier,
|
20
|
+
:async_results_identifier=,
|
21
|
+
:max_wait_time,
|
22
|
+
:max_wait_time=,
|
23
|
+
:wait_interval,
|
24
|
+
:wait_interval=
|
25
|
+
|
26
|
+
# init
|
27
|
+
def initialize
|
28
|
+
@page = :page
|
29
|
+
@skip_iframes = :skip_iframes
|
30
|
+
@jslib_path = get_root + "/node_modules/axe-core/axe.min.js"
|
31
|
+
end
|
32
|
+
|
33
|
+
# jslib
|
34
|
+
def jslib
|
35
|
+
@jslib ||= Pathname.new(@jslib_path).read
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def get_root
|
41
|
+
Gem::Specification.find_by_name('axe-core-api').gem_dir
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/axe/core.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative "../webdriver_script_adapter/execute_async_script_adapter"
|
2
|
+
require_relative "../webdriver_script_adapter/frame_adapter"
|
3
|
+
require_relative "../webdriver_script_adapter/query_selector_adapter"
|
4
|
+
require_relative "../loader"
|
5
|
+
require_relative "./configuration"
|
6
|
+
|
7
|
+
module Axe
|
8
|
+
class Core
|
9
|
+
JS_NAME = "axe"
|
10
|
+
|
11
|
+
def initialize(page)
|
12
|
+
@page = wrap_driver page
|
13
|
+
load_axe_core Axe::Configuration.instance.jslib
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(callable)
|
17
|
+
callable.call(@page)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def load_axe_core(source)
|
23
|
+
Common::Loader.new(@page, self).call(source) unless already_loaded?
|
24
|
+
end
|
25
|
+
|
26
|
+
def already_loaded?
|
27
|
+
@page.evaluate_script <<-JS
|
28
|
+
window.#{JS_NAME} &&
|
29
|
+
typeof #{JS_NAME}.run === 'function'
|
30
|
+
JS
|
31
|
+
end
|
32
|
+
|
33
|
+
def wrap_driver(driver)
|
34
|
+
::WebDriverScriptAdapter::QuerySelectorAdapter.wrap(
|
35
|
+
::WebDriverScriptAdapter::FrameAdapter.wrap(
|
36
|
+
::WebDriverScriptAdapter::ExecuteAsyncScriptAdapter.wrap(
|
37
|
+
::WebDriverScriptAdapter::ExecEvalScriptAdapter.wrap(
|
38
|
+
driver
|
39
|
+
)
|
40
|
+
)
|
41
|
+
)
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/axe/dsl.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative "./matchers/be_accessible"
|
2
|
+
require_relative "./expectation"
|
3
|
+
|
4
|
+
module Axe
|
5
|
+
module DSL
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# get the be_accessible matcher method
|
9
|
+
extend Matchers
|
10
|
+
|
11
|
+
def expect(page)
|
12
|
+
AccessibilityExpectation.new page
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Axe
|
2
|
+
class AccessibleExpectation
|
3
|
+
def assert(page, matcher)
|
4
|
+
raise matcher.failure_message unless matcher.matches? page
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class InaccessibleExpectation
|
9
|
+
def assert(page, matcher)
|
10
|
+
raise matcher.failure_message_when_negated if matcher.matches? page
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class AccessibilityExpectation
|
15
|
+
def self.create(negate)
|
16
|
+
negate ? InaccessibleExpectation.new : AccessibleExpectation.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(page)
|
20
|
+
@page = page
|
21
|
+
end
|
22
|
+
|
23
|
+
def to(matcher)
|
24
|
+
AccessibleExpectation.new.assert @page, matcher
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_not(matcher)
|
28
|
+
InaccessibleExpectation.new.assert @page, matcher
|
29
|
+
end
|
30
|
+
|
31
|
+
alias :not_to :to_not
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative "./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,36 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
require_relative "../../chain_mail/chainable"
|
4
|
+
require_relative "../core"
|
5
|
+
require_relative "../api/run"
|
6
|
+
|
7
|
+
module Axe
|
8
|
+
module Matchers
|
9
|
+
class BeAccessible
|
10
|
+
extend Forwardable
|
11
|
+
def_delegators :@audit, :failure_message, :failure_message_when_negated
|
12
|
+
def_delegators :@run, :within, :excluding, :according_to, :checking, :checking_only, :skipping, :with_options
|
13
|
+
|
14
|
+
extend ChainMail::Chainable
|
15
|
+
chainable :within, :excluding, :according_to, :checking, :checking_only, :skipping, :with_options
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@run = API::Run.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def audit(page)
|
22
|
+
@audit ||= Core.new(page).call @run
|
23
|
+
end
|
24
|
+
|
25
|
+
def matches?(page)
|
26
|
+
audit(page).passed?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module_function
|
31
|
+
|
32
|
+
def be_accessible
|
33
|
+
BeAccessible.new
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# TODO
|
2
|
+
# - able to be extended
|
3
|
+
# - able to be used without extending (module_function)
|
4
|
+
# - variant that returns nil instead of self
|
5
|
+
module ChainMail
|
6
|
+
module Chainable
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def chainable(*methods)
|
10
|
+
methods.each do |method|
|
11
|
+
original = instance_method(method)
|
12
|
+
define_method method do |*args|
|
13
|
+
original.bind(self).call(*args)
|
14
|
+
self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/hooks.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Common
|
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
|
+
end
|
31
|
+
end
|
data/lib/loader.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative "./axe/configuration"
|
2
|
+
require_relative "./hooks"
|
3
|
+
|
4
|
+
module Common
|
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
|
+
Common::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
|
+
@page.find_frames.each do |iframe|
|
21
|
+
@page.within_frame(iframe) { call source }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'dumb_delegator'
|
2
|
+
|
3
|
+
module WebDriverScriptAdapter
|
4
|
+
# Capybara distinguishes eval from exec
|
5
|
+
# (eval is a query, exec is a command)
|
6
|
+
# this decorator makes webdriver act like capybara
|
7
|
+
class ExecEvalScriptAdapter < ::DumbDelegator
|
8
|
+
def self.wrap(driver)
|
9
|
+
raise WebDriverError, "WebDriver must respond to #execute_script" unless driver.respond_to? :execute_script
|
10
|
+
|
11
|
+
driver.respond_to?(:evaluate_script) ? driver : new(driver)
|
12
|
+
end
|
13
|
+
|
14
|
+
# executes script without returning result
|
15
|
+
def execute_script(script)
|
16
|
+
super
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# returns result of executing script
|
21
|
+
def evaluate_script(script)
|
22
|
+
__getobj__.execute_script "return #{script}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class WebDriverError < TypeError; end
|
27
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require "dumb_delegator"
|
2
|
+
require "securerandom"
|
3
|
+
require "timeout"
|
4
|
+
require_relative "./exec_eval_script_adapter"
|
5
|
+
|
6
|
+
module WebDriverScriptAdapter
|
7
|
+
class << self
|
8
|
+
attr_accessor :async_results_identifier,
|
9
|
+
:max_wait_time,
|
10
|
+
:wait_interval
|
11
|
+
|
12
|
+
def configure
|
13
|
+
yield self
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Defaults
|
18
|
+
module_function
|
19
|
+
|
20
|
+
def async_results_identifier
|
21
|
+
-> { ::SecureRandom.uuid }
|
22
|
+
end
|
23
|
+
|
24
|
+
# set max_wait_time based on type of webdriver
|
25
|
+
def max_wait_time
|
26
|
+
if defined? ::Capybara
|
27
|
+
if ::Capybara.respond_to? :default_max_wait_time
|
28
|
+
::Capybara.default_max_wait_time
|
29
|
+
else
|
30
|
+
::Capybara.default_wait_time
|
31
|
+
end
|
32
|
+
elsif defined? ::Selenium::WebDriver::Wait::DEFAULT_TIMEOUT
|
33
|
+
::Selenium::WebDriver::Wait::DEFAULT_TIMEOUT
|
34
|
+
else
|
35
|
+
3
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# set wait interval based on webdriver
|
40
|
+
def wait_interval
|
41
|
+
if defined? ::Selenium::WebDriver::Wait::DEFAULT_INTERVAL
|
42
|
+
::Selenium::WebDriver::Wait::DEFAULT_INTERVAL
|
43
|
+
else
|
44
|
+
0.1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module ScriptWriter
|
50
|
+
module_function
|
51
|
+
|
52
|
+
def async_results_identifier
|
53
|
+
id = WebDriverScriptAdapter.async_results_identifier
|
54
|
+
"window['#{id.respond_to?(:call) ? id.call : id}']"
|
55
|
+
end
|
56
|
+
|
57
|
+
def callback(resultsIdentifier)
|
58
|
+
"function(err, returnValue){ #{resultsIdentifier} = (err || returnValue); }"
|
59
|
+
end
|
60
|
+
|
61
|
+
def async_wrapper(script, *args)
|
62
|
+
"(function(){ #{script} })(#{args.join(", ")});"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module Patiently
|
67
|
+
module_function
|
68
|
+
|
69
|
+
def wait_until
|
70
|
+
::Timeout.timeout(WebDriverScriptAdapter.max_wait_time) do
|
71
|
+
sleep(WebDriverScriptAdapter.wait_interval) while (value = yield).nil?
|
72
|
+
value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class ExecuteAsyncScriptAdapter < ::DumbDelegator
|
78
|
+
def self.wrap(driver)
|
79
|
+
new ExecEvalScriptAdapter.wrap driver
|
80
|
+
end
|
81
|
+
|
82
|
+
def execute_async_script(script, *args)
|
83
|
+
results = ScriptWriter.async_results_identifier
|
84
|
+
execute_script ScriptWriter.async_wrapper(script, *args, ScriptWriter.callback(results))
|
85
|
+
Patiently.wait_until { evaluate_script results }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
configure do |c|
|
90
|
+
c.async_results_identifier = Defaults.async_results_identifier
|
91
|
+
c.max_wait_time = Defaults.max_wait_time
|
92
|
+
c.wait_interval = Defaults.wait_interval
|
93
|
+
end
|
94
|
+
end
|