Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
| 2 # Copyright (c) 2014 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """Semi-automated tests of Chrome with NVDA. | |
|
David Tseng
2014/06/30 16:55:28
What does it mean to be semi automated?
dmazzoni
2014/07/15 06:09:58
Clarified in comments. I basically mean that it's
| |
| 7 | |
| 8 This file performs Semi-automated tests of Chrome with NVDA | |
| 9 (NonVisual Desktop Access), a popular open-source screen reader for | |
| 10 visually impaired users on Windows. It works by launching Chrome in a | |
| 11 subprocess, then launching NVDA in a special environment that simulates | |
| 12 speech rather than actually speaking, and ignores all events coming from | |
| 13 processes other than a specific Chrome process ID. Each test automates | |
| 14 Chrome with a series of actions and asserts that NVDA gives the expected | |
| 15 feedback in response.""" | |
| 16 | |
| 17 import os | |
| 18 import pywinauto | |
| 19 import re | |
| 20 import shutil | |
| 21 import subprocess | |
| 22 import sys | |
| 23 import tempfile | |
| 24 import time | |
| 25 import unittest | |
| 26 | |
| 27 CHROME_PROFILES_PATH = os.path.join(os.getcwd(), 'chrome_profiles') | |
| 28 CHROME_PATH = os.path.join(os.environ['USERPROFILE'], | |
| 29 'AppData', | |
| 30 'Local', | |
| 31 'Google', | |
| 32 'Chrome SxS', | |
| 33 'Application', | |
| 34 'chrome.exe') | |
| 35 NVDA_PATH = os.path.join(os.getcwd(), | |
| 36 'nvdaPortable', | |
| 37 'nvda_noUIAccess.exe') | |
| 38 NVDA_PROCTEST_PATH = os.path.join(os.getcwd(), | |
| 39 'nvda-proctest') | |
| 40 NVDA_LOGPATH = os.path.join(os.getcwd(), | |
| 41 'nvda_log.txt') | |
| 42 | |
| 43 class NvdaChromeTest(unittest.TestCase): | |
| 44 @classmethod | |
| 45 def setUpClass(cls): | |
| 46 print 'user data: %s' % CHROME_PROFILES_PATH | |
| 47 print 'chrome: %s' % CHROME_PATH | |
| 48 print 'nvda: %s' % NVDA_PATH | |
| 49 print 'nvda_proctest: %s' % NVDA_PROCTEST_PATH | |
| 50 | |
| 51 print | |
| 52 print 'Clearing user data directory and log file from previous runs' | |
| 53 if os.access(CHROME_PROFILES_PATH, os.F_OK): | |
| 54 shutil.rmtree(CHROME_PROFILES_PATH) | |
| 55 if os.access(NVDA_LOGPATH, os.F_OK): | |
| 56 os.remove(NVDA_LOGPATH) | |
| 57 os.mkdir(CHROME_PROFILES_PATH, 0777) | |
| 58 | |
| 59 def setUp(self): | |
| 60 user_data_dir = tempfile.mkdtemp(dir = CHROME_PROFILES_PATH) | |
| 61 args = [CHROME_PATH, | |
| 62 '--user-data-dir=%s' % user_data_dir, | |
| 63 '--no-first-run', | |
| 64 'about:blank'] | |
| 65 print | |
| 66 print ' '.join(args) | |
| 67 self._chrome_proc = subprocess.Popen(args) | |
| 68 self._chrome_proc.poll() | |
| 69 if self._chrome_proc.returncode is None: | |
| 70 print 'Chrome is running' | |
| 71 else: | |
| 72 print 'Chrome exited with code', self._chrome_proc.returncode | |
| 73 sys.exit() | |
| 74 print 'Chrome pid: %d' % self._chrome_proc.pid | |
| 75 time.sleep(1) | |
|
David Tseng
2014/06/30 16:55:28
Is this needed? Looks flakey.
dmazzoni
2014/07/15 06:09:58
OK, I successfully removed this and replaced it wi
| |
| 76 | |
| 77 os.environ['NVDA_SPECIFIC_PROCESS'] = str(self._chrome_proc.pid) | |
| 78 | |
| 79 args = [NVDA_PATH, | |
| 80 '-m', | |
| 81 '-c', | |
| 82 NVDA_PROCTEST_PATH, | |
| 83 '-f', | |
| 84 NVDA_LOGPATH] | |
| 85 self._nvda_proc = subprocess.Popen(args) | |
| 86 self._nvda_proc.poll() | |
| 87 if self._nvda_proc.returncode is None: | |
| 88 print 'NVDA is running' | |
| 89 else: | |
| 90 print 'NVDA exited with code', self._nvda_proc.returncode | |
| 91 sys.exit() | |
| 92 print 'NVDA pid: %d' % self._nvda_proc.pid | |
| 93 time.sleep(1) | |
|
David Tseng
2014/06/30 16:55:28
Ditto.
| |
| 94 | |
|
David Tseng
2014/06/30 16:55:29
Wrt ordering, do we ever want to test Chrome initi
dmazzoni
2014/07/15 06:09:58
Sure. My guess is that opening a new browser windo
dmazzoni
2014/07/15 15:41:18
Oh, I almost forgot - one big reason I did it in t
| |
| 95 app = pywinauto.application.Application() | |
| 96 app.connect_(process = self._chrome_proc.pid) | |
| 97 self._pywinauto_window = app.top_window_() | |
| 98 | |
| 99 def tearDown(self): | |
| 100 print | |
| 101 print 'Shutting down' | |
| 102 | |
| 103 self._chrome_proc.poll() | |
| 104 if self._chrome_proc.returncode is None: | |
| 105 print 'Killing Chrome subprocess' | |
| 106 self._chrome_proc.kill() | |
| 107 else: | |
| 108 print 'Chrome already died.' | |
| 109 | |
| 110 self._nvda_proc.poll() | |
| 111 if self._nvda_proc.returncode is None: | |
| 112 print 'Killing Nvda subprocess' | |
|
David Tseng
2014/06/30 16:55:29
nit: NVDA
dmazzoni
2014/07/15 06:09:58
Done.
| |
| 113 self._nvda_proc.kill() | |
| 114 else: | |
| 115 print 'Nvda already died.' | |
|
David Tseng
2014/06/30 16:55:29
Ditto.
dmazzoni
2014/07/15 06:09:58
Done.
| |
| 116 | |
| 117 def _GetSpeechFromNvdaLogFile(self): | |
| 118 """Return everything NVDA would have spoken as a list of strings. | |
| 119 | |
| 120 Parses lines like this from NVDA's log file: | |
| 121 Speaking [LangChangeCommand ('en'), u'Google Chrome', u'window'] | |
| 122 Speaking character u'slash' | |
| 123 | |
| 124 Returns a single list of strings like this: | |
| 125 [u'Google Chrome', u'window', u'slash'] | |
|
David Tseng
2014/06/30 16:55:28
Any chance NVDA could just use json for the log fo
dmazzoni
2014/07/15 06:09:58
Happy to ask - but this seems robust enough for wh
| |
| 126 """ | |
| 127 lines = open(NVDA_LOGPATH).readlines() | |
| 128 regex = re.compile(r"u'((?:[^\'\\]|\\.)*)\'") | |
| 129 result = [] | |
| 130 for line in lines: | |
| 131 for m in regex.finditer(line): | |
| 132 result.append(m.group(1)) | |
| 133 return result | |
| 134 | |
| 135 # | |
| 136 # Tests | |
| 137 # | |
| 138 | |
| 139 def testTypingInOmnibox(self): | |
| 140 # Ctrl+A: Select all. | |
| 141 self._pywinauto_window.TypeKeys("^A") | |
| 142 time.sleep(1) | |
|
David Tseng
2014/06/30 16:55:29
Going to be flakey; any way we could just listen t
dmazzoni
2014/07/15 06:09:58
I think I've removed the flakiness. I really like
| |
| 143 | |
| 144 # Type three characters. | |
| 145 self._pywinauto_window.TypeKeys("xyz") | |
| 146 time.sleep(1) | |
| 147 | |
| 148 # Arrow back over two characters. | |
| 149 self._pywinauto_window.TypeKeys("{LEFT}") | |
| 150 time.sleep(1) | |
| 151 self._pywinauto_window.TypeKeys("{LEFT}") | |
| 152 time.sleep(1) | |
| 153 | |
| 154 speech = self._GetSpeechFromNvdaLogFile() | |
| 155 | |
| 156 self.assertIn('Address and search bar edit', speech) | |
| 157 speech = speech[speech.index('Address and search bar edit'):] | |
| 158 | |
| 159 self.assertEqual(speech, [ | |
| 160 'Address and search bar edit', | |
| 161 'about:blank', | |
| 162 'selecting about:blank', | |
| 163 'x', | |
| 164 'y', | |
| 165 'z', | |
| 166 'z', | |
| 167 'unselecting ', | |
| 168 'y']) | |
| 169 | |
| 170 def testFocusToolbarButton(self): | |
| 171 # Alt+Shift+T. | |
| 172 self._pywinauto_window.TypeKeys("%+T") | |
| 173 time.sleep(1) | |
| 174 | |
| 175 speech = self._GetSpeechFromNvdaLogFile() | |
| 176 self.assertIn('Reload button Reload this page', speech) | |
| 177 | |
| 178 def testReadAllOnPageLoad(self): | |
| 179 # Ctrl+A: Select all | |
| 180 self._pywinauto_window.TypeKeys("^A") | |
| 181 time.sleep(1) | |
| 182 | |
| 183 # Load data url. | |
| 184 self._pywinauto_window.TypeKeys("data:text/html,Hello<p>World") | |
| 185 time.sleep(1) | |
| 186 self._pywinauto_window.TypeKeys("{ENTER}") | |
| 187 time.sleep(1) | |
| 188 | |
| 189 speech = self._GetSpeechFromNvdaLogFile() | |
| 190 | |
| 191 self.assertIn('document', speech) | |
| 192 speech = speech[speech.index('document'):] | |
| 193 | |
| 194 self.assertEqual(speech, [ | |
| 195 'document', | |
| 196 'Hello', | |
| 197 'World']) | |
| 198 | |
| 199 if __name__ == '__main__': | |
| 200 unittest.main() | |
| 201 | |
| OLD | NEW |