axe-matchers 1.0.0
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 +72 -0
- data/lib/axe.rb +17 -0
- data/lib/axe/api.rb +5 -0
- data/lib/axe/api/a11y_check.rb +48 -0
- data/lib/axe/api/audit.rb +25 -0
- data/lib/axe/api/context.rb +60 -0
- data/lib/axe/api/options.rb +34 -0
- data/lib/axe/api/results.rb +23 -0
- data/lib/axe/api/results/check.rb +24 -0
- data/lib/axe/api/results/checked_node.rb +24 -0
- data/lib/axe/api/results/node.rb +21 -0
- data/lib/axe/api/results/rule.rb +27 -0
- data/lib/axe/api/rules.rb +43 -0
- data/lib/axe/api/value_object.rb +9 -0
- data/lib/axe/configuration.rb +42 -0
- data/lib/axe/cucumber/step.rb +89 -0
- data/lib/axe/cucumber/step_definitions.rb +3 -0
- data/lib/axe/javascript_library.rb +26 -0
- data/lib/axe/matchers.rb +1 -0
- data/lib/axe/matchers/be_accessible.rb +62 -0
- data/lib/axe/page.rb +26 -0
- data/lib/axe/rspec.rb +6 -0
- data/lib/axe/version.rb +3 -0
- data/lib/webdriver_script_adapter/exec_eval_script_adapter.rb +27 -0
- data/lib/webdriver_script_adapter/execute_async_script_adapter.rb +90 -0
- data/node_modules/axe-core/axe.min.js +25 -0
- metadata +270 -0
data/lib/axe/api.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require 'axe/api'
|
5
|
+
require 'axe/api/audit'
|
6
|
+
require 'axe/api/context'
|
7
|
+
require 'axe/api/options'
|
8
|
+
require 'axe/api/results'
|
9
|
+
require 'axe/javascript_library'
|
10
|
+
|
11
|
+
module Axe
|
12
|
+
module API
|
13
|
+
class A11yCheck
|
14
|
+
METHOD_NAME = "#{LIBRARY_IDENTIFIER}.a11yCheck"
|
15
|
+
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
def_delegators :@context, :include, :exclude
|
19
|
+
def_delegators :@options, :rules_by_tags, :run_rules, :skip_rules, :run_only_rules, :custom_options
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@context = Context.new
|
23
|
+
@options = Options.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(page)
|
27
|
+
inject_axe_lib page
|
28
|
+
audit page do |results|
|
29
|
+
Audit.new to_js, Results.new(results)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def inject_axe_lib(page)
|
36
|
+
JavaScriptLibrary.new.inject_into page
|
37
|
+
end
|
38
|
+
|
39
|
+
def audit(page)
|
40
|
+
yield page.execute_async_script "#{METHOD_NAME}.apply(#{LIBRARY_IDENTIFIER}, arguments)", @context.to_json, @options.to_json
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_js
|
44
|
+
"#{METHOD_NAME}(#{@context}, #{@options}, callback);"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Axe
|
2
|
+
module API
|
3
|
+
class Audit
|
4
|
+
|
5
|
+
attr_reader :invocation, :results
|
6
|
+
|
7
|
+
def initialize(invocation, results)
|
8
|
+
@invocation = invocation
|
9
|
+
@results = results
|
10
|
+
end
|
11
|
+
|
12
|
+
def passed?
|
13
|
+
results.violations.count == 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def failure_message
|
17
|
+
"#{results.failure_message}\nInvocation: #{invocation}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def failure_message_when_negated
|
21
|
+
"Expected to find accessibility violations. None were detected.\nInvocation: #{invocation}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Axe
|
4
|
+
module API
|
5
|
+
class Context
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :inclusion, :exclusion
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@inclusion = []
|
12
|
+
@exclusion = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def include(selector)
|
16
|
+
@inclusion.concat ensure_nested_array(selector)
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def exclude(selector)
|
21
|
+
@exclusion.concat ensure_nested_array(selector)
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_hash
|
26
|
+
{}.tap do |context_param|
|
27
|
+
# include key must not be included if empty
|
28
|
+
# (when undefined, defaults to `document`)
|
29
|
+
context_param[:include] = @inclusion unless @inclusion.empty?
|
30
|
+
|
31
|
+
# exclude array allowed to be empty
|
32
|
+
# and must exist in case `include` is omitted
|
33
|
+
# because context_param cannot be empty object ({})
|
34
|
+
context_param[:exclude] = @exclusion
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_json
|
39
|
+
if @inclusion.empty?
|
40
|
+
if @exclusion.empty?
|
41
|
+
"document"
|
42
|
+
else
|
43
|
+
%Q({"include":document,"exclude":#{@exclusion.to_json}})
|
44
|
+
end
|
45
|
+
else
|
46
|
+
to_hash.to_json
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
alias :to_s :to_json
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def ensure_nested_array(selector)
|
55
|
+
Array(selector).map { |s| Array(s) }
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'axe/api/rules'
|
3
|
+
|
4
|
+
module Axe
|
5
|
+
module API
|
6
|
+
class Options
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegator :@rules, :by_tags, :rules_by_tags
|
10
|
+
def_delegator :@rules, :run_only, :run_only_rules
|
11
|
+
def_delegator :@rules, :run, :run_rules
|
12
|
+
def_delegator :@rules, :skip, :skip_rules
|
13
|
+
def_delegator :@custom, :merge!, :custom_options
|
14
|
+
|
15
|
+
attr_reader :rules, :custom
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@rules = Rules.new
|
19
|
+
@custom = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_hash
|
23
|
+
@rules.to_hash.merge(@custom)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_json
|
27
|
+
to_hash.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
alias :to_s :to_json
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'axe/api/value_object'
|
2
|
+
|
3
|
+
module Axe
|
4
|
+
module API
|
5
|
+
class Results < ValueObject
|
6
|
+
require 'axe/api/results/rule'
|
7
|
+
|
8
|
+
values do
|
9
|
+
attribute :url, ::String
|
10
|
+
attribute :timestamp
|
11
|
+
attribute :passes, ::Array[Rule]
|
12
|
+
attribute :violations, ::Array[Rule]
|
13
|
+
end
|
14
|
+
|
15
|
+
def failure_message
|
16
|
+
<<-MSG.gsub(/^\s*/,'')
|
17
|
+
Found #{violations.count} accessibility #{violations.count == 1 ? 'violation' : 'violations'}
|
18
|
+
#{ violations.each_with_index.map(&:failure_message).join("\n\n") }
|
19
|
+
MSG
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'axe/api/value_object'
|
2
|
+
require 'axe/api/results/node'
|
3
|
+
|
4
|
+
module Axe
|
5
|
+
module API
|
6
|
+
class Results
|
7
|
+
class Check < ValueObject
|
8
|
+
values do
|
9
|
+
attribute :id, ::Symbol
|
10
|
+
attribute :impact, ::Symbol
|
11
|
+
attribute :message, ::String
|
12
|
+
attribute :data, ::String
|
13
|
+
attribute :relatedNodes, ::Array[Node]
|
14
|
+
end
|
15
|
+
|
16
|
+
def failure_message
|
17
|
+
<<-MSG
|
18
|
+
#{message}
|
19
|
+
MSG
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'axe/api/results/node'
|
2
|
+
require 'axe/api/results/check'
|
3
|
+
|
4
|
+
module Axe
|
5
|
+
module API
|
6
|
+
class Results
|
7
|
+
class CheckedNode < Node
|
8
|
+
values do
|
9
|
+
attribute :impact, ::Symbol
|
10
|
+
attribute :any, ::Array[Check]
|
11
|
+
attribute :all, ::Array[Check]
|
12
|
+
attribute :none, ::Array[Check]
|
13
|
+
end
|
14
|
+
|
15
|
+
def failure_message
|
16
|
+
<<-MSG
|
17
|
+
#{super}
|
18
|
+
#{[].concat(any).concat(all).map(&:failure_message).join("\n")}
|
19
|
+
MSG
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'axe/api/value_object'
|
2
|
+
|
3
|
+
module Axe
|
4
|
+
module API
|
5
|
+
class Results
|
6
|
+
class Node < ValueObject
|
7
|
+
values do
|
8
|
+
attribute :html, ::String
|
9
|
+
attribute :target #String or Array[String]
|
10
|
+
end
|
11
|
+
|
12
|
+
def failure_message
|
13
|
+
<<-MSG
|
14
|
+
#{Array(target).join(', ')}
|
15
|
+
#{html}
|
16
|
+
MSG
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'axe/api/value_object'
|
2
|
+
require 'axe/api/results/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_message(index)
|
19
|
+
<<-MSG
|
20
|
+
#{index+1}) #{help}: #{helpUrl}
|
21
|
+
#{nodes.map(&:failure_message).join("\n")}
|
22
|
+
MSG
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Axe
|
2
|
+
module API
|
3
|
+
class Rules
|
4
|
+
attr_reader :tags, :included, :excluded, :exclusive
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@tags = []
|
8
|
+
@included = []
|
9
|
+
@excluded = []
|
10
|
+
@exclusive = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def by_tags(tags)
|
14
|
+
@tags += tags
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def run_only(rules)
|
19
|
+
@exclusive += rules
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def run(rules)
|
24
|
+
@included += rules
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def skip(rules)
|
29
|
+
@excluded += rules
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_hash
|
34
|
+
{}.tap do |options|
|
35
|
+
#TODO warn that tags + exclusive-rules are incompatible
|
36
|
+
options.merge! runOnly: { type: :tag, values: @tags } unless @tags.empty?
|
37
|
+
options.merge! runOnly: { type: :rule, values: @exclusive } unless @exclusive.empty?
|
38
|
+
options.merge! rules: Hash[@included.product([enabled: true]) + @excluded.product([enabled: false])] unless @included.empty? && @excluded.empty?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'webdriver_script_adapter/execute_async_script_adapter'
|
3
|
+
|
4
|
+
module Axe
|
5
|
+
class Configuration
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_accessor :page
|
9
|
+
def_delegators ::WebDriverScriptAdapter,
|
10
|
+
:async_results_identifier, :async_results_identifier=,
|
11
|
+
:max_wait_time, :max_wait_time=,
|
12
|
+
:wait_interval, :wait_interval=
|
13
|
+
|
14
|
+
def page_from(world)
|
15
|
+
page_from_eval(world) ||
|
16
|
+
page ||
|
17
|
+
default_page_from(world) ||
|
18
|
+
from_ivar(:@page, world) ||
|
19
|
+
from_ivar(:@browser, world) ||
|
20
|
+
from_ivar(:@driver, world) ||
|
21
|
+
from_ivar(:@webdriver, world) ||
|
22
|
+
NullWebDriver.new
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def page_from_eval(world)
|
28
|
+
world.instance_eval "#{page}" if page.is_a?(String) || page.is_a?(Symbol)
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_page_from(world)
|
32
|
+
world.page if world.respond_to? :page
|
33
|
+
end
|
34
|
+
|
35
|
+
def from_ivar(ivar, world)
|
36
|
+
self.page = ivar
|
37
|
+
page_from_eval(world)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class NullWebDriver; end
|
42
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
require 'axe'
|
4
|
+
require 'axe/matchers/be_accessible'
|
5
|
+
|
6
|
+
# The purpose of this class is to enable private helper methods for assertion
|
7
|
+
# and cucumber argument parsing without leaking the helper methods into the
|
8
|
+
# cucumber World object.
|
9
|
+
# Further, using these helper methods for assert/refute removes the dependency
|
10
|
+
# on rspec. So end users may choose to use any (or non) assertion/expectation
|
11
|
+
# library, as this class uses the Axe Accessibility Matcher directly, without
|
12
|
+
# using a matcher/expectation library DSL.
|
13
|
+
module Axe
|
14
|
+
module Cucumber
|
15
|
+
class Step
|
16
|
+
REGEX = /^
|
17
|
+
|
18
|
+
# require initial phrasing, with 'not' to negate the matcher
|
19
|
+
(?-x:the page should(?<negate> not)? be accessible)
|
20
|
+
|
21
|
+
# optionally specify which subtree to check, via CSS selector
|
22
|
+
(?-x:;? within "(?<inclusion>.*?)")?
|
23
|
+
|
24
|
+
# optionally specify subtrees to be excluded, via CSS selector
|
25
|
+
(?-x:;?(?: but)? excluding "(?<exclusion>.*?)")?
|
26
|
+
|
27
|
+
# optionally specify ruleset via list of comma-separated tags
|
28
|
+
(?-x:;? according to: (?<tags>.*?))?
|
29
|
+
|
30
|
+
# optionally specify rules (as comma-separated list of rule ids) to check
|
31
|
+
# in addition to default ruleset or explicit ruleset specified above via tags
|
32
|
+
# if the 'only' keyword is supplied, then *only* the listed rules are checked, not *additionally*
|
33
|
+
(?-x:;?(?: and)? checking(?<run_only> only)?: (?<run_rules>.*?))?
|
34
|
+
|
35
|
+
# optionally specify rules (as comma-separated list of rule ids) to skip
|
36
|
+
(?-x:;?(?: but)? skipping: (?<skip_rules>.*?))?
|
37
|
+
|
38
|
+
# optionally specify custom options (as a yaml-parsed hash or json string) to pass directly to axe-core
|
39
|
+
(?-x:;? with options: (?<options>.*?))?
|
40
|
+
|
41
|
+
$/x
|
42
|
+
|
43
|
+
def self.create_for(world)
|
44
|
+
new Axe.page_from world
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(page)
|
48
|
+
@page = page
|
49
|
+
end
|
50
|
+
|
51
|
+
def be_accessible(negate, inclusion, exclusion, tags, run_only, run_rules, skip_rules, options)
|
52
|
+
accessibility = Matchers::BeAccessible.new
|
53
|
+
|
54
|
+
accessibility.within(selector inclusion) if inclusion
|
55
|
+
accessibility.excluding(selector exclusion) if exclusion
|
56
|
+
accessibility.according_to(split tags) if tags
|
57
|
+
run_only ? accessibility.checking_only(split run_rules) : accessibility.checking(split run_rules) if run_rules
|
58
|
+
accessibility.skipping(split skip_rules) if skip_rules
|
59
|
+
accessibility.with_options(to_hash options) if options
|
60
|
+
|
61
|
+
if negate then refute accessibility else assert accessibility end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
attr_reader :page
|
67
|
+
|
68
|
+
def selector(selector)
|
69
|
+
split(selector)
|
70
|
+
end
|
71
|
+
|
72
|
+
def split(string)
|
73
|
+
String(string).split(/,\s*/)
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_hash(string)
|
77
|
+
YAML.load string
|
78
|
+
end
|
79
|
+
|
80
|
+
def assert(matcher)
|
81
|
+
raise matcher.failure_message unless matcher.matches? page
|
82
|
+
end
|
83
|
+
|
84
|
+
def refute(matcher)
|
85
|
+
raise matcher.failure_message_when_negated if matcher.matches? page
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|