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 |