axe-core-api 2.6.1.pre.0f0b25b
Sign up to get free protection for your applications and to get access to all the features.
- 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
|