Friday, May 30, 2008

Testing OSX applications with Python

Recently I started a new wxPython based project that would be primarily be running on OSX.

I figured it would be rather useful and neat to have some tests managed by Python that would exercise the whole system so I took a stab at setting some up using some of my standard toolkit. You don't have to test exclusively Python-based application. You can use Python to write the test scripts for the application under test which could be written in any language that OSX supports.

How

I found out that Apple has already added all the hooks for scripting the UI via an AppleScript service called GUI Scripting or System Events. You can script it easily via AppleScript. Or you can access it all from Python using the appscript module.

It's Easy

Here's some code to get you started testing with GUI Scripting.
from appscript import *
import time

# some boilerplate setup
sysevents = app('System Events')
app('Finder').activate()
sysevents.processes['Finder'].menu_bars[1].menus[u'Apple'].menu_items[u'About This Mac'].click()

# some operations may need a pause
time.sleep(1)

loginwindow = sysevents.processes['loginwindow']

# Check that an About this Mac window exists
assert [ win for win in loginwindow.windows() if win.title() == 'About This Mac']

# Check that there is an All Rights Reserved present.
assert loginwindow.windows['About This Mac'].static_texts['All Rights Reserved.'].get()
The example is a little convoluted because Finder owns the "About This Mac" menu entry, but loginwindow owns the window that pops up.
The hardest part was figuring out the API to work with Apple's UI scripting support. Especially since all of Apple's examples were in AppleScript. Thankfully theres some tools that come with appscript that help. One called ASDictionary that autogenerates documentation for the various AppleScript plugins on your system. Also ASTranslate helps with the job of translating AppleScript code into equivalent appscript usage under Python.

Is that all?

There don't seem to be many Open Source GUI Functional Testing tools for OSX. Anyone know any ? Lots for Web functional testing but not for UI.

There is pywinauto for Windows.

Might be nice if OSX had a similar test layer. pymacauto anyone?

Thursday, May 22, 2008

quick way to debug testbrowser in doctests

Writing doctests I am sometimes wondering what is going on under the hood. Keeping this function around in my test environment makes it a bit easier to find out. After rendering the output to the browser it will pause waiting for a keypress to continue the test.  Note, this code works only on OSX though it should be trivial to modify for any OS.
>>> def render_browser(browser):
...   open('/tmp/browser.html', 'w').write(browser.contents)
...   import os
...   os.system("open /tmp/browser.html")
...   _ = raw_input()

Monday, October 15, 2007

buildout recipe for installing Selenium RC

This weekend at the Plone Naples Sprint one of the things I worked on was a buildout to download and install Selenium RC. It is now available from PYPI under the name collective.recipe.seleniumrc For some reason the Selenium RC distribution zip file contains version numbers on each subdirectory so this meant I couldn't just use one of the existing download scripts. But I didn't want to re-invent the download recipe wheel, so I decided to base this recipe on a well-written existing download recipe, hexagonit.recipe.download. I asked on IRC if the author was present at the sprint and ironically enough he was sitting in the same room as me only two rows away ;) With some simple changes to his recipe I was able to derive collective.recipe.seleniumrc from it and gain features like download caching and md5 file hash verification. Along the way I learned that if you want your buildout recipe to be derived by others you should make it a new-style Python class so that super() works. Also that it helps to factor your install() method into submethods for overriding. The changes I made to hexagonit.recipe.download were cleaned up and integrated into a 1.1.0 release that we made yesterday, along with the 0.1 release of collective.recipe.seleniumrc If you want to try out the Selenium RC recipe just create a simple buildout.cfg:
[buildout]
parts=seleniumrc

[seleniumrc]
recipe=collective.recipe.seleniumrc
After you are finished running this buildout you will get a nice wrapper script in your bin folder named by the part, in this case seleniumrc. Happy testing!

Wednesday, August 15, 2007

WebDAV, cadaver and metadata

Recently I wanted to automate some document management processes against a Plone site and was trying to figure out how to do set the Title property on the newly created objects via WebDAV, using the cadaver command line client. This led me to look a little more closely at how WebDAV does properties. In fact it is not like a simple Python dictionary, rather there is a nested structure like a dictionary of dictionaries. Each application can have its own namespace. By default cadaver will set properties in its own custom namespace. So you need to use the set namespace command to use the Zope namespace identified by its namespace: http://www.zope.org/propsets/default The following example uses cadaver and creates a collection (folder) and sets the title property:
mkcol just_a_folder
set namespace http://www.zope.org/propsets/default
propset just_a_folder title “A long title”

Thursday, April 19, 2007

Zope 3 Book Arrived!

The book Web Component Development with Zope 3, 2nd Ed. by Philipp von Weitershausen has been on my wishlist for a while now and I finally got around to ordering it. I only got it last night and already I've made it half way through, though certainly I will re-read it all several times. The book offers very excellent descriptions of the component architecture and its applications throughout the rest of the Zope 3 framework. The progression of chapters in the book shows the reuse of the basic building blocks of interfaces, adapters & utilities. And one of the things I also liked was the "Rocky says" section where the relevant technologies of J2EE were constrasted with those of Zope. The explanation of ZCML in the book finally made it clear to me. ZCML is on purpose ;) It forces a separation of roles and make sure that things that would be considered 'configuration' are easily accessible to a non-programmer like a system administrator or integrator. My impression so far the from the book is one that really showcases Zope 3 as a enterprise-grade framework that is solidly Pythonic. For more info you can check' the website for Web Component Development with Zope 3, 2nd Ed.

Monday, April 16, 2007

Buildouts

How annoying is it to deploy an application? How prone to errors due to the sheer repetitiveness of the common deployment? Buildouts to the rescue! What is a buildout? It's a automated set of steps for creating a deployment environment. This could mean one little application, or it could be several and they would setup to talk to each other. zc.buildout came out sometime last year and I've had my eye on it since then. I'd wanted to do Jim Fulton's tutorial on it at Pycon this year but wasn't able to schedule it in! I've had the opportunity in the last couple weeks to start using a bit more and I really appreciate the conceptual basis for this framework. The idea that a deployment consists of parts is something you can explain to anyone, for example. A web app needs a webserver, some kind of storage backend, the application code and some preexisting data: parts=apache mysql application-data application or full Zope-based solution: parts=apache zope zeo plone mywebsite mywebsite-data What I've noticed so far is that for a particular application even though someone might have already wrote a recipe for it actually getting it configured the way you want usually requires extending that recipe slightly. If the recipe was written with this in mind its easy enough to extend. I did have a bit of problems extending some of the plone.recipe.* recipes because they were old style classes and so I couldn't use super() to extend the default behaviour. Easy to fix though!

ZOPE / ZEO buildout recipe with multiple database support

I created a branch that adds the ability to configure multiple databases in your configuration files which I needed for a project, so this should be a fairly decent example for anyone else needed to something similar. See the jbb666-multiple-dbs branch for zeo instance and the corresponding branch for the zope recipe. For a lot of the Zope recipes the config knobs that need to be exposed will be determined and you won't need to touch the underlying recipe except in well exceptional circumstances.

Thursday, April 12, 2007

copying a plone site

Copying sites has recently gotten a lot better since 2.5.2 fixed some bugs with modification times getting reset but still you generally can't use copy & paste to copy sites as I have found it doesn't retain the review state of your documents (and this is by design actually I think). Another approach is to import/export. This actually works great so I put together a script that will automate this process. You need to call this from either an External Method or from zopectl debug (or ipython). That's an exercise up to the reader... # moveSites.py
# Move a Plone site without losing the modification times and workflow status
# we rely on export/import to do this
# You need to provide a valid REQUEST so that the import doesn't fail
# if running from zopectl debug you can use makerequest to wrap your app object
# with a fake request
# Author: Jordan Baker (jbbNOASPM@scryent.com)
import tempfile
import os

def moveSite(portal, new_location):
# export the site to a tempfile
f = tempfile.NamedTemporaryFile()
portal._p_jar.exportFile(portal._p_oid, f)
# re-import in the correct location
f.seek(0)
new_location._importObjectFromFile(f, verify=False, set_owner=False)
# delete the old site
oid = portal.id
portal.aq_parent.manage_delObjects(ids=[oid])
# update the catalog in the new site
new_location[oid].portal_catalog.refreshCatalog(clear=True)
# NamedTemporaryFile should delete the underlying temporary file
# when its object is garbage collected

Thursday, March 22, 2007

I've Joined the Toastmasters!

I've recently joined the Liberty Village Toastmasters. The name perhaps evokes the spirit of another time but Toastmasters is a very relevant organization for today. The picture from my induction ceremony: me on the right, and Ian Garmaise, the club President on the left. Ian had introduced me to the club. We have a neat connection because we are also both fans of the Python programming language. We'd first met at PyGTA, the Toronto Python Users Group. Toastmasters is a bit like a DIY communications class. At the weekly meetings we are working on our communication and presentation skills among many other things. Learning to refine and hone the delivery of ideas for impact all the while feeling natural in front of the audience you are addressing. But the best part is probably all the smart, interesting and generous people I've met at this group. My first speech, known as an Ice Breaker, is only two weeks away. I'm getting nervous just thinking about it.
At last week's meeting on March 14th, Ted Blanchard gave a great educational talk with some tips. I noted down only a scant few of them that I've tried to summarize here: Set Goals - there are a series of 10 speeches in the first stage of the Toastmasters. Put yourself on a regular schedule, like a speech every 4 weeks. Topic Generation Exercise. Make a Speech Topic List in 15 minutes by getting a pen and a piece of paper and writing down:
  • 5 current events
  • 5 things you enjoy doing
  • 5 things that bug you
  • 5-20 action words
Learn to do an outline. An outline is not finished thinking, just the waypoints your speeches' route. It gets you out of the editing chaos of continual revision.

Friday, February 23, 2007

Blogging live from PyCon 2007

I finally made it! Got switched to another hotel but I will be in the right place for tonight. Immediately I ran into a couple familiar faces from last October's Plone Conference, Chris Calloway and Josten Ma. Now I'm sitting in the main room waiting for the morning's keynote to start. More to come... Update Well I didn't end up blogging much at Pycon this year. But I have some pretty wonderful excuses. ;)