diff options
Diffstat (limited to 'uitest/uitest')
-rw-r--r-- | uitest/uitest/bisecting.py | 14 | ||||
-rw-r--r-- | uitest/uitest/framework.py | 67 | ||||
-rw-r--r-- | uitest/uitest/path.py | 31 | ||||
-rw-r--r-- | uitest/uitest/test.py | 260 | ||||
-rw-r--r-- | uitest/uitest/uihelper/__init__.py | 0 | ||||
-rw-r--r-- | uitest/uitest/uihelper/calc.py | 16 | ||||
-rw-r--r-- | uitest/uitest/uihelper/common.py | 58 | ||||
-rw-r--r-- | uitest/uitest/uihelper/keyboard.py | 13 | ||||
-rw-r--r-- | uitest/uitest/uihelper/testDialog.py | 32 |
9 files changed, 491 insertions, 0 deletions
diff --git a/uitest/uitest/bisecting.py b/uitest/uitest/bisecting.py new file mode 100644 index 000000000..938baaa2f --- /dev/null +++ b/uitest/uitest/bisecting.py @@ -0,0 +1,14 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +def requires(revision): + def decorator(f): + f.requires = revision + return f + return decorator + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/uitest/framework.py b/uitest/uitest/framework.py new file mode 100644 index 000000000..072ff5097 --- /dev/null +++ b/uitest/uitest/framework.py @@ -0,0 +1,67 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import signal +import unittest +import time + +from uitest.test import UITest + +from libreoffice.connection import PersistentConnection + +class UITestCase(unittest.TestCase): + + def __init__(self, test_name, opts): + unittest.TestCase.__init__(self, test_name) + self.opts = opts + + def setUp(self): + self.setSignalHandler() + self.connection = PersistentConnection(self.opts) + self.connection.setUp() + self.xContext = self.connection.getContext() + self.xUITest = self.xContext.ServiceManager.createInstanceWithContext( + "org.libreoffice.uitest.UITest", self.xContext) + + self.ui_test = UITest(self.xUITest, self.xContext) + self.startTime = time.time() + + def tearDown(self): + try: + t = time.time() - self.startTime + print("Execution time for %s: %.3f" % (self.id(), t)) + if self.xContext is not None: + try: + desktop = self.ui_test.get_desktop() + components = desktop.getComponents() + for component in components: + component.close(False) + except Exception as e: + print(e) + + self.connection.tearDown() + finally: + self.resetSignalHandler() + self.connection.kill() + + def signalHandler(self, signum, frame): + if self.connection: + self.connection.kill() + + def setSignalHandler(self): + signal.signal(signal.SIGABRT, self.signalHandler) + signal.signal(signal.SIGSEGV, self.signalHandler) + signal.signal(signal.SIGTERM, self.signalHandler) + signal.signal(signal.SIGILL, self.signalHandler) + + def resetSignalHandler(self): + signal.signal(signal.SIGABRT, signal.SIG_IGN) + signal.signal(signal.SIGSEGV, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) + signal.signal(signal.SIGILL, signal.SIG_IGN) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/uitest/path.py b/uitest/uitest/path.py new file mode 100644 index 000000000..5a3aeff22 --- /dev/null +++ b/uitest/uitest/path.py @@ -0,0 +1,31 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import os +from urllib.parse import urljoin +from urllib.request import pathname2url + +def get_src_dir_fallback(): + current_dir = os.path.dirname(os.path.realpath(__file__)) + return os.path.abspath(os.path.join(current_dir, "../../")) + +def path2url(path): + return urljoin('file:', pathname2url(os.path.normpath(path))) + +def get_workdir_url(): + workdir_path = os.environ.get('WORKDIR', os.path.join(get_src_dir_fallback(), 'workdir')) + return path2url(workdir_path) + +def get_srcdir_url(): + srcdir_path = os.environ.get('SRCDIR', get_src_dir_fallback()) + return path2url(srcdir_path) + +def get_instdir_url(): + instdir_path = os.environ.get('INSTDIR', os.path.join(get_src_dir_fallback(), 'instdir')) + return path2url(instdir_path) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/uitest/test.py b/uitest/uitest/test.py new file mode 100644 index 000000000..5ed20add7 --- /dev/null +++ b/uitest/uitest/test.py @@ -0,0 +1,260 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import time +import threading +import os.path +from contextlib import contextmanager +from uitest.uihelper.common import get_state_as_dict + +from com.sun.star.uno import RuntimeException + +from libreoffice.uno.eventlistener import EventListener + +DEFAULT_SLEEP = 0.1 + +class UITest(object): + + def __init__(self, xUITest, xContext): + self._xUITest = xUITest + self._xContext = xContext + self._desktop = None + + def get_desktop(self): + if self._desktop: + return self._desktop + + self._desktop = self._xContext.ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", self._xContext) + return self._desktop + + def get_frames(self): + desktop = self.get_desktop() + frames = desktop.getFrames() + return frames + + def get_component(self): + desktop = self.get_desktop() + components = desktop.getComponents() + for component in components: + if component is not None: + return component + + def wait_for_top_focus_window(self, id): + while True: + win = self._xUITest.getTopFocusWindow() + if get_state_as_dict(win)['ID'] == id: + return win + time.sleep(DEFAULT_SLEEP) + + def wait_until_child_is_available(self, childName): + while True: + xDialog = self._xUITest.getTopFocusWindow() + if childName in xDialog.getChildren(): + return xDialog.getChild(childName) + else: + time.sleep(DEFAULT_SLEEP) + + def wait_until_property_is_updated(self, element, propertyName, value): + while True: + if get_state_as_dict(element)[propertyName] == value: + return + else: + time.sleep(DEFAULT_SLEEP) + + def wait_until_file_is_available(self, fileName): + while True: + if os.path.isfile(fileName): + return + else: + time.sleep(DEFAULT_SLEEP) + + @contextmanager + def wait_until_component_loaded(self): + with EventListener(self._xContext, "OnLoad") as event: + yield + while True: + if event.executed: + frames = self.get_frames() + if len(frames) == 1: + self.get_desktop().setActiveFrame(frames[0]) + time.sleep(DEFAULT_SLEEP) + return + time.sleep(DEFAULT_SLEEP) + + def load_component_from_url(self, url, eventName="OnLoad"): + with EventListener(self._xContext, eventName) as event: + component = self.get_desktop().loadComponentFromURL(url, "_default", 0, tuple()) + while True: + if event.executed: + frames = self.get_frames() + #activate the newest frame + self.get_desktop().setActiveFrame(frames[-1]) + return component + time.sleep(DEFAULT_SLEEP) + + # Calls UITest.close_doc at exit + @contextmanager + def load_file(self, url): + try: + yield self.load_component_from_url(url) + finally: + self.close_doc() + + # Calls UITest.close_doc at exit + @contextmanager + def load_empty_file(self, app): + try: + yield self.load_component_from_url("private:factory/s" + app, "OnNew") + finally: + self.close_doc() + + # Calls UITest.close_dialog_through_button at exit + @contextmanager + def execute_dialog_through_command(self, command, printNames=False, close_button = "ok", eventName = "DialogExecute"): + with EventListener(self._xContext, eventName, printNames=printNames) as event: + if not self._xUITest.executeDialog(command): + raise Exception("Dialog not executed for: " + command) + while True: + if event.executed: + xDialog = self._xUITest.getTopFocusWindow() + try: + yield xDialog + except: + if not close_button: + if 'cancel' in xDialog.getChildren(): + self.close_dialog_through_button(xDialog.getChild("cancel")) + raise + finally: + if close_button: + self.close_dialog_through_button(xDialog.getChild(close_button)) + return + time.sleep(DEFAULT_SLEEP) + + @contextmanager + def execute_modeless_dialog_through_command(self, command, printNames=False, close_button = "ok"): + with self.execute_dialog_through_command(command, printNames, close_button, "ModelessDialogVisible") as xDialog: + yield xDialog + + # Calls UITest.close_dialog_through_button at exit + @contextmanager + def execute_dialog_through_action(self, ui_object, action, parameters = None, event_name = "DialogExecute", close_button = "ok"): + if parameters is None: + parameters = tuple() + + with EventListener(self._xContext, event_name) as event: + ui_object.executeAction(action, parameters) + while True: + if event.executed: + xDialog = self._xUITest.getTopFocusWindow() + try: + yield xDialog + finally: + if close_button: + self.close_dialog_through_button(xDialog.getChild(close_button)) + return + time.sleep(DEFAULT_SLEEP) + + def _handle_crash_reporter(self): + xCrashReportDlg = self._xUITest.getTopFocusWindow() + state = get_state_as_dict(xCrashReportDlg) + print(state) + if state['ID'] != "CrashReportDialog": + return False + print("found a crash reporter") + xCancelBtn = xCrashReportDlg.getChild("btn_cancel") + self.close_dialog_through_button(xCancelBtn) + return True + + # Calls UITest.close_doc at exit + @contextmanager + def create_doc_in_start_center(self, app): + xStartCenter = self._xUITest.getTopFocusWindow() + try: + xBtn = xStartCenter.getChild(app + "_all") + except RuntimeException: + if self._handle_crash_reporter(): + xStartCenter = self._xUITest.getTopFocusWindow() + xBtn = xStartCenter.getChild(app + "_all") + else: + raise + + with EventListener(self._xContext, "OnNew") as event: + xBtn.executeAction("CLICK", tuple()) + while True: + if event.executed: + frames = self.get_frames() + self.get_desktop().setActiveFrame(frames[0]) + component = self.get_component() + try: + yield component + finally: + self.close_doc() + return + time.sleep(DEFAULT_SLEEP) + + def close_dialog_through_button(self, button): + with EventListener(self._xContext, "DialogClosed" ) as event: + button.executeAction("CLICK", tuple()) + while True: + if event.executed: + time.sleep(DEFAULT_SLEEP) + return + time.sleep(DEFAULT_SLEEP) + + def close_doc(self): + desktop = self.get_desktop() + active_frame = desktop.getActiveFrame() + if not active_frame: + print("close_doc: no active frame") + return + component = active_frame.getController().getModel() + if not component: + print("close_doc: active frame has no component") + return + component.dispose() + frames = desktop.getFrames() + if frames: + frames[0].activate() + + @contextmanager + def execute_blocking_action(self, action, args=(), close_button="ok", printNames=False): + """Executes an action which blocks while a dialog is shown. + + Click a button or perform some other action on the dialog when it + is shown. + + Args: + action(callable): Will be called to show a dialog, and is expected + to block while the dialog is shown. + close_button(str): The name of a button which will be clicked to close + the dialog. if it's empty, the dialog won't be closed from here. + This is useful when consecutive dialogs are open one after the other. + args(tuple, optional): The arguments to be passed to `action` + printNames: print all received event names + """ + + thread = threading.Thread(target=action, args=args) + with EventListener(self._xContext, ["DialogExecute", "ModelessDialogExecute", "ModelessDialogVisible"], printNames=printNames) as event: + thread.start() + while True: + if event.executed: + xDialog = self._xUITest.getTopFocusWindow() + try: + yield xDialog + except: + if not close_button: + if 'cancel' in xDialog.getChildren(): + self.close_dialog_through_button(xDialog.getChild("cancel")) + raise + finally: + if close_button: + self.close_dialog_through_button(xDialog.getChild(close_button)) + thread.join() + return + time.sleep(DEFAULT_SLEEP) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/uitest/uihelper/__init__.py b/uitest/uitest/uihelper/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/uitest/uitest/uihelper/__init__.py diff --git a/uitest/uitest/uihelper/calc.py b/uitest/uitest/uihelper/calc.py new file mode 100644 index 000000000..e34304c69 --- /dev/null +++ b/uitest/uitest/uihelper/calc.py @@ -0,0 +1,16 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +from libreoffice.uno.propertyvalue import mkPropertyValues +from uitest.uihelper.common import type_text + +def enter_text_to_cell(gridwin, cell, text): + gridwin.executeAction("SELECT", mkPropertyValues({"CELL": cell})) + type_text(gridwin, text) + gridwin.executeAction("TYPE", mkPropertyValues({"KEYCODE": "RETURN"})) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/uitest/uihelper/common.py b/uitest/uitest/uihelper/common.py new file mode 100644 index 000000000..ffdf90d48 --- /dev/null +++ b/uitest/uitest/uihelper/common.py @@ -0,0 +1,58 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +from libreoffice.uno.propertyvalue import convert_property_values_to_dict, mkPropertyValues +import org.libreoffice.unotest +import pathlib + +def get_state_as_dict(ui_object): + return convert_property_values_to_dict(ui_object.getState()) + +def type_text(ui_object, text): + ui_object.executeAction("TYPE", mkPropertyValues({"TEXT": text})) + +def select_pos(ui_object, pos): + assert isinstance(pos, str), "select_pos: POS must be of type str" + ui_object.executeAction("SELECT", mkPropertyValues({"POS": pos})) + +def select_by_text(ui_object, text): + ui_object.executeAction("SELECT", mkPropertyValues({"TEXT": text})) + +def select_text(ui_object, from_pos, to): + ui_object.executeAction("SELECT", mkPropertyValues({"FROM": from_pos, "TO": to})) + +def get_url_for_data_file(file_name): + return pathlib.Path(org.libreoffice.unotest.makeCopyFromTDOC(file_name)).as_uri() + +def change_measurement_unit(UITestCase, unit): + with UITestCase.ui_test.execute_dialog_through_command(".uno:OptionsTreeDialog") as xDialogOpt: + xPages = xDialogOpt.getChild("pages") + xAppEntry = xPages.getChild('3') + xAppEntry.executeAction("EXPAND", tuple()) + xGeneralEntry = xAppEntry.getChild('0') + xGeneralEntry.executeAction("SELECT", tuple()) + + # Calc + if 'unitlb' in xDialogOpt.getChildren(): + xUnit = xDialogOpt.getChild("unitlb") + + # Writer + elif 'metric' in xDialogOpt.getChildren(): + xUnit = xDialogOpt.getChild("metric") + + # Impress + elif 'units' in xDialogOpt.getChildren(): + xUnit = xDialogOpt.getChild("units") + + select_by_text(xUnit, unit) + + # tdf#137930: Check apply button doesn't reset the value + xApplyBtn = xDialogOpt.getChild("apply") + xApplyBtn.executeAction("CLICK", tuple()) + UITestCase.assertEqual(unit, get_state_as_dict(xUnit)['SelectEntryText']) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/uitest/uihelper/keyboard.py b/uitest/uitest/uihelper/keyboard.py new file mode 100644 index 000000000..f2be76de4 --- /dev/null +++ b/uitest/uitest/uihelper/keyboard.py @@ -0,0 +1,13 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +from libreoffice.uno.propertyvalue import mkPropertyValues + +def select_all(ui_object): + ui_object.executeAction("TYPE", mkPropertyValues({"KEYCODE":"CTRL+A"})) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/uitest/uihelper/testDialog.py b/uitest/uitest/uihelper/testDialog.py new file mode 100644 index 000000000..0e4436436 --- /dev/null +++ b/uitest/uitest/uihelper/testDialog.py @@ -0,0 +1,32 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +from uitest.framework import UITestCase +from uitest.uihelper.common import get_state_as_dict + +# opens the dialogs, closes it with the given close button +# and if there is an "OK" button open the dialog again and close it by using the OK button +# the test only checks if LibreOffice crashes by opening the dialog +def testDialog(UITestCase, app, dialog): + with UITestCase.ui_test.create_doc_in_start_center(app): + with UITestCase.ui_test.execute_dialog_through_command(dialog['command'], close_button=dialog['closeButton']) as xDialog: + if 'skipTestOK' in dialog and dialog['skipTestOK'] == True: + xOKBtn = None + else: + try: + xOKBtn = xDialog.getChild("ok") + if (get_state_as_dict(xOKBtn)["Enabled"] != "true"): + xOKBtn = None + except: + xOKBtn = None + + if (xOKBtn != None): + print("check also OK button") + with UITestCase.ui_test.execute_dialog_through_command(dialog['command']): + pass + +# vim: set shiftwidth=4 softtabstop=4 expandtab: |