|
Testing GUIs with TextTest and xUseCase
The ideal of course is that PyUseCase could take your application and just work "out of the box". In many applications, particularly simpler ones, this will hopefully be the case. This page gives you a guide to how you can change your code to make it work with PyUseCase if it doesn't do so out of the box. Naming the occasional widget is inherently necessary to its way of working, while there are also for example cases where PyGTK offers you several routes to the same end, but PyUseCase can only handle one of the alternatives.
As discussed elsewhere, PyUseCase identifies widgets by Name, Title, Label and Type, in that order of preference. Many widgets simply don't have a Title or a Label attached to them and hence if you don't set names on them, they will be identified by Type, which will not work if you have more than one of them. Besides this, widget titles and labels may not be unique, or they may change depending on e.g. today's date. It's fair to say that almost every non-trivial application is going to need to set at least some widget names before PyUseCase will work smoothely.
If doing so on a gtk.Widget, just set the widget name as normal :
widget.set_name("My Widget")
However, you can also name objects that aren't derived from gtk.Widget and hence don't have a set_name method. This assumes they are derived from gobject.GObject, which pretty much everything in PyGTK is. In this case you should do
gObject.set_data("name", "My Widget")
At the moment this last will be recognised on gtk.TreeViewColumn and gtk.gdk.Pixbuf.
For TreeViewColumns, it's useful when these have titles which are not unique or not necessarily the same from run to run. Without it it will not be possible to record and playback column-related signals, including those on gtk.CellRenderer.
For Pixbufs, it's used to improve the descriptions of images in the autogenerated log, hence making it more readable. The auto-logging will describe stock images via their stock ID and those created from gtk.gdk.pixbuf_new_from_file with the file name: those created via gtk.gdk.pixbuf_new_from_xpm will be described as "XPM image 1", "XPM image 2" etc. Anything that doesn't fit any of these categories will just be "Unknown image". By calling set_data as above, you can provide any name at all which will then be used in the logs.
PyUseCase's replayer works via an idle handler, which is not called during gtk.Dialog.run. This method is therefore not currently supported, even though gtk.Dialog is in general. (There may exist circuitous ways to get around this, and it may be supported in the future.) But right now, the PyUseCase-friendly way to handle dialogs is to connect to the "response" signal (or the "clicked" signal on its buttons, if you prefer) when it should work just fine.
Instead of
result = dialog.run()
do_something(result)
...
you therefore write something like
dialog.connect("response", respond)
dialog.show_all()
def respond(dialog, result):
do_something(result)
...
PyGTK offers you several ways to block the default handling of a signal. Care is needed in this area to avoid also blocking PyUseCase from recording that signal, as PyUseCase's recorder is in a signal handler which is added after your application's handler.
Returning False from your handler will therefore not work, as it will also cause the recorder not to be called. Instead, you should call one of the equivalent methods "stop_emission" or "emit_stop_by_name", which PyUseCase will intercept and call itself from its own recorder handler, thus preserving the effect yet still being able to do its recording. Unfortunately it isn't possible to intercept "return False", so the only thing to do there is replace it with one of the above calls.
Calling widget.unparent() outside of its intended context (i.e. the implementation of "remove" in a container widget) will confuse PyUseCase, because the widget will still be a child of its original parent and will therefore be monitored twice if it subsequently added somewhere else. Call
widget.get_parent().remove(widget)
instead. This is what PyGTK intends you to do anyway so shouldn't be controversial.
|