September 22, 2007
Setting up agile development with Python/Komodo (part 3)
This is the third part in a series of tutorials on setting up a Python project for agile development in Komodo Edit.
In part one, we set up unit testing and started our automated build script. In part two, we created scripts to build an executable and installer. In this part, we're going to use pywinauto to automatically install our application, and then function test it.
So let's get started. First, make sure you've installed pywinauto. Also, in order to make our application more interesting to test, we're going to beef it up by adding a menu to the frame. Here's the modified main.py:
"""
A simple GUI program for the agile development tutorial
"""
__version__ = "0.1"
import wx
class Frame(wx.Frame):
"""The main frame for our application"""
def __init__(self, parent, title, **kwargs):
wx.Frame.__init__(self, parent=parent, title=title, **kwargs)
self.init_menu()
def init_menu(self):
"""Sets up the menu"""
bar = wx.MenuBar()
file_menu = wx.Menu()
exit_item = file_menu.Append(wx.NewId(), u"E&xit")
bar.Append(file_menu, u"&File")
self.SetMenuBar(bar)
self.Bind(wx.EVT_MENU, self.on_exit, exit_item)
def on_exit(self, event):
self.Close()
class App(wx.App):
def OnInit(self):
self.frame = Frame(parent=None, title=u'Agile Development')
self.frame.Show()
self.SetTopWindow(self.frame)
return True
if __name__ == '__main__':
app = App()
app.MainLoop()
Run the app, then run your build script again to get the latest version of the installer.
Automated Install
Now, let's add our install script. Add the file "install.py" to your project, as follows:
"""
Install the application.
This script is slightly complicated, because the Inno setup program
actually creates a new process and then exits, so we need to
connect to that process. Then we need to do the same for our
application, which is launched after installation is complete.
"
from pywinauto.application import Application
from pywinauto.controls import HwndWrapper
from pywinauto import findwindows
import time
import main
import os
def top_text():
"""Get the window text of all the top windows"""
wins = findwindows.enum_windows()
wrap = HwndWrapper.HwndWrapper
return [wrap(win).WindowText() for win in wins]
def wait_for_window(title):
"""Wait for our desired window to appear in the
list of top windows"""
count = 0
while not title in top_text():
count = count+1
assert count < 50 # magic…
def connect(app, title):
wait_for_window(title)
time.sleep(0.25) # Let the window init itself
app.connect_(**dict(title=title))
return app
def install():
"""Install AgileApp"""
print "Installing application"
os.chdir(os.path.dirname(__file__))
filename = 'Setup/AgileApp_Setup_%s.exe' % main.__version__
app = Application().start_(filename)
# Now connect to the new instance
app = connect(app, u'Setup - AgileApp')
win = app.SetupAgileApp
assert win.Exists()
# Run the installer
win.Next.Click()
win.Next.Click()
win.Next.Click()
win.Next.Click()
win.Install.Click()
win.Finish.Click()
assert not win.Exists()
# Now the app pops up. Kill it.
app = connect(app, u'Agile Development')
assert app.AgileDevelopment.Exists()
app.AgileDevelopment.MenuSelect("File->Exit")
assert not app.AgileDevelopment.Exists()
print "Finished installing application"
if __name__ == "__main__":
install()
Run it, and make sure that it installs the application properly.
Now, let's add the install step to our build script. In your build.py script, modify the build function to look like this:
"""Performs the automated build"""
curr_dir = os.path.dirname(__file__)
os.chdir(curr_dir)
# Unit tests
run_command('nosetests -w "%s"' % curr_dir)
# Build executable
run_command('setup.py')
# Build installer
base = "/program files/inno setup 5/iscc.exe"
inno = os.path.abspath(base)
run_command(r'"%s" setup.iss' % inno)
# Install application
run_command('install.py')
Run it, and make sure everything's hunky dory.
Automated function tests
Now, let's write our function test script. Create a folder as a sibling to your project folder, named "TestProjectFuncTests." We're going to put our function tests here. Create a new Komodo project in that folder, with an appropriate name. Just to get started, add the file "test_basic.py" to the new project, as follows:
"""
Test basic functionality
"""
from nose.tools import raises
@raises(AssertionError)
def test_module():
assert False
Run your automated unit tests (F7), and make sure it passes. Personally, I make sure it fails, then put the raises decorator and make sure it passes, but that's just because I'm paranoid.
Now, to make sure that our automated build script is catching this, change the build function in build.py as follows:
"""Performs the automated build"""
curr_dir = os.path.dirname(__file__)
os.chdir(curr_dir)
# Unit tests
run_command('nosetests -w "%s"' % curr_dir)
# Build executable
run_command('setup.py')
# Build installer
base = "/program files/inno setup 5/iscc.exe"
inno = os.path.abspath(base)
run_command(r'"%s" setup.iss' % inno)
# Install application
run_command('install.py')
# Run function tests
base_dir = os.path.split(curr_dir)[0]
test_dir = os.path.join(base_dir, 'TestProjectFuncTests')
run_command('nosetests -w "%s"' % test_dir)
Run and test as usual.
Now we're ready to do the function tests. We'll start with something really simple: start and exit the application. Modify test_basic.py as follows:
"""
Test basic functionality
"""
from pywinauto.application import Application as App
from nose.tools import raises
APP_PATH = "/Program Files/AgileApp/AgileApp.exe"
@raises(AssertionError)
def test_module():
assert False
def test_start_and_exit():
app = App().start_(APP_PATH)
assert app.AgileDevelopment.Exists()
app.AgileDevelopment.MenuSelect("File->Exit")
assert not app.AgileDevelopment.Exists()
Change APP_PATH as appropriate for your install location.
Run build.py, and make sure everything passes. Congrats! You're almost finished. In the next and final part in this series, we'll auto-generate project documentation using epydoc, then put our project under source control using Mercurial, and add a commit to our build cycle.
Is it possible to control the CheckBoxes of the Inno Installer with pywinauto?”TNewCheckListBox” i cannot control the checkboxes when are multiple on the same window of the Inno Installer and I can only perform .Click() where is only one ,whitout determing the status of it (Check/Uncheck)
Hi eusebiu:
If the check boxes have keyboard shortcuts, you can always use sendkeys (a requirement of pywinauto) to select them.
http://www.rutherfurd.net/python/sendkeys/index.html
Thanks.It resolved most of my problem regarding this.I am performing a test regarding the Inno Installer.It would have been nice to read the status of the checkbox but I will create the test assuming that the default settings are there.
I have also a problem with a checkbox that enables/disables an editbox.I cannot get the checkstate as mentioned in my previous post and I cant get an EditBox enabled/disabled (state) also.How do you suggest to deal with this?
Thanks
@eusebiu
The interactive prompt is usually very useful in these situations. You can use Python’s introspective capabilities to find out the hooks and handles you have at each stage of the installer wizard. That’s how I figured out how to automate the Inno setup wizard in the first place…
In the worst case, pywinauto also provides hooks to the raw window handles, and you can drive just about every aspect of the app GUI via brute force. I’d suggest looking at the documentation and samples.