summaryrefslogtreecommitdiffstats
path: root/tools/accessibility
diff options
context:
space:
mode:
Diffstat (limited to 'tools/accessibility')
-rw-r--r--tools/accessibility/nvda/OWNERS3
-rw-r--r--tools/accessibility/nvda/README.txt58
-rw-r--r--tools/accessibility/nvda/nvda_chrome_tests.py229
3 files changed, 290 insertions, 0 deletions
diff --git a/tools/accessibility/nvda/OWNERS b/tools/accessibility/nvda/OWNERS
new file mode 100644
index 0000000..c50d5b8
--- /dev/null
+++ b/tools/accessibility/nvda/OWNERS
@@ -0,0 +1,3 @@
+aboxhall@chromium.org
+dmazzoni@chromium.org
+dtseng@chromium.org
diff --git a/tools/accessibility/nvda/README.txt b/tools/accessibility/nvda/README.txt
new file mode 100644
index 0000000..ce34d93
--- /dev/null
+++ b/tools/accessibility/nvda/README.txt
@@ -0,0 +1,58 @@
+This directory contains 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.
+
+Instructions for running these tests:
+
+1. Install Python 2.7, 32-bit: http://www.python.org/
+
+ Note - the version of Python installed by Chrome's depot_tools will not
+ work, it's 64-bit.
+
+2. Download pywinauto here:
+ https://code.google.com/p/pywinauto/downloads/list
+
+ Unzip it, then install it by running this from a cmd shell in that directory:
+ python setup.py install
+
+ If you get an error, make sure you're using the 32-bit version of Python.
+
+3. Install the latest NVDA "next" snapshot from:
+ http://community.nvda-project.org/wiki/Snapshots
+
+ In the installer, choose "Create Portable copy" rather than "Install...".
+ From the Browse dialog, create an new folder called nvdaPortable inside
+ this folder.
+
+ Note: after NVDA 2014.3 stable is released, just use the stable version
+ instead, from http://www.nvaccess.org/download/
+ - if you do this, you need to run NVDA, then from the NVDA menu, choose
+ Tools > Create Portable Copy.
+ From the Browse dialog, create an new folder called nvdaPortable inside
+ this folder.
+ You should now have something like this:
+ d:\src\nvda_chrome_tests\nvdaPortable\nvda.exe
+ You can now exit NVDA.
+
+4. Install Chrome Canary. The binary is typically installed in:
+ c:\Users\USERNAME\AppData\Local\Google\Chrome SxS\Application\chrome.exe
+ ...if not, edit nvda_chrome_tests.py to point to it.
+
+5. Clone the nvda-proctest environment into this directory:
+ git clone https://bitbucket.org/nvaccess/nvda-proctest.git
+
+6. Run the tests:
+
+ First make sure NVDA is not already running.
+
+ Open a cmd console, change to the nvda_chrome_tests directory, and run:
+ python nvda_chrome_tests.py
+
+ If you get an error, open the Windows task manager and make sure NVDA
+ isn't running, kill it if necessary.
+
diff --git a/tools/accessibility/nvda/nvda_chrome_tests.py b/tools/accessibility/nvda/nvda_chrome_tests.py
new file mode 100644
index 0000000..6cbfd1b
--- /dev/null
+++ b/tools/accessibility/nvda/nvda_chrome_tests.py
@@ -0,0 +1,229 @@
+#!/usr/bin/env python
+# Copyright 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.
+
+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.
+
+The tests are "semi" automated in the sense that they are not intended to be
+run from any developer machine, or on a buildbot - it requires setting up the
+environment according to the instructions in README.txt, then running the
+test script, then filing bugs for any potential failures. If the environment
+is set up correctly, the actual tests should run automatically and unattended.
+"""
+
+import os
+import pywinauto
+import re
+import shutil
+import signal
+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')
+WAIT_FOR_SPEECH_TIMEOUT_SECS = 3.0
+
+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
+ print 'Clearing user data directory and log file from previous runs'
+ if os.access(NVDA_LOGPATH, os.F_OK):
+ os.remove(NVDA_LOGPATH)
+ if os.access(CHROME_PROFILES_PATH, os.F_OK):
+ shutil.rmtree(CHROME_PROFILES_PATH)
+ os.mkdir(CHROME_PROFILES_PATH, 0777)
+
+ def handler(signum, frame):
+ print 'Test interrupted, attempting to kill subprocesses.'
+ self.tearDown()
+ sys.exit()
+ signal.signal(signal.SIGINT, handler)
+
+ 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
+ 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
+
+ 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
+
+ app = pywinauto.application.Application()
+ app.connect_(process = self._chrome_proc.pid)
+ self._pywinauto_window = app.top_window_()
+
+ try:
+ self._WaitForSpeech(['Address and search bar edit', 'about:blank'])
+ except:
+ self.tearDown()
+
+ def tearDown(self):
+ print
+ 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'
+ self._nvda_proc.kill()
+ else:
+ print 'NVDA already died.'
+
+ 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']
+ """
+ if not os.access(NVDA_LOGPATH, os.F_OK):
+ return []
+ lines = open(NVDA_LOGPATH).readlines()
+ regex = re.compile(r"u'((?:[^\'\\]|\\.)*)\'")
+ result = []
+ for line in lines:
+ for m in regex.finditer(line):
+ speech_with_whitespace = m.group(1)
+ speech_stripped = re.sub(r'\s+', ' ', speech_with_whitespace).strip()
+ result.append(speech_stripped)
+ return result
+
+ def _WaitForSpeech(self, expected):
+ """Block until the last speech in NVDA's log file is the given string(s).
+
+ Repeatedly parses the log file until the last speech line(s) in the
+ log file match the given strings, or it times out.
+
+ Args:
+ expected: string or a list of string - only succeeds if these are the last
+ strings spoken, in order.
+
+ Returns when those strings are spoken, or throws an error if it times out
+ waiting for those strings.
+ """
+ if type(expected) is type(''):
+ expected = [expected]
+ start_time = time.time()
+ while True:
+ lines = self._GetSpeechFromNvdaLogFile()
+ if (lines[-len(expected):] == expected):
+ break
+
+ if time.time() - start_time >= WAIT_FOR_SPEECH_TIMEOUT_SECS:
+ print '** Speech from NVDA so far:'
+ for line in lines:
+ print '"%s"' % line
+ print '** Was waiting for:'
+ for line in expected:
+ print '"%s"' % line
+ raise Exception('Timed out')
+ time.sleep(0.1)
+
+ #
+ # Tests
+ #
+
+ def testTypingInOmnibox(self):
+ # Ctrl+A: Select all.
+ self._pywinauto_window.TypeKeys('^A')
+ self._WaitForSpeech('selecting about:blank')
+
+ # Type three characters.
+ self._pywinauto_window.TypeKeys('xyz')
+ self._WaitForSpeech(['x', 'y', 'z'])
+
+ # Arrow back over two characters.
+ self._pywinauto_window.TypeKeys('{LEFT}')
+ self._WaitForSpeech(['z', 'z', 'unselecting'])
+
+ self._pywinauto_window.TypeKeys('{LEFT}')
+ self._WaitForSpeech('y')
+
+ def testFocusToolbarButton(self):
+ # Alt+Shift+T.
+ self._pywinauto_window.TypeKeys('%+T')
+ self._WaitForSpeech('Reload button Reload this page')
+
+ def testReadAllOnPageLoad(self):
+ # Ctrl+A: Select all
+ self._pywinauto_window.TypeKeys('^A')
+ self._WaitForSpeech('selecting about:blank')
+
+ # Load data url.
+ self._pywinauto_window.TypeKeys('data:text/html,Hello<p>World.')
+ self._WaitForSpeech('dot')
+ self._pywinauto_window.TypeKeys('{ENTER}')
+ self._WaitForSpeech(
+ ['document',
+ 'Hello',
+ 'World.'])
+
+if __name__ == '__main__':
+ unittest.main()
+