Some initial infrastructure for UI tests

The uitree module exposes the at-spi tree. We can use it to do
functional tests of the UI, by checking if the expected
widgets exists, clicking them etc.

A simple example of how this can be used is in the test, which
runs a window and check that it has the expected button.
This commit is contained in:
Daniel Narvaez 2012-12-07 16:53:50 +01:00
parent 5ad6c4e236
commit e6397cf2db
6 changed files with 218 additions and 1 deletions

View File

@ -53,5 +53,6 @@ src/sugar3/event-controller/Makefile
src/sugar3/presence/Makefile src/sugar3/presence/Makefile
src/sugar3/datastore/Makefile src/sugar3/datastore/Makefile
src/sugar3/dispatch/Makefile src/sugar3/dispatch/Makefile
src/sugar3/test/Makefile
po/Makefile.in po/Makefile.in
]) ])

View File

@ -1,4 +1,11 @@
SUBDIRS = activity bundle event-controller graphics presence datastore dispatch SUBDIRS = activity \
bundle \
event-controller \
graphics \
presence \
datastore \
dispatch \
test
sugardir = $(pythondir)/sugar3 sugardir = $(pythondir)/sugar3
sugar_PYTHON = \ sugar_PYTHON = \

View File

@ -0,0 +1,4 @@
sugardir = $(pythondir)/sugar3/test
sugar_PYTHON = \
__init__.py \
uitree.py

View File

153
src/sugar3/test/uitree.py Normal file
View File

@ -0,0 +1,153 @@
# Copyright (C) 2012, Daniel Narvaez
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""
UNSTABLE.
"""
import time
from gi.repository import Atspi
Atspi.set_timeout(-1, -1)
def get_root():
return Node(Atspi.get_desktop(0))
def _retry_find(func):
def wrapped(*args, **kwargs):
result = None
n_retries = 1
while n_retries <= 10:
print "Try %d, name=%s role_name=%s" % \
(n_retries,
kwargs.get("name", None),
kwargs.get("role_name", None))
result = func(*args, **kwargs)
expect_none = kwargs.get("expect_none", False)
if (not expect_none and result) or \
(expect_none and not result):
return result
time.sleep(5)
n_retries = n_retries + 1
get_root().dump()
return result
return wrapped
class Node:
def __init__(self, accessible):
self._accessible = accessible
def dump(self):
self._crawl_accessible(self, 0)
def do_action(self, name):
for i in range(self._accessible.get_n_actions()):
if Atspi.Action.get_name(self._accessible, i) == name:
self._accessible.do_action(i)
def click(self, button=1):
point = self._accessible.get_position(Atspi.CoordType.SCREEN)
Atspi.generate_mouse_event(point.x, point.y, "b%sc" % button)
@property
def name(self):
return self._accessible.get_name()
@property
def role_name(self):
return self._accessible.get_role_name()
@property
def text(self):
return Atspi.Text.get_text(self._accessible, 0, -1)
def get_children(self):
children = []
for i in range(self._accessible.get_child_count()):
child = self._accessible.get_child_at_index(i)
# We sometimes get none children from atspi
if child is not None:
children.append(Node(child))
return children
@_retry_find
def find_children(self, name=None, role_name=None):
def predicate(node):
return self._predicate(node, name, role_name)
descendants = []
self._find_all_descendants(self, predicate, descendants)
if not descendants:
return []
return descendants
@_retry_find
def find_child(self, name=None, role_name=None, expect_none=False):
def predicate(node):
return self._predicate(node, name, role_name)
node = self._find_descendant(self, predicate)
if node is None:
return None
return node
def __str__(self):
return "[%s | %s]" % (self.name, self.role_name)
def _predicate(self, node, name, role_name):
if name is not None and name != node.name:
return False
if role_name is not None and role_name != node.role_name:
return False
return True
def _find_descendant(self, node, predicate):
if predicate(node):
return node
for child in node.get_children():
descendant = self._find_descendant(child, predicate)
if descendant is not None:
return descendant
return None
def _find_all_descendants(self, node, predicate, matches):
if predicate(node):
matches.append(node)
for child in node.get_children():
self._find_all_descendants(child, predicate, matches)
def _crawl_accessible(self, node, depth):
print " " * depth + str(node)
for child in node.get_children():
self._crawl_accessible(child, depth + 1)

52
tests/test_uitree.py Normal file
View File

@ -0,0 +1,52 @@
# Copyright (C) 2012, Daniel Narvaez
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import sys
import subprocess
import unittest
from sugar3.test import uitree
class TestUITree(unittest.TestCase):
def test_tree(self):
process = subprocess.Popen(["python", __file__, "show_window1"])
try:
root = uitree.get_root()
window = root.find_child(name="window1", role_name="frame")
button = window.find_child(name="button1", role_name="push button")
finally:
process.terminate()
self.assertIsNotNone(button)
def show_window1():
from gi.repository import Gtk
from gi.repository import GLib
window = Gtk.Window()
window.set_title("window1")
button = Gtk.Button(label="button1")
window.add(button)
button.show()
window.show()
Gtk.main()
if __name__ == '__main__':
globals()[sys.argv[1]]()