diff --git a/configure.ac b/configure.ac index fefb8e80..b55db4d5 100644 --- a/configure.ac +++ b/configure.ac @@ -53,5 +53,6 @@ src/sugar3/event-controller/Makefile src/sugar3/presence/Makefile src/sugar3/datastore/Makefile src/sugar3/dispatch/Makefile +src/sugar3/test/Makefile po/Makefile.in ]) diff --git a/src/sugar3/Makefile.am b/src/sugar3/Makefile.am index ddea9c85..24ef9aa1 100644 --- a/src/sugar3/Makefile.am +++ b/src/sugar3/Makefile.am @@ -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 sugar_PYTHON = \ diff --git a/src/sugar3/test/Makefile.am b/src/sugar3/test/Makefile.am new file mode 100644 index 00000000..0748c137 --- /dev/null +++ b/src/sugar3/test/Makefile.am @@ -0,0 +1,4 @@ +sugardir = $(pythondir)/sugar3/test +sugar_PYTHON = \ + __init__.py \ + uitree.py diff --git a/src/sugar3/test/__init__.py b/src/sugar3/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/sugar3/test/uitree.py b/src/sugar3/test/uitree.py new file mode 100644 index 00000000..34611a44 --- /dev/null +++ b/src/sugar3/test/uitree.py @@ -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) diff --git a/tests/test_uitree.py b/tests/test_uitree.py new file mode 100644 index 00000000..d640e10d --- /dev/null +++ b/tests/test_uitree.py @@ -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]]()