Testing GUIs with TextTest and StoryText
Using StoryText on PyGTK apps
This page gives some hints on how to change your application in cases where things don't work "out of the box". Widget naming is inherent to its way of working, while care is needed if you block default signal handlers to avoid also blocking out StoryText's recorder.
Widget Naming
As discussed elsewhere, StoryText 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 StoryText 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.
Causing default signal handlers not to be called
Care is needed in this area to avoid also blocking StoryText from recording that signal, as StoryText's recorder is in a signal handler which is added after your application's handler.
There are two elements for how to do this:
  1. call widget.stop_emission(signal_name) or widget.emit_stop_by_name(signal_name)
  2. return True from your handler
One of these prevents the signal from propagating to later handlers, while the other prevents it from propagating to parent handlers. In practice either one of these alone may work in specific cases, but it's usually advisable to do both.
To do this in a way friendly to StoryText's recorder, call it like this:
return not widget.emit_stop_by_name(signal_name)
Without StoryText, this is of course equivalent, as emit_stop_by_name returns None ordinarily. With StoryText, a patch for emit_stop_by_name returns True and then calls the real version and returns True from its own recorder handler, thus preserving the effect yet still being able to do its recording.
"Unparenting" widgets
Calling widget.unparent() outside of its intended context (i.e. the implementation of "remove" in a container widget) will confuse StoryText, 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.


Last updated: 07 April 2014