summaryrefslogtreecommitdiffstats
path: root/uitest/uitest
diff options
context:
space:
mode:
Diffstat (limited to 'uitest/uitest')
-rw-r--r--uitest/uitest/bisecting.py14
-rw-r--r--uitest/uitest/framework.py67
-rw-r--r--uitest/uitest/path.py31
-rw-r--r--uitest/uitest/test.py260
-rw-r--r--uitest/uitest/uihelper/__init__.py0
-rw-r--r--uitest/uitest/uihelper/calc.py16
-rw-r--r--uitest/uitest/uihelper/common.py58
-rw-r--r--uitest/uitest/uihelper/keyboard.py13
-rw-r--r--uitest/uitest/uihelper/testDialog.py32
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: