Monday, April 21, 2014

GUI Testing with Sikuli

Sikuli is an open-source tool for automating tests of GUI applications. It is platform-independent (has Mac OS, Linux, and Windows versions) and is not linked in any way to a GUI package such as Qt. It uses little screen-grabs to locate targets of operation. Here, for example, are four lines from a Sikuli test script:

Line 28 finds the image of my editview context menu somewhere on the screen. That verifies that the menu is active. The next line sends the mouse to click on the third line of the menu. That opens a Mac OS file selection dialog, and I have arranged that the current working directory at this time will include the file I want. So line 30 of the script finds and clicks-on that filename, and the next line finds and clicks on the Open button of the file dialog.

This exactly answers the problems I was listing in the immediately prior blog post. You can specify mouse actions in terms of the look of their targets, regardless of whether these are Qt widgets or operating system dialogs. You can make it "find" the exact image of what you expect to have on the screen; for example, to find the image of a checkable menu item with its check mark in place, or to find the image of a word with or without a spelling-error underline.

A Sikuli script is a Python 2.7 script, but that's not a problem even though my code uses Python 3. The Sikuli app is a Java app which embeds its own Jython 2.7! So Sikuli's Python is completely unconnected from the target app's Python.

The test setup needed to combine Sikuli tests and ordinary Python test scripts is a bit complex. Here's the folder setup:

ppqt
   all app source scripts
   Tests
      all unit-test scripts run by pytest
      Sikuli
         all Sikuli test scripts

The unit-test driver, Tests/editview_sikuli.py reads like this:

path_to_self = os.path.realpath(__file__)
path_to_Sikuli = os.path.join(os.path.dirname(path_to_self),'Sikuli')
import subprocess
r = subprocess.call(['/Applications/SikuliX-IDE.app/Contents/runIDE',
                 '-r',
                 os.path.join(path_to_Sikuli,'editview.sikuli')])
assert not(r)

So this simple script just fires off the Sikuli app asking it to run Sikuli/editview.sikuli. That is actually a folder containing a python script and all the little .png files that represent target images.

The Sikuli script (which is in Python 2.7 syntax, remember) starts out by firing off a copy of the test GUI:

import subprocess
subprocess.Popen(['/usr/local/bin/python',
            '/Users/dcortes1/Dropbox/David/PPQT/V2/ppqt/Tests/Sikuli/editview_runner.py'])

The new subprocess based on editview_runner.py sets up and launches the editview test window. That's the window where Sikuli will find visual matches as it runs its "click" and "find" statements. It does that, and if everything matches, the script ends with "type('q',KeyModifier.META)", in other words, command-Quit. That terminates the test app. The Sikuli script ends with a 0 return code. Back in the test driver, the subprocess call ends and assert not(r) is satisfied.

The Sikuli interactive IDE is a bit clumsy to use, and preparing a useful test is a bit tedious. (But on the other hand, it also revealed a couple of small bugs.) I expect to get good use out it the rest of the way.

As of now, the editview module is functionally complete. As are several auxiliary modules: colors (keeps track of default colors and highlights, eventually will link up with a Preferences dialog); dictionaries (keeps track of available spelling dictionaries and defines the Speller class of spellcheck objects); and the basic skeleton of the Book class. So: on to imageview! When that's working, it will be time to set up the main window and actually have a whole app to play with.

No comments: