bbc-a11y 0.0.2
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/.rspec +1 -0
- data/CONTRIBUTING.md +19 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +54 -0
- data/Rakefile +12 -0
- data/bbc-a11y.gemspec +32 -0
- data/bin/a11y +5 -0
- data/example/.a11y.rb +64 -0
- data/example/Gemfile +4 -0
- data/example/Rakefile +3 -0
- data/example/config.ru +1 -0
- data/example/public/missing_header.html +13 -0
- data/example/public/perfect.html +14 -0
- data/example/readme.md +0 -0
- data/features/01_core-purpose.md +24 -0
- data/features/02_validation.feature +31 -0
- data/features/03_javascript.feature +40 -0
- data/features/04_language.feature +58 -0
- data/features/05_page_title.feature +45 -0
- data/features/06_main_landmark.feature +24 -0
- data/features/07_headings.feature +65 -0
- data/features/08_title_attribute.feature +71 -0
- data/features/09_tabindex.feature +38 -0
- data/features/10_form-labels.md +47 -0
- data/features/11_visible-on-focus.md +58 -0
- data/features/13_colour-contrast.md +27 -0
- data/features/14_colour-meaning.md +19 -0
- data/features/15_focusable-controls.md +45 -0
- data/features/16_table.md +109 -0
- data/features/17_control-styles.md +78 -0
- data/features/18_focus-styles.md +36 -0
- data/features/19_form-interactions.md +33 -0
- data/features/20_image-alt.md +34 -0
- data/features/21_min-font-sizes.md +64 -0
- data/features/22_resize-zoom.md +80 -0
- data/features/step_definitions/core_content_steps.rb +3 -0
- data/features/step_definitions/language_steps.rb +21 -0
- data/features/step_definitions/page_steps.rb +46 -0
- data/features/step_definitions/w3c_steps.rb +7 -0
- data/features/support/capybara.rb +38 -0
- data/features/support/skipper.rb +5 -0
- data/features/support/world.rb +3 -0
- data/features/support/world_extender.rb +5 -0
- data/lib/bbc/a11y/cli.rb +49 -0
- data/lib/bbc/a11y/configuration.rb +83 -0
- data/lib/bbc/a11y/cucumber_runner.rb +133 -0
- data/lib/bbc/a11y/cucumber_support/heading_hierarchy.rb +92 -0
- data/lib/bbc/a11y/cucumber_support/language_detector.rb +26 -0
- data/lib/bbc/a11y/cucumber_support/page.rb +81 -0
- data/lib/bbc/a11y/cucumber_support/per_page_checks.rb +28 -0
- data/lib/bbc/a11y/cucumber_support/w3c.rb +36 -0
- data/lib/bbc/a11y/cucumber_support.rb +49 -0
- data/lib/bbc/a11y/version +1 -0
- data/lib/bbc/a11y.rb +4 -0
- data/spec/bbc/a11y/cucumber_support/heading_hierarchy_spec.rb +123 -0
- data/spec/bbc/a11y/cucumber_support/page_spec.rb +130 -0
- metadata +274 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
Then(/^the <html> element must have a `lang` attribute$/) do
|
2
|
+
page.must_have_lang_attribute
|
3
|
+
end
|
4
|
+
|
5
|
+
Then(/^the main natural language of the page must match that attribute$/) do
|
6
|
+
expected_language = language.detect(page)
|
7
|
+
puts "Detected page language: #{expected_language}"
|
8
|
+
page.must_have_lang_attribute_of(expected_language)
|
9
|
+
end
|
10
|
+
|
11
|
+
Then(/^any other tags with a lang attribute must contain text of that language$/) do
|
12
|
+
pending # express the regexp above with the code you wish you had
|
13
|
+
end
|
14
|
+
Then(/^all elements with `lang` attribute must have content in that natural language$/) do
|
15
|
+
pending # express the regexp above with the code you wish you had
|
16
|
+
end
|
17
|
+
|
18
|
+
Then(/^any parts expressed in a natural language different to the main language of the page must have a matching `lang` attribute$/) do
|
19
|
+
pending # express the regexp above with the code you wish you had
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
When(/^I visit the page$/) do
|
2
|
+
browser.visit settings.url
|
3
|
+
end
|
4
|
+
|
5
|
+
When(/^I view the page with JavaScript and CSS disabled$/) do
|
6
|
+
disable_javascript_and_css
|
7
|
+
browser.visit settings.url
|
8
|
+
end
|
9
|
+
|
10
|
+
Then(/^the document should have a title$/) do
|
11
|
+
page.must_have_title
|
12
|
+
end
|
13
|
+
|
14
|
+
Then(/^the title should describe the primary content of the document$/) do
|
15
|
+
begin
|
16
|
+
page.must_have_title_that_contains_h1_text
|
17
|
+
rescue => error
|
18
|
+
assert_title_describes_primary_content_of_document(browser.title, browser)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Then(/^there should be exactly one element with `role="(.*?)"`$/) do |arg1|
|
23
|
+
page.must_have_one_main_element
|
24
|
+
end
|
25
|
+
|
26
|
+
Then(/^there must be exactly one h1 element$/) do
|
27
|
+
page.must_have_one_h1
|
28
|
+
end
|
29
|
+
|
30
|
+
Then(/^each heading must be followed by content or a heading of one level deeper \(h2\-h6\)$/) do
|
31
|
+
puts "Heading hierarchy:"
|
32
|
+
puts page.heading_hierarchy.to_s
|
33
|
+
page.must_have_correct_heading_hierarchy
|
34
|
+
end
|
35
|
+
|
36
|
+
Then(/^there must be no elements with a title attribute whose content is repeated within the element$/) do
|
37
|
+
page.must_have_no_elements_with_title_attribute_content_repeated_within
|
38
|
+
end
|
39
|
+
|
40
|
+
Then(/^any form fields with associated labels do not have a title attribute$/) do
|
41
|
+
page.must_have_no_form_fields_with_label_and_title
|
42
|
+
end
|
43
|
+
|
44
|
+
Then(/^there should be no elements with a tabindex attribte of 0 or greater$/) do
|
45
|
+
page.must_not_have_any_positive_tabindex_values
|
46
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'capybara'
|
2
|
+
require 'capybara/dsl'
|
3
|
+
require 'selenium/webdriver'
|
4
|
+
|
5
|
+
Capybara.register_driver(:without_javascript_or_css) do |app|
|
6
|
+
profile = Selenium::WebDriver::Firefox::Profile.new
|
7
|
+
profile['permissions.default.stylesheet'] = 2
|
8
|
+
profile['javascript.enabled'] = false
|
9
|
+
Capybara::Selenium::Driver.new(app, profile: profile)
|
10
|
+
end
|
11
|
+
|
12
|
+
Capybara.default_driver = :selenium
|
13
|
+
|
14
|
+
Before do
|
15
|
+
Capybara.use_default_driver
|
16
|
+
end
|
17
|
+
|
18
|
+
After do
|
19
|
+
Capybara.reset_sessions!
|
20
|
+
end
|
21
|
+
|
22
|
+
module BBC
|
23
|
+
module A11y
|
24
|
+
|
25
|
+
module Browser
|
26
|
+
def browser
|
27
|
+
Capybara.current_session
|
28
|
+
end
|
29
|
+
|
30
|
+
def disable_javascript_and_css
|
31
|
+
Capybara.current_driver = :without_javascript_or_css
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
World(BBC::A11y::Browser)
|
data/lib/bbc/a11y/cli.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'bbc/a11y/cucumber_runner'
|
2
|
+
require 'bbc/a11y/configuration'
|
3
|
+
|
4
|
+
module BBC
|
5
|
+
module A11y
|
6
|
+
|
7
|
+
# A very thin wrapper around Cucumber which takes settings on the command-line,
|
8
|
+
# stores them somewhere our automation code will be able to find it, and then
|
9
|
+
# runs Cucumber
|
10
|
+
class CLI
|
11
|
+
MissingArgument = Class.new(StandardError)
|
12
|
+
|
13
|
+
def initialize(stdin, stdout, stderr, args)
|
14
|
+
@stdin, @stdout, @stderr, @args = stdin, stdout, stderr, args
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
CucumberRunner.new(settings, cucumber_args).call
|
19
|
+
rescue MissingArgument => error
|
20
|
+
stderr.puts "You missed an argument: #{error.message}"
|
21
|
+
stderr.puts HELP
|
22
|
+
raise SystemExit
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def settings
|
28
|
+
Configuration.parse(File.expand_path(".a11y.rb"))
|
29
|
+
end
|
30
|
+
|
31
|
+
def cucumber_args
|
32
|
+
return unless args.include?('--')
|
33
|
+
args[args.find_index('--')+1..-1].join(' ')
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :stdin, :stderr, :stdout, :args
|
37
|
+
private :stdin, :stderr, :stdout, :args
|
38
|
+
|
39
|
+
HELP = %{
|
40
|
+
Usage: a11y url-to-test [-- cucumber-args]
|
41
|
+
|
42
|
+
url-to-test - URL of the page to run the full set of accessiblity tests against
|
43
|
+
cucumber-args - Arguments to pass to Cucumber when running the tests. See cucumber --help
|
44
|
+
for details.
|
45
|
+
}
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module BBC
|
2
|
+
module A11y
|
3
|
+
|
4
|
+
def self.configure(&block)
|
5
|
+
@settings = Configuration::DSL.new(block).settings
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.configuration
|
9
|
+
@settings ||= Configuration::Settings.new
|
10
|
+
end
|
11
|
+
|
12
|
+
module Configuration
|
13
|
+
def self.parse(file)
|
14
|
+
require file
|
15
|
+
# the file should call BBC::A11y.configure
|
16
|
+
BBC::A11y.configuration
|
17
|
+
end
|
18
|
+
|
19
|
+
class Settings
|
20
|
+
attr_reader :before_all_hooks,
|
21
|
+
:after_all_hooks,
|
22
|
+
:pages
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@before_all_hooks = []
|
26
|
+
@after_all_hooks = []
|
27
|
+
@pages = []
|
28
|
+
freeze
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class PageSettings
|
33
|
+
attr_reader :url, :scenarios_to_skip, :world_extensions
|
34
|
+
|
35
|
+
def initialize(url)
|
36
|
+
@url = url
|
37
|
+
@scenarios_to_skip = []
|
38
|
+
@world_extensions = []
|
39
|
+
freeze
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class DSL
|
44
|
+
attr_reader :settings
|
45
|
+
def initialize(block)
|
46
|
+
@settings = Settings.new
|
47
|
+
instance_eval &block
|
48
|
+
end
|
49
|
+
|
50
|
+
def before_all(&block)
|
51
|
+
settings.before_all_hooks << block
|
52
|
+
end
|
53
|
+
|
54
|
+
def after_all(&block)
|
55
|
+
settings.after_all_hooks << block
|
56
|
+
end
|
57
|
+
|
58
|
+
def page(url, &block)
|
59
|
+
settings.pages << PageDSL.new(url, block).settings
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class PageDSL
|
64
|
+
attr_reader :settings
|
65
|
+
|
66
|
+
def initialize(url, block)
|
67
|
+
@settings = PageSettings.new(url)
|
68
|
+
instance_eval &block if block
|
69
|
+
end
|
70
|
+
|
71
|
+
def skip_scenario(name)
|
72
|
+
@settings.scenarios_to_skip << name
|
73
|
+
end
|
74
|
+
|
75
|
+
def customize_world(&block)
|
76
|
+
@settings.world_extensions << Module.new(&block)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'cucumber'
|
3
|
+
require 'bbc/a11y/cucumber_support'
|
4
|
+
require 'cucumber/core/filter'
|
5
|
+
require 'colorize'
|
6
|
+
|
7
|
+
module BBC
|
8
|
+
module A11y
|
9
|
+
class CucumberFormatter
|
10
|
+
def initialize(*args)
|
11
|
+
@current_feature = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def before_test_case(test_case)
|
15
|
+
on_new_feature(test_case) do |feature|
|
16
|
+
puts
|
17
|
+
puts "#{feature.name}"
|
18
|
+
puts "#{"-" * feature.name.length}"
|
19
|
+
puts
|
20
|
+
end
|
21
|
+
print " - #{test_case.name} "
|
22
|
+
end
|
23
|
+
|
24
|
+
def after_test_case(test_case, result)
|
25
|
+
print colour(result)
|
26
|
+
if result.failed? || result.pending?
|
27
|
+
puts
|
28
|
+
puts result.exception.message.to_s.red
|
29
|
+
puts result.exception.backtrace.join("\n").red
|
30
|
+
end
|
31
|
+
puts
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def colour(result)
|
37
|
+
ResultColour.new(result).apply_to(result.to_s)
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_new_feature(test_case)
|
41
|
+
feature = test_case.source.first
|
42
|
+
if feature != @current_feature
|
43
|
+
@current_feature = feature
|
44
|
+
yield feature
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class ResultColour
|
49
|
+
def initialize(result)
|
50
|
+
@color = :white
|
51
|
+
result.describe_to(self)
|
52
|
+
end
|
53
|
+
|
54
|
+
def passed
|
55
|
+
@color = :green
|
56
|
+
end
|
57
|
+
|
58
|
+
def skipped
|
59
|
+
@color = :blue
|
60
|
+
end
|
61
|
+
|
62
|
+
def pending(*)
|
63
|
+
@color = :yellow
|
64
|
+
end
|
65
|
+
|
66
|
+
def failed
|
67
|
+
@color = :red
|
68
|
+
end
|
69
|
+
|
70
|
+
def duration(*)
|
71
|
+
end
|
72
|
+
|
73
|
+
def exception(*)
|
74
|
+
end
|
75
|
+
|
76
|
+
def apply_to(string)
|
77
|
+
string.send(@color)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
class CucumberRunner
|
84
|
+
def initialize(settings, cucumber_args)
|
85
|
+
@settings = settings
|
86
|
+
end
|
87
|
+
|
88
|
+
def call
|
89
|
+
runtime = Cucumber::Runtime.new(configuration)
|
90
|
+
run_before_all_hooks
|
91
|
+
@settings.pages.each do |page_settings|
|
92
|
+
# stash the settings where the support code will find them
|
93
|
+
BBC::A11y::CucumberSupport.current_page_settings = page_settings
|
94
|
+
print_page_header page_settings
|
95
|
+
runtime.run!
|
96
|
+
end
|
97
|
+
ensure
|
98
|
+
run_after_all_hooks
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def configuration
|
104
|
+
return @configuration if @configuration
|
105
|
+
features_path = File.expand_path(File.dirname(__FILE__) + "/../../../features")
|
106
|
+
@configuration = Cucumber::Cli::Configuration.new
|
107
|
+
# This is ugly, but until Cucumber offers a better API, we have to pass in our settings as though
|
108
|
+
# they were CLI arguments
|
109
|
+
@configuration.parse!([
|
110
|
+
features_path,
|
111
|
+
"--format", "BBC::A11y::CucumberFormatter"])
|
112
|
+
@configuration
|
113
|
+
end
|
114
|
+
|
115
|
+
def run_before_all_hooks
|
116
|
+
@settings.before_all_hooks.each &:call
|
117
|
+
end
|
118
|
+
|
119
|
+
def run_after_all_hooks
|
120
|
+
@settings.after_all_hooks.each &:call
|
121
|
+
end
|
122
|
+
|
123
|
+
def print_page_header(page_settings)
|
124
|
+
puts
|
125
|
+
puts
|
126
|
+
puts "BBC Accesibility: #{page_settings.url}"
|
127
|
+
puts "=" * "BBC Accesibility: #{page_settings.url}".length
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module BBC
|
2
|
+
module A11y
|
3
|
+
module CucumberSupport
|
4
|
+
|
5
|
+
class HeadingHierarchy
|
6
|
+
include RSpec::Matchers
|
7
|
+
|
8
|
+
def initialize(page)
|
9
|
+
@page = page
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate
|
13
|
+
return self unless headings.count > 1
|
14
|
+
adjacent_pairs = headings.zip(headings[1..-1])[0..-2]
|
15
|
+
errors = adjacent_pairs.reduce([]) { |errors, pair|
|
16
|
+
previous, current = *pair
|
17
|
+
if current > previous
|
18
|
+
delta = current.number - previous.number
|
19
|
+
if delta != 1
|
20
|
+
errors << current
|
21
|
+
end
|
22
|
+
end
|
23
|
+
errors
|
24
|
+
}
|
25
|
+
expect(errors).to be_empty, error_message(errors)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
headings.map { |h|
|
31
|
+
indent = " " * (h.number - 1)
|
32
|
+
indent + h.to_s
|
33
|
+
}.join("\n")
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def error_message(errors)
|
39
|
+
"Headings were not in order: " +
|
40
|
+
headings.map { |h| errors.include?(h) ? "**#{h}**" : h }.
|
41
|
+
join(", ")
|
42
|
+
end
|
43
|
+
|
44
|
+
def headings
|
45
|
+
heading_elements.map { |h| Heading.new(h) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def heading_elements
|
49
|
+
# can't work out how to get Capybara to do this reliably, so we'll reach down to the XML parser
|
50
|
+
xml = Nokogiri::XML.parse(page.source).remove_namespaces!
|
51
|
+
header_xml_nodes = xml.xpath('//*[substring(name(),1,1) = "h" and number(substring(name(),2,1))]')
|
52
|
+
header_xml_nodes.map(&:path).map { |xpath|
|
53
|
+
page.find(:xpath, xpath, visible: false)
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_reader :page
|
58
|
+
private :page
|
59
|
+
|
60
|
+
class Heading
|
61
|
+
attr_reader :number
|
62
|
+
|
63
|
+
def initialize(element)
|
64
|
+
@element = element
|
65
|
+
@tag_name = element.tag_name
|
66
|
+
@number = @tag_name[1].to_i
|
67
|
+
end
|
68
|
+
|
69
|
+
def > (other)
|
70
|
+
number > other.number
|
71
|
+
end
|
72
|
+
|
73
|
+
def == (other)
|
74
|
+
begin
|
75
|
+
other.element.path == @element.path
|
76
|
+
rescue NotSupportedByDriverError
|
77
|
+
other.element == @element
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_s
|
82
|
+
@tag_name
|
83
|
+
end
|
84
|
+
|
85
|
+
attr_reader :element
|
86
|
+
protected :element
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module BBC
|
2
|
+
module A11y
|
3
|
+
module CucumberSupport
|
4
|
+
|
5
|
+
module LanguageDetector
|
6
|
+
# factory for language detector, allows us to use different mechanisms (e.g. a hard-coded language passed from settings)
|
7
|
+
def self.new
|
8
|
+
CLDLanguageDetector.new
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'cld'
|
12
|
+
class CLDLanguageDetector
|
13
|
+
InsufficientConfidence = Class.new(StandardError)
|
14
|
+
|
15
|
+
# returns the code of the language, or raises an error if insufficient confidence
|
16
|
+
def detect(text)
|
17
|
+
detected_language = CLD.detect_language(text.to_s)
|
18
|
+
raise InsufficientConfidence unless detected_language[:reliable]
|
19
|
+
detected_language[:code]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'rspec/expectations'
|
2
|
+
require 'capybara/rspec/matchers'
|
3
|
+
require 'bbc/a11y/cucumber_support/heading_hierarchy'
|
4
|
+
|
5
|
+
module BBC
|
6
|
+
module A11y
|
7
|
+
module CucumberSupport
|
8
|
+
|
9
|
+
class Page
|
10
|
+
|
11
|
+
include RSpec::Matchers
|
12
|
+
include Capybara::RSpecMatchers
|
13
|
+
|
14
|
+
def initialize(browser)
|
15
|
+
@browser = browser
|
16
|
+
end
|
17
|
+
|
18
|
+
def title
|
19
|
+
browser.title
|
20
|
+
end
|
21
|
+
|
22
|
+
def must_have_lang_attribute
|
23
|
+
expect(browser).to have_css('html[lang]')
|
24
|
+
end
|
25
|
+
|
26
|
+
def must_have_lang_attribute_of(expected_code)
|
27
|
+
expect(browser.find('html')['lang'].split('-')[0]).to eq expected_code
|
28
|
+
end
|
29
|
+
|
30
|
+
def must_have_title
|
31
|
+
expect(browser.title).not_to be_empty
|
32
|
+
end
|
33
|
+
|
34
|
+
def must_have_title_that_contains_h1_text
|
35
|
+
expect(browser.title).to include(browser.find('h1').text)
|
36
|
+
end
|
37
|
+
|
38
|
+
def must_have_one_main_element
|
39
|
+
expect(browser.all('[role="main"]').length).to eq 1
|
40
|
+
end
|
41
|
+
|
42
|
+
def must_have_one_h1
|
43
|
+
expect(browser.all('h1', visible: false).length).to eq 1
|
44
|
+
end
|
45
|
+
|
46
|
+
def must_have_correct_heading_hierarchy
|
47
|
+
heading_hierarchy.validate
|
48
|
+
end
|
49
|
+
|
50
|
+
def must_have_no_elements_with_title_attribute_content_repeated_within
|
51
|
+
bad_nodes = browser.all('[title]').select { |node| node.text.include? node['title'] }
|
52
|
+
expect(bad_nodes).to be_empty
|
53
|
+
end
|
54
|
+
|
55
|
+
def must_have_no_form_fields_with_label_and_title
|
56
|
+
bad_nodes = browser.all('form *[id][title]').select { |node| browser.has_css?("label[for='#{node['id']}']") }
|
57
|
+
expect(bad_nodes).to be_empty
|
58
|
+
end
|
59
|
+
|
60
|
+
def must_not_have_any_positive_tabindex_values
|
61
|
+
bad_nodes = browser.all('[tabindex]').select { |node| node['tabindex'].to_i >= 0 }
|
62
|
+
expect(bad_nodes).to be_empty
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
browser.text
|
67
|
+
end
|
68
|
+
|
69
|
+
def heading_hierarchy
|
70
|
+
HeadingHierarchy.new(browser)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
attr_reader :browser
|
76
|
+
private :browser
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module BBC
|
2
|
+
module A11y
|
3
|
+
module CucumberSupport
|
4
|
+
module PerPageChecks
|
5
|
+
def assert_title_describes_primary_content_of_document(title, page)
|
6
|
+
pending <<-ERROR
|
7
|
+
Because the title did not contain the header text, you need to write a custom
|
8
|
+
method to define how to make this check.
|
9
|
+
|
10
|
+
In your .a11y.rb file, add the following code:
|
11
|
+
|
12
|
+
BBC::A11y.configure do
|
13
|
+
page "my_page.html" do
|
14
|
+
|
15
|
+
customize_world do
|
16
|
+
def assert_title_describes_primary_content_of_document(title, page)
|
17
|
+
# TODO: add your custom code here to make the check
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
ERROR
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'w3c_validators'
|
2
|
+
|
3
|
+
module BBC
|
4
|
+
module A11y
|
5
|
+
module CucumberSupport
|
6
|
+
|
7
|
+
class W3C
|
8
|
+
include W3CValidators
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@validator = MarkupValidator.new(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate(url)
|
15
|
+
@results = @validator.validate_uri(url)
|
16
|
+
end
|
17
|
+
|
18
|
+
def errors
|
19
|
+
@results.errors
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def options
|
25
|
+
return {} unless proxy
|
26
|
+
{ proxy_server: proxy, proxy_port: 80 }
|
27
|
+
end
|
28
|
+
|
29
|
+
def proxy
|
30
|
+
@proxy ||= ['http_proxy', 'https_proxy', 'HTTP_PROXY', 'HTTPS_PROXY'].map { |key| ENV[key] }.compact.first
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|