Chromium Code Reviews| Index: tools/accessibility/nvda/nvda_chrome_tests.py |
| diff --git a/tools/accessibility/nvda/nvda_chrome_tests.py b/tools/accessibility/nvda/nvda_chrome_tests.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..2061377d4bac583fe7eeeb796f98bc5ab555031d |
| --- /dev/null |
| +++ b/tools/accessibility/nvda/nvda_chrome_tests.py |
| @@ -0,0 +1,201 @@ |
| +#!/usr/bin/env python |
| +# Copyright (c) 2014 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. |
| + |
| +"""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
|
| + |
| +This file performs Semi-automated tests of Chrome with NVDA |
| +(NonVisual Desktop Access), a popular open-source screen reader for |
| +visually impaired users on Windows. It works by launching Chrome in a |
| +subprocess, then launching NVDA in a special environment that simulates |
| +speech rather than actually speaking, and ignores all events coming from |
| +processes other than a specific Chrome process ID. Each test automates |
| +Chrome with a series of actions and asserts that NVDA gives the expected |
| +feedback in response.""" |
| + |
| +import os |
| +import pywinauto |
| +import re |
| +import shutil |
| +import subprocess |
| +import sys |
| +import tempfile |
| +import time |
| +import unittest |
| + |
| +CHROME_PROFILES_PATH = os.path.join(os.getcwd(), 'chrome_profiles') |
| +CHROME_PATH = os.path.join(os.environ['USERPROFILE'], |
| + 'AppData', |
| + 'Local', |
| + 'Google', |
| + 'Chrome SxS', |
| + 'Application', |
| + 'chrome.exe') |
| +NVDA_PATH = os.path.join(os.getcwd(), |
| + 'nvdaPortable', |
| + 'nvda_noUIAccess.exe') |
| +NVDA_PROCTEST_PATH = os.path.join(os.getcwd(), |
| + 'nvda-proctest') |
| +NVDA_LOGPATH = os.path.join(os.getcwd(), |
| + 'nvda_log.txt') |
| + |
| +class NvdaChromeTest(unittest.TestCase): |
| + @classmethod |
| + def setUpClass(cls): |
| + print 'user data: %s' % CHROME_PROFILES_PATH |
| + print 'chrome: %s' % CHROME_PATH |
| + print 'nvda: %s' % NVDA_PATH |
| + print 'nvda_proctest: %s' % NVDA_PROCTEST_PATH |
| + |
| + print 'Clearing user data directory and log file from previous runs' |
| + if os.access(CHROME_PROFILES_PATH, os.F_OK): |
| + shutil.rmtree(CHROME_PROFILES_PATH) |
| + if os.access(NVDA_LOGPATH, os.F_OK): |
| + os.remove(NVDA_LOGPATH) |
| + os.mkdir(CHROME_PROFILES_PATH, 0777) |
| + |
| + def setUp(self): |
| + user_data_dir = tempfile.mkdtemp(dir = CHROME_PROFILES_PATH) |
| + args = [CHROME_PATH, |
| + '--user-data-dir=%s' % user_data_dir, |
| + '--no-first-run', |
| + 'about:blank'] |
| + print ' '.join(args) |
| + self._chrome_proc = subprocess.Popen(args) |
| + self._chrome_proc.poll() |
| + if self._chrome_proc.returncode is None: |
| + print 'Chrome is running' |
| + else: |
| + print 'Chrome exited with code', self._chrome_proc.returncode |
| + sys.exit() |
| + print 'Chrome pid: %d' % self._chrome_proc.pid |
| + 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
|
| + |
| + os.environ['NVDA_SPECIFIC_PROCESS'] = str(self._chrome_proc.pid) |
| + |
| + args = [NVDA_PATH, |
| + '-m', |
| + '-c', |
| + NVDA_PROCTEST_PATH, |
| + '-f', |
| + NVDA_LOGPATH] |
| + self._nvda_proc = subprocess.Popen(args) |
| + self._nvda_proc.poll() |
| + if self._nvda_proc.returncode is None: |
| + print 'NVDA is running' |
| + else: |
| + print 'NVDA exited with code', self._nvda_proc.returncode |
| + sys.exit() |
| + print 'NVDA pid: %d' % self._nvda_proc.pid |
| + time.sleep(1) |
|
David Tseng
2014/06/30 16:55:28
Ditto.
|
| + |
|
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
|
| + app = pywinauto.application.Application() |
| + app.connect_(process = self._chrome_proc.pid) |
| + self._pywinauto_window = app.top_window_() |
| + |
| + def tearDown(self): |
| + print 'Shutting down' |
| + |
| + self._chrome_proc.poll() |
| + if self._chrome_proc.returncode is None: |
| + print 'Killing Chrome subprocess' |
| + self._chrome_proc.kill() |
| + else: |
| + print 'Chrome already died.' |
| + |
| + self._nvda_proc.poll() |
| + if self._nvda_proc.returncode is None: |
| + print 'Killing Nvda subprocess' |
|
David Tseng
2014/06/30 16:55:29
nit: NVDA
dmazzoni
2014/07/15 06:09:58
Done.
|
| + self._nvda_proc.kill() |
| + else: |
| + print 'Nvda already died.' |
|
David Tseng
2014/06/30 16:55:29
Ditto.
dmazzoni
2014/07/15 06:09:58
Done.
|
| + |
| + def _GetSpeechFromNvdaLogFile(self): |
| + """Return everything NVDA would have spoken as a list of strings. |
| + |
| + Parses lines like this from NVDA's log file: |
| + Speaking [LangChangeCommand ('en'), u'Google Chrome', u'window'] |
| + Speaking character u'slash' |
| + |
| + Returns a single list of strings like this: |
| + [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
|
| + """ |
| + lines = open(NVDA_LOGPATH).readlines() |
| + regex = re.compile(r"u'((?:[^\'\\]|\\.)*)\'") |
| + result = [] |
| + for line in lines: |
| + for m in regex.finditer(line): |
| + result.append(m.group(1)) |
| + return result |
| + |
| + # |
| + # Tests |
| + # |
| + |
| + def testTypingInOmnibox(self): |
| + # Ctrl+A: Select all. |
| + self._pywinauto_window.TypeKeys("^A") |
| + 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
|
| + |
| + # Type three characters. |
| + self._pywinauto_window.TypeKeys("xyz") |
| + time.sleep(1) |
| + |
| + # Arrow back over two characters. |
| + self._pywinauto_window.TypeKeys("{LEFT}") |
| + time.sleep(1) |
| + self._pywinauto_window.TypeKeys("{LEFT}") |
| + time.sleep(1) |
| + |
| + speech = self._GetSpeechFromNvdaLogFile() |
| + |
| + self.assertIn('Address and search bar edit', speech) |
| + speech = speech[speech.index('Address and search bar edit'):] |
| + |
| + self.assertEqual(speech, [ |
| + 'Address and search bar edit', |
| + 'about:blank', |
| + 'selecting about:blank', |
| + 'x', |
| + 'y', |
| + 'z', |
| + 'z', |
| + 'unselecting ', |
| + 'y']) |
| + |
| + def testFocusToolbarButton(self): |
| + # Alt+Shift+T. |
| + self._pywinauto_window.TypeKeys("%+T") |
| + time.sleep(1) |
| + |
| + speech = self._GetSpeechFromNvdaLogFile() |
| + self.assertIn('Reload button Reload this page', speech) |
| + |
| + def testReadAllOnPageLoad(self): |
| + # Ctrl+A: Select all |
| + self._pywinauto_window.TypeKeys("^A") |
| + time.sleep(1) |
| + |
| + # Load data url. |
| + self._pywinauto_window.TypeKeys("data:text/html,Hello<p>World") |
| + time.sleep(1) |
| + self._pywinauto_window.TypeKeys("{ENTER}") |
| + time.sleep(1) |
| + |
| + speech = self._GetSpeechFromNvdaLogFile() |
| + |
| + self.assertIn('document', speech) |
| + speech = speech[speech.index('document'):] |
| + |
| + self.assertEqual(speech, [ |
| + 'document', |
| + 'Hello', |
| + 'World']) |
| + |
| +if __name__ == '__main__': |
| + unittest.main() |
| + |