1# Copyright 2016 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import os
6
7from autotest_lib.client.bin import test
8from autotest_lib.client.bin import utils
9from autotest_lib.client.common_lib import error
10from autotest_lib.client.cros.input_playback import input_playback
11
12
13class a11y_test_base(test.test):
14    """Base class for a11y tests."""
15    version = 1
16
17    # ChromeVox extension id
18    _CHROMEVOX_ID = 'mndnfokpggljbaajbnioimlmbfngpief'
19    _CVOX_STATE_TIMEOUT = 40
20    _CVOX_INDICATOR_TIMEOUT = 40
21
22
23    def warmup(self):
24        """Test setup."""
25        # Emulate a keyboard for later ChromeVox toggle (if needed).
26        # See input_playback. The keyboard is used to play back shortcuts.
27        self._player = input_playback.InputPlayback()
28        self._player.emulate(input_type='keyboard')
29        self._player.find_connected_inputs()
30
31
32    def _child_test_cleanup(self):
33        """Can be overwritten by child classes and run duing parent cleanup."""
34        return
35
36
37    def cleanup(self):
38        self._player.close()
39        self._child_test_cleanup()
40
41
42    def _toggle_chromevox(self):
43        """Use keyboard shortcut and emulated keyboard to toggle ChromeVox."""
44        self._player.blocking_playback_of_default_file(
45                input_type='keyboard', filename='keyboard_ctrl+alt+z')
46
47
48    def _search_shift_move(self, direction):
49        """Playback the keyboard movement shortcut for given direction.
50
51        @param direction: right, left, up, or down.
52
53        """
54        assert direction in ['right', 'left', 'up', 'down']
55        self._player.blocking_playback_of_default_file(
56                input_type='keyboard',
57                filename='keyboard_search+shift+%s' % direction)
58
59
60    def _tab_move(self, direction='forwards'):
61        """Playback a tab or shift+tab for the given direction.
62
63        @param direction: forwards or backwards.
64
65        """
66        assert direction in ['forwards', 'backwards']
67        is_forwards = direction is 'forwards'
68        filename = 'keyboard_tab' if is_forwards else 'keyboard_shift+tab'
69        self._player.blocking_playback_of_default_file(
70                input_type='keyboard', filename=filename)
71
72
73    def _set_feature(self, feature, value):
74        """Set given feature to given value using a11y API call.
75
76        Presupposes self._extension (with accessibilityFeatures enabled).
77
78        @param feature: string of accessibility feature to change.
79        @param value: boolean of expected value.
80
81        @raises: error.TestError if feature cannot be set.
82
83        """
84        value_str = 'true' if value else 'false'
85        cmd = ('window.__result = null;\n'
86               'chrome.accessibilityFeatures.%s.set({value: %s});\n'
87               'chrome.accessibilityFeatures.%s.get({}, function(d) {'
88               'window.__result = d[\'value\']; });' % (
89                       feature, value_str, feature))
90        self._extension.ExecuteJavaScript(cmd)
91
92        poll_cmd = 'window.__result == %s;' % value_str
93        utils.poll_for_condition(
94                lambda: self._extension.EvaluateJavaScript(poll_cmd),
95                exception = error.TestError(
96                        'Timeout while trying to set %s to %s' %
97                        (feature, value_str)))
98
99
100    def _get_chromevox_state(self):
101        """Return whether ChromeVox is enabled or not.
102
103        Presupposes self._extension (with management enabled).
104
105        @returns: value of management.get.enabled.
106
107        @raises: error.TestError if state cannot be determined.
108
109        """
110        cmd = ('window.__enabled = null;\n'
111               'chrome.management.get(\'%s\', function(r) {'
112               'if (r) {window.__enabled = r[\'enabled\'];} '
113               'else {window.__enabled = false;}});' % self._CHROMEVOX_ID)
114        self._extension.ExecuteJavaScript(cmd)
115
116        poll_cmd = 'window.__enabled != null;'
117        utils.poll_for_condition(
118                lambda: self._extension.EvaluateJavaScript(poll_cmd),
119                exception=error.TestError(
120                        'ChromeVox: management.get.enabled was not set!'))
121        return self._extension.EvaluateJavaScript('window.__enabled')
122
123
124    def _confirm_chromevox_state(self, value):
125        """Fail test unless ChromeVox state is given value.
126
127        Presupposes self._extension (with management enabled).
128
129        @param value: True or False, whether ChromeVox should be enabled.
130
131        @raises: error.TestFail if actual state doesn't match expected.
132
133        """
134        utils.poll_for_condition(
135                lambda: self._get_chromevox_state() == value,
136                exception=error.TestFail('ChromeVox: enabled state '
137                                         'was not %s.' % value),
138                timeout=self._CVOX_STATE_TIMEOUT)
139
140
141    def _get_chromevox_indicator(self, tab):
142        """Return whether the orange ChromeVox highlight is present or not.
143
144        Looks for the orange highlight on the given tab.
145
146        @returns: whether 'cvox_indicator_container' is found on the page
147
148        """
149        cmd = ('document.getElementsByClassName('
150               '  "cvox_indicator_container").length > 0;')
151        return tab.EvaluateJavaScript(cmd)
152
153
154    def _confirm_chromevox_indicator(self, value):
155        """Fail test unless indicator state is given value.
156
157        Presupposes self._tab (the tab on which to check).
158
159        @param value: True or False, whether ChromeVox indicator should show.
160
161        @raises: error.TestFail if actual state doesn't match expected.
162
163        """
164        utils.poll_for_condition(
165                lambda: self._get_chromevox_indicator(self._tab) == value,
166                exception=error.TestFail('ChromeVox: "Indicator present" '
167                                         'was not %s.' % value),
168                timeout=self._CVOX_INDICATOR_TIMEOUT)
169
170
171    def _get_extension_path(self):
172        """Return the path to the default accessibility extension.
173
174        @returns: string of path to default extension.
175
176        """
177        this_dir = os.path.dirname(__file__)
178        return os.path.join(this_dir, 'a11y_ext')
179