capybara-accessible 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9916aa4b7def229c4a35a26aad3b02560b91440e
4
+ data.tar.gz: f03f28c2fc5027ee3fd591903085194bf9c8b6bd
5
+ SHA512:
6
+ metadata.gz: 2883f351111bbc0c701fcb395d98a5e87cd7c8e355b013654fb52045ead485fa2cbcf40065cfbc342b0d39200709ef5cc5d3f46581504abd4424ef973e147d04
7
+ data.tar.gz: 84b9457fe048585e24e1837efe5bd140a03f3d9ec8bbdc32d28e09cce4714f24929be8537d14a7b4e8d0f1f147fdee4d276b7d09e22256503e9932dfb986cf8e
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in capybara-praseodymium.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2010-13 Case Commons, Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,45 @@
1
+ # Capybara::Accessible
2
+
3
+ Defines a web driver and extends Capybara to assert accessibility on page visits in [RSpec feature specs](https://www.relishapp.com/rspec/rspec-rails/docs/feature-specs/feature-spec).
4
+
5
+ capybara-accessible uses [Google's Accessibility Developer Tools](https://code.google.com/p/accessibility-developer-tools/) assertions to performs automated accessibility audits.
6
+
7
+ Use capybara-accessible in place of the Selenium or capybara-webkit drivers to invoke these assertions on link clicks and page visits.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'selenium-webdriver'
14
+ gem 'capybara-accessible'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install capybara-accessible
23
+
24
+ ## Usage
25
+
26
+ Require and register the driver in spec_helper.rb:
27
+
28
+ require 'capybara/rspec'
29
+ require 'capybara/accessible'
30
+
31
+ Capybara.current_driver = :accessible
32
+
33
+ Optionally exclude rules from being run:
34
+
35
+ Capybara::Accessible::Auditor.exclusions = ['AX_FOCUS_01']
36
+
37
+ The full list of rules is on the [Google Accessibility Developer Tools wiki](https://code.google.com/p/accessibility-developer-tools/wiki/AuditRules).
38
+
39
+ ## Contributing
40
+
41
+ 1. Fork it
42
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
43
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
44
+ 4. Push to the branch (`git push origin my-new-feature`)
45
+ 5. Create new Pull Request
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.pattern = "spec/**/*_spec.rb"
8
+ t.rspec_opts = "--format progress"
9
+ end
10
+
11
+ desc "Default: build and run all specs"
12
+ task :default => [:build, :spec]
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'capybara/accessible/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "capybara-accessible"
8
+ spec.version = Capybara::Accessible::VERSION
9
+ spec.authors = ["Case Commons"]
10
+ spec.email = ["accessibility@casecommons.org"]
11
+ spec.description = %q{Capybara extension and webdriver for automated accessibility testing}
12
+ spec.summary = %q{A Selenium based webdriver and Capybara extension that runs Google Accessibility Developer Tools auditing assertions on page visits.}
13
+ spec.homepage = "https://github.com/Casecommons/capybara-accessible"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency("capybara", "~> 2.0", ">= 2.0.2")
22
+ spec.add_dependency("selenium-webdriver")
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec"
27
+ spec.add_development_dependency "pry"
28
+
29
+ # Sinatra is used by Capybara's TestApp
30
+ spec.add_development_dependency("sinatra")
31
+ end
@@ -0,0 +1,15 @@
1
+ require 'capybara'
2
+ require 'capybara/accessible/auditor'
3
+ require 'capybara/accessible/element'
4
+ require "capybara/accessible/version"
5
+
6
+ module Capybara
7
+ module Accessible
8
+ end
9
+ end
10
+
11
+ require "capybara/accessible/driver"
12
+
13
+ Capybara.register_driver :accessible do |app|
14
+ Capybara::Accessible::Driver.new(app)
15
+ end
@@ -0,0 +1,41 @@
1
+ module Capybara::Accessible
2
+ class InaccessibleError < Capybara::CapybaraError; end
3
+
4
+ module Auditor
5
+ def self.exclusions=(rules)
6
+ @@exclusions = rules
7
+ end
8
+
9
+ def self.exclusions
10
+ @@exclusions ||= []
11
+ end
12
+
13
+ def audit_rules
14
+ File.read(File.expand_path("../axs_testing.js", __FILE__))
15
+ end
16
+
17
+ def audit_results
18
+ excluded_assertions = Capybara::Accessible::Auditor.exclusions
19
+
20
+ results.reject do |r|
21
+ excluded_assertions.include?(r['rule']['code'])
22
+ end
23
+ end
24
+
25
+ def failure_messages
26
+ audit_results.collect do |f|
27
+ "<#{f['elements'].first.tag_name}> tag with text \"#{f['elements'].first.text}\" - #{f['rule']['heading']}" if f['result'] == 'FAIL'
28
+ end.join("\n")
29
+ end
30
+
31
+ def results
32
+ script = "#{audit_rules} return axs.Audit.run();"
33
+
34
+ if @session
35
+ @session.driver.execute_script(script)
36
+ else
37
+ execute_script(script)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,715 @@
1
+ var axs = {};
2
+ axs.constants = {};
3
+ axs.constants.ARIA_ROLES = {alert:{namefrom:["author"], parent:["region"]}, alertdialog:{namefrom:["author"], namerequired:!0, parent:["alert", "dialog"]}, application:{namefrom:["author"], namerequired:!0, parent:["landmark"]}, article:{namefrom:["author"], parent:["document", "region"]}, banner:{namefrom:["author"], parent:["landmark"]}, button:{childpresentational:!0, namefrom:["contents", "author"], namerequired:!0, parent:["command"], properties:["aria-expanded", "aria-pressed"]}, checkbox:{namefrom:["contents",
4
+ "author"], namerequired:!0, parent:["input"], requiredProperties:["aria-checked"], properties:["aria-checked"]}, columnheader:{namefrom:["contents", "author"], namerequired:!0, parent:["gridcell", "sectionhead", "widget"], properties:["aria-sort"]}, combobox:{mustcontain:["listbox", "textbox"], namefrom:["author"], namerequired:!0, parent:["select"], requiredProperties:["aria-expanded"], properties:["aria-expanded", "aria-autocomplete", "aria-required"]}, command:{"abstract":!0, namefrom:["author"],
5
+ parent:["widget"]}, complementary:{namefrom:["author"], parent:["landmark"]}, composite:{"abstract":!0, childpresentational:!1, namefrom:["author"], parent:["widget"], properties:["aria-activedescendant"]}, contentinfo:{namefrom:["author"], parent:["landmark"]}, definition:{namefrom:["author"], parent:["section"]}, dialog:{namefrom:["author"], namerequired:!0, parent:["window"]}, directory:{namefrom:["contents", "author"], parent:["list"]}, document:{namefrom:[" author"], namerequired:!0, parent:["structure"],
6
+ properties:["aria-expanded"]}, form:{namefrom:["author"], parent:["landmark"]}, grid:{mustcontain:["row", "rowgroup"], namefrom:["author"], namerequired:!0, parent:["composite", "region"], properties:["aria-level", "aria-multiselectable", "aria-readonly"]}, gridcell:{namefrom:["contents", "author"], namerequired:!0, parent:["section", "widget"], properties:["aria-readonly", "aria-required", "aria-selected"]}, group:{namefrom:[" author"], parent:["section"], properties:["aria-activedescendant"]},
7
+ heading:{namerequired:!0, parent:["sectionhead"], properties:["aria-level"]}, img:{childpresentational:!0, namefrom:["author"], namerequired:!0, parent:["section"]}, input:{"abstract":!0, namefrom:["author"], parent:["widget"]}, landmark:{"abstract":!0, namefrom:["contents", "author"], namerequired:!1, parent:["region"]}, link:{namefrom:["contents", "author"], namerequired:!0, parent:["command"], properties:["aria-expanded"]}, list:{mustcontain:["group", "listitem"], namefrom:["author"], parent:["region"]},
8
+ listbox:{mustcontain:["option"], namefrom:["author"], namerequired:!0, parent:["list", "select"], properties:["aria-multiselectable", "aria-required"]}, listitem:{namefrom:["contents", "author"], namerequired:!0, parent:["section"], properties:["aria-level", "aria-posinset", "aria-setsize"]}, log:{namefrom:[" author"], namerequired:!0, parent:["region"]}, main:{namefrom:["author"], parent:["landmark"]}, marquee:{namerequired:!0, parent:["section"]}, math:{childpresentational:!0, namefrom:["author"],
9
+ parent:["section"]}, menu:{mustcontain:["group", "menuitemradio", "menuitem", "menuitemcheckbox"], namefrom:["author"], namerequired:!0, parent:["list", "select"]}, menubar:{namefrom:["author"], parent:["menu"]}, menuitem:{namefrom:["contents", "author"], namerequired:!0, parent:["command"]}, menuitemcheckbox:{namefrom:["contents", "author"], namerequired:!0, parent:["checkbox", "menuitem"]}, menuitemradio:{namefrom:["contents", "author"], namerequired:!0, parent:["menuitemcheckbox", "radio"]}, navigation:{namefrom:["author"],
10
+ parent:["landmark"]}, note:{namefrom:["author"], parent:["section"]}, option:{namefrom:["contents", "author"], namerequired:!0, parent:["input"], properties:["aria-checked", "aria-posinset", "aria-selected", "aria-setsize"]}, presentation:{parent:["structure"]}, progressbar:{childpresentational:!0, namefrom:["author"], namerequired:!0, parent:["range"]}, radio:{namefrom:["contents", "author"], namerequired:!0, parent:["checkbox", "option"]}, radiogroup:{mustcontain:["radio"], namefrom:["author"],
11
+ namerequired:!0, parent:["select"], properties:["aria-required"]}, range:{"abstract":!0, namefrom:["author"], parent:["widget"], properties:["aria-valuemax", "aria-valuemin", "aria-valuenow", "aria-valuetext"]}, region:{namefrom:[" author"], parent:["section"]}, roletype:{"abstract":!0, properties:"aria-atomic aria-busy aria-controls aria-describedby aria-disabled aria-dropeffect aria-flowto aria-grabbed aria-haspopup aria-hidden aria-invalid aria-label aria-labelledby aria-live aria-owns aria-relevant".split(" ")},
12
+ row:{mustcontain:["columnheader", "gridcell", "rowheader"], namefrom:["contents", "author"], parent:["group", "widget"], properties:["aria-level", "aria-selected"]}, rowgroup:{mustcontain:["row"], namefrom:["contents", "author"], parent:["group"]}, rowheader:{namefrom:["contents", "author"], namerequired:!0, parent:["gridcell", "sectionhead", "widget"], properties:["aria-sort"]}, search:{namefrom:["author"], parent:["landmark"]}, section:{"abstract":!0, namefrom:["contents", "author"], parent:["structure"],
13
+ properties:["aria-expanded"]}, sectionhead:{"abstract":!0, namefrom:["contents", "author"], parent:["structure"], properties:["aria-expanded"]}, select:{"abstract":!0, namefrom:["author"], parent:["composite", "group", "input"]}, separator:{childpresentational:!0, namefrom:["author"], parent:["structure"], properties:["aria-expanded", "aria-orientation"]}, scrollbar:{childpresentational:!0, namefrom:["author"], namerequired:!1, parent:["input", "range"], requiredProperties:["aria-controls", "aria-orientation",
14
+ "aria-valuemax", "aria-valuemin", "aria-valuenow"], properties:["aria-controls", "aria-orientation", "aria-valuemax", "aria-valuemin", "aria-valuenow"]}, slider:{childpresentational:!0, namefrom:["author"], namerequired:!0, parent:["input", "range"], requiredProperties:["aria-valuemax", "aria-valuemin", "aria-valuenow"], properties:["aria-valuemax", "aria-valuemin", "aria-valuenow", "aria-orientation"]}, spinbutton:{namefrom:["author"], namerequired:!0, parent:["input", "range"], requiredProperties:["aria-valuemax",
15
+ "aria-valuemin", "aria-valuenow"], properties:["aria-valuemax", "aria-valuemin", "aria-valuenow", "aria-required"]}, status:{parent:["region"]}, structure:{"abstract":!0, parent:["roletype"]}, tab:{namefrom:["contents", "author"], parent:["sectionhead", "widget"], properties:["aria-selected"]}, tablist:{mustcontain:["tab"], namefrom:["author"], parent:["composite", "directory"], properties:["aria-level"]}, tabpanel:{namefrom:["author"], namerequired:!0, parent:["region"]}, textbox:{namefrom:["author"],
16
+ namerequired:!0, parent:["input"], properties:["aria-activedescendant", "aria-autocomplete", "aria-multiline", "aria-readonly", "aria-required"]}, timer:{namefrom:["author"], namerequired:!0, parent:["status"]}, toolbar:{namefrom:["author"], parent:["group"]}, tooltip:{namerequired:!0, parent:["section"]}, tree:{mustcontain:["group", "treeitem"], namefrom:["author"], namerequired:!0, parent:["select"], properties:["aria-multiselectable", "aria-required"]}, treegrid:{mustcontain:["row"], namefrom:["author"],
17
+ namerequired:!0, parent:["grid", "tree"]}, treeitem:{namefrom:["contents", "author"], namerequired:!0, parent:["listitem", "option"]}, widget:{"abstract":!0, parent:["roletype"]}, window:{"abstract":!0, namefrom:[" author"], parent:["roletype"], properties:["aria-expanded"]}};
18
+ axs.constants.WIDGET_ROLES = {};
19
+ axs.constants.addAllParentRolesToSet_ = function(a, b) {
20
+ if(a.parent) {
21
+ for(var c = a.parent, d = 0;d < c.length;d++) {
22
+ var e = c[d];
23
+ b[e] = !0;
24
+ axs.constants.addAllParentRolesToSet_(axs.constants.ARIA_ROLES[e], b)
25
+ }
26
+ }
27
+ };
28
+ axs.constants.addAllPropertiesToSet_ = function(a, b, c) {
29
+ var d = a[b];
30
+ if(d) {
31
+ for(var e = 0;e < d.length;e++) {
32
+ c[d[e]] = !0
33
+ }
34
+ }
35
+ if(a.parent) {
36
+ a = a.parent;
37
+ for(d = 0;d < a.length;d++) {
38
+ axs.constants.addAllPropertiesToSet_(axs.constants.ARIA_ROLES[a[d]], b, c)
39
+ }
40
+ }
41
+ };
42
+ for(var roleName in axs.constants.ARIA_ROLES) {
43
+ var role = axs.constants.ARIA_ROLES[roleName], propertiesSet = {};
44
+ axs.constants.addAllPropertiesToSet_(role, "properties", propertiesSet);
45
+ role.propertiesSet = propertiesSet;
46
+ var requiredPropertiesSet = {};
47
+ axs.constants.addAllPropertiesToSet_(role, "requiredProperties", requiredPropertiesSet);
48
+ role.requiredPropertiesSet = requiredPropertiesSet;
49
+ var parentRolesSet = {};
50
+ axs.constants.addAllParentRolesToSet_(role, parentRolesSet);
51
+ role.allParentRolesSet = parentRolesSet;
52
+ "widget" in parentRolesSet && (axs.constants.WIDGET_ROLES[roleName] = role)
53
+ }
54
+ axs.constants.ARIA_PROPERTIES = {activedescendant:{type:"property", valueType:"idref"}, atomic:{defaultValue:"false", type:"property", valueType:"boolean"}, autocomplete:{defaultValue:"none", type:"property", valueType:"token", values:["inline", "list", "both", "none"]}, busy:{defaultValue:"false", type:"state", valueType:"boolean"}, checked:{defaultValue:"undefined", type:"state", valueType:"token", values:["true", "false", "mixed", "undefined"]}, controls:{type:"property", valueType:"idref_list"},
55
+ describedby:{type:"property", valueType:"idref_list"}, disabled:{defaultValue:"false", type:"state", valueType:"boolean"}, dropeffect:{defaultValue:"none", type:"property", valueType:"token_list", values:"copy move link execute popup none".split(" ")}, expanded:{defaultValue:"undefined", type:"state", valueType:"token", values:["true", "false", "undefined"]}, flowto:{type:"property", valueType:"idref_list"}, grabbed:{defaultValue:"undefined", type:"state", valueType:"token", values:["true", "false",
56
+ "undefined"]}, haspopup:{defaultValue:"false", type:"property", valueType:"boolean"}, hidden:{defaultValue:"false", type:"state", valueType:"boolean"}, invalid:{defaultValue:"false", type:"state", valueType:"token", values:["grammar", "false", "spelling", "true"]}, label:{type:"property", valueType:"string"}, labelledby:{type:"property", valueType:"idref_list"}, level:{type:"property", valueType:"integer"}, live:{defaultValue:"off", type:"property", valueType:"token", values:["off", "polite", "assertive"]},
57
+ multiline:{defaultValue:"false", type:"property", valueType:"boolean"}, multiselectable:{defaultValue:"false", type:"property", valueType:"boolean"}, orientation:{defaultValue:"vertical", type:"property", valueType:"token", values:["horizontal", "vertical"]}, owns:{type:"property", valueType:"idref_list"}, posinset:{type:"property", valueType:"integer"}, pressed:{defaultValue:"undefined", type:"state", valueType:"token", values:["true", "false", "mixed", "undefined"]}, readonly:{defaultValue:"false",
58
+ type:"property", valueType:"boolean"}, relevant:{defaultValue:"additions text", type:"property", valueType:"token_list", values:["additions", "removals", "text", "all"]}, required:{defaultValue:"false", type:"property", valueType:"boolean"}, selected:{defaultValue:"undefined", type:"state", valueType:"token", values:["true", "false", "undefined"]}, setsize:{type:"property", valueType:"integer"}, sort:{defaultValue:"none", type:"property", valueType:"token", values:["ascending", "descending", "none",
59
+ "other"]}, valuemax:{type:"property", valueType:"decimal"}, valuemin:{type:"property", valueType:"decimal"}, valuenow:{type:"property", valueType:"decimal"}, valuetext:{type:"property", valueType:"string"}};
60
+ axs.constants.GLOBAL_PROPERTIES = "aria-atomic aria-busy aria-controls aria-describedby aria-disabled aria-dropeffect aria-flowto aria-grabbed aria-haspopup aria-hidden aria-invalid aria-label aria-labelledby aria-live aria-owns aria-relevant".split(" ");
61
+ axs.constants.NO_ROLE_NAME = " ";
62
+ axs.constants.WIDGET_ROLE_TO_NAME = {alert:"aria_role_alert", alertdialog:"aria_role_alertdialog", button:"aria_role_button", checkbox:"aria_role_checkbox", columnheader:"aria_role_columnheader", combobox:"aria_role_combobox", dialog:"aria_role_dialog", grid:"aria_role_grid", gridcell:"aria_role_gridcell", link:"aria_role_link", listbox:"aria_role_listbox", log:"aria_role_log", marquee:"aria_role_marquee", menu:"aria_role_menu", menubar:"aria_role_menubar", menuitem:"aria_role_menuitem", menuitemcheckbox:"aria_role_menuitemcheckbox",
63
+ menuitemradio:"aria_role_menuitemradio", option:axs.constants.NO_ROLE_NAME, progressbar:"aria_role_progressbar", radio:"aria_role_radio", radiogroup:"aria_role_radiogroup", rowheader:"aria_role_rowheader", scrollbar:"aria_role_scrollbar", slider:"aria_role_slider", spinbutton:"aria_role_spinbutton", status:"aria_role_status", tab:"aria_role_tab", tabpanel:"aria_role_tabpanel", textbox:"aria_role_textbox", timer:"aria_role_timer", toolbar:"aria_role_toolbar", tooltip:"aria_role_tooltip", treeitem:"aria_role_treeitem"};
64
+ axs.constants.STRUCTURE_ROLE_TO_NAME = {article:"aria_role_article", application:"aria_role_application", banner:"aria_role_banner", columnheader:"aria_role_columnheader", complementary:"aria_role_complementary", contentinfo:"aria_role_contentinfo", definition:"aria_role_definition", directory:"aria_role_directory", document:"aria_role_document", form:"aria_role_form", group:"aria_role_group", heading:"aria_role_heading", img:"aria_role_img", list:"aria_role_list", listitem:"aria_role_listitem",
65
+ main:"aria_role_main", math:"aria_role_math", navigation:"aria_role_navigation", note:"aria_role_note", region:"aria_role_region", rowheader:"aria_role_rowheader", search:"aria_role_search", separator:"aria_role_separator"};
66
+ axs.constants.ATTRIBUTE_VALUE_TO_STATUS = [{name:"aria-autocomplete", values:{inline:"aria_autocomplete_inline", list:"aria_autocomplete_list", both:"aria_autocomplete_both"}}, {name:"aria-checked", values:{"true":"aria_checked_true", "false":"aria_checked_false", mixed:"aria_checked_mixed"}}, {name:"aria-disabled", values:{"true":"aria_disabled_true"}}, {name:"aria-expanded", values:{"true":"aria_expanded_true", "false":"aria_expanded_false"}}, {name:"aria-invalid", values:{"true":"aria_invalid_true",
67
+ grammar:"aria_invalid_grammar", spelling:"aria_invalid_spelling"}}, {name:"aria-multiline", values:{"true":"aria_multiline_true"}}, {name:"aria-multiselectable", values:{"true":"aria_multiselectable_true"}}, {name:"aria-pressed", values:{"true":"aria_pressed_true", "false":"aria_pressed_false", mixed:"aria_pressed_mixed"}}, {name:"aria-readonly", values:{"true":"aria_readonly_true"}}, {name:"aria-required", values:{"true":"aria_required_true"}}, {name:"aria-selected", values:{"true":"aria_selected_true",
68
+ "false":"aria_selected_false"}}];
69
+ axs.constants.INPUT_TYPE_TO_INFORMATION_TABLE_MSG = {button:"input_type_button", checkbox:"input_type_checkbox", color:"input_type_color", datetime:"input_type_datetime", "datetime-local":"input_type_datetime_local", date:"input_type_date", email:"input_type_email", file:"input_type_file", image:"input_type_image", month:"input_type_month", number:"input_type_number", password:"input_type_password", radio:"input_type_radio", range:"input_type_range", reset:"input_type_reset", search:"input_type_search",
70
+ submit:"input_type_submit", tel:"input_type_tel", text:"input_type_text", url:"input_type_url", week:"input_type_week"};
71
+ axs.constants.TAG_TO_INFORMATION_TABLE_VERBOSE_MSG = {A:"tag_link", BUTTON:"tag_button", H1:"tag_h1", H2:"tag_h2", H3:"tag_h3", H4:"tag_h4", H5:"tag_h5", H6:"tag_h6", LI:"tag_li", OL:"tag_ol", SELECT:"tag_select", TEXTAREA:"tag_textarea", UL:"tag_ul", SECTION:"tag_section", NAV:"tag_nav", ARTICLE:"tag_article", ASIDE:"tag_aside", HGROUP:"tag_hgroup", HEADER:"tag_header", FOOTER:"tag_footer", TIME:"tag_time", MARK:"tag_mark"};
72
+ axs.constants.TAG_TO_INFORMATION_TABLE_BRIEF_MSG = {BUTTON:"tag_button", SELECT:"tag_select", TEXTAREA:"tag_textarea"};
73
+ axs.constants.MIXED_VALUES = {"true":!0, "false":!0, mixed:!0};
74
+ for(var propertyName in axs.constants.ARIA_PROPERTIES) {
75
+ var propertyDetails = axs.constants.ARIA_PROPERTIES[propertyName];
76
+ if(propertyDetails.values) {
77
+ for(var valuesSet = {}, i = 0;i < propertyDetails.values.length;i++) {
78
+ valuesSet[propertyDetails.values[i]] = !0
79
+ }
80
+ propertyDetails.valuesSet = valuesSet
81
+ }
82
+ }
83
+ axs.constants.Severity = {INFO:"Info", WARNING:"Warning", SEVERE:"Severe"};
84
+ axs.constants.AuditResult = {PASS:"PASS", FAIL:"FAIL", NA:"NA"};
85
+ axs.utils = {};
86
+ axs.utils.FOCUSABLE_ELEMENTS_SELECTOR = "input:not([type=hidden]):not([disabled]),select:not([disabled]),textarea:not([disabled]),button:not([disabled]),a[href],iframe,[tabindex]";
87
+ axs.utils.Color = function(a, b, c, d) {
88
+ this.red = a;
89
+ this.green = b;
90
+ this.blue = c;
91
+ this.alpha = d
92
+ };
93
+ axs.utils.calculateContrastRatio = function(a, b) {
94
+ if(!a || !b) {
95
+ return null
96
+ }
97
+ 1 > a.alpha && (a = axs.utils.flattenColors(a, b));
98
+ var c = axs.utils.calculateLuminance(a), d = axs.utils.calculateLuminance(b);
99
+ return(Math.max(c, d) + 0.05) / (Math.min(c, d) + 0.05)
100
+ };
101
+ axs.utils.elementIsTransparent = function(a) {
102
+ return"0" == a.style.opacity
103
+ };
104
+ axs.utils.elementHasZeroArea = function(a) {
105
+ a = a.getBoundingClientRect();
106
+ var b = a.top - a.bottom;
107
+ return!(a.right - a.left) || !b ? !0 : !1
108
+ };
109
+ axs.utils.elementIsOutsideScrollArea = function(a) {
110
+ a = a.getBoundingClientRect();
111
+ var b = document.body.scrollWidth, c = document.body.scrollTop, d = document.body.scrollLeft;
112
+ return a.top >= document.body.scrollHeight || a.bottom <= -c || a.left >= b || a.right <= -d ? !0 : !1
113
+ };
114
+ axs.utils.overlappingElement = function(a) {
115
+ function b(a, c) {
116
+ return null == c ? !1 : c === a ? !0 : b(a, c.parentNode)
117
+ }
118
+ if(axs.utils.elementHasZeroArea(a)) {
119
+ return null
120
+ }
121
+ var c = a.getBoundingClientRect(), c = document.elementFromPoint((c.left + c.right) / 2, (c.top + c.bottom) / 2);
122
+ return null != c && c != a && !b(c, a) ? c : null
123
+ };
124
+ axs.utils.elementIsHtmlControl = function(a) {
125
+ return a instanceof HTMLButtonElement || a instanceof HTMLInputElement || a instanceof HTMLSelectElement || a instanceof HTMLTextAreaElement ? !0 : !1
126
+ };
127
+ axs.utils.elementIsAriaWidget = function(a) {
128
+ if(a.hasAttribute("role") && (a = a.getAttribute("role"))) {
129
+ if((a = axs.constants.ARIA_ROLES[a]) && "widget" in a.allParentRolesSet) {
130
+ return!0
131
+ }
132
+ }
133
+ return!1
134
+ };
135
+ axs.utils.elementIsVisible = function(a) {
136
+ if(axs.utils.elementIsTransparent(a) || axs.utils.elementHasZeroArea(a) || axs.utils.elementIsOutsideScrollArea(a)) {
137
+ return!1
138
+ }
139
+ if(a = axs.utils.overlappingElement(a)) {
140
+ var b = window.getComputedStyle(a, null);
141
+ if(b && (a = axs.utils.getBgColor(b, a)) && 0 < a.alpha) {
142
+ return!1
143
+ }
144
+ }
145
+ return!0
146
+ };
147
+ axs.utils.isLargeFont = function(a) {
148
+ var b = a.fontSize;
149
+ a = "bold" == a.fontWeight;
150
+ var c = b.match(/(\d+)px/);
151
+ if(c) {
152
+ return b = parseInt(c[1], 10), a && 19.2 <= b || 24 <= b ? !0 : !1
153
+ }
154
+ if(c = b.match(/(\d+)em/)) {
155
+ return b = parseInt(c[1], 10), a && 1.2 <= b || 1.5 <= b ? !0 : !1
156
+ }
157
+ if(c = b.match(/(\d+)%/)) {
158
+ return b = parseInt(c[1], 10), a && 120 <= b || 150 <= b ? !0 : !1
159
+ }
160
+ if(c = b.match(/(\d+)pt/)) {
161
+ if(b = parseInt(c[1], 10), a && 14 <= b || 14 <= b) {
162
+ return!0
163
+ }
164
+ }
165
+ return!1
166
+ };
167
+ axs.utils.getBgColor = function(a, b) {
168
+ var c = axs.utils.parseColor(a.backgroundColor);
169
+ if(!c || a.backgroundImage && "none" != a.backgroundImage) {
170
+ return null
171
+ }
172
+ if(1 > c.alpha) {
173
+ var d = b, e = [];
174
+ e.push(c);
175
+ for(c = null;d = d.parentElement;) {
176
+ var f = window.getComputedStyle(d, null);
177
+ if(f) {
178
+ if(f.backgroundImage && "none" != f.backgroundImage) {
179
+ return null
180
+ }
181
+ if((f = axs.utils.parseColor(f.backgroundColor)) && 0 != f.alpha) {
182
+ if(e.push(f), 1 == f.alpha) {
183
+ c = null;
184
+ break
185
+ }
186
+ }
187
+ }
188
+ }
189
+ c || e.push(new axs.utils.Color(255, 255, 255, 1));
190
+ for(d = e.pop();e.length;) {
191
+ c = e.pop(), d = axs.utils.flattenColors(c, d)
192
+ }
193
+ c = d
194
+ }
195
+ return c
196
+ };
197
+ axs.utils.getFgColor = function(a, b) {
198
+ var c = axs.utils.parseColor(a.color);
199
+ if(!c) {
200
+ return null
201
+ }
202
+ 1 > c.alpha && (c = axs.utils.flattenColors(c, b));
203
+ return c
204
+ };
205
+ axs.utils.parseColor = function(a) {
206
+ var b = a.match(/^rgb\((\d+), (\d+), (\d+)\)$/);
207
+ if(b) {
208
+ a = parseInt(b[1], 10);
209
+ var c = parseInt(b[2], 10), b = parseInt(b[3], 10), d;
210
+ return new axs.utils.Color(a, c, b, 1)
211
+ }
212
+ return(b = a.match(/^rgba\((\d+), (\d+), (\d+), (\d+(\.\d+)?)\)/)) ? (d = parseInt(b[4], 10), a = parseInt(b[1], 10), c = parseInt(b[2], 10), b = parseInt(b[3], 10), new axs.utils.Color(a, c, b, d)) : null
213
+ };
214
+ axs.utils.colorToString = function(a) {
215
+ return"rgba(" + [a.red, a.green, a.blue, a.alpha].join() + ")"
216
+ };
217
+ axs.utils.flattenColors = function(a, b) {
218
+ var c = a.alpha;
219
+ return new axs.utils.Color((1 - c) * b.red + c * a.red, (1 - c) * b.green + c * a.green, (1 - c) * b.blue + c * a.blue, 1)
220
+ };
221
+ axs.utils.calculateLuminance = function(a) {
222
+ var b = a.red / 255, c = a.green / 255;
223
+ a = a.blue / 255;
224
+ b = 0.03928 >= b ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);
225
+ c = 0.03928 >= c ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
226
+ a = 0.03928 >= a ? a / 12.92 : Math.pow((a + 0.055) / 1.055, 2.4);
227
+ return 0.2126 * b + 0.7152 * c + 0.0722 * a
228
+ };
229
+ axs.utils.getContrastRatioForElement = function(a) {
230
+ var b = window.getComputedStyle(a, null);
231
+ return axs.utils.getContrastRatioForElementWithComputedStyle(b, a)
232
+ };
233
+ axs.utils.getContrastRatioForElementWithComputedStyle = function(a, b) {
234
+ if(!axs.utils.elementIsVisible(b)) {
235
+ return null
236
+ }
237
+ var c = axs.utils.getBgColor(a, b);
238
+ if(!c) {
239
+ return null
240
+ }
241
+ var d = axs.utils.getFgColor(a, c);
242
+ return!d ? null : axs.utils.calculateContrastRatio(d, c)
243
+ };
244
+ axs.utils.isNativeTextElement = function(a) {
245
+ var b = a.tagName.toLowerCase();
246
+ a = a.type ? a.type.toLowerCase() : "";
247
+ if("textarea" == b) {
248
+ return!0
249
+ }
250
+ if("input" != b) {
251
+ return!1
252
+ }
253
+ switch(a) {
254
+ case "email":
255
+ ;
256
+ case "number":
257
+ ;
258
+ case "password":
259
+ ;
260
+ case "search":
261
+ ;
262
+ case "text":
263
+ ;
264
+ case "tel":
265
+ ;
266
+ case "url":
267
+ ;
268
+ case "":
269
+ return!0;
270
+ default:
271
+ return!1
272
+ }
273
+ };
274
+ axs.utils.isLowContrast = function(a, b) {
275
+ return 3 > a || !axs.utils.isLargeFont(b) && 4.5 > a
276
+ };
277
+ axs.utils.hasLabel = function(a) {
278
+ var b = a.tagName.toLowerCase(), c = a.type ? a.type.toLowerCase() : "";
279
+ if(a.hasAttribute("aria-label") || a.hasAttribute("title") || "img" == b && a.hasAttribute("alt") || "input" == b && "image" == c && a.hasAttribute("alt") || "input" == b && ("submit" == c || "reset" == c) || a.hasAttribute("aria-labelledby") || axs.utils.isNativeTextElement(a) && a.hasAttribute("placeholder") || a.hasAttribute("id") && 0 < document.querySelectorAll("label[for=" + a.id + "]").length) {
280
+ return!0
281
+ }
282
+ for(b = a.parentElement;b;) {
283
+ if("label" == b.tagName.toLowerCase() && b.control == a) {
284
+ return!0
285
+ }
286
+ b = b.parentElement
287
+ }
288
+ return!1
289
+ };
290
+ axs.utils.isElementHidden = function(a) {
291
+ if(!(a instanceof HTMLElement)) {
292
+ return!1
293
+ }
294
+ if(a.hasAttribute("chromevoxignoreariahidden")) {
295
+ var b = !0
296
+ }
297
+ var c = window.getComputedStyle(a, null);
298
+ return"none" == c.display || "hidden" == c.visibility ? !0 : a.hasAttribute("aria-hidden") && "true" == a.getAttribute("aria-hidden").toLowerCase() ? !b : !1
299
+ };
300
+ axs.utils.isElementOrAncestorHidden = function(a) {
301
+ return axs.utils.isElementHidden(a) ? !0 : a.parentElement ? axs.utils.isElementOrAncestorHidden(a.parentElement) : !1
302
+ };
303
+ axs.utils.getRole = function(a) {
304
+ if(!a.hasAttribute("role")) {
305
+ return!1
306
+ }
307
+ a = a.getAttribute("role");
308
+ return axs.constants.ARIA_ROLES[a] ? {name:a, details:axs.constants.ARIA_ROLES[a], valid:!0} : {name:a, valid:!1}
309
+ };
310
+ axs.utils.getAriaPropertyValue = function(a, b, c) {
311
+ var d = a.replace(/^aria-/, ""), e = axs.constants.ARIA_PROPERTIES[d], d = {name:a, rawValue:b};
312
+ if(!e) {
313
+ return d.valid = !1, d.reason = '"' + a + '" is not a valid ARIA property', d
314
+ }
315
+ e = e.valueType;
316
+ if(!e) {
317
+ return d.valid = !1, d.reason = '"' + a + '" is not a valid ARIA property', d
318
+ }
319
+ switch(e) {
320
+ case "idref":
321
+ a = axs.utils.isValidIDRefValue(b, c), d.valid = a.valid, d.reason = a.reason, d.idref = a.idref;
322
+ case "idref_list":
323
+ a = b.split(/\s+/);
324
+ d.valid = !0;
325
+ for(b = 0;b < a.length;b++) {
326
+ e = axs.utils.isValidIDRefValue(a[b], c), e.valid || (d.valid = !1), d.values ? d.values.push(e) : d.values = [e]
327
+ }
328
+ return d;
329
+ case "integer":
330
+ c = axs.utils.isValidNumber(b);
331
+ if(!c.valid) {
332
+ return d.valid = !1, d.reason = c.reason, d
333
+ }
334
+ Math.floor(c.value) != c.value ? (d.valid = !1, d.reason = "" + b + " is not a whole integer") : (d.valid = !0, d.value = c.value);
335
+ return d;
336
+ case "number":
337
+ c = axs.utils.isValidNumber(b), c.valid && (d.valid = !0, d.value = c.value);
338
+ case "string":
339
+ return d.valid = !0, d.value = b, d;
340
+ case "token":
341
+ return c = axs.utils.isValidTokenValue(a, b.toLowerCase()), c.valid ? (d.valid = !0, d.value = c.value) : (d.valid = !1, d.value = b, d.reason = c.reason), d;
342
+ case "token_list":
343
+ e = b.split(/\s+/);
344
+ d.valid = !0;
345
+ for(b = 0;b < e.length;b++) {
346
+ c = axs.utils.isValidTokenValue(a, e[b].toLowerCase()), c.valid || (d.valid = !1, d.reason ? (d.reason = [d.reason], d.reason.push(c.reason)) : (d.reason = c.reason, d.possibleValues = c.possibleValues)), d.values ? d.values.push(c.value) : d.values = [c.value]
347
+ }
348
+ return d;
349
+ case "tristate":
350
+ return c = axs.utils.isPossibleValue(b.toLowerCase(), axs.constants.MIXED_VALUES, a), c.valid ? (d.valid = !0, d.value = c.value) : (d.valid = !1, d.value = b, d.reason = c.reason), d;
351
+ case "boolean":
352
+ return c = axs.utils.isValidBoolean(b), c.valid ? (d.valid = !0, d.value = c.value) : (d.valid = !1, d.value = b, d.reason = c.reason), d
353
+ }
354
+ d.valid = !1;
355
+ d.reason = "Not a valid ARIA property";
356
+ return d
357
+ };
358
+ axs.utils.isValidTokenValue = function(a, b) {
359
+ var c = a.replace(/^aria-/, "");
360
+ return axs.utils.isPossibleValue(b, axs.constants.ARIA_PROPERTIES[c].valuesSet, a)
361
+ };
362
+ axs.utils.isPossibleValue = function(a, b, c) {
363
+ return!b[a] ? {valid:!1, value:a, reason:'"' + a + '" is not a valid value for ' + c, possibleValues:Object.keys(b)} : {valid:!0, value:a}
364
+ };
365
+ axs.utils.isValidBoolean = function(a) {
366
+ try {
367
+ var b = JSON.parse(a)
368
+ }catch(c) {
369
+ b = ""
370
+ }
371
+ return"boolean" != typeof b ? {valid:!1, value:a, reason:'"' + a + '" is not a true/false value'} : {valid:!0, value:b}
372
+ };
373
+ axs.utils.isValidIDRefValue = function(a, b) {
374
+ return!b.ownerDocument.getElementById(a) ? {valid:!1, idref:a, reason:'No element with ID "' + a + '"'} : {valid:!0, idref:a}
375
+ };
376
+ axs.utils.isValidNumber = function(a) {
377
+ var b = JSON.parse(a);
378
+ return"number" != typeof b ? {valid:!1, value:a, reason:'"' + a + '" is not a number'} : {valid:!0, value:b}
379
+ };
380
+ axs.utils.isElementImplicitlyFocusable = function(a) {
381
+ return a instanceof HTMLAnchorElement || a instanceof HTMLAreaElement ? a.hasAttribute("href") : a instanceof HTMLInputElement || a instanceof HTMLSelectElement || a instanceof HTMLTextAreaElement || a instanceof HTMLButtonElement || a instanceof HTMLIFrameElement ? !a.disabled : !1
382
+ };
383
+ axs.utils.values = function(a) {
384
+ var b = [], c;
385
+ for(c in a) {
386
+ a.hasOwnProperty(c) && "function" != typeof a[c] && b.push(a[c])
387
+ }
388
+ return b
389
+ };
390
+ axs.utils.namedValues = function(a) {
391
+ var b = {}, c;
392
+ for(c in a) {
393
+ a.hasOwnProperty(c) && "function" != typeof a[c] && (b[c] = a[c])
394
+ }
395
+ return b
396
+ };
397
+ axs.utils.getQuerySelectorText = function(a) {
398
+ if(null == a || "HTML" == a.tagName) {
399
+ return"html"
400
+ }
401
+ if("BODY" == a.tagName) {
402
+ return"body"
403
+ }
404
+ if(a.hasAttribute) {
405
+ if(a.id) {
406
+ return"#" + a.id
407
+ }
408
+ if(a.className) {
409
+ for(var b = "", c = 0;c < a.classList.length;c++) {
410
+ b += "." + a.classList[c]
411
+ }
412
+ var d = 0;
413
+ if(a.parentNode) {
414
+ for(c = 0;c < a.parentNode.children.length;c++) {
415
+ var e = a.parentNode.children[c];
416
+ e.webkitMatchesSelector(b) && d++;
417
+ if(e === a) {
418
+ break
419
+ }
420
+ }
421
+ }else {
422
+ d = 1
423
+ }
424
+ return 1 == d ? axs.utils.getQuerySelectorText(a.parentNode) + " > " + b : axs.utils.getQuerySelectorText(a.parentNode) + " > " + b + ":nth-of-type(" + d + ")"
425
+ }
426
+ if(a.parentNode) {
427
+ b = a.parentNode.children;
428
+ d = 1;
429
+ for(c = 0;b[c] !== a;) {
430
+ b[c].tagName == a.tagName && d++, c++
431
+ }
432
+ c = "";
433
+ "BODY" != a.parentNode.tagName && (c = axs.utils.getQuerySelectorText(a.parentNode) + " > ");
434
+ return 1 == d ? c + a.tagName : c + a.tagName + ":nth-of-type(" + d + ")"
435
+ }
436
+ }else {
437
+ if(a.selectorText) {
438
+ return a.selectorText
439
+ }
440
+ }
441
+ return""
442
+ };
443
+ axs.AuditRule = function(a) {
444
+ for(var b = !0, c = [], d = 0;d < axs.AuditRule.requiredFields.length;d++) {
445
+ var e = axs.AuditRule.requiredFields[d];
446
+ e in a || (b = !1, c.push(e))
447
+ }
448
+ if(!b) {
449
+ throw"Invalid spec; the following fields were not specified: " + c.join(", ") + "\n" + JSON.stringify(a);
450
+ }
451
+ this.name = a.name;
452
+ this.severity = a.severity;
453
+ this.relevantNodesSelector_ = a.relevantNodesSelector;
454
+ this.test_ = a.test;
455
+ this.code = a.code;
456
+ this.heading = a.heading || "";
457
+ this.url = a.url || "";
458
+ this.requiresConsoleAPI = !!a.opt_requiresConsoleAPI
459
+ };
460
+ axs.AuditRule.requiredFields = ["name", "severity", "relevantNodesSelector", "test", "code"];
461
+ axs.AuditRule.NOT_APPLICABLE = {result:axs.constants.AuditResult.NA};
462
+ axs.AuditRule.prototype.addNode = function(a, b) {
463
+ a.push(b)
464
+ };
465
+ axs.AuditRule.prototype.run = function(a, b) {
466
+ function c(a) {
467
+ for(var b = 0;b < d.length;b++) {
468
+ if(a.webkitMatchesSelector(d[b])) {
469
+ return!0
470
+ }
471
+ }
472
+ return!1
473
+ }
474
+ var d = a || [], e = this.relevantNodesSelector_(b || document), f = [];
475
+ if(e instanceof XPathResult) {
476
+ if(e.resultType == XPathResult.ORDERED_NODE_SNAPSHOT_TYPE) {
477
+ if(!e.snapshotLength) {
478
+ return axs.AuditRule.NOT_APPLICABLE
479
+ }
480
+ for(var g = 0;g < e.snapshotLength;g++) {
481
+ var h = e.snapshotItem(g);
482
+ this.test_(h) && !c(h) && this.addNode(f, h)
483
+ }
484
+ }else {
485
+ return console.warn("Unknown XPath result type", e), null
486
+ }
487
+ }else {
488
+ if(!e.length) {
489
+ return{result:axs.constants.AuditResult.NA}
490
+ }
491
+ for(g = 0;g < e.length;g++) {
492
+ h = e[g], this.test_(h) && !c(h) && this.addNode(f, h)
493
+ }
494
+ }
495
+ return{result:f.length ? axs.constants.AuditResult.FAIL : axs.constants.AuditResult.PASS, elements:f}
496
+ };
497
+ axs.AuditRule.specs = {};
498
+ axs.AuditRules = {};
499
+ axs.AuditRules.getRule = function(a) {
500
+ if(!axs.AuditRules.rules) {
501
+ axs.AuditRules.rules = {};
502
+ for(var b in axs.AuditRule.specs) {
503
+ var c = axs.AuditRule.specs[b], d = new axs.AuditRule(c);
504
+ axs.AuditRules.rules[c.name] = d
505
+ }
506
+ }
507
+ return axs.AuditRules.rules[a]
508
+ };
509
+ axs.AuditResults = function() {
510
+ this.errors_ = [];
511
+ this.warnings_ = []
512
+ };
513
+ axs.AuditResults.prototype.addError = function(a) {
514
+ "" != a && this.errors_.push(a)
515
+ };
516
+ axs.AuditResults.prototype.addWarning = function(a) {
517
+ "" != a && this.warnings_.push(a)
518
+ };
519
+ axs.AuditResults.prototype.numErrors = function() {
520
+ return this.errors_.length
521
+ };
522
+ axs.AuditResults.prototype.numWarnings = function() {
523
+ return this.warnings_.length
524
+ };
525
+ axs.AuditResults.prototype.getErrors = function() {
526
+ return this.errors_
527
+ };
528
+ axs.AuditResults.prototype.getWarnings = function() {
529
+ return this.warnings_
530
+ };
531
+ axs.AuditResults.prototype.toString = function() {
532
+ for(var a = "", b = 0;b < this.errors_.length;b++) {
533
+ 0 == b && (a += "\nErrors:\n");
534
+ var c = this.errors_[b], a = a + (c + "\n\n")
535
+ }
536
+ for(b = 0;b < this.warnings_.length;b++) {
537
+ 0 == b && (a += "\nWarnings:\n"), c = this.warnings_[b], a += c + "\n\n"
538
+ }
539
+ return a
540
+ };
541
+ axs.Audit = {};
542
+ axs.AuditConfiguration = function() {
543
+ this.rules_ = {};
544
+ this.auditRulesToIgnore = this.auditRulesToRun = this.scope = null;
545
+ this.withConsoleApi = !1
546
+ };
547
+ axs.AuditConfiguration.prototype = {ignoreSelectors:function(a, b) {
548
+ a in this.rules_ || (this.rules_[a] = {});
549
+ "ignore" in this.rules_[a] || (this.rules_[a].ignore = []);
550
+ Array.prototype.push.call(this.rules_[a].ignore, b)
551
+ }, getIgnoreSelectors:function(a) {
552
+ return a in this.rules_ && "ignore" in this.rules_[a] ? this.rules_[a].ignore : []
553
+ }};
554
+ axs.Audit.run = function(a) {
555
+ a = a || new axs.AuditConfiguration;
556
+ var b = a.withConsoleApi, c = [], d;
557
+ d = a.auditRulesToRun && 0 < a.auditRulesToRun.length ? a.auditRulesToRun : Object.keys(axs.AuditRule.specs);
558
+ if(a.auditRulesToIgnore) {
559
+ for(var e = 0;e < a.auditRulesToIgnore.length;e++) {
560
+ var f = a.auditRulesToIgnore[e];
561
+ 0 > d.indexOf(f) || d.splice(d.indexOf(f), 1)
562
+ }
563
+ }
564
+ for(e = 0;e < d.length;e++) {
565
+ if((f = axs.AuditRules.getRule(d[e])) && !f.disabled && (b || !f.requiresConsoleAPI)) {
566
+ var g = [], h = a.getIgnoreSelectors(f.name);
567
+ (0 < h.length || a.scope) && g.push(h);
568
+ a.scope && g.push(a.scope);
569
+ g = f.run.apply(f, g);
570
+ g.rule = axs.utils.namedValues(f);
571
+ c.push(g)
572
+ }
573
+ }
574
+ return c
575
+ };
576
+ axs.Audit.auditResults = function(a) {
577
+ for(var b = new axs.AuditResults, c = 0;c < a.length;c++) {
578
+ var d = a[c];
579
+ d.result == axs.constants.AuditResult.FAIL && (d.rule.severity == axs.constants.Severity.SEVERE ? b.addError(axs.Audit.accessibilityErrorMessage(d)) : b.addWarning(axs.Audit.accessibilityErrorMessage(d)))
580
+ }
581
+ return b
582
+ };
583
+ axs.Audit.createReport = function(a, b) {
584
+ var c;
585
+ c = "*** Begin accessibility audit results ***\nAn accessibility audit found " + axs.Audit.auditResults(a).toString();
586
+ b && (c += "\nFor more information, please see ", c += b);
587
+ return c += "\n*** End accessibility audit results ***"
588
+ };
589
+ axs.Audit.accessibilityErrorMessage = function(a) {
590
+ for(var b = a.rule.severity == axs.constants.Severity.SEVERE ? "Error: " : "Warning: ", b = b + (a.rule.code + " (" + a.rule.heading + ") failed on the following " + (1 == a.elements.length ? "element" : "elements")), b = 1 == a.elements.length ? b + ":" : b + (" (1 - " + Math.min(5, a.elements.length) + " of " + a.elements.length + "):"), c = Math.min(a.elements.length, 5), d = 0;d < c;d++) {
591
+ var e = a.elements[d], b = b + "\n";
592
+ try {
593
+ b += axs.utils.getQuerySelectorText(e)
594
+ }catch(f) {
595
+ b += " tagName:" + e.tagName, b += " id:" + e.id
596
+ }
597
+ }
598
+ "" != a.rule.url && (b += "\nSee " + a.rule.url + " for more information.");
599
+ return b
600
+ };
601
+ axs.AuditRule.specs.audioWithoutControls = {name:"audioWithoutControls", heading:"Audio elements should have controls", url:"", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
602
+ return a.querySelectorAll("audio[autoplay]")
603
+ }, test:function(a) {
604
+ return!a.querySelectorAll("[controls]").length && 3 < a.duration
605
+ }, code:"AX_AUDIO_01"};
606
+ axs.AuditRule.specs.badAriaAttributeValue = {name:"badAriaAttributeValue", heading:"ARIA state and property values must be valid", url:"", severity:axs.constants.Severity.SEVERE, relevantNodesSelector:function(a) {
607
+ var b = "", c;
608
+ for(c in axs.constants.ARIA_PROPERTIES) {
609
+ b += "[aria-" + c + "],"
610
+ }
611
+ b = b.substring(0, b.length - 1);
612
+ return a.querySelectorAll(b)
613
+ }, test:function(a) {
614
+ for(var b in axs.constants.ARIA_PROPERTIES) {
615
+ var c = "aria-" + b;
616
+ if(a.hasAttribute(c)) {
617
+ var d = a.getAttribute(c);
618
+ if(!axs.utils.getAriaPropertyValue(c, d, a).valid) {
619
+ return!0
620
+ }
621
+ }
622
+ }
623
+ return!1
624
+ }, code:"AX_ARIA_04"};
625
+ axs.AuditRule.specs.badAriaRole = {name:"badAriaRole", heading:"Elements with ARIA roles must use a valid, non-abstract ARIA role", url:"https://code.google.com/p/accessibility-developer-tools/wiki/AuditRules#AX_ARIA_01:_Elements_with_ARIA_roles_must_use_a_valid,_non-abstr", severity:axs.constants.Severity.SEVERE, relevantNodesSelector:function(a) {
626
+ return a.querySelectorAll("[role]")
627
+ }, test:function(a) {
628
+ return!axs.utils.getRole(a).valid
629
+ }, code:"AX_ARIA_01"};
630
+ axs.AuditRule.specs.controlsWithoutLabel = {name:"controlsWithoutLabel", heading:"Controls and media elements should have labels", url:"https://code.google.com/p/accessibility-developer-tools/wiki/AuditRules#AX_TEXT_01:_Controls_and_media_elements_should_have_labels", severity:axs.constants.Severity.SEVERE, relevantNodesSelector:function(a) {
631
+ return a.querySelectorAll('input:not([type="hidden"]):not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), video:not([disabled])')
632
+ }, test:function(a) {
633
+ return axs.utils.isElementOrAncestorHidden(a) || "button" == a.tagName.toLowerCase() && a.textContent.replace(/^\s+|\s+$/g, "").length ? !1 : !axs.utils.hasLabel(a) ? !0 : !1
634
+ }, code:"AX_TEXT_01", ruleName:"Controls and media elements should have labels"};
635
+ axs.AuditRule.specs.focusableElementNotVisibleAndNotAriaHidden = {name:"focusableElementNotVisibleAndNotAriaHidden", heading:"These elements are focusable but either invisible or obscured by another element", url:"https://code.google.com/p/accessibility-developer-tools/wiki/AuditRules#AX_FOCUS_01:_These_elements_are_focusable_but_either_invisible_o", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
636
+ return a.querySelectorAll(axs.utils.FOCUSABLE_ELEMENTS_SELECTOR)
637
+ }, test:function(a) {
638
+ return axs.utils.isElementOrAncestorHidden(a) ? !1 : !axs.utils.elementIsVisible(a)
639
+ }, code:"AX_FOCUS_01"};
640
+ axs.AuditRule.specs.imagesWithoutAltText = {name:"imagesWithoutAltText", heading:"Images should have an alt attribute", url:"https://code.google.com/p/accessibility-developer-tools/wiki/AuditRules#AX_TEXT_02:_Images_should_have_an_alt_attribute,_unless_they_hav", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
641
+ a = a.querySelectorAll("img");
642
+ for(var b = [], c = 0;c < a.length;c++) {
643
+ var d = a[c];
644
+ axs.utils.isElementOrAncestorHidden(d) || b.push(d)
645
+ }
646
+ return b
647
+ }, test:function(a) {
648
+ return!a.hasAttribute("alt") && "presentation" != a.getAttribute("role")
649
+ }, code:"AX_TEXT_02"};
650
+ axs.AuditRule.specs.linkWithUnclearPurpose = {name:"linkWithUnclearPurpose", heading:"The purpose of each link should be clear from the link text", url:"", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
651
+ return a.querySelectorAll("a")
652
+ }, test:function(a) {
653
+ return/^\s*click\s*here\s*[^a-z]?$/i.test(a.textContent)
654
+ }, code:"AX_TITLE_01"};
655
+ axs.AuditRule.specs.lowContrastElements = {name:"lowContrastElements", heading:"Text elements should have a reasonable contrast ratio", url:"https://code.google.com/p/accessibility-developer-tools/wiki/AuditRules#AX_COLOR_01:_Text_elements_should_have_a_reasonable_contrast_rat", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
656
+ return document.evaluate('/html/body//text()[normalize-space(.)!=""]/parent::*[name()!="script"]', a, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
657
+ }, test:function(a) {
658
+ var b = window.getComputedStyle(a, null);
659
+ return(a = axs.utils.getContrastRatioForElementWithComputedStyle(b, a)) && axs.utils.isLowContrast(a, b)
660
+ }, code:"AX_COLOR_01"};
661
+ axs.AuditRule.specs.nonExistentAriaLabelledbyElement = {name:"nonExistentAriaLabelledbyElement", heading:"aria-labelledby attributes should refer to an element which exists in the DOM", url:"https://code.google.com/p/accessibility-developer-tools/wiki/AuditRules#AX_ARIA_02:__aria-labelledby_attributes_should_refer_to_an_eleme", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
662
+ return a.querySelectorAll("[aria-labelledby]")
663
+ }, test:function(a) {
664
+ a = a.getAttribute("aria-labelledby").split(/\s+/);
665
+ for(var b = 0;b < a.length;b++) {
666
+ if(!document.getElementById(a[b])) {
667
+ return!0
668
+ }
669
+ }
670
+ return!1
671
+ }, code:"AX_ARIA_02"};
672
+ axs.AuditRule.specs.pageWithoutTitle = {name:"pageWithoutTitle", heading:"Web pages have titles that describe topic or purpose", url:"", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
673
+ return a
674
+ }, test:function(a) {
675
+ a = a.querySelector("head");
676
+ if(!a) {
677
+ return!0
678
+ }
679
+ a = a.querySelector("title");
680
+ return!a.length || !a[0].textContent
681
+ }, code:"AX_TITLE_01"};
682
+ axs.AuditRule.specs.requiredAriaAttributeMissing = {name:"requiredAriaAttributeMissing", heading:"Elements with ARIA roles must have all required attributes for that role", url:"", severity:axs.constants.Severity.SEVERE, relevantNodesSelector:function(a) {
683
+ return a.querySelectorAll("[role]")
684
+ }, test:function(a) {
685
+ var b = axs.utils.getRole(a);
686
+ if(!b.valid) {
687
+ return!1
688
+ }
689
+ var b = b.details.requiredPropertiesSet, c;
690
+ for(c in b) {
691
+ b = c.replace(/^aria-/, "");
692
+ if("defaultValue" in axs.constants.ARIA_PROPERTIES[b]) {
693
+ return!1
694
+ }
695
+ if(!a.hasAttribute(c)) {
696
+ return!0
697
+ }
698
+ }
699
+ }, code:"AX_ARIA_03"};
700
+ axs.AuditRule.specs.unfocusableElementsWithOnClick = {name:"unfocusableElementsWithOnClick", heading:"Elements with onclick handlers must be focusable", url:"https://code.google.com/p/accessibility-developer-tools/wiki/AuditRules#AX_FOCUS_02:_Elements_with_onclick_handlers_must_be_focusable", severity:axs.constants.Severity.WARNING, opt_requiresConsoleAPI:!0, relevantNodesSelector:function(a) {
701
+ a = a.querySelectorAll("*");
702
+ for(var b = [], c = 0;c < a.length;c++) {
703
+ var d = a[c];
704
+ d instanceof HTMLBodyElement || axs.utils.isElementOrAncestorHidden(d) || "click" in getEventListeners(d) && b.push(d)
705
+ }
706
+ return b
707
+ }, test:function(a) {
708
+ return!a.hasAttribute("tabindex") && !axs.utils.isElementImplicitlyFocusable(a)
709
+ }, code:"AX_FOCUS_02"};
710
+ axs.AuditRule.specs.videoWithoutCaptions = {name:"videoWithoutCaptions", heading:"Video elements should use <track> elements to provide captions", url:"https://code.google.com/p/accessibility-developer-tools/wiki/AuditRules#AX_VIDEO_01:_Video_elements_should_use_<track>_elements_to", severity:axs.constants.Severity.WARNING, relevantNodesSelector:function(a) {
711
+ return a.querySelectorAll("video")
712
+ }, test:function(a) {
713
+ return!a.querySelectorAll("track[kind=captions]").length
714
+ }, code:"AX_VIDEO_01"};
715
+
@@ -0,0 +1,12 @@
1
+ module Capybara::Accessible
2
+ class Driver < Capybara::Selenium::Driver
3
+ include Capybara::Accessible::Auditor
4
+
5
+ def visit(path)
6
+ super
7
+ if audit_results.any? { |r| r['result'] == 'FAIL' }
8
+ raise Capybara::Accessible::InaccessibleError, failure_messages
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ module Capybara
2
+ module Node
3
+ class Element < Base
4
+ include Capybara::Accessible::Auditor
5
+
6
+ def click
7
+ synchronize { base.click }
8
+ begin
9
+ if Capybara.current_driver == :accessible && audit_results.any? { |r| r['result'] == 'FAIL' }
10
+ raise Capybara::Accessible::InaccessibleError, failure_messages
11
+ end
12
+ rescue ::Selenium::WebDriver::Error::UnhandledAlertError => e
13
+ puts "Skipping accessibility audit: #{e}"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ module Capybara
2
+ module Accessible
3
+ VERSION = "0.0.2"
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ class AccessibleApp < TestApp
2
+ get '/accessible' do
3
+ '<form><label for="foo">Foo</label>' +
4
+ '<input type="text" name="foo" id="foo"/></form>' +
5
+ '<a href="/inaccessible">inaccessible</a>'
6
+ end
7
+
8
+ get '/inaccessible' do
9
+ '<form><input type="text" name="foo" id="foo"/></form>'
10
+ end
11
+
12
+ get '/alert' do
13
+ '<a href="#" onclick="alert(\'whoa!\')">Alert!</a>'
14
+ end
15
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Capybara::Accessible::Driver do
4
+ before do
5
+ @session = Capybara::Session.new(:accessible, AccessibleApp)
6
+ end
7
+
8
+ context 'a page without accessibility errors' do
9
+ it 'does not raise an exception on audit failures' do
10
+ expect { @session.visit('/accessible') }.to_not raise_error
11
+ end
12
+ end
13
+
14
+ context 'a page with inaccessible elements' do
15
+ it 'raises an exception on visiting the page' do
16
+ expect { @session.visit('/inaccessible') }.to raise_error(Capybara::Accessible::InaccessibleError)
17
+ end
18
+
19
+ it 'raises an exception when visiting the page via a link' do
20
+ @session.visit('/accessible')
21
+ expect { @session.click_link('inaccessible') }.to raise_error(Capybara::Accessible::InaccessibleError)
22
+ end
23
+
24
+ context 'with configuration that excludes rules' do
25
+ before do
26
+ Capybara::Accessible::Auditor.exclusions = ['AX_TEXT_01']
27
+ end
28
+
29
+ it 'does not raise an error on an excluded rule' do
30
+ expect { @session.visit('/inaccessible') }.to_not raise_error(Capybara::Accessible::InaccessibleError)
31
+ end
32
+ end
33
+ end
34
+
35
+ context 'a page with a javascript popup' do
36
+ it 'does not raise an exception' do
37
+ @session.visit('/alert')
38
+ expect { @session.click_link('Alert!') }.to_not raise_error
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ require 'rspec'
2
+ require 'rspec/autorun'
3
+ require 'rbconfig'
4
+ require 'capybara'
5
+ require 'capybara/spec/spec_helper'
6
+ require 'capybara/accessible'
7
+ require 'accessible_app'
8
+ require 'pry'
9
+
10
+ PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..')).freeze
11
+ $LOAD_PATH << File.join(PROJECT_ROOT, 'lib')
12
+
13
+ RSpec.configure do |c|
14
+ Capybara::SpecHelper.configure(c)
15
+ end
16
+
17
+ Capybara.current_driver = :accessible
metadata ADDED
@@ -0,0 +1,168 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: capybara-accessible
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Case Commons
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-06-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: capybara
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ - - '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.0'
30
+ - - '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.2
33
+ - !ruby/object:Gem::Dependency
34
+ name: selenium-webdriver
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '1.3'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ version: '1.3'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: pry
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: sinatra
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ description: Capybara extension and webdriver for automated accessibility testing
118
+ email:
119
+ - accessibility@casecommons.org
120
+ executables: []
121
+ extensions: []
122
+ extra_rdoc_files: []
123
+ files:
124
+ - .gitignore
125
+ - Gemfile
126
+ - LICENSE.txt
127
+ - README.md
128
+ - Rakefile
129
+ - capybara-accessible.gemspec
130
+ - lib/capybara/accessible.rb
131
+ - lib/capybara/accessible/auditor.rb
132
+ - lib/capybara/accessible/axs_testing.js
133
+ - lib/capybara/accessible/driver.rb
134
+ - lib/capybara/accessible/element.rb
135
+ - lib/capybara/accessible/version.rb
136
+ - spec/accessible_app.rb
137
+ - spec/driver_spec.rb
138
+ - spec/spec_helper.rb
139
+ homepage: https://github.com/Casecommons/capybara-accessible
140
+ licenses:
141
+ - MIT
142
+ metadata: {}
143
+ post_install_message:
144
+ rdoc_options: []
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - '>='
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ requirements: []
158
+ rubyforge_project:
159
+ rubygems_version: 2.0.2
160
+ signing_key:
161
+ specification_version: 4
162
+ summary: A Selenium based webdriver and Capybara extension that runs Google Accessibility
163
+ Developer Tools auditing assertions on page visits.
164
+ test_files:
165
+ - spec/accessible_app.rb
166
+ - spec/driver_spec.rb
167
+ - spec/spec_helper.rb
168
+ has_rdoc: