[POS-commit] r5362 - in kiwi/trunk: . kiwi tests/ui
Johan Dahlin
jdahlin at async.com.br
Tue Nov 21 14:21:07 BRST 2006
Author: jdahlin
Date: Tue Nov 21 14:21:07 2006
New Revision: 5362
Added:
kiwi/trunk/kiwi/_kiwi.c
kiwi/trunk/kiwi/ui/test/runner.py
Removed:
kiwi/trunk/kiwi/ui/test/player.py
Modified:
kiwi/trunk/Makefile
kiwi/trunk/kiwi/ (props changed)
kiwi/trunk/kiwi/ui/test/common.py
kiwi/trunk/kiwi/ui/test/main.py
kiwi/trunk/kiwi/ui/test/recorder.py
kiwi/trunk/setup.py
kiwi/trunk/tests/ui/diary.py
kiwi/trunk/tests/ui/diary2.py
kiwi/trunk/tests/ui/personalinformation.py
Log:
Rewrite UI testing framework to use doctests and a gdk event handler function. Port the old ui tests to the new syntax
Modified: kiwi/trunk/Makefile
==============================================================================
--- kiwi/trunk/Makefile (original)
+++ kiwi/trunk/Makefile Tue Nov 21 14:21:07 2006
@@ -6,6 +6,9 @@
DEBVERSION=$(shell dpkg-parsechangelog -ldebian/changelog |grep Version|cut -d: -f3)
DLDIR=/mondo/htdocs/download.stoq.com.br/ubuntu
+all:
+ python setup.py build_ext -i
+
clean-docs:
rm -fr doc/api
rm -fr doc/howto
@@ -14,6 +17,7 @@
debclean
rm -fr $(BUILDDIR)
rm -f MANIFEST
+ rm -fr kiwi/_kiwi.so
docs:
make -s -C doc api howto
Added: kiwi/trunk/kiwi/_kiwi.c
==============================================================================
--- (empty file)
+++ kiwi/trunk/kiwi/_kiwi.c Tue Nov 21 14:21:07 2006
@@ -0,0 +1,266 @@
+/* -*- Mode: C; c-basic-offset: 4 -*-
+ * Kiwi: a Framework and Enhanced Widgets for Python
+ *
+ * Copyright (C) 2006 Async Open Source
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * Author(s): Johan Dahlin <jdahlin at async.com.br>
+ */
+
+/* This module contains backports of pygobject/pygtk functions,
+ * so we don't have to require the latest versions of them
+ */
+#include <Python.h>
+#include <pygobject.h>
+#include <pygtk/pygtk.h>
+
+#if PY_VERSION_HEX < 0x02050000
+typedef int Py_ssize_t;
+#define PY_SSIZE_T_MAX INT_MAX
+#define PY_SSIZE_T_MIN INT_MIN
+typedef inquiry lenfunc;
+#endif
+
+typedef struct {
+ PyObject *func, *data;
+} PyGtkCustomNotify;
+
+static void
+pygtk_custom_destroy_notify(gpointer user_data)
+{
+ PyGtkCustomNotify *cunote = user_data;
+ PyGILState_STATE state;
+
+ g_return_if_fail(user_data);
+ state = pyg_gil_state_ensure();
+ Py_XDECREF(cunote->func);
+ Py_XDECREF(cunote->data);
+ pyg_gil_state_release(state);
+
+ g_free(cunote);
+}
+
+static gboolean
+marshal_emission_hook(GSignalInvocationHint *ihint,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer user_data)
+{
+ PyGILState_STATE state;
+ gboolean retval = FALSE;
+ PyObject *func, *args;
+ PyObject *retobj;
+ PyObject *params;
+ guint i;
+
+ state = pyg_gil_state_ensure();
+
+ /* construct Python tuple for the parameter values */
+ params = PyTuple_New(n_param_values);
+
+ for (i = 0; i < n_param_values; i++) {
+ PyObject *item = pyg_value_as_pyobject(¶m_values[i], FALSE);
+
+ /* error condition */
+ if (!item) {
+ goto out;
+ }
+ PyTuple_SetItem(params, i, item);
+ }
+
+ args = (PyObject *)user_data;
+ func = PyTuple_GetItem(args, 0);
+ args = PySequence_Concat(params, PyTuple_GetItem(args, 1));
+ Py_DECREF(params);
+
+ /* params passed to function may have extra arguments */
+
+ retobj = PyObject_CallObject(func, args);
+ Py_DECREF(args);
+ if (retobj == NULL) {
+ PyErr_Print();
+ }
+
+ retval = (retobj == Py_True ? TRUE : FALSE);
+ Py_XDECREF(retobj);
+out:
+ pyg_gil_state_release(state);
+ return retval;
+}
+
+static PyObject *
+pyg_add_emission_hook(PyGObject *self, PyObject *args)
+{
+ PyObject *first, *callback, *extra_args, *data;
+ gchar *name;
+ gulong hook_id;
+ guint sigid;
+ Py_ssize_t len;
+ GQuark detail = 0;
+ GType gtype;
+ PyObject *pygtype;
+
+ len = PyTuple_Size(args);
+ if (len < 3) {
+ PyErr_SetString(PyExc_TypeError,
+ "gobject.add_emission_hook requires at least 3 arguments");
+ return NULL;
+ }
+ first = PySequence_GetSlice(args, 0, 3);
+ if (!PyArg_ParseTuple(first, "OsO:add_emission_hook",
+ &pygtype, &name, &callback)) {
+ Py_DECREF(first);
+ return NULL;
+ }
+ Py_DECREF(first);
+
+ if ((gtype = pyg_type_from_object(pygtype)) == 0) {
+ return NULL;
+ }
+ if (!PyCallable_Check(callback)) {
+ PyErr_SetString(PyExc_TypeError, "third argument must be callable");
+ return NULL;
+ }
+
+ if (!g_signal_parse_name(name, gtype, &sigid, &detail, TRUE)) {
+ PyErr_Format(PyExc_TypeError, "%s: unknown signal name: %s",
+ PyString_AsString(PyObject_Repr((PyObject*)self)),
+ name);
+ return NULL;
+ }
+ extra_args = PySequence_GetSlice(args, 3, len);
+ if (extra_args == NULL)
+ return NULL;
+
+ data = Py_BuildValue("(ON)", callback, extra_args);
+ if (data == NULL)
+ return NULL;
+
+ hook_id = g_signal_add_emission_hook(sigid, detail,
+ marshal_emission_hook,
+ data,
+ (GDestroyNotify)pyg_destroy_notify);
+
+ return PyLong_FromUnsignedLong(hook_id);
+}
+
+static PyObject *
+pyg_remove_emission_hook(PyGObject *self, PyObject *args)
+{
+ PyObject *pygtype;
+ char *name;
+ guint signal_id;
+ gulong hook_id;
+ GType gtype;
+
+ if (!PyArg_ParseTuple(args, "Osk:gobject.remove_emission_hook",
+ &pygtype, &name, &hook_id))
+ return NULL;
+
+ if ((gtype = pyg_type_from_object(pygtype)) == 0) {
+ return NULL;
+ }
+
+ if (!g_signal_parse_name(name, gtype, &signal_id, NULL, TRUE)) {
+ PyErr_Format(PyExc_TypeError, "%s: unknown signal name: %s",
+ PyString_AsString(PyObject_Repr((PyObject*)self)),
+ name);
+ return NULL;
+ }
+
+ g_signal_remove_emission_hook(signal_id, hook_id);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static void
+pygdk_event_handler_marshal(GdkEvent *event, gpointer data)
+{
+ PyGILState_STATE state;
+ PyGtkCustomNotify *cunote = data;
+ PyObject *retobj;
+ PyObject *pyevent;
+
+ g_assert (cunote->func);
+
+ state = pyg_gil_state_ensure();
+
+ pyevent = pyg_boxed_new(GDK_TYPE_EVENT, event, TRUE, TRUE);
+ if (cunote->data)
+ retobj = PyEval_CallFunction(cunote->func, "(NO)",
+ pyevent, cunote->data);
+ else
+ retobj = PyEval_CallFunction(cunote->func, "(N)", pyevent);
+
+ if (retobj == NULL) {
+ PyErr_Print();
+ } else
+ Py_DECREF(retobj);
+
+ pyg_gil_state_release(state);
+}
+
+static PyObject *
+_wrap_gdk_event_handler_set(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ PyObject *pyfunc, *pyarg = NULL;
+ PyGtkCustomNotify *cunote;
+
+ if (!PyArg_ParseTuple(args, "O|O:event_handler_set",
+ &pyfunc, &pyarg))
+ return NULL;
+
+ if (pyfunc == Py_None) {
+ gdk_event_handler_set(NULL, NULL, NULL);
+ } else {
+ cunote = g_new0(PyGtkCustomNotify, 1);
+ cunote->func = pyfunc;
+ cunote->data = pyarg;
+ Py_INCREF(cunote->func);
+ Py_XINCREF(cunote->data);
+
+ gdk_event_handler_set(pygdk_event_handler_marshal,
+ cunote,
+ pygtk_custom_destroy_notify);
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyMethodDef _kiwi_functions[] = {
+ { "add_emission_hook",
+ (PyCFunction)pyg_add_emission_hook, METH_VARARGS },
+ { "remove_emission_hook",
+ (PyCFunction)pyg_remove_emission_hook, METH_VARARGS },
+ { "event_handler_set",
+ (PyCFunction)_wrap_gdk_event_handler_set, METH_VARARGS },
+
+ { NULL, NULL, 0 }
+};
+
+DL_EXPORT(void)
+init_kiwi(void)
+{
+
+ init_pygobject();
+ init_pygtk();
+
+ Py_InitModule("kiwi._kiwi", _kiwi_functions);
+
+}
Modified: kiwi/trunk/kiwi/ui/test/common.py
==============================================================================
--- kiwi/trunk/kiwi/ui/test/common.py (original)
+++ kiwi/trunk/kiwi/ui/test/common.py Tue Nov 21 14:21:07 2006
@@ -29,25 +29,123 @@
import gobject
import gtk
+from gtk import gdk
-class Base(object):
- """
- Base class used by L{kiwi.ui.test.recorder.Recorder} and
- L{kiwi.ui.test.player.Player}.
-
- The most important functionallity here is the ability track
- creation and destruction of windows. When a new window is
- located all the children are traversed recursively.
- """
+from kiwi.utils import gsignal
+
+try:
+ from gtk.gdk import event_handler_set
+except ImportError:
+ try:
+ from kiwi._kiwi import event_handler_set
+ except ImportError:
+ event_handler_set = None
+
+class WidgetIntrospecter(gobject.GObject):
+ gsignal('window-added', object, str, object)
+ gsignal('window-removed', object, str)
def __init__(self):
- self._windows = {}
- self._window_list = self._list_windows()
- gobject.timeout_add(25, self._check_windows)
+ gobject.GObject.__init__(self)
self._objects = {}
+ self._id_to_obj = {} # GdkWindow -> GtkWindow
+ self._windows = {} # toplevels ?
+
+ def _event_handler(self, event):
+ # Separate method so we can use return inside
+ self._check_event(event)
+ gtk.main_do_event(event)
+
+ def _check_event(self, event):
+ if not event.window:
+ return
+
+ window = event.window
+ event_type = event.type
+ window_type = window.get_window_type()
+ try:
+ widget = window.get_user_data()
+ except ValueError:
+ widget = self._id_to_obj.get(window)
+
+ if not isinstance(widget, gtk.Window):
+ return
+ widget_name = widget.get_name()
+
+ if event_type == gdk.MAP:
+ if window_type != gdk.WINDOW_TOPLEVEL:
+ # For non toplevels we only care about those which has a menu
+ # as the child
+ child = widget.child
+ if not child or not isinstance(child, gtk.Menu):
+ return
+
+ # Hack to get all the children of a popup menu in
+ # the same namespace as the window they were launched in.
+ parent_menu = child.get_data('parent-menu')
+ if parent_menu:
+ main = parent_menu.get_toplevel()
+ widget_name = main.get_name()
+ else:
+ self._window_added(widget, widget_name)
+ self._id_to_obj[window] = widget
+ elif (event_type == gdk.DELETE or
+ (event_type == gdk.WINDOW_STATE and
+ event.new_window_state == gdk.WINDOW_STATE_WITHDRAWN)):
+ self._window_removed(widget, widget_name)
+
+ def _window_added(self, window, name):
+ if name in self._windows:
+ return
+ self._windows[name] = window
+
+ # Toplevel
+ self.parse_one(window, window)
+ ns = self._objects[name]
+ self.emit('window-added', window, name, ns)
+
+ def _window_removed(self, window, name):
+ if not name in self._windows:
+ # Error?
+ return
+
+ del self._windows[name]
+ self.emit('window-removed', window, name)
+
+ def _add_widget(self, toplevel, widget, name):
+ toplevel_widgets = self._objects.setdefault(toplevel.get_name(), {})
+ if name in toplevel_widgets:
+ return
+
+ toplevel_widgets[name] = widget
+
+ # Listen to when the widget is removed from the interface, eg when
+ # ::parent changes to None. At that time remove the widget and all
+ # the children from the namespace.
+
+ def on_widget__notify_parent(widget, pspec, name, widgets,
+ signal_container):
+ # Only take action when the widget is removed from a parent
+ if widget.parent is not None:
+ return
+
+ for child_name, child in widgets.items():
+ if child.is_ancestor(widget):
+ del widgets[child_name]
+ widget.disconnect(signal_container.pop())
+
+ signal_container = []
+ sig_id = widget.connect('notify::parent', on_widget__notify_parent,
+ name, toplevel_widgets, signal_container)
+ signal_container.append(sig_id)
# Public API
+ def register_event_handler(self):
+ if not event_handler_set:
+ raise NotImplementedError
+ event_handler_set(self._event_handler)
+
def parse_one(self, toplevel, gobj):
"""
@param toplevel:
@@ -69,96 +167,15 @@
gtype = gobject.type_parent(gtype)
- def get_object(self, attr):
- """
- @param attr: name of toplevel object to get
- @returns: toplevel object
- """
- return self._objects[attr]
-
- # Override in subclass
-
- def window_added(self, window):
- """
- This will be called when a window is displayed
- @param window:
- """
-
- def window_removed(self, window):
- """
- This will be called when a window is destroyed
- @param window:
- """
-
- # Private
-
- def _on_window_name_change(self, window, pspec, old_name):
- # Update datastructures, no need to notify that the dialog
- # was added, we already know about it and all its children
- self._windows[window.get_name()] = self._windows.pop(old_name)
-
- def _list_windows(self):
- # We're only interested in toplevels for now, tooltip windows are
- # popups for example
- rv = []
- for window in gtk.window_list_toplevels():
- if window.type != gtk.WINDOW_TOPLEVEL:
- if not isinstance(window.child, gtk.Menu):
- continue
-
- # Hack to get all the entries of a popup menu in
- # the same namespace as the window they were launched
- # in.
- parent_menu = window.child.get_data('parent-menu')
- if parent_menu:
- main = parent_menu.get_toplevel()
- rv.append((main.get_name(), window))
- else:
- rv.append((window.get_name(), window))
-
- return sets.Set(rv)
-
- def _check_windows(self):
- new_windows = self._list_windows()
- if self._windows != new_windows:
- for name, window in new_windows.difference(self._window_list):
- # Popup window, eg menu popups needs to be treated
- # specially, only parse the contained widgets, do not
- # add it or listen to name changes, we don't care about them
- if window.type == gtk.WINDOW_POPUP:
- # XXX: This is trigged by stoq.test.gui.salewizard
- if name in self._windows:
- toplevel = self._windows[name]
- self.parse_one(toplevel, window)
- else:
- self.parse_one(window, window)
-
- window.connect('notify::name', self._on_window_name_change,
- window.get_name())
- self.window_added(window)
- self._windows[name] = window
-
- for name, window in self._window_list.difference(new_windows):
- # We don care about popup windows, see above
- if window.type == gtk.WINDOW_POPUP:
- continue
-
- self.window_removed(window)
- del self._windows[name]
-
- self._window_list = new_windows
- return True
+ #
+ # Special widget handling
+ #
def ignore(self, toplevel, gobj):
pass
GtkSeparatorMenuItem = GtkTearoffMenuItem = ignore
- def _add_widget(self, toplevel, widget, name):
- toplevel_widgets = self._objects.setdefault(toplevel.get_name(), {})
- if not name in toplevel_widgets:
- toplevel_widgets[name] = widget
-
def GtkWidget(self, toplevel, widget):
"""
Called when a GtkWidget is about to be traversed
@@ -204,3 +221,5 @@
def GtkToolButton(self, toplevel, item):
item.child.set_name(item.get_name())
+
+gobject.type_register(WidgetIntrospecter)
Modified: kiwi/trunk/kiwi/ui/test/main.py
==============================================================================
--- kiwi/trunk/kiwi/ui/test/main.py (original)
+++ kiwi/trunk/kiwi/ui/test/main.py Tue Nov 21 14:21:07 2006
@@ -28,23 +28,29 @@
from kiwi.log import set_log_level
def _play(options, filename, args):
- from kiwi.ui.test.player import play_file
+ from kiwi.ui.test.runner import play_file
- play_file(filename, args)
+ play_file(filename, options.command, args)
def _record(options, filename, args):
from kiwi.ui.test.recorder import Recorder
- Recorder(filename, args)
+ recorder = Recorder(filename)
+ recorder.execute(args)
def main(args):
parser = optparse.OptionParser()
+ parser.add_option('', '--command', action="store",
+ dest="command")
parser.add_option('', '--record', action="store",
dest="record")
parser.add_option('-v', '--verbose', action="store_true",
dest="verbose")
options, args = parser.parse_args(args)
+ if options.record and options.command:
+ raise SystemExit(
+ "You can't specify a command and recording at the same time")
if options.record:
if options.verbose:
set_log_level('recorder', 5)
Modified: kiwi/trunk/kiwi/ui/test/recorder.py
==============================================================================
--- kiwi/trunk/kiwi/ui/test/recorder.py (original)
+++ kiwi/trunk/kiwi/ui/test/recorder.py Tue Nov 21 14:21:07 2006
@@ -27,7 +27,8 @@
This module provides an interface for creating, listening to
and saving events.
It uses the gobject introspection base class
-L{kiwi.ui.test.common.Base} to gather widgets, windows and other objects.
+L{kiwi.ui.test.common.WidgetIntrospecter} to gather widgets, windows and
+other objects.
The user interfaces are saved in a format so they can easily be played
back by simply executing the script through a standard python interpreter.
@@ -35,15 +36,24 @@
import atexit
import sys
+import time
import gobject
from gtk import gdk
import gtk
from kiwi.log import Logger
-from kiwi.ui.test.common import Base
+from kiwi.ui.test.common import WidgetIntrospecter
from kiwi.ui.objectlist import ObjectList
+try:
+ from gobject import add_emission_hook
+except ImportError:
+ try:
+ from kiwi._kiwi import add_emission_hook
+ except ImportError:
+ add_emission_hook = None
+
_events = []
log = Logger('recorder')
@@ -152,9 +162,6 @@
window manager.
"""
- def serialize(self):
- return 'delete_window("%s")' % self.name
-
#
# Signal Events
#
@@ -340,7 +347,7 @@
# register_event_type(KiwiComboBoxChangedEvent)
-class Recorder(Base):
+class Recorder(WidgetIntrospecter):
"""
Recorder takes care of attaching events to widgets, when the appear,
and creates the events when the user is interacting with some widgets.
@@ -349,18 +356,19 @@
L{kiwi.ui.test.player.Player}.
"""
- def __init__(self, filename, args):
+ def __init__(self, filename):
"""
@param filename: name of the script
@param args: command line used to run the script
"""
- Base.__init__(self)
+ WidgetIntrospecter.__init__(self)
+ self.register_event_handler()
+ self.connect('window-removed', self.window_removed)
+
self._filename = filename
- self._args = args
self._events = []
self._listened_objects = []
self._event_types = self._configure_event_types()
- self._has_emission_hook = False
# This is sort of a hack, but there are no other realiable ways
# of actually having something executed after the application
@@ -370,10 +378,12 @@
# Register a hook that is called before normal delete-events
# because if it's connected using a normal callback it will not
# be called if the application returns True in it's signal handler.
- if hasattr(gobject, 'add_emission_hook'):
- gobject.add_emission_hook(gtk.Window, 'delete-event',
- self._emission_window__delete_event)
- self._has_emission_hook = True
+ if add_emission_hook:
+ add_emission_hook(gtk.Window, 'delete-event',
+ self._emission_window__delete_event)
+
+ def execute(self, args):
+ self._start_timestamp = time.time()
# Run the script
sys.argv = args
@@ -397,7 +407,7 @@
def _add_event(self, event):
log("Added event %s" % event.serialize())
- self._events.append(event)
+ self._events.append((event, time.time()))
def _listen_event(self, object, event_type):
if not issubclass(event_type, SignalEvent):
@@ -419,15 +429,15 @@
pass
event_type.connect(object, event_type.signal_name, on_signal)
- def window_removed(self, window):
+ def window_removed(self, wi, window, name):
# It'll already be trapped if we can use an emission hook
# skip it here to avoid duplicates
- if self._has_emission_hook:
+ if not add_emission_hook:
return
self._add_event(WindowDeleteEvent(window))
def parse_one(self, toplevel, gobj):
- Base.parse_one(self, toplevel, gobj)
+ WidgetIntrospecter.parse_one(self, toplevel, gobj)
# mark the object as "listened" to ensure we'll always
# receive unique objects
@@ -461,33 +471,42 @@
finished executing.
"""
+ if not self._events:
+ return
+
try:
fd = open(self._filename, 'w')
except IOError:
raise SystemExit("Could not write: %s" % self._filename)
- fd.write("from kiwi.ui.test.player import Player\n"
- "\n"
- "player = Player(%s)\n"
- "app = player.get_app()\n" % repr(self._args))
+ fd.write(">>> from kiwi.ui.test.runner import runner\n")
+ fd.write(">>> runner.start()\n")
windows = {}
- for event in self._events:
+ last = self._events[0][1]
+ fd.write('>>> runner.sleep(%2.1f)\n' % (last - self._start_timestamp,))
+
+ for event, timestamp in self._events:
toplevel = event.toplevel_name
if not toplevel in windows:
- fd.write('\n'
- 'player.wait_for_window("%s")\n' % toplevel)
+ fd.write('>>> %s = runner.waitopen("%s")\n' % (toplevel,
+ toplevel))
windows[toplevel] = True
if isinstance(event, WindowDeleteEvent):
- fd.write("player.%s\n\n" % (event.serialize()))
+ fd.write(">>> %s.delete()\n" % (event.name,))
+ fd.write(">>> runner.waitclose('%s')\n" % (event.name,))
if not event.name in windows:
# Actually a bug
continue
del windows[event.name]
else:
- fd.write("app.%s.%s\n" % (toplevel,
- event.serialize()))
+ fd.write(">>> %s.%s\n" % (toplevel, event.serialize()))
+
+ delta = timestamp - last
+ if delta > 0.05:
+ fd.write('>>> runner.sleep(%2.1f)\n' % (delta,))
+ last = timestamp
- fd.write('player.finish()\n')
+ fd.write('>>> runner.quit()\n')
fd.close()
Added: kiwi/trunk/kiwi/ui/test/runner.py
==============================================================================
--- (empty file)
+++ kiwi/trunk/kiwi/ui/test/runner.py Tue Nov 21 14:21:07 2006
@@ -0,0 +1,207 @@
+#
+# Kiwi: a Framework and Enhanced Widgets for Python
+#
+# Copyright (C) 2006 Async Open Source
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+#
+# Author(s): Johan Dahlin <jdahlin at async.com.br>
+#
+
+"""
+Runner - executes recorded scripts
+"""
+
+import doctest
+import sys
+import time
+
+import gobject
+from gtk import gdk
+
+from kiwi.log import Logger
+from kiwi.ui.test.common import WidgetIntrospecter
+
+log = Logger('kiwi.ui.test.player')
+
+class NotReadyYet(Exception):
+ pass
+
+class MissingWidget(KeyError):
+ pass
+
+class MagicWindowWrapper(object):
+ def __init__(self, window, ns):
+ self.window = window
+ self.ns = ns
+
+ def delete(self):
+ self.window.emit('delete-event', gdk.Event(gdk.DELETE))
+
+ def __getattr__(self, attr):
+ if not attr in self.ns:
+ raise MissingWidget(attr)
+ return self.ns[attr]
+
+class Runner(object):
+ """
+ @ivar parser:
+ """
+ def __init__(self, filename):
+ self._filename = filename
+ self._pos = 0
+ self._windows = {}
+ self._ns = {}
+ self._source_id = -1
+
+ self.parser = doctest.DocTestParser()
+ self._stmts = self.parser.get_examples(open(filename).read())
+
+ wi = WidgetIntrospecter()
+ wi.register_event_handler()
+ wi.connect('window-added', self._on_wi__window_added)
+ wi.connect('window-removed', self._on_wi__window_removed)
+
+ # Callbacks
+
+ def _on_wi__window_added(self, wi, window, name, ns):
+ log.info('Window added: %s' % (name,))
+ self._windows[name] = MagicWindowWrapper(window, ns)
+
+ self._iterate()
+
+ def _on_wi__window_removed(self, wi, window, name):
+ log.info('Window removed: %s' % (name,))
+ del self._windows[name]
+
+ self._iterate()
+
+ # Private
+
+ def _iterate(self):
+ stmts = self._stmts
+ while True:
+ if self._pos == len(stmts):
+ self.quit()
+ break
+
+ ex = stmts[self._pos]
+ self._pos += 1
+
+ log.info('will now execute %r' % (ex.source[:-1],))
+ try:
+ exec compile(ex.source, self._filename,
+ 'single', 0, 1) in self._ns
+ except NotReadyYet:
+ self._pos -= 1
+ break
+ except (SystemExit, KeyboardInterrupt):
+ raise SystemExit
+ except MissingWidget, e:
+ raise SystemExit(
+ "ERROR: Could not find widget: %s" % str(e))
+ except Exception, e:
+ import traceback
+ traceback.print_exc()
+ raise SystemExit
+
+ log.info('Executed %r' % (ex.source[:-1],))
+ self._last = time.time()
+
+ # Public API
+
+ def quit(self):
+ print '* Executed successfully'
+ sys.exit(0)
+
+ def start(self):
+ self._last = time.time()
+
+ def sleep(self, duration):
+ """
+ @param duration:
+ """
+ # We don't want to block the interface here which means that
+ # we cannot use time.sleep.
+ # Instead we schedule another execute iteration in the future
+ # and raises NotReadyYet which stops the interpreter until
+ # iterate is called again.
+
+ def _iter():
+ # Turn ourselves off and allow future calls to wait() to
+ # queue new waits.
+ self._source_id = -1
+
+ # Iterate, which will call us again
+ self._iterate()
+
+ return False
+
+ if self._source_id != -1:
+ raise NotReadyYet
+
+ # The delta is the last time we executed a statement minus
+ delta = (self._last + duration) - time.time()
+ if delta > 0:
+ ms = int(delta * 1000)
+ self._source_id = gobject.timeout_add(ms, _iter)
+ raise NotReadyYet
+
+ # Okay, we've waited enough, let's go back to business
+
+ def waitopen(self, window_name):
+ """
+ @param window_name:
+ """
+ if not window_name in self._windows:
+ raise NotReadyYet(window_name)
+ return self._windows[window_name]
+
+ def waitclose(self, window_name):
+ """
+ @param window_name:
+ """
+ if window_name in self._windows:
+ raise NotReadyYet(window_name)
+
+runner = None
+
+def play_file(script, filename=None, args=None):
+ """
+ @param script:
+ @param filename:
+ @param args:
+ """
+
+ global runner
+
+ log.info('Running script %s' % script)
+ runner = Runner(script)
+
+ if filename is None:
+ fd = open(script)
+ data = fd.readline()[:-1] + fd.readline()[:-1]
+ pos = data.find('run:')
+ if pos != -1:
+ filename, rest = data[pos+5:].split(' ', 1)
+ if rest:
+ args = rest.split(' ')
+ else:
+ if args is None:
+ args = []
+
+ sys.argv = [filename] + args[:]
+ execfile(sys.argv[0], globals(), globals())
Modified: kiwi/trunk/setup.py
==============================================================================
--- kiwi/trunk/setup.py (original)
+++ kiwi/trunk/setup.py Tue Nov 21 14:21:07 2006
@@ -12,9 +12,30 @@
simpler.
"""
+import commands
+from distutils.extension import Extension
+import sys
+
from kiwi import kiwi_version
from kiwi.dist import setup, listfiles, listpackages, get_site_packages_dir
+import gtk
+
+ext_modules = []
+
+# Build a helper module for testing on gtk+ versions lower than 2.10.
+# Don't build it on windows due to easy availability compilers and
+# the lack of pkg-config.
+if gtk.pygtk_version < (2, 10) and sys.platform != 'win32':
+ pkgs = 'gdk-2.0 gtk+-2.0 pygtk-2.0'
+ cflags = commands.getoutput('pkg-config --cflags %s' % pkgs)
+ libs = commands.getoutput('pkg-config --libs %s' % pkgs)
+ include_dirs = [part.strip() for part in cflags.split('-I') if part]
+ libraries = [part.strip() for part in libs.split('-l') if part]
+ ext_modules.append(Extension('kiwi/_kiwi', ['kiwi/_kiwi.c'],
+ include_dirs=include_dirs,
+ libraries=libraries))
+
setup(name="kiwi",
version=".".join(map(str, kiwi_version)),
description="A framework and a set of enhanced widgets based on PyGTK",
@@ -44,6 +65,7 @@
scripts=['bin/kiwi-i18n',
'bin/kiwi-ui-test'],
packages=listpackages('kiwi'),
+ ext_modules=ext_modules,
resources=dict(locale='$prefix/share/locale'),
global_resources=dict(glade='$datadir/glade',
pixmap='$datadir/pixmaps'),
Modified: kiwi/trunk/tests/ui/diary.py
==============================================================================
--- kiwi/trunk/tests/ui/diary.py (original)
+++ kiwi/trunk/tests/ui/diary.py Tue Nov 21 14:21:07 2006
@@ -1,42 +1,29 @@
-from kiwi.ui.test.player import Player
+... -*- Mode: doctest -*-
+... run: examples/framework/diary/diary.py
-player = Player(['examples/framework/diary/diary.py'])
-app = player.get_app()
-
-player.wait_for_window("Diary")
-app.Diary.add.clicked()
-app.Diary.title.set_text("New title")
-app.Diary.title.set_text("")
-app.Diary.title.set_text("F")
-app.Diary.title.set_text("Fo")
-app.Diary.title.set_text("Foo")
-app.Diary.title.set_text("Foob")
-app.Diary.title.set_text("Fooba")
-app.Diary.title.set_text("Foobar")
-app.Diary.add.clicked()
-app.Diary.title.set_text("")
-app.Diary.title.set_text("New title")
-app.Diary.ObjectList.select_paths(['1'])
-app.Diary.title.set_text("")
-app.Diary.title.set_text("T")
-app.Diary.title.set_text("Te")
-app.Diary.title.set_text("Tes")
-app.Diary.title.set_text("Test")
-app.Diary.title.set_text("Testi")
-app.Diary.title.set_text("Testin")
-app.Diary.title.set_text("Testing")
-app.Diary.title.set_text("")
-app.Diary.title.set_text("Foobar")
-app.Diary.ObjectList.select_paths(['0'])
-app.Diary.remove.clicked()
-app.Diary.ObjectList.select_paths([])
-app.Diary.title.set_text("")
-app.Diary.title.set_text("Testing")
-app.Diary.ObjectList.select_paths(['0'])
-app.Diary.period.clicked()
-app.Diary.evening.clicked()
-app.Diary.remove.clicked()
-app.Diary.ObjectList.select_paths([])
-player.delete_window("Diary")
-
-player.finish()
+>>> from kiwi.ui.test.runner import runner
+>>> runner.start()
+>>> Diary = runner.waitopen("Diary")
+>>> Diary.add.clicked()
+>>> Diary.title.set_text("New title")
+>>> Diary.title.set_text("Foobar")
+>>> Diary.add.clicked()
+>>> Diary.title.set_text("")
+>>> Diary.title.set_text("New title")
+>>> Diary.ObjectList.select_paths(['1'])
+>>> Diary.title.set_text("Testing")
+>>> Diary.title.set_text("")
+>>> Diary.title.set_text("Foobar")
+>>> Diary.ObjectList.select_paths(['0'])
+>>> Diary.remove.clicked()
+>>> Diary.ObjectList.select_paths([])
+>>> Diary.title.set_text("")
+>>> Diary.title.set_text("Testing")
+>>> Diary.ObjectList.select_paths(['0'])
+>>> Diary.period.clicked()
+>>> Diary.evening.clicked()
+>>> Diary.remove.clicked()
+>>> Diary.ObjectList.select_paths([])
+>>> Diary.delete()
+>>> runner.waitclose("Diary")
+>>> runner.quit()
Modified: kiwi/trunk/tests/ui/diary2.py
==============================================================================
--- kiwi/trunk/tests/ui/diary2.py (original)
+++ kiwi/trunk/tests/ui/diary2.py Tue Nov 21 14:21:07 2006
@@ -1,83 +1,67 @@
-from kiwi.ui.test.player import Player
+... -*- Mode: doctest -*-
+... run: examples/framework/diary/diary2.py
-player = Player(['examples/framework/diary/diary2.py'])
-app = player.get_app()
+>>> from kiwi.ui.test.runner import runner
+>>> runner.start()
+>>> Diary = runner.waitopen("Diary")
+>>> Diary.add.clicked()
+>>> Diary.title.set_text("Untitled")
+>>> Diary.ObjectList.select_paths(['0'])
+>>> Diary.title.set_text("First")
+>>> Diary.add.clicked()
+>>> Diary.title.set_text("")
+>>> Diary.title.set_text("Untitled")
+>>> Diary.ObjectList.select_paths(['1'])
+>>> Diary.title.set_text("Second")
+>>> Diary.period.clicked()
+>>> Diary.afternoon.clicked()
+>>> Diary.add.clicked()
+>>> Diary.afternoon.clicked()
+>>> Diary.period.clicked()
+>>> Diary.title.set_text("")
+>>> Diary.title.set_text("Untitled")
+>>> Diary.ObjectList.select_paths(['2'])
+>>> Diary.title.set_text("Third")
+>>> Diary.period.clicked()
+>>> Diary.evening.clicked()
+>>> Diary.evening.clicked()
+>>> Diary.afternoon.clicked()
+>>> Diary.title.set_text("")
+>>> Diary.title.set_text("Second")
+>>> Diary.ObjectList.select_paths(['1'])
+>>> Diary.afternoon.clicked()
+>>> Diary.period.clicked()
+>>> Diary.title.set_text("")
+>>> Diary.title.set_text("First")
+>>> Diary.ObjectList.select_paths(['0'])
+>>> Diary.period.clicked()
+>>> Diary.afternoon.clicked()
+>>> Diary.title.set_text("")
+>>> Diary.title.set_text("Second")
+>>> Diary.ObjectList.select_paths(['1'])
+>>> Diary.afternoon.clicked()
+>>> Diary.evening.clicked()
+>>> Diary.title.set_text("")
+>>> Diary.title.set_text("Third")
+>>> Diary.ObjectList.select_paths(['2'])
+>>> Diary.remove.clicked()
+>>> Diary.ObjectList.select_paths([])
+>>> Diary.evening.clicked()
+>>> Diary.afternoon.clicked()
+>>> Diary.title.set_text("")
+>>> Diary.title.set_text("Second")
+>>> Diary.ObjectList.select_paths(['1'])
+>>> Diary.remove.clicked()
+>>> Diary.ObjectList.select_paths([])
+>>> Diary.afternoon.clicked()
+>>> Diary.period.clicked()
+>>> Diary.title.set_text("")
+>>> Diary.title.set_text("First")
+>>> Diary.ObjectList.select_paths(['0'])
+>>> Diary.remove.clicked()
+>>> Diary.ObjectList.select_paths([])
+>>> Diary.title.set_text("")
+>>> Diary.title.set_text("")
+>>> Diary.delete()
+>>> runner.quit()
-player.wait_for_window("Diary")
-app.Diary.add.clicked()
-app.Diary.title.set_text("Untitled")
-app.Diary.ObjectList.select_paths(['0'])
-app.Diary.title.set_text("")
-app.Diary.title.set_text("F")
-app.Diary.title.set_text("Fi")
-app.Diary.title.set_text("Fir")
-app.Diary.title.set_text("Firs")
-app.Diary.title.set_text("First")
-app.Diary.add.clicked()
-app.Diary.title.set_text("")
-app.Diary.title.set_text("Untitled")
-app.Diary.ObjectList.select_paths(['1'])
-app.Diary.title.set_text("")
-app.Diary.title.set_text("S")
-app.Diary.title.set_text("Se")
-app.Diary.title.set_text("Sec")
-app.Diary.title.set_text("Seco")
-app.Diary.title.set_text("Secon")
-app.Diary.title.set_text("Second")
-app.Diary.period.clicked()
-app.Diary.afternoon.clicked()
-app.Diary.add.clicked()
-app.Diary.afternoon.clicked()
-app.Diary.period.clicked()
-app.Diary.title.set_text("")
-app.Diary.title.set_text("Untitled")
-app.Diary.ObjectList.select_paths(['2'])
-app.Diary.title.set_text("")
-app.Diary.title.set_text("T")
-app.Diary.title.set_text("Th")
-app.Diary.title.set_text("Thi")
-app.Diary.title.set_text("Thir")
-app.Diary.title.set_text("Third")
-app.Diary.period.clicked()
-app.Diary.evening.clicked()
-app.Diary.evening.clicked()
-app.Diary.afternoon.clicked()
-app.Diary.title.set_text("")
-app.Diary.title.set_text("Second")
-app.Diary.ObjectList.select_paths(['1'])
-app.Diary.afternoon.clicked()
-app.Diary.period.clicked()
-app.Diary.title.set_text("")
-app.Diary.title.set_text("First")
-app.Diary.ObjectList.select_paths(['0'])
-app.Diary.period.clicked()
-app.Diary.afternoon.clicked()
-app.Diary.title.set_text("")
-app.Diary.title.set_text("Second")
-app.Diary.ObjectList.select_paths(['1'])
-app.Diary.afternoon.clicked()
-app.Diary.evening.clicked()
-app.Diary.title.set_text("")
-app.Diary.title.set_text("Third")
-app.Diary.ObjectList.select_paths(['2'])
-app.Diary.remove.clicked()
-app.Diary.ObjectList.select_paths([])
-app.Diary.evening.clicked()
-app.Diary.afternoon.clicked()
-app.Diary.title.set_text("")
-app.Diary.title.set_text("Second")
-app.Diary.ObjectList.select_paths(['1'])
-app.Diary.remove.clicked()
-app.Diary.ObjectList.select_paths([])
-app.Diary.afternoon.clicked()
-app.Diary.period.clicked()
-app.Diary.title.set_text("")
-app.Diary.title.set_text("First")
-app.Diary.ObjectList.select_paths(['0'])
-app.Diary.remove.clicked()
-app.Diary.ObjectList.select_paths([])
-app.Diary.title.set_text("")
-app.Diary.title.set_text("")
-player.delete_window("Diary")
-
-player.finish()
Modified: kiwi/trunk/tests/ui/personalinformation.py
==============================================================================
--- kiwi/trunk/tests/ui/personalinformation.py (original)
+++ kiwi/trunk/tests/ui/personalinformation.py Tue Nov 21 14:21:07 2006
@@ -1,115 +1,114 @@
-from kiwi.ui.test.player import Player
+... -*- Mode: doctest -*-
+... run: examples/validation/personalinformation.py
-player = Player(['examples/validation/personalinformation.py'])
-app = player.get_app()
-
-player.wait_for_window("Form")
-app.Form.name.set_text("")
-app.Form.name.set_text("J")
-app.Form.name.set_text("Jo")
-app.Form.name.set_text("Joh")
-app.Form.name.set_text("Joha")
-app.Form.name.set_text("Johan")
-app.Form.age.set_text("")
-app.Form.age.set_text(" ")
-app.Form.age.set_text(" ")
-app.Form.age.set_text("1 ")
-app.Form.age.set_text("1")
-app.Form.age.set_text("12")
-app.Form.age.set_text("1")
-app.Form.age.set_text("1 ")
-app.Form.age.set_text(" ")
-app.Form.age.set_text(" ")
-app.Form.age.set_text(" ")
-app.Form.age.set_text("9 ")
-app.Form.age.set_text("9")
-app.Form.age.set_text("99")
-app.Form.GtkToggleButton.clicked()
-app.Form.ProxyEntry.set_text("")
-app.Form.ProxyEntry.set_text(" / / ")
-app.Form.ProxyEntry.set_text(" / / ")
-app.Form.ProxyEntry.set_text("/ / ")
-app.Form.ProxyEntry.set_text(" / ")
-app.Form.ProxyEntry.set_text(" / ")
-app.Form.ProxyEntry.set_text("/ ")
-app.Form.ProxyEntry.set_text(" ")
-app.Form.ProxyEntry.set_text(" ")
-app.Form.ProxyEntry.set_text(" ")
-app.Form.ProxyEntry.set_text(" ")
-app.Form.ProxyEntry.set_text("")
-app.Form.ProxyEntry.set_text("02/14/1969")
-app.Form.GtkToggleButton.clicked()
-app.Form.ProxyEntry.set_text("")
-app.Form.ProxyEntry.set_text(" / / ")
-app.Form.ProxyEntry.set_text(" / / ")
-app.Form.ProxyEntry.set_text("/ / ")
-app.Form.ProxyEntry.set_text(" / ")
-app.Form.ProxyEntry.set_text(" / ")
-app.Form.ProxyEntry.set_text("/ ")
-app.Form.ProxyEntry.set_text(" ")
-app.Form.ProxyEntry.set_text(" ")
-app.Form.ProxyEntry.set_text(" ")
-app.Form.ProxyEntry.set_text(" ")
-app.Form.ProxyEntry.set_text("")
-app.Form.ProxyEntry.set_text("02/13/1969")
-app.Form.height.set_text("")
-app.Form.height.set_text("1")
-app.Form.height.set_text("12")
-app.Form.height.set_text("123")
-app.Form.height.set_text("1234")
-app.Form.height.set_text("12345")
-app.Form.weight.set_text("")
-app.Form.weight.set_text("87")
-app.Form.weight.set_text("")
-app.Form.weight.set_text("88")
-app.Form.weight.set_text("")
-app.Form.weight.set_text("89")
-app.Form.weight.set_text("")
-app.Form.weight.set_text("90")
-app.Form.weight.set_text("")
-app.Form.weight.set_text("91")
-app.Form.weight.set_text("")
-app.Form.weight.set_text("92")
-app.Form.weight.set_text("")
-app.Form.weight.set_text("93")
-app.Form.weight.set_text("")
-app.Form.weight.set_text("92")
-app.Form.weight.set_text("")
-app.Form.weight.set_text("91")
-app.Form.weight.set_text("")
-app.Form.weight.set_text("90")
-app.Form.weight.set_text("")
-app.Form.weight.set_text("89")
-app.Form.weight.set_text("")
-app.Form.weight.set_text("90")
-app.Form.height.set_text("")
-app.Form.height.set_text("1")
-app.Form.height.set_text("12")
-app.Form.age.set_text("")
-app.Form.age.set_text(" ")
-app.Form.age.set_text(" ")
-app.Form.age.set_text("1 ")
-app.Form.age.set_text("1")
-app.Form.age.set_text("12")
-app.Form.GtkToggleButton.clicked()
-app.Form.ProxyEntry.set_text("")
-app.Form.ProxyEntry.set_text("Brazilian")
-app.Form.GtkToggleButton.clicked()
-app.Form.ProxyEntry.set_text("")
-app.Form.ProxyEntry.set_text("Yankee")
-app.Form.ProxyEntry.set_text("")
-app.Form.ProxyEntry.set_text("Other")
-app.Form.ProxyEntry.set_text("")
-app.Form.ProxyEntry.set_text("Yankee")
-app.Form.ProxyEntry.set_text("")
-app.Form.ProxyEntry.set_text("Brazilian")
-app.Form.ProxyEntry.set_text("")
-app.Form.ProxyEntry.set_text("Yankee")
-app.Form.gender.select_item_by_label("Male")
-app.Form.gender.select_item_by_label("Female")
-app.Form.status_single.clicked()
-app.Form.status.clicked()
-app.Form.status.clicked()
-app.Form.status_single.clicked()
-app.Form.ok_btn.clicked()
-player.finish()
+>>> from kiwi.ui.test.runner import runner
+>>> Form = runner.waitopen("Form")
+>>> Form.name.set_text("")
+>>> Form.name.set_text("J")
+>>> Form.name.set_text("Jo")
+>>> Form.name.set_text("Joh")
+>>> Form.name.set_text("Joha")
+>>> Form.name.set_text("Johan")
+>>> Form.age.set_text("")
+>>> Form.age.set_text(" ")
+>>> Form.age.set_text(" ")
+>>> Form.age.set_text("1 ")
+>>> Form.age.set_text("1")
+>>> Form.age.set_text("12")
+>>> Form.age.set_text("1")
+>>> Form.age.set_text("1 ")
+>>> Form.age.set_text(" ")
+>>> Form.age.set_text(" ")
+>>> Form.age.set_text(" ")
+>>> Form.age.set_text("9 ")
+>>> Form.age.set_text("9")
+>>> Form.age.set_text("99")
+>>> Form.GtkToggleButton.clicked()
+>>> Form.ProxyEntry.set_text("")
+>>> Form.ProxyEntry.set_text(" / / ")
+>>> Form.ProxyEntry.set_text(" / / ")
+>>> Form.ProxyEntry.set_text("/ / ")
+>>> Form.ProxyEntry.set_text(" / ")
+>>> Form.ProxyEntry.set_text(" / ")
+>>> Form.ProxyEntry.set_text("/ ")
+>>> Form.ProxyEntry.set_text(" ")
+>>> Form.ProxyEntry.set_text(" ")
+>>> Form.ProxyEntry.set_text(" ")
+>>> Form.ProxyEntry.set_text(" ")
+>>> Form.ProxyEntry.set_text("")
+>>> Form.ProxyEntry.set_text("02/14/1969")
+>>> Form.GtkToggleButton.clicked()
+>>> Form.ProxyEntry.set_text("")
+>>> Form.ProxyEntry.set_text(" / / ")
+>>> Form.ProxyEntry.set_text(" / / ")
+>>> Form.ProxyEntry.set_text("/ / ")
+>>> Form.ProxyEntry.set_text(" / ")
+>>> Form.ProxyEntry.set_text(" / ")
+>>> Form.ProxyEntry.set_text("/ ")
+>>> Form.ProxyEntry.set_text(" ")
+>>> Form.ProxyEntry.set_text(" ")
+>>> Form.ProxyEntry.set_text(" ")
+>>> Form.ProxyEntry.set_text(" ")
+>>> Form.ProxyEntry.set_text("")
+>>> Form.ProxyEntry.set_text("02/13/1969")
+>>> Form.height.set_text("")
+>>> Form.height.set_text("1")
+>>> Form.height.set_text("12")
+>>> Form.height.set_text("123")
+>>> Form.height.set_text("1234")
+>>> Form.height.set_text("12345")
+>>> Form.weight.set_text("")
+>>> Form.weight.set_text("87")
+>>> Form.weight.set_text("")
+>>> Form.weight.set_text("88")
+>>> Form.weight.set_text("")
+>>> Form.weight.set_text("89")
+>>> Form.weight.set_text("")
+>>> Form.weight.set_text("90")
+>>> Form.weight.set_text("")
+>>> Form.weight.set_text("91")
+>>> Form.weight.set_text("")
+>>> Form.weight.set_text("92")
+>>> Form.weight.set_text("")
+>>> Form.weight.set_text("93")
+>>> Form.weight.set_text("")
+>>> Form.weight.set_text("92")
+>>> Form.weight.set_text("")
+>>> Form.weight.set_text("91")
+>>> Form.weight.set_text("")
+>>> Form.weight.set_text("90")
+>>> Form.weight.set_text("")
+>>> Form.weight.set_text("89")
+>>> Form.weight.set_text("")
+>>> Form.weight.set_text("90")
+>>> Form.height.set_text("")
+>>> Form.height.set_text("1")
+>>> Form.height.set_text("12")
+>>> Form.age.set_text("")
+>>> Form.age.set_text(" ")
+>>> Form.age.set_text(" ")
+>>> Form.age.set_text("1 ")
+>>> Form.age.set_text("1")
+>>> Form.age.set_text("12")
+>>> Form.GtkToggleButton.clicked()
+>>> Form.ProxyEntry.set_text("")
+>>> Form.ProxyEntry.set_text("Brazilian")
+>>> Form.GtkToggleButton.clicked()
+>>> Form.ProxyEntry.set_text("")
+>>> Form.ProxyEntry.set_text("Yankee")
+>>> Form.ProxyEntry.set_text("")
+>>> Form.ProxyEntry.set_text("Other")
+>>> Form.ProxyEntry.set_text("")
+>>> Form.ProxyEntry.set_text("Yankee")
+>>> Form.ProxyEntry.set_text("")
+>>> Form.ProxyEntry.set_text("Brazilian")
+>>> Form.ProxyEntry.set_text("")
+>>> Form.ProxyEntry.set_text("Yankee")
+>>> Form.gender.select_item_by_label("Male")
+>>> Form.gender.select_item_by_label("Female")
+>>> Form.status_single.clicked()
+>>> Form.status.clicked()
+>>> Form.status.clicked()
+>>> Form.status_single.clicked()
+>>> Form.ok_btn.clicked()
+>>> runner.quit()
More information about the POS-commit
mailing list