Currently Being Moderated

Getting Started with Strongwind

VERSION 3

Created on: Aug 28, 2008 10:47 PM by Jonathan Tai - Last Modified:  Aug 28, 2008 11:03 PM by Jonathan Tai

A few Strongwind test scripts for gcalctool are included in the examples folder of the Strongwind distribution.  This tutorial walks through the first few lines of the gcalctool-changing-modes.py script to illustrate how Strongwind tests work.

 

Here's the first few lines of the gcalctool-changing-modes.py script:

 


 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 """
 4 Test changing modes (Basic, Advanced, Financial, Scientific)
 5 """
 6
 7 from strongwind import *
 8 from gcalctool import *
 9
10 app = launchGcalctool()
11
12 app.calculatorFrame.changeMode('Basic')

 

Importing Strongwind

 

On line 7, the strongwind package is imported.  At this point, the procedure logger is initialized and the watchdog timer is started.

 

Procedure Logger

 

Strongwind tries to automatically log test procedures in a human-readable format so that a human tester can verify test results manually if necessary.

 

When the procedure logger is initialized, it tries to figure out what the name of the running test script is by stripping the .py extension off the file name.  It also reads the __doc__ block of the script (lines 3-5) to get the test's description.  In this case, the name of the test will be "gcalctool-changing-modes" and the test description will be "Test changing modes (Basic, Advanced, Financial, Scientific)."  The test name and test description are included in the procedure log.

 

Watchdog

 

A watchdog timer is automatically started when the strongwind package is imported.  The watchdog runs in a separate thread and is mostly transparent to the test script.  If there is no activity after a certain timeout, the watchdog will step in and stop the test script.

 

Test scripts (and application wrappers; see below) can optionally register a callback with the watchdog to be called every n seconds.

 

Typically, application failures will cause some other aspect of the test to fail before the watchdog kicks in.  (e.g., a dialog is supposed to appear, but it cannot be found, so the test fails)  However, applications can sometimes hang in a way that causes a pyatspi call to hang.  In this case, the main Strongwind thread will hang waiting for the pyatspi call to return — this is when the watchdog would step in and kill the test script.

 

The watchdog is most useful when running a batch of Strongwind tests unattended.  If one test hangs, it will eventually be killed by the watchdog so that other tests can be run.

 

Application Wrappers

 

On line 8, the gcalctool package is imported.  The gcalctool package is not really part of Strongwind itself; it is a Strongwind application wrapper around the actual application.

 

Applicaiton wrappers are object-oriented representations of real applications.  They exist to promote code reuse and to speed up test development.  Tests for an application almost always have common tasks specific to the application - starting the application, logging in, switching to the right mode or tab, etc.  Many test scripts written for the same application will need to perform these common tasks.  By consolidating the code to perform these common tasks into an application wrapper, tests can be developed faster and the resulting output will be more consistent.

 

The provided application wrapper for gcalctool is very basic.  It consists of a launchGcalctool() function, a Gcalctool class to represent the application, and a CalculatorFrame class to represent the main calculator window.

 

Launching Applications

 

On line 10, gcalctool is launched.  Here's a code again:

 


 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 """
 4 Test changing modes (Basic, Advanced, Financial, Scientific)
 5 """
 6
 7 from strongwind import *
 8 from gcalctool import *
 9
10 app = launchGcalctool()
11
12 app.calculatorFrame.changeMode('Basic')

 

launchGcalctool() takes care of launching an application with the GTK_MODULES environmental variable set to gail:atk-bridge so that pyatspi can communicate with the application.

 

After launching the application, Strongwind searches for the application in pyatspi's application registry and creates an instance of the Gcalctool class.  launchGcalctool() returns the instance of the Gcalctool class.

 

Accessibles

 

pyatspi represents applications as a tree of widgets.  accerciser, "an interactive Python accessibility explorer", can be used to inspect this tree:

 

Accerciser Screenshot

 

The root of the depicted tree is the gcalctool application.  The Calculator window is the application's direct child.  (A GTK "window" is given the "frame" ATK role.)  Widgets inside the Calculator frame, like menu bars, menus, menu items, and buttons are descendants of the Calculator frame.  In turn, those widgets may have descendants.

 

Strongwind provides a generic implementation for all widgets: the Accessible class.  Strongwind also provides some subclasses of Accessible that provide additional functionality specific to certain ATK roles.  Strongwind "promotes" widgets to the most specific class possible, e.g., a menu bar will be "promoted" to a MenuBar class automatically because Strongwind provides a MenuBar class, but a filler will be represented as a generic Accessible (because Strongwind does not provide a Filler class).

 

Getting back to the example, the Gcalctool class is a subclass of Strongwind's Application class, and the CalculatorFrame class is a subclass of Strongwind's Frame class.  (Again, an ATK "frame" is a GTK "window" — Strongwind uses the ATK naming convention for its classes.)

 

Searching

 

On line 12, the changeMode() function of the CalculatorFrame is called.  Here's the code again:

 


 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 """
 4 Test changing modes (Basic, Advanced, Financial, Scientific)
 5 """
 6
 7 from strongwind import *
 8 from gcalctool import *
 9
10 app = launchGcalctool()
11
12 app.calculatorFrame.changeMode('Basic')

 

launchGcalctool() returned a Gcalctool object, so app is a Gcalctool object.   The calculatorFrame attribute of the Gcalctool object was set in the initializer of the

Gcalctool object.  Here's the body of the Gcalctool class:

 


1 class Gcalctool(accessibles.Application):
2     def __init__(self, accessible, subproc=None):
3         'Get a reference to the calculator window'
4         super(Gcalctool, self).__init__(accessible, subproc)
5
6         self.findFrame(re.compile('^Calculator'), logName='Calculator')

 

The interesting bit is line 6, where Gcalctool, a subclass of Application, calls its own findFrame() function.

 

Strongwind's base Accessible class provides find*() functions for every ATK role.  The first argument to the find*() functions can be a string, a re (regular expression) object, or None.   The functions search all descendants of the current widget for a widget whose name matches the string or regular expression given in the first argument and whose role matches the function.  If the first argument is None, any name will match (so essentially, the ATK role is the only match criteria).  The first widget found to match the criteria is returned.

 

The find*() functions also take a number of other arguments that control various aspects of the search — whether or not to search recursively (recursive), whether or not to include only widgets that are showing (checkShowing), whether to return None or to raise an exception if the search returns no results (raiseException), etc.

 

The call to findFrame() on line 6 will return the first frame descendant of the Gcalctool whose name starts with "Calculator".  (There's only going to be one.)  When the described widget is found, it is promoted to the most specific class possible.  Strongwind defines a Frame class, so the widget could be promoted to a Frame, but the find*() functions also look for classes named in a special way.  The convention is name-of-widget followed by role-name, camel case.  In this case, since the gcalctool application wrapper defines a calculatorFrame class, the widget is automatically promoted to a calculatorFrame.

 

Finally, there is a parameter (setReference) that can be passed to the find*() functions.  If setReference is True, the found widget's name is converted to camel case and set as an attribute on the object.

 

The Gcalctool class inherits from the Application class, which sets setReference to True by default for findFrame() and findDialog().  (The default in the Accessible class is False.)  Thus, the call to findFrame() on line 6 sets Gcalctool.calculatorFrame to the promoted calculatorFrame object.

 

Logging Actions & Expected Results

 

Strongwind tries to log actions and expected results automatically where appropriate, but sometimes the automatic logging is not enough.  Here's the body of calculatorFrame's changeMode() function:

 


 1     def changeMode(self, mode, expectChangeModeDialog=False, assertModeChanged=True):
 2         '''
 3         Change the mode
 4
 5         Mode can be one of: Basic, Advanced, Financial, or Scientific.
 6         '''
 7
 8         if expectChangeModeDialog:
 9             sleep(config.SHORT_DELAY)
10
11         self.menuBar.select(['View', mode])
12
13         if expectChangeModeDialog:
14             self.app.findDialog(None, logName='Changing Modes Clears Calculation')._clickPushButton('Change Mode')
15
16         if assertModeChanged:
17             procedurelogger.expectedResult('The mode changes to %s mode and the result region displays 0.' % mode)
18
19             def modeChanged():
20                 return self.menuBar.findMenu('View').findCheckMenuItem(mode).checked and self.resultRegion.text == '0'
21
22             assert retryUntilTrue(modeChanged)

 

The call to menuBar.select() on line 11 is logged automatically, and so are the calls to app.findDialog() and dialog._clickPushButton() on line 14.  However, the expected result of selecting Basic in the View menu is specific to gcalctool — there's no way Strongwind can provide a reasonable log message.  Thus, it is up to the application wrapper to call procedurelogger.expectedResult() and provide a log message.

 

Immediately following a call to procedurelogger.expectedResult(), a script (or application wrapper) should do something to check that the expected result actually occurs.  (Otherwise, the script will not fail if the application doesn't actually behave correctly.)  On lines 19-20, gcalctool's application wrapper checks that the mode appears checked in the View menu and that the result region has been reset to 0.

 

Some applications need a little time to react, so to prevent false positives (tests failing when they shouldn't), the checks are wrapped by an inline function which is called by retryUntilTrue() on line 22.  retryUntilTrue() is a Strongwind utility function that calls its first argument over and over again (with short sleeps in between) until the result is True.  At that point, retryUntilTrue() itself returns True.  If, after a number of tries, the function continues to return False, retryUntilTrue() gives up and returns False.

 

To finish off the example, here's the output of the procedure logger when changeMode('Advanced', expectChangeModeDialog=True) is called:

 


Action: Under the View menu, select Advanced.
Expected result: The Changing Modes Clears Calculation dialog appears.
Screenshot: screen07.png

Action: Click the Change Mode button.
Expected result: The Changing Modes Clears Calculation dialog disappears.
Expected result: The mode changes to Advanced mode and the result region displays 0.
Screenshot: screen08.png

 

This output is logged in XML format to /tmp/strongwind/procedures.xml by default.  XSL and CSS files for transforming this XML into a printable page are included with the Strongwind distribution in the resources directory.  Try copying the XSL and CSS files to /tmp/strongwind and opening the XML file with Firefox.

 

Tags: strongwind
Average User Rating
(0 ratings)




There are no comments on this document

More Like This

  • Retrieving data ...