// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview Tests to ensure that the accessibility audit and mechanisms * to enable/disable it work as expected. * @author aboxhall@google.com (Alice Boxhall) * @see test_api.js */ /** * Test fixture for accessibility audit test. * @constructor * @extends testing.Test */ function WebUIAccessibilityAuditBrowserTest() {} WebUIAccessibilityAuditBrowserTest.prototype = { __proto__: testing.Test.prototype, browsePreload: 'chrome://terms', runAccessibilityChecks: true, accessibilityIssuesAreErrors: true, /** * Number of expected accessibility errors, if it should be checked, otherwise * null. Note: 'a11y' is short for 'accessibility' * @type {?number} */ expectedWarnings: null, /** * Number of expected accessibility warnings, if it should be checked, * otherwise null. * @type {?number} */ expectedErrors: null, isAsync: false, /** @override */ setUp: function() { this.accessibilityAuditConfig.auditRulesToIgnore = []; this.accessibilityAuditConfig.auditRulesToRun = ['lowContrastElements', 'badAriaRole', 'controlsWithoutLabel']; }, tearDown: function() { var accessibilityResults = this.getAccessibilityResults(); var numAccessibilityErrors = 0; var numAccessibilityWarnings = 0; for (var i = 0; i < accessibilityResults.length; i++) { var result = accessibilityResults[i]; if (result.rule.severity == axs.constants.Severity.Warning) numAccessibilityWarnings++; else numAccessibilityErrors++; } if (this.expectedErrors != null) expectEquals(this.expectedErrors, numAccessibilityErrors); if (this.expectedWarnings != null) { expectEquals(this.expectedWarnings, numAccessibilityWarnings); } testing.Test.prototype.tearDown.call(this); } }; /** * Test fixture for tests that are expected to fail. */ function WebUIAccessibilityAuditBrowserTest_ShouldFail() {} WebUIAccessibilityAuditBrowserTest_ShouldFail.prototype = { __proto__: WebUIAccessibilityAuditBrowserTest.prototype, testShouldFail: true }; /** * Adds some canned audit failures to the page being tested: * - A low-contrast (white on white) element * - An element with a non-existent ARIA role * - A control without a label */ function addAuditFailures() { // Contrast ratio var style = document.createElement('style'); style.innerText = 'body { background-color: #ffff }\n' + 'p { color: #ffffff }'; document.head.appendChild(style); // Bad ARIA role var div = document.createElement('div'); div.setAttribute('role', 'not-a-role'); document.body.appendChild(div); // Unlabelled control var input = document.createElement('input'); input.type = 'text'; document.body.appendChild(input); } /** * Adds an expectation that console.warn() will be called at least once with * a string matching '.*accessibility.*'. */ function expectReportConsoleWarning() { function StubConsole() {}; StubConsole.prototype.warn = function() {}; StubConsole.prototype.log = function() {}; StubConsole.prototype.info = function() {}; StubConsole.prototype.error = function() {}; var mockConsole = mock(StubConsole); mockConsole.expects(atLeastOnce()).warn(stringContains('accessibility')); mockConsole.stubs().log(ANYTHING); mockConsole.stubs().info(ANYTHING); mockConsole.stubs().error(ANYTHING); console = mockConsole.proxy(); } /** * Creates a mock axs.Audit object. * @return {object} a mock object with a run() method. */ function createMockAudit() { function StubAudit() {}; StubAudit.prototype.run = function() {}; return mock(StubAudit); } /** * Creates an expectation that the global axs.Audit object will never have its * run() method called. */ function expectAuditWillNotRun() { var audit = createMockAudit(); audit.expects(never()).run(); axs.Audit = audit.proxy(); } /** * Creates an expectation that the global axs.Audit object will have its run() * method called |times| times. * This creates an interstitial mock axs.Audit object with the expectation, and * delegates to the real axs.Audit object to run the actual audit. * @param {number} times The number of times the audit is expected to run. */ function expectAuditWillRun(times, auditConfig) { var audit = createMockAudit(); var realAudit = axs.Audit; var expectedInvocation = audit.expects(exactly(times)).run(ANYTHING); var willArgs = []; for (var i = 0; i < times; i++) willArgs.push(callFunction(realAudit.run, auditConfig)); expectedInvocation.will.apply(expectedInvocation, willArgs); axs.Audit = audit.proxy(); axs.Audit.createReport = realAudit.createReport; axs.Audit.auditResults = realAudit.auditResults; axs.Audit.accessibilityErrorMessage = realAudit.accessibilityErrorMessage; } // Test that an audit failure causes a test failure, if both // |runAccessibilityChecks| and |accessibilityIssuesAreErrors| are true. TEST_F('WebUIAccessibilityAuditBrowserTest_ShouldFail', 'testWithAuditFailures', function() { expectAuditWillRun(1, this.accessibilityAuditConfig); addAuditFailures(); }); // Test that the accessibility audit does not run if |runAccessibilityChecks| // is false. TEST_F('WebUIAccessibilityAuditBrowserTest', 'testWithAuditFailures_a11yChecksDisabled', function() { expectAuditWillNotRun(); this.disableAccessibilityChecks(); addAuditFailures(); }); // Tests that the accessibility audit will run but not cause a test failure when // accessibilityIssuesAreErrors(false) is called in the test function TEST_F('WebUIAccessibilityAuditBrowserTest', 'testWithAuditFailures_a11yIssuesAreWarnings', function() { this.accessibilityIssuesAreErrors = false; expectAuditWillRun(1, this.accessibilityAuditConfig); expectReportConsoleWarning(); this.expectedWarnings = 1; this.expectedErrors = 2; this.enableAccessibilityChecks(); addAuditFailures(); }); /** * Test fixture with |runAccessibilityChecks| set to false. * @constructor * @extends {WebUIAccessibilityAuditBrowserTest} */ function WebUIAccessibilityAuditBrowserTest_TestsDisabledInFixture() {} WebUIAccessibilityAuditBrowserTest_TestsDisabledInFixture.prototype = { __proto__: WebUIAccessibilityAuditBrowserTest.prototype, runAccessibilityChecks: false, accessibilityIssuesAreErrors: true, }; /** * Test fixture with |runAccessibilityChecks| set to false for tests that should * fail. * @constructor * @extends {WebUIAccessibilityAuditBrowserTest_TestsDisabledInFixture} */ function WebUIAccessibilityAuditBrowserTest_TestsDisabledInFixture_ShouldFail() {} WebUIAccessibilityAuditBrowserTest_TestsDisabledInFixture_ShouldFail.prototype = { __proto__: WebUIAccessibilityAuditBrowserTest_TestsDisabledInFixture.prototype, testShouldFail: true }; // Test that the accessibility audit does not run when |runAccessibilityChecks| // is set to false in the test fixture. TEST_F('WebUIAccessibilityAuditBrowserTest_TestsDisabledInFixture', 'testWithAuditFailures_a11yChecksNotEnabled', function() { expectAuditWillNotRun(); addAuditFailures(); }); // Test that the accessibility audit does run if the enableAccessibilityChecks() // method is called in the test function. TEST_F('WebUIAccessibilityAuditBrowserTest_TestsDisabledInFixture_ShouldFail', 'testWithAuditFailures', function() { expectAuditWillRun(1, this.accessibilityAuditConfig); this.enableAccessibilityChecks(); addAuditFailures(); }); // Test that the accessibility audit runs when the expectAccessibilityOk() // method is called. TEST_F('WebUIAccessibilityAuditBrowserTest_TestsDisabledInFixture', 'testRunningAuditManually_noErrors', function() { expectAuditWillRun(1, this.accessibilityAuditConfig); expectAccessibilityOk(); }); // Test that calling expectAccessibilityOk() when there are accessibility issues // on the page causes the test to fail. TEST_F('WebUIAccessibilityAuditBrowserTest_TestsDisabledInFixture_ShouldFail', 'testRunningAuditManually_withErrors', function() { expectAuditWillRun(1, this.accessibilityAuditConfig); addAuditFailures(); expectAccessibilityOk(); }); // Test that calling expectAccessibilityOk() multiple times will cause the // accessibility audit to run multiple times. TEST_F('WebUIAccessibilityAuditBrowserTest_TestsDisabledInFixture', 'testRunningAuditManuallySeveralTimes', function() { expectAuditWillRun(2, this.accessibilityAuditConfig); expectAccessibilityOk(); expectAccessibilityOk(); }); /** * Test fixture with |accessibilityIssuesAreErrors| set to false. * @constructor * @extends {WebUIAccessibilityAuditBrowserTest} */ function WebUIAccessibilityAuditBrowserTest_IssuesAreWarnings() {} WebUIAccessibilityAuditBrowserTest_IssuesAreWarnings.prototype = { __proto__: WebUIAccessibilityAuditBrowserTest.prototype, accessibilityIssuesAreErrors: false, }; /** * Test fixture with |accessibilityIssuesAreErrors| set to false for tests that * should fail * @constructor * @extends {WebUIAccessibilityAuditBrowserTest_IssuesAreWarnings} */ function WebUIAccessibilityAuditBrowserTest_IssuesAreWarnings_ShouldFail() {} WebUIAccessibilityAuditBrowserTest_IssuesAreWarnings_ShouldFail.prototype = { __proto__: WebUIAccessibilityAuditBrowserTest_IssuesAreWarnings.prototype, testShouldFail: true }; // Tests that the accessibility audit will run but not cause a test failure when // |accessibilityIssuesAreErrors| is false in the test fixture. TEST_F('WebUIAccessibilityAuditBrowserTest_IssuesAreWarnings', 'testWithAuditFailures', function() { expectAuditWillRun(1, this.accessibilityAuditConfig); expectReportConsoleWarning(); this.expectedWarnings = 1; this.expectedErrors = 2; this.enableAccessibilityChecks(); addAuditFailures(); }); // Tests that the accessibility audit will run and call a test failure when // accessibilityIssuesAreErrors(true) is called in the test function. TEST_F('WebUIAccessibilityAuditBrowserTest_IssuesAreWarnings_ShouldFail', 'testWithAuditFailuresAndIssuesAreErrors', function() { expectAuditWillRun(1, this.accessibilityAuditConfig); this.expectedWarnings = 1; this.expectedErrors = 2; this.accessibilityIssuesAreErrors = true; this.enableAccessibilityChecks(); addAuditFailures(); }); // Tests that the accessibility audit will run twice if expectAccessibilityOk() // is called during the test function and |runAccessibilityChecks| is true in // the test fixture. TEST_F('WebUIAccessibilityAuditBrowserTest_IssuesAreWarnings', 'testWithAuditFailuresAndExpectA11yOk', function() { expectAuditWillRun(2, this.accessibilityAuditConfig); expectAccessibilityOk(); this.expectedWarnings = 1; this.expectedErrors = 2; expectReportConsoleWarning(); this.enableAccessibilityChecks(); addAuditFailures(); }); // Tests that parts of the page can be ignored on a per-audit rule basis. TEST_F('WebUIAccessibilityAuditBrowserTest_IssuesAreWarnings', 'testCanIgnoreSelectors', function() { this.disableAccessibilityChecks(); addAuditFailures(); var accessibilityResults = []; try { assertAccessibilityOk(accessibilityResults); } catch (e) { // Expected error from assertion } expectEquals(3, accessibilityResults.length); accessibilityResults.length = 0; this.accessibilityAuditConfig.ignoreSelectors('lowContrastElements', '*'); try { assertAccessibilityOk(accessibilityResults); } catch (e) { // Expected error from assertion } expectEquals(2, accessibilityResults.length); for (var i = 0; i < accessibilityResults.length; i++) { expectFalse(accessibilityResults[i].rule.name == 'lowContrastElements'); } });