diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /uitest | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'uitest')
64 files changed, 4131 insertions, 0 deletions
diff --git a/uitest/Makefile b/uitest/Makefile new file mode 100644 index 0000000000..0997e62848 --- /dev/null +++ b/uitest/Makefile @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# 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/. +# + +module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +include $(module_directory)/../solenv/gbuild/partial_build.mk + +# vim: set noet sw=4 ts=4: diff --git a/uitest/Module_uitest.mk b/uitest/Module_uitest.mk new file mode 100644 index 0000000000..0ea8a94cfe --- /dev/null +++ b/uitest/Module_uitest.mk @@ -0,0 +1,17 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# +# This file is part of the LibreOffice project. +# +# 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/. +# + +$(eval $(call gb_Module_Module,uitest)) + +$(eval $(call gb_Module_add_uicheck_targets,uitest,\ + UITest_impress_demo \ + UITest_demo_ui \ + UITest_math_demo \ +)) diff --git a/uitest/README.md b/uitest/README.md new file mode 100644 index 0000000000..8c872bed6f --- /dev/null +++ b/uitest/README.md @@ -0,0 +1,3 @@ +# UI Testing Framework + +The code for the UI testing framework and the UI tests. diff --git a/uitest/UITest_demo_ui.mk b/uitest/UITest_demo_ui.mk new file mode 100644 index 0000000000..bb67e21b8b --- /dev/null +++ b/uitest/UITest_demo_ui.mk @@ -0,0 +1,20 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# 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/. +# + +$(eval $(call gb_UITest_UITest,demo_ui)) + +$(eval $(call gb_UITest_add_modules,demo_ui,$(SRCDIR)/uitest,\ + demo_ui/ \ +)) + +$(eval $(call gb_UITest_set_defs,demo_ui, \ + TDOC="$(SRCDIR)/uitest/demo_ui/data" \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/uitest/UITest_impress_demo.mk b/uitest/UITest_impress_demo.mk new file mode 100644 index 0000000000..5b822fe69e --- /dev/null +++ b/uitest/UITest_impress_demo.mk @@ -0,0 +1,16 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# 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/. +# + +$(eval $(call gb_UITest_UITest,impress_demo)) + +$(eval $(call gb_UITest_add_modules,impress_demo,$(SRCDIR)/uitest,\ + impress_tests/ \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/uitest/UITest_math_demo.mk b/uitest/UITest_math_demo.mk new file mode 100644 index 0000000000..b66cb87f68 --- /dev/null +++ b/uitest/UITest_math_demo.mk @@ -0,0 +1,20 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# 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/. +# + +$(eval $(call gb_UITest_UITest,math_demo)) + +$(eval $(call gb_UITest_add_modules,math_demo,$(SRCDIR)/uitest,\ + math_tests/ \ +)) + +$(eval $(call gb_UITest_set_defs,math_demo, \ + TDOC="$(SRCDIR)/uitest/math_tests/data" \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/uitest/__init__.py b/uitest/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/uitest/__init__.py diff --git a/uitest/demo_ui/char_dialog.py b/uitest/demo_ui/char_dialog.py new file mode 100644 index 0000000000..9475f3b16e --- /dev/null +++ b/uitest/demo_ui/char_dialog.py @@ -0,0 +1,26 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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.framework import UITestCase + +class CharDialogText(UITestCase): + + def test_select_char(self): + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_dialog_through_command(".uno:InsertSymbol", close_button="cancel") as xCharDialog: + + xCharSet = xCharDialog.getChild("showcharset") + + xCharSet.executeAction("SELECT", mkPropertyValues({"COLUMN": "2", "ROW": "2"})) + + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/demo_ui/checkbox.py b/uitest/demo_ui/checkbox.py new file mode 100644 index 0000000000..7c83d71f0a --- /dev/null +++ b/uitest/demo_ui/checkbox.py @@ -0,0 +1,24 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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 + +class CheckBoxTest(UITestCase): + + def test_toggle_checkbox(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_dialog_through_command(".uno:FormatCellDialog") as xCellsDlg: + xNegativeNumRedCB = xCellsDlg.getChild("negnumred") + xNegativeNumRedCB.executeAction("CLICK",tuple()) + + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/demo_ui/combobox.py b/uitest/demo_ui/combobox.py new file mode 100644 index 0000000000..1ec4b30d6f --- /dev/null +++ b/uitest/demo_ui/combobox.py @@ -0,0 +1,26 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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 select_pos + +class ComboBoxTest(UITestCase): + + def test_select_entry_pos(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_modeless_dialog_through_command(".uno:AddName", close_button="cancel") as xAddNameDlg: + + scopeCB = xAddNameDlg.getChild("scope") + select_pos(scopeCB, "1") + + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/demo_ui/command_with_parameters.py b/uitest/demo_ui/command_with_parameters.py new file mode 100644 index 0000000000..91d3acec9c --- /dev/null +++ b/uitest/demo_ui/command_with_parameters.py @@ -0,0 +1,25 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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 type_text +from libreoffice.uno.propertyvalue import mkPropertyValues + +class CommandWithParametersTest(UITestCase): + + def test_text_color_change(self): + + with self.ui_test.create_doc_in_start_center("writer"): + + self.xUITest.executeCommandWithParameters(".uno:Color", + mkPropertyValues({"Color": 16776960})) + xWriterEdit = self.xUITest.getTopFocusWindow().getChild("writer_edit") + type_text(xWriterEdit, "LibreOffice") + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/demo_ui/data/test.ods b/uitest/demo_ui/data/test.ods Binary files differnew file mode 100644 index 0000000000..571291d265 --- /dev/null +++ b/uitest/demo_ui/data/test.ods diff --git a/uitest/demo_ui/data/test2.ods b/uitest/demo_ui/data/test2.ods Binary files differnew file mode 100644 index 0000000000..550115cb9a --- /dev/null +++ b/uitest/demo_ui/data/test2.ods diff --git a/uitest/demo_ui/edit.py b/uitest/demo_ui/edit.py new file mode 100644 index 0000000000..7a0fbb2b8a --- /dev/null +++ b/uitest/demo_ui/edit.py @@ -0,0 +1,47 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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.framework import UITestCase +from uitest.uihelper.common import type_text, get_state_as_dict, select_text + +class EditTest(UITestCase): + + def test_type_text(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_modeless_dialog_through_command(".uno:AddName", close_button="cancel") as xAddNameDlg: + + xEdit = xAddNameDlg.getChild("edit") + + type_text(xEdit, "simpleRangeName") + + + + def test_select_text(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_modeless_dialog_through_command(".uno:AddName", close_button="cancel") as xAddNameDlg: + + xEdit = xAddNameDlg.getChild("edit") + + type_text(xEdit, "simpleRangeName") + xEdit.executeAction("SELECT", mkPropertyValues({"FROM": "2", "TO": "9"})) + type_text(xEdit, "otherChars") + self.assertEqual("siotherCharsgeName", get_state_as_dict(xEdit)["Text"]) + + select_text(xEdit, from_pos="2", to="12") + self.assertEqual("otherChars", get_state_as_dict(xEdit)["SelectedText"]) + + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/demo_ui/gridwin.py b/uitest/demo_ui/gridwin.py new file mode 100644 index 0000000000..272cdffc10 --- /dev/null +++ b/uitest/demo_ui/gridwin.py @@ -0,0 +1,50 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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.framework import UITestCase + +class GridWinTest(UITestCase): + + def test_select_cell(self): + + with self.ui_test.create_doc_in_start_center("calc"): + xCalcDoc = self.xUITest.getTopFocusWindow() + xGridWindow = xCalcDoc.getChild("grid_window") + + selectProps = mkPropertyValues({"CELL": "B10"}) + xGridWindow.executeAction("SELECT", selectProps) + + + def test_select_range(self): + + with self.ui_test.create_doc_in_start_center("calc"): + xCalcDoc = self.xUITest.getTopFocusWindow() + xGridWindow = xCalcDoc.getChild("grid_window") + + selectProps = mkPropertyValues({"RANGE": "B10:C20"}) + xGridWindow.executeAction("SELECT", selectProps) + + + def test_extend_range(self): + + with self.ui_test.create_doc_in_start_center("calc"): + xTopWindow = self.xUITest.getTopFocusWindow() + + xGridWindow = xTopWindow.getChild("grid_window") + + selectProps = mkPropertyValues({"RANGE": "B10:C20"}) + xGridWindow.executeAction("SELECT", selectProps) + + select2Props = mkPropertyValues({"RANGE": "D3:F5", "EXTEND": "true"}) + xGridWindow.executeAction("SELECT", select2Props) + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/demo_ui/handle_multiple_files.py b/uitest/demo_ui/handle_multiple_files.py new file mode 100644 index 0000000000..a15b92deec --- /dev/null +++ b/uitest/demo_ui/handle_multiple_files.py @@ -0,0 +1,47 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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_url_for_data_file +import time + +class HandleFiles(UITestCase): + + def test_load_file(self): + + with self.ui_test.load_file(get_url_for_data_file("test.ods")): + + with self.ui_test.load_file(get_url_for_data_file("test2.ods")): + + frames = self.ui_test.get_frames() + self.assertEqual(len(frames), 2) + + frames = self.ui_test.get_frames() + self.assertEqual(len(frames), 1) + + # this is currently still necessary as otherwise + # the command is not forwarded to the correct frame + # TODO: provide an additional event that we can use + # and get rid of the sleep + time.sleep(1) + + def test_select_frame(self): + with self.ui_test.load_file(get_url_for_data_file("test.ods")): + + with self.ui_test.load_file(get_url_for_data_file("test2.ods")): + frames = self.ui_test.get_frames() + self.assertEqual(len(frames), 2) + frames[0].activate() + + frames = self.ui_test.get_frames() + self.assertEqual(len(frames), 1) + + self.assertTrue(frames[0].getTitle().startswith("test2.ods")) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/demo_ui/hierarchy.py b/uitest/demo_ui/hierarchy.py new file mode 100644 index 0000000000..a447de563e --- /dev/null +++ b/uitest/demo_ui/hierarchy.py @@ -0,0 +1,31 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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 + +import json + +class CheckBoxTest(UITestCase): + + def test_get_json(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_dialog_through_command(".uno:About", close_button="btnClose") as xAboutDlg: + + + json_string = xAboutDlg.getHierarchy() + print(json_string) + json_content = json.loads(json_string) + print(json_content) + print(json.dumps(json_content, indent=4)) + + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/demo_ui/listbox.py b/uitest/demo_ui/listbox.py new file mode 100644 index 0000000000..b59a7b0a0a --- /dev/null +++ b/uitest/demo_ui/listbox.py @@ -0,0 +1,30 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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 + +class ListBoxTest(UITestCase): + + def test_select_entry_pos(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_dialog_through_command(".uno:FormatCellDialog"): + pass + + + def test_select_entry_text(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_dialog_through_command(".uno:FormatCellDialog"): + pass + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/demo_ui/radiobutton.py b/uitest/demo_ui/radiobutton.py new file mode 100644 index 0000000000..268696e56e --- /dev/null +++ b/uitest/demo_ui/radiobutton.py @@ -0,0 +1,24 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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 + +class RadioButtonTest(UITestCase): + + def test_toggle_radiobutton(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_dialog_through_command(".uno:FormatCellDialog") as xCellsDlg: + xNegativeNumRedCB = xCellsDlg.getChild("negnumred") + xNegativeNumRedCB.executeAction("CLICK",tuple()) + + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/demo_ui/spinfield.py b/uitest/demo_ui/spinfield.py new file mode 100644 index 0000000000..6b6a3bce0f --- /dev/null +++ b/uitest/demo_ui/spinfield.py @@ -0,0 +1,63 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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, type_text + +class SpinFieldTest(UITestCase): + + def test_up(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_dialog_through_command(".uno:FormatCellDialog") as xCellsDlg: + + xDecimalPlaces = xCellsDlg.getChild("leadzerosed") + xDecimalPlaces.executeAction("UP", tuple()) + + decimal_places_state = get_state_as_dict(xDecimalPlaces) + assert(decimal_places_state["Text"] == "2") + + + + def test_down(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_dialog_through_command(".uno:FormatCellDialog") as xCellsDlg: + + xDecimalPlaces = xCellsDlg.getChild("leadzerosed") + xDecimalPlaces.executeAction("UP", tuple()) + xDecimalPlaces.executeAction("UP", tuple()) + + decimal_places_state = get_state_as_dict(xDecimalPlaces) + assert(decimal_places_state["Text"] == "3") + + xDecimalPlaces.executeAction("DOWN", tuple()) + + decimal_places_state = get_state_as_dict(xDecimalPlaces) + assert(decimal_places_state["Text"] == "2") + + + + def test_text(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_dialog_through_command(".uno:FormatCellDialog") as xCellsDlg: + + xDecimalPlaces = xCellsDlg.getChild("leadzerosed") + type_text(xDecimalPlaces, "4") + + decimal_places_state = get_state_as_dict(xDecimalPlaces) + assert(decimal_places_state["Text"] == "41") + + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/demo_ui/tabcontrol.py b/uitest/demo_ui/tabcontrol.py new file mode 100644 index 0000000000..5a1809c98a --- /dev/null +++ b/uitest/demo_ui/tabcontrol.py @@ -0,0 +1,36 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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.calc import enter_text_to_cell +from uitest.uihelper.common import select_pos + +from uitest.framework import UITestCase + +class TabControlTest(UITestCase): + + def test_select_pos(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + xCalcDoc = self.xUITest.getTopFocusWindow() + xGridWindow = xCalcDoc.getChild("grid_window") + enter_text_to_cell(xGridWindow, "B2", "=2+3+4") + xGridWindow.executeAction("SELECT", mkPropertyValues({"CELL": "B2"})) + + with self.ui_test.execute_modeless_dialog_through_command(".uno:FunctionDialog", close_button="cancel") as xFunctionDlg: + + + xTabs = xFunctionDlg.getChild("tabcontrol") + select_pos(xTabs, "1") + + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/demo_ui/tabdialog.py b/uitest/demo_ui/tabdialog.py new file mode 100644 index 0000000000..6ed5bcc069 --- /dev/null +++ b/uitest/demo_ui/tabdialog.py @@ -0,0 +1,30 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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 + +class TabDialogTest(UITestCase): + + def test_select_tab_page_pos(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_dialog_through_command(".uno:FormatCellDialog"): + pass + + + def test_select_tab_page_name(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + with self.ui_test.execute_dialog_through_command(".uno:FormatCellDialog"): + pass + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/demo_ui/treelist.py b/uitest/demo_ui/treelist.py new file mode 100644 index 0000000000..214fc4b08e --- /dev/null +++ b/uitest/demo_ui/treelist.py @@ -0,0 +1,44 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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.calc import enter_text_to_cell + +from uitest.uihelper.common import select_pos + +from uitest.framework import UITestCase + +class TreeListTest(UITestCase): + + def test_expand(self): + + with self.ui_test.create_doc_in_start_center("calc"): + + xCalcDoc = self.xUITest.getTopFocusWindow() + xGridWindow = xCalcDoc.getChild("grid_window") + enter_text_to_cell(xGridWindow, "B2", "=2+3+4") + xGridWindow.executeAction("SELECT", mkPropertyValues({"CELL": "B2"})) + + with self.ui_test.execute_modeless_dialog_through_command(".uno:FunctionDialog", close_button="cancel") as xFunctionDlg: + + + xTabs = xFunctionDlg.getChild("tabcontrol") + select_pos(xTabs, "1") + + xTreelist = xTabs.getChild("struct") + + xTreeEntry = xTreelist.getChild('0') + + xTreeEntry.executeAction("COLLAPSE", tuple()) + + xTreeEntry.executeAction("EXPAND", tuple()) + + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/impress_tests/backgrounds.py b/uitest/impress_tests/backgrounds.py new file mode 100644 index 0000000000..e427995bfa --- /dev/null +++ b/uitest/impress_tests/backgrounds.py @@ -0,0 +1,144 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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 select_pos +from com.sun.star.awt.GradientStyle import LINEAR +from com.sun.star.drawing.HatchStyle import SINGLE +from com.sun.star.drawing.BitmapMode import REPEAT +from com.sun.star.drawing.RectanglePoint import MIDDLE_MIDDLE + +class ImpressBackgrounds(UITestCase): + + def checkDefaultBackground(self, btn): + document = self.ui_test.get_component() + if btn == 'btnnone': + self.assertEqual(document.DrawPages.getByIndex(0).Background, None) + elif btn == 'btncolor': + self.assertEqual( + hex(document.DrawPages.getByIndex(0).Background.FillColor), '0x729fcf') + self.assertEqual( + hex(document.DrawPages.getByIndex(0).Background.FillColor), '0x729fcf') + elif btn == 'btngradient': + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillGradient.Style, LINEAR) + self.assertEqual( + hex(document.DrawPages.getByIndex(0).Background.FillGradient.StartColor), '0xdde8cb') + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillGradient.Angle, 300) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillGradient.Border, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillGradient.XOffset, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillGradient.YOffset, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillGradient.StartIntensity, 100) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillGradient.EndIntensity, 100) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillGradientName, 'Pastel Bouquet') + elif btn == 'btnhatch': + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillHatch.Style, SINGLE ) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillHatch.Color, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillHatch.Distance, 102) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillHatch.Angle, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillHatchName, 'Black 0 Degrees') + elif btn == 'btnbitmap': + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapMode, REPEAT) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapPositionOffsetX, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapPositionOffsetY, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapRectanglePoint, MIDDLE_MIDDLE) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapStretch, False) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapTile, True) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapOffsetX, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapOffsetY, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapLogicalSize, True) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapSizeX, 2540) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapSizeY, 2540) + self.assertEqual(document.DrawPages.getByIndex(0).Background.FillBitmapName, 'Painted White') + elif btn == 'btnpattern': + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapMode, REPEAT) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapPositionOffsetX, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapPositionOffsetY, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapRectanglePoint, MIDDLE_MIDDLE) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapStretch, True) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapTile, True) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapOffsetX, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapOffsetY, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapLogicalSize, True) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapSizeX, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapSizeY, 0) + self.assertEqual( + document.DrawPages.getByIndex(0).Background.FillBitmapName, '5 Percent') + + + def test_background_dialog(self): + + with self.ui_test.create_doc_in_start_center("impress"): + + xTemplateDlg = self.xUITest.getTopFocusWindow() + xCancelBtn = xTemplateDlg.getChild("close") + self.ui_test.close_dialog_through_button(xCancelBtn) + + buttons = ['btnbitmap', 'btncolor', 'btngradient', 'btnhatch', 'btnpattern'] + for index, button in enumerate(buttons): + with self.ui_test.execute_dialog_through_command(".uno:PageSetup") as xPageSetupDlg: + + tabcontrol = xPageSetupDlg.getChild("tabcontrol") + select_pos(tabcontrol, "1") + + xBtn = xPageSetupDlg.getChild(button) + xBtn.executeAction("CLICK", tuple()) + + # tdf#100024: Without the fix in place, this test would have crashed here + # changing the background to bitmap + + self.checkDefaultBackground(button) + + with self.ui_test.execute_dialog_through_command(".uno:PageSetup") as xPageSetupDlg: + + tabcontrol = xPageSetupDlg.getChild("tabcontrol") + select_pos(tabcontrol, "1") + + xBtn = xPageSetupDlg.getChild('btnnone') + xBtn.executeAction("CLICK", tuple()) + + + self.checkDefaultBackground('btnnone') + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/impress_tests/drawinglayer.py b/uitest/impress_tests/drawinglayer.py new file mode 100644 index 0000000000..ca9d84bb16 --- /dev/null +++ b/uitest/impress_tests/drawinglayer.py @@ -0,0 +1,133 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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.uihelper.common import get_state_as_dict +from libreoffice.uno.propertyvalue import mkPropertyValues +from uitest.uihelper.common import change_measurement_unit +from uitest.framework import UITestCase + +class ImpressDrawinglayerTest(UITestCase): + + def test_move_object(self): + with self.ui_test.create_doc_in_start_center("impress") as document: + + xTemplateDlg = self.xUITest.getTopFocusWindow() + xCancelBtn = xTemplateDlg.getChild("close") + self.ui_test.close_dialog_through_button(xCancelBtn) + + with change_measurement_unit(self, 'Centimeter'): + + xImpressDoc = self.xUITest.getTopFocusWindow() + + self.assertIsNone(document.CurrentSelection) + + xEditWin = xImpressDoc.getChild("impress_win") + xEditWin.executeAction("SELECT", mkPropertyValues({"OBJECT":"Unnamed Drawinglayer object 1"})) + self.assertEqual("com.sun.star.drawing.SvxShapeCollection", document.CurrentSelection.getImplementationName()) + + with self.ui_test.execute_dialog_through_command(".uno:Size") as xDialog: + self.assertEqual('25.2', get_state_as_dict(xDialog.getChild('MTR_FLD_WIDTH'))['Value']) + self.assertEqual('9.13', get_state_as_dict(xDialog.getChild('MTR_FLD_HEIGHT'))['Value']) + self.assertEqual('1.4', get_state_as_dict(xDialog.getChild('MTR_FLD_POS_X'))['Value']) + self.assertEqual('3.69', get_state_as_dict(xDialog.getChild('MTR_FLD_POS_Y'))['Value']) + self.assertEqual('0', get_state_as_dict(xDialog.getChild('NF_ANGLE'))['Value']) + + xDrawinglayerObject = xEditWin.getChild("Unnamed Drawinglayer object 1") + xDrawinglayerObject.executeAction("MOVE", mkPropertyValues({"X": "1000", "Y":"1000"})) + + with self.ui_test.execute_dialog_through_command(".uno:Size") as xDialog: + self.assertEqual('25.2', get_state_as_dict(xDialog.getChild('MTR_FLD_WIDTH'))['Value']) + self.assertEqual('9.13', get_state_as_dict(xDialog.getChild('MTR_FLD_HEIGHT'))['Value']) + self.assertEqual('2.4', get_state_as_dict(xDialog.getChild('MTR_FLD_POS_X'))['Value']) + self.assertEqual('4.69', get_state_as_dict(xDialog.getChild('MTR_FLD_POS_Y'))['Value']) + self.assertEqual('0', get_state_as_dict(xDialog.getChild('NF_ANGLE'))['Value']) + + self.assertEqual("com.sun.star.drawing.SvxShapeCollection", document.CurrentSelection.getImplementationName()) + xEditWin.executeAction("DESELECT", tuple()) + self.assertIsNone(document.CurrentSelection) + + def test_resize_object(self): + with self.ui_test.create_doc_in_start_center("impress") as document: + + xTemplateDlg = self.xUITest.getTopFocusWindow() + xCancelBtn = xTemplateDlg.getChild("close") + self.ui_test.close_dialog_through_button(xCancelBtn) + + with change_measurement_unit(self, 'Centimeter'): + + xImpressDoc = self.xUITest.getTopFocusWindow() + + self.assertIsNone(document.CurrentSelection) + + xEditWin = xImpressDoc.getChild("impress_win") + xEditWin.executeAction("SELECT", mkPropertyValues({"OBJECT":"Unnamed Drawinglayer object 1"})) + self.assertEqual("com.sun.star.drawing.SvxShapeCollection", document.CurrentSelection.getImplementationName()) + + with self.ui_test.execute_dialog_through_command(".uno:Size") as xDialog: + self.assertEqual('25.2', get_state_as_dict(xDialog.getChild('MTR_FLD_WIDTH'))['Value']) + self.assertEqual('9.13', get_state_as_dict(xDialog.getChild('MTR_FLD_HEIGHT'))['Value']) + self.assertEqual('1.4', get_state_as_dict(xDialog.getChild('MTR_FLD_POS_X'))['Value']) + self.assertEqual('3.69', get_state_as_dict(xDialog.getChild('MTR_FLD_POS_Y'))['Value']) + self.assertEqual('0', get_state_as_dict(xDialog.getChild('NF_ANGLE'))['Value']) + + xDrawinglayerObject = xEditWin.getChild("Unnamed Drawinglayer object 1") + xDrawinglayerObject.executeAction("RESIZE", mkPropertyValues({"X": "500", "Y":"4000", "FRAC_X": "0.5", "FRAC_Y": "0.5"})) + + xEditWin.executeAction("SELECT", mkPropertyValues({"OBJECT":"Unnamed Drawinglayer object 1"})) + self.assertEqual("com.sun.star.drawing.SvxShapeCollection", document.CurrentSelection.getImplementationName()) + + with self.ui_test.execute_dialog_through_command(".uno:Size") as xDialog: + self.assertEqual('12.6', get_state_as_dict(xDialog.getChild('MTR_FLD_WIDTH'))['Value']) + self.assertEqual('4.57', get_state_as_dict(xDialog.getChild('MTR_FLD_HEIGHT'))['Value']) + self.assertEqual('0.95', get_state_as_dict(xDialog.getChild('MTR_FLD_POS_X'))['Value']) + self.assertEqual('3.84', get_state_as_dict(xDialog.getChild('MTR_FLD_POS_Y'))['Value']) + self.assertEqual('0', get_state_as_dict(xDialog.getChild('NF_ANGLE'))['Value']) + + self.assertEqual("com.sun.star.drawing.SvxShapeCollection", document.CurrentSelection.getImplementationName()) + xEditWin.executeAction("DESELECT", tuple()) + self.assertIsNone(document.CurrentSelection) + + def test_rotate_object(self): + with self.ui_test.create_doc_in_start_center("impress") as document: + + xTemplateDlg = self.xUITest.getTopFocusWindow() + xCancelBtn = xTemplateDlg.getChild("close") + self.ui_test.close_dialog_through_button(xCancelBtn) + + with change_measurement_unit(self, 'Centimeter'): + xImpressDoc = self.xUITest.getTopFocusWindow() + + self.assertIsNone(document.CurrentSelection) + + xEditWin = xImpressDoc.getChild("impress_win") + xEditWin.executeAction("SELECT", mkPropertyValues({"OBJECT":"Unnamed Drawinglayer object 1"})) + self.assertEqual("com.sun.star.drawing.SvxShapeCollection", document.CurrentSelection.getImplementationName()) + + with self.ui_test.execute_dialog_through_command(".uno:Size") as xDialog: + self.assertEqual('25.2', get_state_as_dict(xDialog.getChild('MTR_FLD_WIDTH'))['Value']) + self.assertEqual('9.13', get_state_as_dict(xDialog.getChild('MTR_FLD_HEIGHT'))['Value']) + self.assertEqual('1.4', get_state_as_dict(xDialog.getChild('MTR_FLD_POS_X'))['Value']) + self.assertEqual('3.69', get_state_as_dict(xDialog.getChild('MTR_FLD_POS_Y'))['Value']) + self.assertEqual('0', get_state_as_dict(xDialog.getChild('NF_ANGLE'))['Value']) + + xDrawinglayerObject = xEditWin.getChild("Unnamed Drawinglayer object 1") + xDrawinglayerObject.executeAction("ROTATE", mkPropertyValues({"X": "500", "Y":"4000", "ANGLE": "3000"})) + + xEditWin.executeAction("SELECT", mkPropertyValues({"OBJECT":"Unnamed Drawinglayer object 1"})) + self.assertEqual("com.sun.star.drawing.SvxShapeCollection", document.CurrentSelection.getImplementationName()) + + with self.ui_test.execute_dialog_through_command(".uno:Size") as xDialog: + self.assertEqual('30', get_state_as_dict(xDialog.getChild('NF_ANGLE'))['Value']) + + self.assertEqual("com.sun.star.drawing.SvxShapeCollection", document.CurrentSelection.getImplementationName()) + xEditWin.executeAction("DESELECT", tuple()) + self.assertIsNone(document.CurrentSelection) + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/impress_tests/layouts.py b/uitest/impress_tests/layouts.py new file mode 100644 index 0000000000..22c75e504f --- /dev/null +++ b/uitest/impress_tests/layouts.py @@ -0,0 +1,45 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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 libreoffice.uno.propertyvalue import mkPropertyValues + +class ImpressLayouts(UITestCase): + + def test_impress_layouts(self): + + with self.ui_test.create_doc_in_start_center("impress"): + + xTemplateDlg = self.xUITest.getTopFocusWindow() + xCancelBtn = xTemplateDlg.getChild("close") + self.ui_test.close_dialog_through_button(xCancelBtn) + + layouts= (".uno:AssignLayout?WhatLayout:long=20", ".uno:AssignLayout?WhatLayout:long=19", + ".uno:AssignLayout?WhatLayout:long=0", ".uno:AssignLayout?WhatLayout:long=1", + ".uno:AssignLayout?WhatLayout:long=32", ".uno:AssignLayout?WhatLayout:long=3", + ".uno:AssignLayout?WhatLayout:long=12", ".uno:AssignLayout?WhatLayout:long=15", + ".uno:AssignLayout?WhatLayout:long=14", ".uno:AssignLayout?WhatLayout:long=16", + ".uno:AssignLayout?WhatLayout:long=18", ".uno:AssignLayout?WhatLayout:long=34", + ".uno:AssignLayout?WhatLayout:long=28", ".uno:AssignLayout?WhatLayout:long=27", + ".uno:AssignLayout?WhatLayout:long=29", ".uno:AssignLayout?WhatLayout:long=30") + + for i in layouts: + self.xUITest.executeCommand(i) + + xImpressDoc = self.xUITest.getTopFocusWindow() + + xEditWin = xImpressDoc.getChild("impress_win") + + # There's a layout with 7 objects + for j in range(0,6): + xEditWin.executeAction("SELECT", mkPropertyValues({"OBJECT":"Unnamed Drawinglayer object " + str(j)})) + xEditWin.executeAction("DESELECT", tuple()) + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/impress_tests/start.py b/uitest/impress_tests/start.py new file mode 100644 index 0000000000..cbf38b88b7 --- /dev/null +++ b/uitest/impress_tests/start.py @@ -0,0 +1,54 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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.uihelper.common import get_state_as_dict +from libreoffice.uno.propertyvalue import mkPropertyValues +from uitest.framework import UITestCase + +class SimpleImpressTest(UITestCase): + def test_start_impress(self): + + with self.ui_test.create_doc_in_start_center("impress"): + + xTemplateDlg = self.xUITest.getTopFocusWindow() + xCancelBtn = xTemplateDlg.getChild("close") + self.ui_test.close_dialog_through_button(xCancelBtn) + + xImpressDoc = self.xUITest.getTopFocusWindow() + + xEditWin = xImpressDoc.getChild("impress_win") + xEditWin.executeAction("SET", mkPropertyValues({"ZOOM": "200"})) + + self.assertEqual(get_state_as_dict(xEditWin)["Zoom"], "200") + + + def test_select_page(self): + + with self.ui_test.create_doc_in_start_center("impress"): + + xTemplateDlg = self.xUITest.getTopFocusWindow() + xCancelBtn = xTemplateDlg.getChild("close") + self.ui_test.close_dialog_through_button(xCancelBtn) + + xImpressDoc = self.xUITest.getTopFocusWindow() + + xEditWin = xImpressDoc.getChild("impress_win") + + self.assertEqual(get_state_as_dict(xEditWin)["CurrentSlide"], "1") + + self.xUITest.executeCommand(".uno:InsertPage") + + self.assertEqual(get_state_as_dict(xEditWin)["CurrentSlide"], "2") + + xEditWin.executeAction("GOTO", mkPropertyValues({"PAGE": "1"})) + + self.assertEqual(get_state_as_dict(xEditWin)["CurrentSlide"], "1") + + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/libreoffice/calc/conditional_format.py b/uitest/libreoffice/calc/conditional_format.py new file mode 100644 index 0000000000..40e7669438 --- /dev/null +++ b/uitest/libreoffice/calc/conditional_format.py @@ -0,0 +1,17 @@ +# -*- 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/. +# + +# this file provides methods to interact with the new conditional format API + +def get_conditional_format_from_sheet(sheet): + """ Returns a conditional format object belonging to a sheet + + Keyword arguments: + sheet -- a XSheet object""" + return sheet.getPropertyValue("ConditionalFormats") + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/libreoffice/calc/csv_dialog.py b/uitest/libreoffice/calc/csv_dialog.py new file mode 100644 index 0000000000..1313903e47 --- /dev/null +++ b/uitest/libreoffice/calc/csv_dialog.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/. +# + +from uitest.uihelper.common import get_state_as_dict, get_url_for_data_file +from libreoffice.uno.propertyvalue import mkPropertyValues +from contextlib import contextmanager + +@contextmanager +def load_csv_file(UITestCase, fileName, bUseDefaultOptions): + with UITestCase.ui_test.execute_dialog_through_command(".uno:Open", close_button="open") as xOpenDialog: + + xFileName = xOpenDialog.getChild("file_name") + xFileName.executeAction("TYPE", mkPropertyValues({"TEXT": get_url_for_data_file(fileName)})) + + xDialog = UITestCase.ui_test.wait_for_top_focus_window('TextImportCsvDialog') + + try: + if bUseDefaultOptions: + xSeparatedBy = xDialog.getChild("toseparatedby") + xSeparatedBy.executeAction("CLICK", tuple()) + + xTextDelimiter = xDialog.getChild("textdelimiter") + xTextDelimiter.executeAction("TYPE", mkPropertyValues({"KEYCODE":"CTRL+A"})) + xTextDelimiter.executeAction("TYPE", mkPropertyValues({"KEYCODE":"BACKSPACE"})) + xTextDelimiter.executeAction("TYPE", mkPropertyValues({"TEXT": "\""})) + + setToTrue = ['tab', 'comma', 'semicolon'] + for childName in setToTrue: + xChild = xDialog.getChild(childName) + if get_state_as_dict(xChild)['Selected'] == 'false': + xChild.executeAction("CLICK", tuple()) + UITestCase.assertEqual('true', get_state_as_dict(xChild)['Selected']) + + setToFalse = ['space', 'other', 'removespace', 'mergedelimiters', + 'evaluateformulas', 'quotedfieldastext', 'detectspecialnumbers'] + for childName in setToFalse: + xChild = xDialog.getChild(childName) + if get_state_as_dict(xChild)['Selected'] == 'true': + xChild.executeAction("CLICK", tuple()) + UITestCase.assertEqual('false', get_state_as_dict(xChild)['Selected']) + # tdf#154131 + if childName == 'detectspecialnumbers': + # if 'Detect special numbers' is false, 'Detect scientific numbers' can be modified + xDetectScientific = xDialog.getChild('detectscientificnumbers') + if get_state_as_dict(xDetectScientific)['Selected'] == 'false': + xDetectScientific.executeAction("CLICK", tuple()) + UITestCase.assertEqual('true', get_state_as_dict(xDetectScientific)['Selected']) + xDetectScientific.executeAction("CLICK", tuple()) + UITestCase.assertEqual('false', get_state_as_dict(xDetectScientific)['Selected']) + # if 'Detect special numbers' is true, 'Detect scientific numbers' is true and disabled + xChild.executeAction("CLICK", tuple()) + UITestCase.assertEqual('true', get_state_as_dict(xChild)['Selected']) + UITestCase.assertEqual('true', get_state_as_dict(xDetectScientific)['Selected']) + UITestCase.assertEqual('false', get_state_as_dict(xDetectScientific)['Enabled']) + xChild.executeAction("CLICK", tuple()) + + UITestCase.assertEqual('1', get_state_as_dict(xDialog.getChild("fromrow"))['Text']) + + yield xDialog + finally: + xOK = xDialog.getChild('ok') + with UITestCase.ui_test.wait_until_component_loaded(): + UITestCase.ui_test.close_dialog_through_button(xOK) diff --git a/uitest/libreoffice/calc/document.py b/uitest/libreoffice/calc/document.py new file mode 100644 index 0000000000..b837bda725 --- /dev/null +++ b/uitest/libreoffice/calc/document.py @@ -0,0 +1,63 @@ +# -*- 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 get_sheet_from_doc(document, index=None, name=None): + """ Returns a sheet object for a Spreadsheet document + + Keyword arguments: + index -- the 0-based index of the sheet (may not be used together with name) + name -- the name of the sheet (may not be used together with index) + """ + return document.getSheets().getByIndex(index) + +def get_cell_by_position(document, tab, column, row): + """ Get the cell object through its position in a document + + Keyword arguments: + document -- The document that should be used + tab -- The 0-based sheet number + column -- The 0-based column number + row -- The 0-based row number + """ + sheet = get_sheet_from_doc(document, tab) + return sheet[row,column] + +def get_column(document, column, tab = 0): + """ Get the column object through the column index + + Keyword arguments: + document -- The document that should be used + tab -- The 0-based sheet number + column -- The 0-based column number + """ + sheet = get_sheet_from_doc(document, tab) + return sheet.getColumns().getByIndex(column) + +def get_row(document, row, tab = 0): + """ Get the row object through the row index + + Keyword arguments: + document -- The document that should be used + tab -- The 0-based sheet number + column -- The 0-based row number + """ + sheet = get_sheet_from_doc(document, tab) + return sheet.getRows().getByIndex(row) + +def is_row_hidden(document, row, tab = 0): + """ Check whether a row object is hidden + + Keyword arguments: + document -- The document that should be used + tab -- The 0-based sheet number + column -- The 0-based row number + """ + xRow = get_row(document, row, tab) + bVisible = xRow.getPropertyValue("IsVisible") + return not bVisible + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/libreoffice/calc/paste_special.py b/uitest/libreoffice/calc/paste_special.py new file mode 100644 index 0000000000..6f153ad14c --- /dev/null +++ b/uitest/libreoffice/calc/paste_special.py @@ -0,0 +1,30 @@ +# -*- 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.uihelper.common import get_state_as_dict + +def reset_default_values(UITestCase, xDialog): + setToFalse = ['paste_all', 'formats', 'comments', 'objects', 'formulas', + 'link', 'transpose', 'skip_empty'] + for childName in setToFalse: + xChild = xDialog.getChild(childName) + if get_state_as_dict(xChild)['Selected'] == 'true': + xChild.executeAction("CLICK", tuple()) + UITestCase.assertEqual('false', get_state_as_dict(xChild)['Selected']) + + setToTrue = ['text', 'numbers', 'datetime', 'cbImmediately'] + for childName in setToTrue: + xChild = xDialog.getChild(childName) + if get_state_as_dict(xChild)['Selected'] == 'false': + xChild.executeAction("CLICK", tuple()) + UITestCase.assertEqual('true', get_state_as_dict(xChild)['Selected']) + + setToCheck = ['none', 'no_shift'] + for childName in setToCheck: + xChild = xDialog.getChild(childName) + xChild.executeAction("CLICK", tuple()) + UITestCase.assertEqual('true', get_state_as_dict(xChild)['Checked']) diff --git a/uitest/libreoffice/connection.py b/uitest/libreoffice/connection.py new file mode 100644 index 0000000000..4f901130f2 --- /dev/null +++ b/uitest/libreoffice/connection.py @@ -0,0 +1,201 @@ +# -*- 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 subprocess +import time +import traceback +import uuid +import os +import platform +import signal + +try: + import pyuno + import uno +except ImportError: + print("pyuno not found: try to set PYTHONPATH and URE_BOOTSTRAP variables", flush=True) + print("PYTHONPATH=/installation/opt/program", flush=True) + print("URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc", flush=True) + raise + +def signal_handler(signal_num, frame): + signal_name = signal.Signals(signal_num).name + print(f'Signal handler called with signal {signal_name} ({signal_num})', flush=True) + +class OfficeConnection: + def __init__(self, args): + self.args = args + self.soffice = None + self.xContext = None + + def setUp(self): + """ Create a new connection to a LibreOffice process + + If the connection method is path the instance will be created as a + new subprocess. If the connection method is connect the instance tries + to connect to an existing instance with the specified socket string """ + if platform.system() != "Windows": + signal.signal(signal.SIGCHLD, signal_handler) + signal.signal(signal.SIGPIPE, signal_handler) + + (method, sep, rest) = self.args["--soffice"].partition(":") + if sep != ":": + raise Exception("soffice parameter does not specify method") + if method == "path": + socket = "pipe,name=pytest" + str(uuid.uuid1()) + try: + userdir = self.args["--userdir"] + except KeyError: + raise Exception("'path' method requires --userdir") + if not(userdir.startswith("file://")): + raise Exception("--userdir must be file URL") + self.soffice = self.bootstrap(rest, userdir, socket) + elif method == "connect": + socket = rest + else: + raise Exception("unsupported connection method: " + method) + + # connect to the soffice instance + success = False + try: + self.xContext = self.connect(socket) + success = True + finally: + if not success and self.soffice: + self.soffice.terminate() + self.soffice.wait() + self.soffice = None + + def bootstrap(self, soffice, userdir, socket): + """ Creates a new LibreOffice process + + @param soffice Path to the soffice installation + @param userdir Directory of the user profile, only one process per user + profile is possible + @param socket The socket string used for the PyUNO connection """ + + argv = [soffice, "--accept=" + socket + ";urp", + "-env:UserInstallation=" + userdir, + "--quickstart=no", "--nofirststartwizard", + "--norestore", "--nologo"] + if "--valgrind" in self.args: + argv.append("--valgrind") + + if "--gdb" in self.args: + argv.insert(0, "gdb") + argv.insert(1, "-ex") + argv.insert(2, "run") + argv.insert(3, "--args") + argv[4] = argv[4].replace("soffice", "soffice.bin") + + env = None + environ = dict(os.environ) + if 'LIBO_LANG' in environ: + env = environ + env['LC_ALL'] = environ['LIBO_LANG'] + + return subprocess.Popen(argv, env=env) + + def connect(self, socket): + """ Tries to connect to the LibreOffice instance through the specified socket""" + xLocalContext = uno.getComponentContext() + xUnoResolver = xLocalContext.ServiceManager.createInstanceWithContext( + "com.sun.star.bridge.UnoUrlResolver", xLocalContext) + url = "uno:" + socket + ";urp;StarOffice.ComponentContext" + print("OfficeConnection: connecting to: " + url, flush=True) + while True: + if self.soffice and self.soffice.poll() is not None: + raise Exception("soffice has stopped.") + + try: + xContext = xUnoResolver.resolve(url) + return xContext + except pyuno.getClass("com.sun.star.connection.NoConnectException"): + print("NoConnectException: sleeping...", flush=True) + time.sleep(1) + + def tearDown(self): + """Terminate a LibreOffice instance created with the path connection method. + + Tries to terminate the soffice instance through the normal + XDesktop::terminate method and waits indefinitely for the subprocess + to terminate """ + + if self.soffice: + if self.xContext: + try: + print("tearDown: calling terminate()...", flush=True) + xMgr = self.xContext.ServiceManager + xDesktop = xMgr.createInstanceWithContext( + "com.sun.star.frame.Desktop", self.xContext) + xDesktop.terminate() + print("...done", flush=True) + except pyuno.getClass("com.sun.star.beans.UnknownPropertyException"): + print("caught while TearDown:\n", traceback.format_exc(), flush=True) + pass # ignore, also means disposed + except pyuno.getClass("com.sun.star.lang.DisposedException"): + print("caught while TearDown:\n", traceback.format_exc(), flush=True) + pass # ignore + else: + self.soffice.terminate() + + ret = self.soffice.wait() + self.xContext = None + self.soffice = None + if ret != 0: + raise Exception("Exit status indicates failure: " + str(ret)) + + @classmethod + def getHelpText(cls): + message = """ + --soffice=method:location + specify soffice instance to connect to + supported methods: 'path', 'connect' + --userdir=URL specify user installation directory for 'path' method + --valgrind pass --valgrind to soffice for 'path' method + + 'location' is a pathname, not a URL. 'userdir' is a URL. + """ + return message + + +class PersistentConnection: + def __init__(self, args): + self.args = args + self.connection = None + + def getContext(self): + """ Returns the XContext corresponding to the LibreOffice instance + + This is the starting point for any PyUNO access to the LibreOffice + instance.""" + return self.connection.xContext + + def setUp(self): + # don't create two connections + if self.connection: + return + + conn = OfficeConnection(self.args) + conn.setUp() + self.connection = conn + + def tearDown(self): + if self.connection: + try: + self.connection.tearDown() + finally: + self.connection = None + + def kill(self): + """ Kills the LibreOffice instance if it was created through the connection + + Only works with the connection method path""" + if self.connection and self.connection.soffice: + self.connection.soffice.kill() + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/libreoffice/linguistic/linguservice.py b/uitest/libreoffice/linguistic/linguservice.py new file mode 100644 index 0000000000..7ff9c1ac6c --- /dev/null +++ b/uitest/libreoffice/linguistic/linguservice.py @@ -0,0 +1,27 @@ +# -*- Mode: python; tab-width: 4; indent-tabs-mode: nil; c-basic-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 get_lingu_service_manager(xContext): + """ Returns the com.sun.star.linguistic2.LinguServiceManager + + Further information: https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1linguistic2_1_1LinguServiceManager.html + """ + xServiceManager = xContext.getServiceManager() + xLinguServiceManager = xServiceManager.createInstanceWithContext("com.sun.star.linguistic2.LinguServiceManager", xContext) + return xLinguServiceManager + + +def get_spellchecker(xContext): + """ Returns the com.sun.star.linguistic2.XSpellChecker through the + com.sun.star.linguistic2.LinguServiceManager + + Further information: https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1linguistic2_1_1SpellChecker.html""" + xLinguServiceManager = get_lingu_service_manager(xContext) + return xLinguServiceManager.getSpellChecker() + +# vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/uitest/libreoffice/uno/eventlistener.py b/uitest/libreoffice/uno/eventlistener.py new file mode 100644 index 0000000000..54076efe33 --- /dev/null +++ b/uitest/libreoffice/uno/eventlistener.py @@ -0,0 +1,55 @@ +# -*- 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/. +# + +try: + import unohelper + from com.sun.star.document import XDocumentEventListener +except ImportError: + print("pyuno not found: try to set PYTHONPATH and URE_BOOTSTRAP variables") + print("PYTHONPATH=/installation/opt/program") + print("URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc") + raise + +class EventListener(XDocumentEventListener,unohelper.Base): + + def __init__(self, xContext, eventNames, **kwargs): + self.xGEB = xContext.ServiceManager.createInstanceWithContext( + "com.sun.star.frame.GlobalEventBroadcaster", xContext) + self.xContext = xContext + self.executed = False + self.eventExecuted = [] + self.printEvents = kwargs.get('printNames', False) + if isinstance(eventNames, str): + self.eventNames = [eventNames] + elif isinstance(eventNames, list): + self.eventNames = eventNames + + def __enter__(self): + self.xGEB.addDocumentEventListener(self) + return self + + def __exit__(self, type, value, traceback): + self.xGEB.removeDocumentEventListener(self) + + def documentEventOccured(self, event): + if self.printEvents is True: + print(event.EventName) + + if event.EventName in self.eventNames: + self.executed = True + self.eventExecuted.append(event.EventName) + else: + print(self.eventNames) + print(event.EventName) + + def hasExecuted(self, eventName): + return eventName in self.eventExecuted + + def disposing(event): + pass + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/libreoffice/uno/propertyvalue.py b/uitest/libreoffice/uno/propertyvalue.py new file mode 100644 index 0000000000..ce8622113a --- /dev/null +++ b/uitest/libreoffice/uno/propertyvalue.py @@ -0,0 +1,38 @@ +# -*- 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/. +# + +try: + import uno +except ImportError: + print("pyuno not found: try to set PYTHONPATH and URE_BOOTSTRAP variables") + print("PYTHONPATH=/installation/opt/program") + print("URE_BOOTSTRAP=file:///installation/opt/program/fundamentalrc") + raise + +def mkPropertyValue(name, value): + """ Create a UNO PropertyValue from two input values. + """ + return uno.createUnoStruct("com.sun.star.beans.PropertyValue", + name, 0, value, 0) + +def mkPropertyValues(vals): + """ Create UNO property values from a map. + """ + return tuple([mkPropertyValue(name, value) for (name, value) in vals.items()]) + +def convert_property_values_to_dict(propMap): + """ Create a dictionary from a sequence of property values + """ + ret = {} + for entry in propMap: + name = entry.Name + val = entry.Value + ret[name] = val + + return ret + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/loginterpreter.py b/uitest/loginterpreter.py new file mode 100755 index 0000000000..e84d9a0ed7 --- /dev/null +++ b/uitest/loginterpreter.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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 +import sys +import argparse + +def parse_line(line): + """ + This function parses a line from log file + and returns the parsed values as a python dictionary + """ + if (line == ""): + return + dict = {} + if "{" in line: + start_index_of_parameters = line.find("{") + end_index_of_parameters = line.find("}") + 1 + parameters = line[start_index_of_parameters:end_index_of_parameters] + if parameters != "": + dict["parameters"] = parameters + line = line[:start_index_of_parameters-1] + word_list = line.split() + dict["keyword"] = word_list[0] + + for index in range(1,len(word_list)): + key, val = word_list[index].split(":",1) + dict[key] = val + return dict + +def parse_args(): + """ + This function parses the command-line arguments + to get the input and output file details + """ + parser = argparse.ArgumentParser(description = "Generate a UI test file from log") + parser.add_argument("input_address", type = str, help = "The log file address") + parser.add_argument("output_address", type = str, help = "The test file address") + parser.add_argument("-d", "--document", metavar = "", help = "Address of the document to be opened") + args = parser.parse_args() + return args + +def get_log_file(input_address): + try: + with open(input_address) as f: + content = f.readlines() + except IOError as err: + print("IO error: {0}".format(err)) + print("Use " + os.path.basename(sys.argv[0]) + " -h to get usage instructions") + sys.exit(1) + + content = [x.strip() for x in content if not x.startswith("Action on element")] + return content + +def initiate_test_generation(address): + try: + f = open(address,"w") + except IOError as err: + print("IO error: {0}".format(err)) + print("Use " + os.path.basename(sys.argv[0]) + " -h to get usage instructions") + sys.exit(1) + initial_text = \ + "# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-\n\n" + \ + "from uitest.framework import UITestCase\n" + \ + "from libreoffice.uno.propertyvalue import mkPropertyValues\n" + \ + "import importlib\n\n" + \ + "class TestClass(UITestCase):\n" + \ + " def test_function(self):\n" + f.write(initial_text) + return f + +def get_coupling_type(line1, line2): + """ + This function checks if two consecutive lines of log file + refer to the same event + """ + action_dict1 = parse_line(line1) + action_dict2 = parse_line(line2) + + if action_dict1["keyword"] == "CommandSent" and \ + action_dict2["keyword"] == "ModalDialogExecuted": + return "COMMAND_MODAL_COUPLE" + + elif action_dict1["keyword"] == "CommandSent" and \ + action_dict2["keyword"] == "ModelessDialogConstructed": + return "COMMAND_MODELESS_COUPLE" + + elif action_dict1["keyword"] == "ButtonUIObject" and \ + action_dict2["keyword"] == "DialogClosed": + return "BUTTON_DIALOGCLOSE_COUPLE" + + elif "parameters" in action_dict1 and \ + "KEYCODE" in action_dict1["parameters"] and \ + action_dict2["keyword"] == "CommandSent": + return "REDUNDANT_COUPLE" + + return "NOT_A_COUPLE" + +def check_app_starting_action(action_dict): + app_starter_button_ids = \ + set(["draw_all", "impress_all", "calc_all" , "writer_all", "database_all", "math_all"]) + + if action_dict["keyword"] == "ButtonUIObject" and action_dict["Action"] == "CLICK" and \ + action_dict["Id"] in app_starter_button_ids: + return True + return False + +def get_test_line_from_one_log_line(log_line): + action_dict = parse_line(log_line) + test_line = " " + if action_dict["keyword"].endswith("UIObject"): + parent = action_dict["Parent"] + if (check_app_starting_action(action_dict)): + test_line +=\ + "MainDoc = self.ui_test.create_doc_in_start_center(\"" + \ + action_dict["Id"][:-4] +"\")\n MainWindow = " + \ + "self.xUITest.getTopFocusWindow()\n" + return test_line + else: + if (parent == ""): + parent = "MainWindow" + test_line += \ + action_dict["Id"] + " = " + parent + ".getChild(\"" + \ + action_dict["Id"] + "\")\n " + \ + action_dict["Id"] + ".executeAction(\"" + \ + action_dict["Action"] + "\"" + if "parameters" in action_dict: + test_line += ", mkPropertyValues(" + \ + action_dict["parameters"] + "))\n" + else: + test_line += ",tuple())\n" + return test_line + elif action_dict["keyword"] == "CommandSent": + if "parameters" not in action_dict: + test_line += "self.xUITest.executeCommand(\"" + \ + action_dict["Name"] + "\")\n" + return test_line + else: + test_line += "self.xUITest.executeCommandWithParameters(\"" + \ + action_dict["Name"] + "\", mkPropertyValues(" + action_dict["parameters"] + \ + "))\n" + return test_line + elif action_dict["keyword"] == "ModalDialogExecuted" or \ + action_dict["keyword"] == "ModelessDialogConstructed": + test_line += action_dict["Id"] + " = " + "self.xUITest.getTopFocusWindow()\n" + return test_line + + return "" + +def get_test_line_from_two_log_lines(log_line1,log_line2): + coupling_type = get_coupling_type(log_line1, log_line2) + action_dict1 = parse_line(log_line1) + action_dict2 = parse_line(log_line2) + test_line = " " + if coupling_type == "COMMAND_MODAL_COUPLE": + test_line += \ + "self.ui_test.execute_dialog_through_command(\"" + \ + action_dict1["Name"] + "\")\n " + \ + action_dict2["Id"] + " = self.xUITest.getTopFocusWindow()\n" + elif coupling_type == "COMMAND_MODELESS_COUPLE": + test_line += \ + "self.ui_test.execute_modeless_dialog_through_command(\"" + \ + action_dict1["Name"] + "\")\n " + \ + action_dict2["Id"] + " = self.xUITest.getTopFocusWindow()\n" + elif coupling_type == "BUTTON_DIALOGCLOSE_COUPLE": + test_line += \ + action_dict1["Id"] + " = " + action_dict1["Parent"] + ".getChild(\"" + \ + action_dict1["Id"] + "\")\n self.ui_test.close_dialog_through_button(" + \ + action_dict1["Id"] + ")\n" + return test_line + +def main(): + args = parse_args() + log_lines = get_log_file(args.input_address) + output_stream = initiate_test_generation(args.output_address) + if args.document is not None: + output_line = " pathmodule = importlib.import_module(\"uitest.path\")\n" + \ + " doc_path = pathmodule.get_srcdir_url() + \"" + args.document + "\"\n" + \ + " MainDoc = self.ui_test.load_file(doc_path)\n" + \ + " MainWindow = self.xUITest.getTopFocusWindow()\n" + output_stream.write(output_line) + line_number = 0 + while line_number < len(log_lines): + if line_number == len(log_lines)-1 or \ + get_coupling_type(log_lines[line_number],log_lines[line_number + 1]) == "NOT_A_COUPLE": + test_line = get_test_line_from_one_log_line(log_lines[line_number]) + output_stream.write(test_line) + line_number += 1 + elif get_coupling_type(log_lines[line_number],log_lines[line_number + 1]) == "REDUNDANT_COUPLE": + line_number += 1 + else: + test_line = get_test_line_from_two_log_lines(log_lines[line_number],log_lines[line_number + 1]) + output_stream.write(test_line) + line_number += 2 + output_stream.write(" self.ui_test.close_doc()") + output_stream.write("\n\n# vim: set shiftwidth=4 softtabstop=4 expandtab:") + output_stream.close() + +if __name__ == '__main__': + main() + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/math_tests/data/tdf128610.fodt b/uitest/math_tests/data/tdf128610.fodt new file mode 100644 index 0000000000..cee4ed8e59 --- /dev/null +++ b/uitest/math_tests/data/tdf128610.fodt @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<office:document xmlns:officeooo="http://openoffice.org/2009/office" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rpt="http://openoffice.org/2005/report" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:xforms="http://www.w3.org/2002/xforms" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:body> + <office:text> + <text:sequence-decls> + <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/> + <text:sequence-decl text:display-outline-level="0" text:name="Table"/> + <text:sequence-decl text:display-outline-level="0" text:name="Text"/> + <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/> + <text:sequence-decl text:display-outline-level="0" text:name="Figure"/> + </text:sequence-decls> + <text:p text:style-name="Preformatted_20_Text"><text:bookmark text:name="comment_text_19"/><?xml version="1.0" encoding="UTF-8"?></text:p> + <text:p text:style-name="Preformatted_20_Text"><math xmlns="<text:a xlink:type="simple" xlink:href="http://www.w3.org/1998/Math/MathML" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">http://www.w3.org/1998/Math/MathML</text:a>" display="block"></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s/><semantics></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="2"/><mrow></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="3"/><msub></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="4"/><mi>f</mi></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="4"/><mi>c</mi></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="3"/></msub></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="3"/><mo stretchy="false">=</mo></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="3"/><mfrac></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="4"/><mn>1</mn></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="4"/><msub></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="5"/><mi>K</mi></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="5"/><mi>m</mi></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="4"/></msub></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="3"/></mfrac></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s text:c="2"/></mrow></text:p> + <text:p text:style-name="Preformatted_20_Text"><text:s/></semantics></text:p> + <text:p text:style-name="P1"></math></text:p> + </office:text> + </office:body> +</office:document> diff --git a/uitest/math_tests/start.py b/uitest/math_tests/start.py new file mode 100644 index 0000000000..7504387a16 --- /dev/null +++ b/uitest/math_tests/start.py @@ -0,0 +1,67 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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.uihelper.common import get_state_as_dict + +from libreoffice.uno.propertyvalue import mkPropertyValues + +from uitest.framework import UITestCase +from uitest.uihelper.common import type_text, select_pos + +class SimpleMathTest(UITestCase): + + def test_math_unoCommand(self): + with self.ui_test.create_doc_in_start_center("math"): + + xMathDoc = self.xUITest.getTopFocusWindow() + + # tdf#140386 + self.xUITest.executeCommand(".uno:InsertCommandText?Text:string=backepsilon") + + xEditView = xMathDoc.getChild("editview") + + self.assertEqual("backepsilon", get_state_as_dict(xEditView)["Text"]) + + def test_math_edit(self): + with self.ui_test.create_doc_in_start_center("math"): + + xMathDoc = self.xUITest.getTopFocusWindow() + + xEditView = xMathDoc.getChild("editview") + + type_text(xEditView, "E=mc^2") + + self.assertEqual("E=mc^2", get_state_as_dict(xEditView)["Text"]) + + def test_complete_math(self): + with self.ui_test.create_doc_in_start_center("math"): + + xMathDoc = self.xUITest.getTopFocusWindow() + + xList = xMathDoc.getChild("categorylist") + state = get_state_as_dict(xList) + self.assertEqual(state["SelectEntryText"], "Unary/Binary Operators") + select_pos(xList, "1") + state = get_state_as_dict(xList) + self.assertEqual(state["SelectEntryText"], "Relations") + + xMathSelector = xMathDoc.getChild("elements") + + xElement = xMathSelector.getChild("1") + xElement.executeAction("DOUBLECLICK", tuple()) + + xEditView = xMathDoc.getChild("editview") + xEditView.executeAction("TYPE", mkPropertyValues({"KEYCODE":"F4"})) + type_text(xEditView, "1") + xEditView.executeAction("TYPE", mkPropertyValues({"KEYCODE":"F4"})) + type_text(xEditView, "2") + + self.assertEqual("{ 1 <> 2 }", get_state_as_dict(xEditView)["Text"]) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/math_tests/tdf128610.py b/uitest/math_tests/tdf128610.py new file mode 100644 index 0000000000..29b7d5ac45 --- /dev/null +++ b/uitest/math_tests/tdf128610.py @@ -0,0 +1,32 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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 +from uitest.uihelper.common import get_url_for_data_file + +class Tdf128610(UITestCase): + + def test_tdf128610(self): + with self.ui_test.load_file(get_url_for_data_file('tdf128610.fodt')): + self.xUITest.executeCommand('.uno:SelectAll') + self.xUITest.executeCommand('.uno:Copy') + + with self.ui_test.load_empty_file("math"): + + self.xUITest.executeCommand('.uno:ImportMathMLClipboard') + + xMathDoc = self.xUITest.getTopFocusWindow() + xEditView = xMathDoc.getChild("editview") + + # Without the fix in place, this test would have failed with + # AssertionError: '{ f _ c = frac { 1 } { K _ m } }' != '' + self.assertEqual("{ f _ c = { frac { 1 } { K _ m } } }", get_state_as_dict(xEditView)["Text"]) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/math_tests/tdf147755.py b/uitest/math_tests/tdf147755.py new file mode 100644 index 0000000000..4919747878 --- /dev/null +++ b/uitest/math_tests/tdf147755.py @@ -0,0 +1,32 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# 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 + +class Tdf147755(UITestCase): + + def test_tdf147755(self): + with self.ui_test.create_doc_in_start_center("math"): + + with self.ui_test.execute_dialog_through_command(".uno:SymbolCatalogue", close_button="close") as xDialog: + xSymbolset = xDialog.getChild("symbolset") + self.assertEqual("Arabic", get_state_as_dict(xSymbolset)["DisplayText"]) + + xOk = xDialog.getChild("ok") + xOk.executeAction("CLICK", tuple()) + + xMathDoc = self.xUITest.getTopFocusWindow() + xEditView = xMathDoc.getChild("editview") + + # Without the fix in place, this test would have failed with + # AssertionError: '%arRay' != '' + self.assertEqual("%arRay", get_state_as_dict(xEditView)["Text"]) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/packaging/Makefile b/uitest/packaging/Makefile new file mode 100644 index 0000000000..af63c946e7 --- /dev/null +++ b/uitest/packaging/Makefile @@ -0,0 +1,16 @@ +all: + @echo "Packaging the libreoffice-connection code" + @mkdir -p libreoffice + @cp ../libreoffice/connection.py libreoffice/. + @touch libreoffice/__init__.py + @python3 setup.py sdist + +clean: + rm -r dist/ + rm -r libreoffice_connection.egg-info/ + rm -r libreoffice/ + rm -r build/ + +publish: + @echo "Uploading the release to pypi" + twine upload dist/* diff --git a/uitest/packaging/README.md b/uitest/packaging/README.md new file mode 100644 index 0000000000..41dacf5204 --- /dev/null +++ b/uitest/packaging/README.md @@ -0,0 +1,3 @@ +# Connection code for LibreOffice's pyUNO + +This code allows out-of-process communication with a LibreOffice instance. The instance can either be started by the script or a connection to a running LibreOffice instance. The code contains several safety checks for hanging and crashed LibreOffice instances. diff --git a/uitest/packaging/setup.cfg b/uitest/packaging/setup.cfg new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/uitest/packaging/setup.cfg diff --git a/uitest/packaging/setup.py b/uitest/packaging/setup.py new file mode 100644 index 0000000000..dcaa24512b --- /dev/null +++ b/uitest/packaging/setup.py @@ -0,0 +1,35 @@ +from setuptools import setup, find_packages +from codecs import open +from os import path + +here = path.abspath(path.dirname(__file__)) + +# Get the long description from the README file +with open(path.join(here, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + +setup( + name="libreoffice-connection", + version="0.0.1", + description="Connection code for LibreOffice's pyUNO", + long_description=long_description, + long_description_content_type='text/markdown', + url="http://www.libreoffice.org", + author="The LibreOffice developers", + author_email="libreoffice@lists.freedesktop.org", + license="MPL2", + classifiers=[ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", + "Programming Language :: Python :: 3.2", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Topic :: Office/Business :: Office Suites", + "Topic :: Software Development :: Libraries", + ], + keywords="office automation", + packages=find_packages(), +) diff --git a/uitest/test_main.py b/uitest/test_main.py new file mode 100644 index 0000000000..1957f54dc3 --- /dev/null +++ b/uitest/test_main.py @@ -0,0 +1,150 @@ +# -*- 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 sys +import getopt +import os +import unittest +import importlib +import importlib.machinery +import types + +from uitest.framework import UITestCase + +from libreoffice.connection import OfficeConnection +from libreoffice.connection import PersistentConnection + +test_name_limit_found = False + +def parseArgs(argv): + (optlist,args) = getopt.getopt(argv[1:], "hr", + ["help", "soffice=", "oneprocess", "userdir=", "dir=", "file=", "gdb"]) + return (dict(optlist), args) + +def usage(): + message = """usage: {program} [option]... [task_file]..." + -h | --help: print usage information + {connection_params} + the 'task_file' parameters should be + full absolute pathnames, not URLs.""" + print(message.format(program = os.path.basename(sys.argv[0]), \ + connection_params = OfficeConnection.getHelpText())) + + +def find_test_files(dir_path): + valid_files = [] + for f in sorted(os.listdir(dir_path)): + file_path = os.path.join(dir_path, f) + + # don't go through the sub-directories + if not os.path.isfile(file_path): + continue + + if os.path.splitext(file_path)[1] == ".swp": + continue # ignore VIM swap files + + if file_path[-1:] == "~": + continue # ignore backup files + + # fail on any non .py files + if not os.path.splitext(file_path)[1] == ".py": + raise Exception("file with an extension which is not .py: " + file_path) + + # ignore the __init__.py file + # it is obviously not a test file + if f == "__init__.py": + continue + + valid_files.append(file_path) + + return valid_files + +def get_classes_of_module(module): + md = module.__dict__ + return [ md[c] for c in md if ( + isinstance(md[c], type) and md[c].__module__ == module.__name__ ) ] + +def get_test_case_classes_of_module(module): + classes = get_classes_of_module(module) + return [ c for c in classes if issubclass(c, UITestCase) ] + +def add_tests_for_file(test_file, test_suite): + test_name_limit = os.environ.get('UITEST_TEST_NAME', '') + test_loader = unittest.TestLoader() + module_name = os.path.splitext(os.path.split(test_file)[1])[0] + + loader = importlib.machinery.SourceFileLoader(module_name, test_file) + # exec_module was only introduced in 3.4 + if sys.version_info < (3,4): + mod = loader.load_module() + else: + mod = types.ModuleType(loader.name) + loader.exec_module(mod) + classes = get_test_case_classes_of_module(mod) + global test_name_limit_found + for c in classes: + test_names = test_loader.getTestCaseNames(c) + for test_name in test_names: + full_name = ".".join([module_name, c.__name__, test_name]) + if len(test_name_limit) > 0: + if test_name_limit != full_name: + continue + test_name_limit_found = True + + obj = c(test_name, opts, connection) + test_suite.addTest(obj) + +def get_test_suite_for_dir(opts): + test_suite = unittest.TestSuite() + + valid_test_files = find_test_files(opts['--dir']) + for test_file in valid_test_files: + add_tests_for_file(test_file, test_suite) + return test_suite + + +if __name__ == '__main__': + (opts,args) = parseArgs(sys.argv) + connection = None + if "--oneprocess" in opts: + connection = PersistentConnection(opts) + connection.setUp() + if "-h" in opts or "--help" in opts: + usage() + sys.exit() + elif not "--soffice" in opts: + usage() + sys.exit(1) + elif "--dir" in opts: + test_suite = get_test_suite_for_dir(opts) + test_name_limit = os.environ.get('UITEST_TEST_NAME', '') + if len(test_name_limit) > 0: + if not test_name_limit_found: + print("UITEST_TEST_NAME '%s' does not match any test" % test_name_limit) + sys.exit(1) + else: + print("UITEST_TEST_NAME '%s' active" % test_name_limit) + elif "--file" in opts: + test_suite = unittest.TestSuite() + add_tests_for_file(opts['--file'], test_suite) + else: + usage() + sys.exit() + + result = unittest.TextTestRunner(stream=sys.stdout, verbosity=2).run(test_suite) + print("Tests run: %d" % result.testsRun) + print("Tests failed: %d" % len(result.failures)) + print("Tests errors: %d" % len(result.errors)) + print("Tests skipped: %d" % len(result.skipped)) + if connection: + connection.tearDown() + connection.kill() + if not result.wasSuccessful(): + sys.exit(1) + sys.exit(0) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/uitest/ui_logger_dsl/General_commands.tx b/uitest/ui_logger_dsl/General_commands.tx new file mode 100644 index 0000000000..2be59ce75f --- /dev/null +++ b/uitest/ui_logger_dsl/General_commands.tx @@ -0,0 +1,27 @@ +/* + This file is for the log statements that are general for all applications. + We can use them as general rules for commands and relate them to the + applications found in starter_commands.tx + Zoom is also general as it is better to treat it that way. +*/ + +import type_options + +GeneralCommand: + SideBar | setZoom_command | Select_command | General_type_command_on_UI_Object +; +SideBar: + 'From SIDEBAR ' 'Choose ' '{"PANEL":' name=STRING '}' +; +setZoom_command: + 'Set Zoom to ' zoom_value=INT +; +Select_command: + 'Select ' '{"OBJECT":' name=STRING '}' +; + +// This part is for typing text in any of these UI elements + +General_type_command_on_UI_Object: + 'Type on' UI_Obj=STRING what_to_type=Type_options 'from' parent_id=ID +; diff --git a/uitest/ui_logger_dsl/Special_commands.tx b/uitest/ui_logger_dsl/Special_commands.tx new file mode 100644 index 0000000000..fb3a6e235f --- /dev/null +++ b/uitest/ui_logger_dsl/Special_commands.tx @@ -0,0 +1,238 @@ +/* + This file is for the log statements that relate to each application. + Each grammar rule here is related to a specific application. +*/ + +import type_options + +SpecialCommand: + writer_command | calc_command | impress_command | math_command | draw_command +; + +/* + This part is for all the Writer log statements: + + 1) Type + 2) Select + 3) GOTO page + 4) Create Table + 5) Copy Text + 6) Cut Text + 7) Paste Text + 8) Insert Break Page +*/ +writer_command: + writer_Type_command | writer_Select_command | writer_GOTO_command | + writer_Create_table | writer_Copy_Text | writer_Cut_Text | + writer_Paste_Text | writer_Insert_BreakPage | writer_Comment_command +; +writer_Type_command: + 'Type on writer' what_to_type=Type_options +; +writer_Select_command: + 'Select from Pos' from_pos=INT 'to Pos' to_pos=INT +; +writer_GOTO_command: + 'GOTO page number' page_num=INT +; +writer_Create_table: + 'Create Table with Columns :' cols=INT ', Rows :' rows=INT +; +writer_Copy_Text: + 'Copy the Selected Text' +; +writer_Cut_Text: + 'Cut the Selected Text' +; +writer_Paste_Text: + 'Paste in the Current Cursor Location' +; +writer_Insert_BreakPage: + 'Insert Break Page' +; + +/* + This part is for all the Calc log statements: + + 1) select sheet + 2) Select cell or range + 3) launch AutoFill + 4) launch SELECTMENU + 5) Delete Cells + 6) Remove Content of a cell + 7) Insert new Cells + 8) Cut Cells + 9) Copy Cells + 10) Paste Cells + 11) Merge Cells + 12) Unmerge Cells + 13) Open Comment + 14) Close Comment +*/ +calc_command: + calc_Type_command | calc_switch_sheet | calc_Select_cell | calc_AutoFill_filter | + calc_SelectMenu_filter | calc_Delete_Cells | calc_Remove_Content | calc_insert_cells | + calc_Cut_Cells | calc_Copy_Cells | calc_Paste_Cells | calc_UNMerge_Cells | + calc_Merge_Cells | calc_Rename_Sheet | calc_Insert_sheet | calc_Open_Comment | + calc_Close_Comment +; +calc_Type_command: + 'Type on current cell' what_to_type=Type_options +; +calc_switch_sheet: + 'Switch to sheet number' sheet_num=INT +; +calc_Select_cell: + 'Select from calc' select_op=select_options +; +calc_AutoFill_filter: + 'Launch AutoFilter from Col' col_num=INT 'and Row' row_num=INT +; +calc_SelectMenu_filter: + 'Launch SELECTMENU from Col' col_num=INT 'and Row' row_num=INT +; +range_of_cells: + '{' '"RANGE":' input_range=STRING '}' +; +one_cell: + '{' '"CELL":' input_cell=STRING '}' +; +calc_Delete_Cells: + 'Delete The Cells in' '{' '"RANGE":' input_range=STRING '}' +; +calc_Remove_Content: + 'Remove Content from This' '{' '"RANGE":' input_range=STRING '}' +; +calc_insert_cells: + 'Insert Cell around the ' '{' '"RANGE":' input_range=STRING '}' +; +calc_Cut_Cells: + 'CUT the selected ' '{' '"RANGE":' input_range=STRING '}' +; +calc_Copy_Cells: + 'COPY the selected ' '{' '"RANGE":' input_range=STRING '}' +; +calc_Paste_Cells: + 'Paste in the' '{' '"RANGE":' input_range=STRING '}' +; +calc_Merge_Cells: + 'Merge' '{' '"RANGE":' input_range=STRING '}' +; +calc_UNMerge_Cells: + 'Delete the merge between' '{' '"CELL":' input_range=STRING '}' +; +calc_Rename_Sheet: + 'Rename The Selected Tab to ' new_name=STRING +; +calc_Insert_sheet: + 'Insert New Tab ' +; +calc_Open_Comment: + 'Open Comment' (txt=STRING)? +; +calc_Close_Comment: + 'Close Comment' (txt=STRING)? +; + +//this is the select options +select_options: + one_cell | range_of_cells +; + +/* + This part is for all the Impress log statements: + + 1) Type + 2) Insert New Slide + 3) Delete Slide + 4) Duplicate Slide + 5) Rename Slide +*/ +impress_command: + impress_Type_command | impress_Insert_Slide | impress_Delete_Page | + impress_Duplicate_Slide | impress_Rename_Slide +; +impress_Type_command: + 'Type on impress ' what_to_type=Type_options +; +impress_Insert_Slide: + 'Insert New Slide at Position ' position_num=INT +; +impress_Delete_Page: + 'Delete Slide number ' position_num=INT +; +impress_Duplicate_Slide: + 'Duplicate The Selected Slide ' +; +impress_Rename_Slide: + 'Rename The Selected Slide from ' old_name=STRING 'to' new_name=STRING +; +/* + This part is for all the Math log statements: + + 1) element selector + 2) Type +*/ +math_command: + math_element_selector | math_Type_command +; +math_element_selector: + 'Select element no ' element_no=INT 'From' place=ID +; +math_Type_command: + 'Type on math ' what_to_type=Type_options +; + +/* + This part is for all the Draw log statements: + + 1) Type + 2) Insert New Page + 3) Delete Page + 4) Rename Page +*/ +draw_command: + draw_Type_command | draw_Insert_Page | draw_Delete_Page | + draw_Rename_Page +; +draw_Type_command: + 'Type on draw ' what_to_type=Type_options +; +draw_Insert_Page: + 'Insert New Page at Position ' position_num=INT +; +draw_Delete_Page: + 'Delete Page number ' position_num=INT +; +draw_Rename_Page: + 'Rename The Selected Page from ' old_name=STRING 'to' new_name=STRING +; + +/* + This part is for all the Writer Comment log statements: + + 1) Leave + 2) Hide + 3) Show + 4) Delete + 5) Set Resolved +*/ +writer_Comment_command: + writer_Comment_leave | writer_Comment_show | writer_Comment_hide | + writer_Comment_delete | writer_Comment_setresolved +; +writer_Comment_leave: + 'Leave ' comment_id=STRING +; +writer_Comment_show: + 'Show ' comment_id=STRING +; +writer_Comment_hide: + 'Hide ' comment_id=STRING +; +writer_Comment_delete: + 'Delete ' comment_id=STRING +; +writer_Comment_setresolved: + 'Resolve' comment_id=STRING +; diff --git a/uitest/ui_logger_dsl/UI_Object_commands.tx b/uitest/ui_logger_dsl/UI_Object_commands.tx new file mode 100644 index 0000000000..4711ad6ac4 --- /dev/null +++ b/uitest/ui_logger_dsl/UI_Object_commands.tx @@ -0,0 +1,90 @@ +/* + This file is for the grammar of: + 1) ButtonUIObject : ( Click event ) + 2) EditUIObject : ( Type event - Clear event - Select Text event ) + 3) CheckBoxUIObject : ( Toggle the value ) + 4) RadioButtonUIObject : ( Select event ) + 5) ListBoxUIObject : ( Select event ) + 6) ComboBoxUIObject ( Select event ) + 7) SpinUIObject ( Increase event - Decrease event ) + 8) TabControlUIObject ( Change tab event ) + 9) ToolBoxUIObject ( Click on item event ) + 10) ValueSetUIObject (Choose item) + 10) MenuBtnUIObject ( Select - Open - Close ) +*/ + +import type_options + +UIObjectCommand: + ButtonUIObject | CheckBoxUIObject | EditUIObject | + RadioButtonUIObject | ListBoxUIObject | ComboBoxUIObject | + SpinFieldUIObject | TabControlUIObject | ToolBoxUIObject | + ValueSetUIObject | MenuBtnUIObject +; +TabPageNumber: + INT | ID +; +ButtonUIObject: + 'Click on' ui_button=STRING ('from' parent_id=ID)? +; +CheckBoxUIObject: + 'Toggle' Check_box_id=STRING 'CheckBox' ('from' parent_id=ID)? +; +RadioButtonUIObject: + 'Select' Radio_button_id=STRING 'RadioButton' ('from' parent_id=ID)? +; +ComboBoxUIObject: + 'Select in' Combo_box_id=STRING 'ComboBox' 'item number' item_num=INT ('from' parent_id=ID)? +; +TabControlUIObject: + 'Choose Tab number' tab_page_number=TabPageNumber 'in' tab_id=STRING ('from' parent_id=ID)? +; +EditUIObject: + action=action_on_UIObject ('from' parent_id=ID)? +; +SpinFieldUIObject: + change=increase_or_decrease Spin_id=STRING ('from' parent_id=ID)? +; +ListBoxUIObject: + 'Select element with position ' POS=INT 'in' list_id=STRING ('from' parent_id=ID)? +; +ToolBoxUIObject: + 'Click on item number' POS=INT 'in' toolbox_id=ID +; +ValueSetUIObject: + 'Choose element with position ' POS=INT 'in' value_set_id=STRING 'from' parent_id=STRING +; +//============================================================= +MenuBtnUIObject: + MenuBtnUIObjectOpen | MenuBtnUIObjectSelect | MenuBtnUIObjectClose +; +MenuBtnUIObjectOpen: + 'Open List From' + MenuBtn_ID=ID +; +MenuBtnUIObjectClose: + 'Close List From' + MenuBtn_ID=ID +; +MenuBtnUIObjectSelect: + 'Select item no' + item_num=INT + 'From List of' + MenuBtn_ID=ID +; +//============================================================= +// Helper grammar for EditUIObject +action_on_UIObject: + Type_action | SELECT | Clear +; +Type_action: + 'Type on' edit_button=STRING what_to_type=Type_options +; +SELECT: + 'Select in ' edit_button=STRING + '{' + '"FROM"' + ':' + '"' from_pos=INT '"' + ',' + '"TO"' + ':' + '"' to_pos=INT '"'+'}' +; +Clear: + 'Clear' edit_button=STRING +; + +//============================================================= +// Helper functions for SpinUIObject +increase_or_decrease: + 'Increase' | 'Decrease' +; diff --git a/uitest/ui_logger_dsl/dialog_commands.tx b/uitest/ui_logger_dsl/dialog_commands.tx new file mode 100644 index 0000000000..637b54f0c9 --- /dev/null +++ b/uitest/ui_logger_dsl/dialog_commands.tx @@ -0,0 +1,24 @@ +/* + This file is for the Dialog commands. + It handles all types of Dialog: the Modeless and the Modal. + It also handles the Close Dialog commands. +*/ +DialogCommand: + OpenDialog | CloseDialog +; + +OpenDialog: + OpenModalDialog | OpenModelessDialog +; +OpenModalDialog : + 'Open Modal ' dialog_name=ID +; +OpenModelessDialog : + 'Open Modeless ' dialog_name=ID +; + +CloseDialog: + // If there is a need to match a dialog name in the future, additional_note=STRING? can be used. + // It is also used to make an instance of the command of type CloseDialog. + 'Close Dialog' additional_note=STRING? +; diff --git a/uitest/ui_logger_dsl/dsl_core.py b/uitest/ui_logger_dsl/dsl_core.py new file mode 100644 index 0000000000..77c75eae9b --- /dev/null +++ b/uitest/ui_logger_dsl/dsl_core.py @@ -0,0 +1,1092 @@ +#!/usr/bin/env python3 +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice UI_logger project. +# +# 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/. +# +# This file contains the implementation of the Compiler +# for the new logger grammar +# +# ul stands for Ui_Logger + +import os +import sys +import argparse +import keyword + +try: + from textx.metamodel import metamodel_from_file +except ImportError: + print("textx is a required package.") + print('Please install the package for example with "pip3 install --user textx"') + sys.exit(1) + +tab = " " + +def parse_args(): + """ + This function parses the command-line arguments + to get the input and output file details + """ + parser = argparse.ArgumentParser(description="Generate a UI test file from log") + parser.add_argument("input_address", type=str, help="The log file address") + parser.add_argument("output_address", type=str, help="The test file address") + args = parser.parse_args() + return args + + +class ul_Compiler: + prev_command = "" + variables = [] + objects = dict() + current_app = "" + parent_hierarchy_count = 0 + last_parent = [] + flag_for_QuerySaveDialog = False + math_element_selector_initializer= False; + + def __init__(self, input_address, output_address): + self.ui_dsl_mm = metamodel_from_file("ui_logger_dsl_grammar.tx") + self.output_stream = self.initiate_test_generation(output_address) + self.input_address = input_address + + def get_log_file(self, input_address): + try: + # load the program + content = self.ui_dsl_mm.model_from_file(input_address) + except IOError as err: + print("IO error: {0}".format(err)) + print( + "Use " + os.path.basename(sys.argv[0]) + " -h to get usage instructions" + ) + sys.exit(1) + + return content + + def initiate_test_generation(self, output_address): + self.last_parent.append("MainWindow") + try: + f = open(output_address, "w") + except IOError as err: + print("IO error: {0}".format(err)) + print( + "Use " + os.path.basename(sys.argv[0]) + " -h to get usage instructions" + ) + sys.exit(1) + line = ( + "# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-\n" + + "#\n" + + "# This file is part of the LibreOffice project.\n" + + "#\n" + + "# This Source Code Form is subject to the terms of the Mozilla Public\n" + + "# License, v. 2.0. If a copy of the MPL was not distributed with this\n" + + "# file, You can obtain one at http://mozilla.org/MPL/2.0/\n" + + "#\n\n" + + "from uitest.framework import UITestCase\n" + + "from libreoffice.uno.propertyvalue import mkPropertyValues\n" + + "from uitest.uihelper.common import get_state_as_dict\n" + + "import importlib\n\n" + + "class TestClass(UITestCase):\n" + + tab + + "def test_function(self):\n" + ) + + self.variables.append(line) + + return f + + def compile(self): + self.ui_dsl_mm.register_obj_processors( + { + "UNOCommand": self.handle_uno, + "StarterCommand": self.handle_start, + "CloseDialog": self.handle_Dialog, + "OpenModelessDialog": self.handle_Dialog, + "OpenModalDialog": self.handle_Dialog, + "ButtonUIObject": self.handle_button, + "CheckBoxUIObject": self.handle_check_box, + "TabControlUIObject": self.handle_tab, + "ComboBoxUIObject": self.handle_Combo_box, + "RadioButtonUIObject": self.handle_Radio_button, + "ListBoxUIObject": self.handle_List_box, + "SpinFieldUIObject": self.handle_spin_field, + "EditUIObject": self.handle_Edit_uiObject, + "ToolBoxUIObject": self.handle_ToolBox_uiObject, + "ValueSetUIObject": self.handle_ValueSet_uiObject, + "MenuBtnUIObjectOpen":self.handle_MenuBtnUIObjectOpen, + "MenuBtnUIObjectSelect":self.handle_MenuBtnUIObjectSelect, + "MenuBtnUIObjectClose":self.handle_MenuBtnUIObjectClose, + "writer_Type_command": self.handle_writer_type, + "writer_Select_command": self.handle_writer_select, + "writer_GOTO_command": self.handle_writer_goto, + "calc_Select_cell": self.handle_calc_select, + "calc_switch_sheet": self.handle_calc_switch_sheet, + "calc_Type_command": self.handle_calc_Type_command, + "calc_AutoFill_filter": self.handle_calc_AutoFill_filter, + "calc_SelectMenu_filter": self.handle_calc_SelectMenu_filter, + "calc_Open_Comment": self.handle_calc_Open_Comment, + "calc_Close_Comment": self.handle_calc_Close_Comment, + "impress_Type_command": self.handle_impress_Type_command, + "math_element_selector": self.handle_math_element_selector, + "math_Type_command": self.handle_math_Type_command, + "setZoom_command": self.handle_setZoom_command, + "draw_Type_command": self.handle_draw_Type_command, + "SideBar": self.handle_SideBar, + "writer_Comment_leave":self.handle_writer_Comment_leave, + "writer_Comment_show":self.handle_writer_Comment_show, + "writer_Comment_hide":self.handle_writer_Comment_hide, + "writer_Comment_delete":self.handle_writer_Comment_delete, + "writer_Comment_setresolved":self.handle_writer_Comment_setresolved, + "writer_Copy_Text": self.do_nothing, + "writer_Cut_Text": self.do_nothing, + "writer_Paste_Text": self.do_nothing, + "writer_Insert_BreakPage": self.do_nothing, + "writer_Create_table": self.do_nothing, + "calc_Remove_Content": self.do_nothing, + "calc_Delete_Cells": self.do_nothing, + "calc_insert_cells": self.do_nothing, + "calc_Cut_Cells": self.do_nothing, + "calc_Copy_Cells": self.do_nothing, + "calc_Merge_Cells": self.do_nothing, + "calc_UNMerge_Cells": self.do_nothing, + "calc_Rename_Sheet": self.do_nothing, + "calc_Insert_sheet": self.do_nothing, + "impress_Insert_Slide": self.do_nothing, + "impress_Delete_Page": self.do_nothing, + "impress_Duplicate_Slide": self.do_nothing, + "impress_Rename_Slide": self.do_nothing, + "draw_Insert_Page": self.do_nothing, + "draw_Delete_Page": self.do_nothing, + "draw_Rename_Page": self.do_nothing, + } + ) + + self.log_lines = self.get_log_file(self.input_address) + + def init_app(self): + if self.current_app in self.objects: + self.objects[self.current_app] += 1 + else: + self.objects[self.current_app] = 1 + line = ( + tab * 4 + + self.current_app + + ' = MainWindow.getChild("' + + self.current_app + + '")\n' + ) + self.variables.append(line) + + def init_Object(self, Id_of_Object, name_of_child, Obj_parent): + + if Id_of_Object in self.objects: + self.objects[Id_of_Object] += 1 + else: + self.objects[Id_of_Object] = 1 + line = ( + tab * 4 + + Id_of_Object + + " = " + + Obj_parent + + '.getChild("' + + name_of_child + + '")\n' + ) + + self.variables.append(line) + + def write_line_without_parameters(self, Action_holder, Action, Action_type): + line = ( + tab * 4 + + Action_holder + + '.executeAction("' + + Action + + '",' + + Action_type + + "())\n" + ) + self.variables.append(line) + + def write_line_with_one_parameters( + self, Action_holder, Action, Parameter_name, parameter_value + ): + line = ( + tab * 4 + + Action_holder + + '.executeAction("' + + Action + + '", mkPropertyValues({"' + + Parameter_name + + '": "' + + str(parameter_value) + + '"}))\n' + ) + self.variables.append(line) + + def write_line_with_two_parameters( + self, + Action_holder, + Action, + Parameter_name_1, + parameter_value_1, + Parameter_name_2, + parameter_value_2, + ): + + line = ( + tab * 3 + + Action_holder + + '.executeAction("' + + Action + + '", mkPropertyValues({"' + + Parameter_name_1 + + '": "' + + str(parameter_value_1) + + '", "' + + Parameter_name_2 + + '": "' + + str(parameter_value_2) + + '"}))\n' + ) + self.variables.append(line) + + def handle_uno(self, UNOCommand): + if UNOCommand.parameters == None: + line = ( + tab * 3 + + 'self.xUITest.executeCommand("' + + UNOCommand.uno_command_name + + '")\n' + ) + else: + parameters = "" + for p in UNOCommand.parameters.parameter_data: + parameters = parameters + '"' + p.key + '" : ' + str(p.value) + " ," + parameters = parameters[:-1] + + line = ( + tab * 3 + + 'self.xUITest.executeCommandWithParameters("' + + UNOCommand.uno_command_name + + '", mkPropertyValues({' + + parameters + + "}) )\n" + ) + + self.variables.append(line) + self.prev_command = UNOCommand + + def handle_start(self, StarterCommand): + line = ( + tab * 2 + + 'with self.ui_test.create_doc_in_start_center("' + + StarterCommand.program_name + + '") as document:\n' + ) + self.variables.append(line) + + line = tab * 3 + "MainWindow = self.xUITest.getTopFocusWindow()\n" + self.variables.append(line) + app = { + "writer": "writer_edit", + "calc": "grid_window", + "impress": "impress_win", + "math": "math_edit", + "draw": "draw_win", + } + self.current_app = app[StarterCommand.program_name] + self.prev_command = StarterCommand + + def handle_SideBar(self, SideBar): + + line = ' self.xUITest.executeCommand(".uno:Sidebar")\n' + self.variables.append(line) + + self.write_line_with_one_parameters( + "MainWindow", "SIDEBAR", "PANEL", SideBar.name + ) + + self.prev_command = SideBar + + def handle_Dialog(self, DialogCommand): + + if DialogCommand.__class__.__name__ == "OpenModalDialog": + + if DialogCommand.dialog_name != "QuerySaveDialog": + # This part is just to ignore saving the Save dialog while closing the app + + old_line = self.variables.pop() + if self.prev_command.__class__.__name__ == "UNOCommand": + key_word = self.prev_command.uno_command_name[-6:] + else: + key_word = old_line[-9:-3] + + if key_word == "Dialog": + old_line = ( + tab * 3 + + 'with self.ui_test.execute_dialog_through_command("' + + self.prev_command.uno_command_name + + '") as ' + + DialogCommand.dialog_name + + ':\n' + ) + self.variables.append(old_line) + self.last_parent.append(DialogCommand.dialog_name) + self.parent_hierarchy_count = self.parent_hierarchy_count + 1 + + else: + self.flag_for_QuerySaveDialog = True + + elif DialogCommand.__class__.__name__ == "OpenModelessDialog": + old_line = self.variables.pop() + if self.prev_command.__class__.__name__ == "UNOCommand": + key_word = self.prev_command.uno_command_name[-6:] + else: + key_word = old_line[-9:-3] + + if key_word == "Dialog": + old_line = ( + tab * 3 + + 'with self.ui_test.execute_modeless_dialog_through_command("' + + self.prev_command.uno_command_name + + '") as ' + + DialogCommand.dialog_name + + ':\n' + ) + self.variables.append(old_line) + self.last_parent.append(DialogCommand.dialog_name) + self.parent_hierarchy_count = self.parent_hierarchy_count + 1 + + elif DialogCommand.__class__.__name__ == "CloseDialog": + + if not (self.flag_for_QuerySaveDialog): + # This part is just to ignore saving the Save dialog while closing the app + + if self.prev_command.__class__.__name__ == "ButtonUIObject": + old_line = self.variables.pop() + line = "" + if keyword.iskeyword(self.prev_command.ui_button): + line = ( + tab * 4 + + "self.ui_test.close_dialog_through_button(x" + + self.prev_command.ui_button + + ")\n" + ) + else: + line = ( + tab * 4 + + "self.ui_test.close_dialog_through_button(" + + self.prev_command.ui_button + + ")\n" + ) + self.variables.append(line) + self.last_parent.pop() + self.parent_hierarchy_count = self.parent_hierarchy_count - 1 + else: + self.flag_for_QuerySaveDialog = False + + # This is to solve the problem of re-using the same id again in different Dialogs + + self.objects.clear() + + self.prev_command = DialogCommand + + def handle_button(self, ButtonUIObject): + + if ButtonUIObject.parent_id != "QuerySaveDialog": + # This part is just to ignore saving the Save dialog while closing the app + + name_of_obj = "" + if keyword.iskeyword(ButtonUIObject.ui_button): + name_of_obj = "x" + ButtonUIObject.ui_button + else: + name_of_obj = ButtonUIObject.ui_button + + if ButtonUIObject.parent_id == "": + self.init_Object( + name_of_obj, + ButtonUIObject.ui_button, + self.last_parent[self.parent_hierarchy_count], + ) + else: + self.init_Object( + name_of_obj, ButtonUIObject.ui_button, ButtonUIObject.parent_id + ) + + self.write_line_without_parameters(name_of_obj, "CLICK", "tuple") + + self.prev_command = ButtonUIObject + + def handle_check_box(self, CheckBoxUIObject): + + name_of_obj = "" + if keyword.iskeyword(CheckBoxUIObject.Check_box_id): + name_of_obj = "x" + CheckBoxUIObject.Check_box_id + else: + name_of_obj = CheckBoxUIObject.Check_box_id + + if CheckBoxUIObject.parent_id == "": + self.init_Object( + name_of_obj, + CheckBoxUIObject.Check_box_id, + self.last_parent[self.parent_hierarchy_count], + ) + else: + self.init_Object( + name_of_obj, CheckBoxUIObject.Check_box_id, CheckBoxUIObject.parent_id + ) + + self.write_line_without_parameters(name_of_obj, "CLICK", "tuple") + + self.prev_command = CheckBoxUIObject + + def handle_tab(self, TabControlUIObject): + + name_of_obj = "" + if keyword.iskeyword(TabControlUIObject.tab_id): + name_of_obj = "x" + TabControlUIObject.tab_id + else: + name_of_obj = TabControlUIObject.tab_id + + if TabControlUIObject.parent_id == "": + self.init_Object( + name_of_obj, + TabControlUIObject.tab_id, + self.last_parent[self.parent_hierarchy_count], + ) + else: + self.init_Object( + name_of_obj, TabControlUIObject.tab_id, TabControlUIObject.parent_id + ) + + self.write_line_with_one_parameters( + name_of_obj, "SELECT", "POS", TabControlUIObject.tab_page_number + ) + + self.prev_command = TabControlUIObject + + def handle_Combo_box(self, ComboBoxUIObject): + + name_of_obj = "" + if keyword.iskeyword(ComboBoxUIObject.Combo_box_id): + name_of_obj = "x" + ComboBoxUIObject.Combo_box_id + else: + name_of_obj = ComboBoxUIObject.Combo_box_id + + if ComboBoxUIObject.parent_id == "": + self.init_Object( + name_of_obj, + ComboBoxUIObject.Combo_box_id, + self.last_parent[self.parent_hierarchy_count], + ) + else: + self.init_Object( + name_of_obj, ComboBoxUIObject.Combo_box_id, ComboBoxUIObject.parent_id + ) + + self.write_line_with_one_parameters( + name_of_obj, "SELECT", "POS", ComboBoxUIObject.item_num + ) + + self.prev_command = ComboBoxUIObject + + def handle_Radio_button(self, RadioButtonUIObject): + + name_of_obj = "" + if keyword.iskeyword(RadioButtonUIObject.Radio_button_id): + name_of_obj = "x" + RadioButtonUIObject.Radio_button_id + else: + name_of_obj = RadioButtonUIObject.Radio_button_id + + if RadioButtonUIObject.parent_id == "": + self.init_Object( + name_of_obj, + RadioButtonUIObject.Radio_button_id, + self.last_parent[self.parent_hierarchy_count], + ) + else: + self.init_Object( + name_of_obj, + RadioButtonUIObject.Radio_button_id, + RadioButtonUIObject.parent_id, + ) + + self.write_line_without_parameters(name_of_obj, "CLICK", "tuple") + + self.prev_command = RadioButtonUIObject + + def handle_List_box(self, ListBoxUIObject): + + name_of_obj = "" + if keyword.iskeyword(ListBoxUIObject.list_id): + name_of_obj = "x" + ListBoxUIObject.list_id + else: + name_of_obj = ListBoxUIObject.list_id + + if ListBoxUIObject.parent_id == "": + self.init_Object( + name_of_obj, + ListBoxUIObject.list_id, + self.last_parent[self.parent_hierarchy_count], + ) + else: + self.init_Object( + name_of_obj, ListBoxUIObject.list_id, ListBoxUIObject.parent_id + ) + + self.write_line_with_one_parameters( + name_of_obj, "SELECT", "POS", ListBoxUIObject.POS + ) + + self.prev_command = ListBoxUIObject + + def handle_spin_field(self, SpinFieldUIObject): + + name_of_obj = "" + if keyword.iskeyword(SpinFieldUIObject.Spin_id): + name_of_obj = "x" + SpinFieldUIObject.Spin_id + else: + name_of_obj = SpinFieldUIObject.Spin_id + + if SpinFieldUIObject.parent_id == "": + self.init_Object( + name_of_obj, + SpinFieldUIObject.Spin_id, + self.last_parent[self.parent_hierarchy_count], + ) + else: + self.init_Object( + name_of_obj, SpinFieldUIObject.Spin_id, SpinFieldUIObject.parent_id + ) + + if SpinFieldUIObject.change == "Increase": + self.write_line_without_parameters(name_of_obj, "UP", "tuple") + elif SpinFieldUIObject.change == "Decrease": + self.write_line_without_parameters(name_of_obj, "DOWN", "tuple") + self.prev_command = SpinFieldUIObject + + def handle_Edit_uiObject(self, EditUIObject): + + name_of_obj = "" + if keyword.iskeyword(EditUIObject.action.edit_button): + name_of_obj = "x" + EditUIObject.action.edit_button + else: + name_of_obj = EditUIObject.action.edit_button + + if EditUIObject.parent_id == "": + self.init_Object( + name_of_obj, + EditUIObject.action.edit_button, + self.last_parent[self.parent_hierarchy_count], + ) + else: + self.init_Object( + name_of_obj, EditUIObject.action.edit_button, EditUIObject.parent_id + ) + + if EditUIObject.action.__class__.__name__ == "Type_action": + + if EditUIObject.action.what_to_type.__class__.__name__ == "char": + self.write_line_with_one_parameters( + name_of_obj, + "TYPE", + "TEXT", + EditUIObject.action.what_to_type.input_char, + ) + + elif EditUIObject.action.what_to_type.__class__.__name__ == "KeyCode": + self.write_line_with_one_parameters( + name_of_obj, + "TYPE", + "KEYCODE", + EditUIObject.action.what_to_type.input_key_code, + ) + + if EditUIObject.action.__class__.__name__ == "SELECT": + + self.write_line_with_two_parameters( + name_of_obj, + "SELECT", + "FROM", + EditUIObject.action.from_pos, + "TO", + EditUIObject.action.to_pos, + ) + + if EditUIObject.action.__class__.__name__ == "Clear": + + self.write_line_without_parameters(name_of_obj, "CLEAR", "tuple") + + self.prev_command = EditUIObject + + def handle_ToolBox_uiObject(self, ToolBoxUIObject): + name_of_obj = "" + if keyword.iskeyword(ToolBoxUIObject.toolbox_id): + name_of_obj = "x" + ToolBoxUIObject.toolbox_id + else: + name_of_obj = ToolBoxUIObject.toolbox_id + + self.init_Object( + name_of_obj, + ToolBoxUIObject.toolbox_id, + self.last_parent[self.parent_hierarchy_count], + ) + + self.write_line_with_one_parameters( + name_of_obj, "CLICK", "POS", ToolBoxUIObject.POS + ) + + self.prev_command = ToolBoxUIObject + + def handle_ValueSet_uiObject(self, ValueSetUIObject): + + name_of_obj = "" + if keyword.iskeyword(ValueSetUIObject.value_set_id): + name_of_obj = "x" + ValueSetUIObject.value_set_id + else: + name_of_obj = ValueSetUIObject.value_set_id + + parent_txt = ValueSetUIObject.parent_id.split("/") + parent = parent_txt[len(parent_txt)-2] + if( parent.upper() != self.last_parent[self.parent_hierarchy_count].upper()): + self.init_Object( + parent, + parent, + self.last_parent[self.parent_hierarchy_count], + ) + + self.init_Object( + name_of_obj, ValueSetUIObject.value_set_id, parent + ) + + else: + self.init_Object( + name_of_obj, ValueSetUIObject.value_set_id, self.last_parent[self.parent_hierarchy_count] + ) + + self.write_line_with_one_parameters( + name_of_obj, "CHOOSE", "POS", ValueSetUIObject.POS + ) + + self.prev_command = ValueSetUIObject + + def handle_MenuBtnUIObjectOpen(self, MenuBtnUIObjectOpen): + name_of_obj = "" + if keyword.iskeyword(MenuBtnUIObjectOpen.MenuBtn_ID): + name_of_obj = "x" + MenuBtnUIObjectOpen.MenuBtn_ID + else: + name_of_obj = MenuBtnUIObjectOpen.MenuBtn_ID + + self.init_Object( + name_of_obj, + MenuBtnUIObjectOpen.MenuBtn_ID, + self.last_parent[self.parent_hierarchy_count], + ) + + self.write_line_with_one_parameters( + name_of_obj, "OPENLIST", "", "" + ) + + self.prev_command = MenuBtnUIObjectOpen + + def handle_MenuBtnUIObjectClose(self, MenuBtnUIObjectClose): + name_of_obj = "" + if keyword.iskeyword(MenuBtnUIObjectClose.MenuBtn_ID): + name_of_obj = "x" + MenuBtnUIObjectClose.MenuBtn_ID + else: + name_of_obj = MenuBtnUIObjectClose.MenuBtn_ID + + self.init_Object( + name_of_obj, + MenuBtnUIObjectClose.MenuBtn_ID, + self.last_parent[self.parent_hierarchy_count], + ) + + self.write_line_with_one_parameters( + name_of_obj, "CLOSELIST", "", "" + ) + + self.prev_command = MenuBtnUIObjectClose + + def handle_MenuBtnUIObjectSelect(self, MenuBtnUIObjectSelect): + name_of_obj = "" + if keyword.iskeyword(MenuBtnUIObjectSelect.MenuBtn_ID): + name_of_obj = "x" + MenuBtnUIObjectSelect.MenuBtn_ID + else: + name_of_obj = MenuBtnUIObjectSelect.MenuBtn_ID + + self.init_Object( + name_of_obj, + MenuBtnUIObjectSelect.MenuBtn_ID, + self.last_parent[self.parent_hierarchy_count], + ) + + self.write_line_with_one_parameters( + name_of_obj, "OPENFROMLIST", "POS", MenuBtnUIObjectSelect.item_num[0] + ) + + self.prev_command = MenuBtnUIObjectSelect + + def handle_writer_type(self, writer_Type_command): + + self.init_app() + + if writer_Type_command.what_to_type.__class__.__name__ == "char": + self.write_line_with_one_parameters( + self.current_app, + "TYPE", + "TEXT", + writer_Type_command.what_to_type.input_char, + ) + + elif writer_Type_command.what_to_type.__class__.__name__ == "KeyCode": + self.write_line_with_one_parameters( + self.current_app, + "TYPE", + "KEYCODE", + writer_Type_command.what_to_type.input_key_code, + ) + + self.prev_command = writer_Type_command + + def handle_writer_select(self, writer_Select_command): + + self.init_app() + + self.write_line_with_two_parameters( + self.current_app, + "SELECT", + "END_POS", + writer_Select_command.from_pos, + "START_POS", + writer_Select_command.to_pos, + ) + + self.prev_command = writer_Select_command + + def handle_writer_goto(self, writer_GOTO_command): + + self.init_app() + + self.write_line_with_one_parameters( + self.current_app, "GOTO", "PAGE", writer_GOTO_command.page_num + ) + + self.prev_command = writer_GOTO_command + + def handle_calc_select(self, calc_Select_cell): + + self.init_app() + + if calc_Select_cell.select_op.__class__.__name__ == "range_of_cells": + self.write_line_with_one_parameters( + self.current_app, + "SELECT", + "RANGE", + calc_Select_cell.select_op.input_range, + ) + + elif calc_Select_cell.select_op.__class__.__name__ == "one_cell": + self.write_line_with_one_parameters( + self.current_app, + "SELECT", + "CELL", + calc_Select_cell.select_op.input_cell, + ) + + self.prev_command = calc_Select_cell + + def handle_calc_switch_sheet(self, calc_switch_sheet): + + self.init_app() + + self.write_line_with_one_parameters( + self.current_app, "SELECT", "TABLE", calc_switch_sheet.sheet_num + ) + + self.prev_command = calc_switch_sheet + + def handle_calc_Type_command(self, calc_Type_command): + + self.init_app() + + if calc_Type_command.what_to_type.__class__.__name__ == "char": + self.write_line_with_one_parameters( + self.current_app, + "TYPE", + "TEXT", + calc_Type_command.what_to_type.input_char, + ) + + elif calc_Type_command.what_to_type.__class__.__name__ == "KeyCode": + self.write_line_with_one_parameters( + self.current_app, + "TYPE", + "KEYCODE", + calc_Type_command.what_to_type.input_key_code, + ) + + self.prev_command = calc_Type_command + + def handle_calc_AutoFill_filter(self, calc_AutoFill_filter): + + self.init_app() + + line = ( + tab * 3 + + self.current_app + + '.executeAction("LAUNCH", mkPropertyValues' + + '({"AUTOFILTER": "", "COL": "' + + str(calc_AutoFill_filter.col_num) + + '"' + + ', "ROW": "' + + str(calc_AutoFill_filter.row_num) + + '"}))\n' + ) + + self.variables.append(line) + self.prev_command = calc_AutoFill_filter + + def handle_calc_Open_Comment(self, calc_Open_Comment): + + line = ( + tab * 3 + + self.current_app + + '.executeAction("COMMENT", mkPropertyValues' + + '({"OPEN": " "}))\n' + ) + + self.variables.append(line) + + self.prev_command = calc_Open_Comment + + def handle_calc_Close_Comment(self, calc_Close_Comment): + + line = ( + tab * 3 + + self.current_app + + '.executeAction("COMMENT", mkPropertyValues' + + '({"CLOSE": " "}))\n' + ) + + self.variables.append(line) + + self.prev_command = calc_Close_Comment + + def handle_calc_SelectMenu_filter(self, calc_SelectMenu_filter): + + self.init_app() + + line = ( + tab * 3 + + self.current_app + + '.executeAction("LAUNCH", mkPropertyValues' + + '({"SELECTMENU": "", "COL": "' + + str(calc_SelectMenu_filter.col_num) + + '"' + + ', "ROW": "' + + str(calc_SelectMenu_filter.row_num) + + '"}))\n' + ) + + self.variables.append(line) + self.prev_command = calc_SelectMenu_filter + + def handle_impress_Type_command(self, impress_Type_command): + + self.init_app() + + if impress_Type_command.what_to_type.__class__.__name__ == "char": + self.write_line_with_one_parameters( + self.current_app, + "TYPE", + "TEXT", + impress_Type_command.what_to_type.input_char, + ) + + elif impress_Type_command.what_to_type.__class__.__name__ == "KeyCode": + self.write_line_with_one_parameters( + self.current_app, + "TYPE", + "KEYCODE", + impress_Type_command.what_to_type.input_key_code, + ) + + self.prev_command = impress_Type_command + + def handle_math_Type_command(self, math_Type_command): + + self.init_app() + if math_Type_command.what_to_type.__class__.__name__ == "char": + self.write_line_with_one_parameters( + self.current_app, + "TYPE", + "TEXT", + math_Type_command.what_to_type.input_char, + ) + + elif math_Type_command.what_to_type.__class__.__name__ == "KeyCode": + self.write_line_with_one_parameters( + self.current_app, + "TYPE", + "KEYCODE", + math_Type_command.what_to_type.input_key_code, + ) + + self.prev_command = math_Type_command + + def handle_draw_Type_command(self, draw_Type_command): + + self.init_app() + if draw_Type_command.what_to_type.__class__.__name__ == "char": + self.write_line_with_one_parameters( + self.current_app, + "TYPE", + "TEXT", + draw_Type_command.what_to_type.input_char, + ) + + elif draw_Type_command.what_to_type.__class__.__name__ == "KeyCode": + self.write_line_with_one_parameters( + self.current_app, + "TYPE", + "KEYCODE", + draw_Type_command.what_to_type.input_key_code, + ) + + self.prev_command = draw_Type_command + + def handle_math_element_selector(self, math_element_selector): + + if( self.math_element_selector_initializer == False ): + # This part is for initializing the element selector in the Math application + self.math_element_selector_initializer = True + line = ( + tab * 4 + + "element_selector" + + ' = MainWindow.getChild("' + + "element_selector" + + '")\n' + ) + self.variables.append(line) + + # This inserts a prefix of 'x' to avoid creating variables with only numeric characters + element_name="x"+str(math_element_selector.element_no) + + self.init_Object(element_name,str(math_element_selector.element_no),"element_selector") + + self.write_line_without_parameters( + str(element_name), "SELECT", "tuple" + ) + + self.prev_command = math_element_selector + + def handle_writer_Comment_leave(self,writer_Comment_leave): + + self.init_app() + + self.init_Object( + writer_Comment_leave.comment_id, writer_Comment_leave.comment_id, "MainWindow" + ) + + self.write_line_with_one_parameters( + writer_Comment_leave.comment_id, "LEAVE", "", "" + ) + + self.prev_command = writer_Comment_leave + + def handle_writer_Comment_show(self,writer_Comment_show): + + self.init_app() + + self.init_Object( + writer_Comment_show.comment_id, writer_Comment_show.comment_id, "MainWindow" + ) + + self.write_line_with_one_parameters( + writer_Comment_show.comment_id, "SHOW", "", "" + ) + + self.prev_command = writer_Comment_show + + def handle_writer_Comment_hide(self,writer_Comment_hide): + + self.init_app() + + self.init_Object( + writer_Comment_hide.comment_id, writer_Comment_hide.comment_id, "MainWindow" + ) + + self.write_line_with_one_parameters( + writer_Comment_hide.comment_id, "HIDE", "", "" + ) + + self.prev_command = writer_Comment_hide + + def handle_writer_Comment_delete(self,writer_Comment_delete): + + self.init_app() + + self.init_Object( + writer_Comment_delete.comment_id, writer_Comment_delete.comment_id, "MainWindow" + ) + + self.write_line_with_one_parameters( + writer_Comment_delete.comment_id, "DELETE", "", "" + ) + + self.prev_command = writer_Comment_delete + + def handle_writer_Comment_setresolved(self,writer_Comment_setresolved): + + self.init_app() + + self.init_Object( + writer_Comment_setresolved.comment_id, writer_Comment_setresolved.comment_id, "MainWindow" + ) + + self.write_line_with_one_parameters( + writer_Comment_setresolved.comment_id, "RESOLVE", "", "" + ) + + self.prev_command = writer_Comment_setresolved + + def handle_setZoom_command(self, setZoom_command): + + self.init_app() + + self.write_line_with_one_parameters( + self.current_app, "SET", "ZOOM", setZoom_command.zoom_value + ) + + self.prev_command = setZoom_command + + def Generate_UI_test(self): + line = "\n\n# vim: set shiftwidth=4 softtabstop=4 expandtab:" + self.variables.append(line) + + for line in self.variables: + self.output_stream.write(str(line)) + + def do_nothing(self, Command): + line = "to be added in the future" + + def __del__(self): + self.output_stream.close() + + +def main(): + args = parse_args() + ui_logger = ul_Compiler(args.input_address, args.output_address) + ui_logger.compile() + for statement in ui_logger.log_lines.commands: + print(statement) + ui_logger.Generate_UI_test() + del ui_logger + + +if __name__ == "__main__": + main() diff --git a/uitest/ui_logger_dsl/example.ul b/uitest/ui_logger_dsl/example.ul new file mode 100644 index 0000000000..d82c71a29f --- /dev/null +++ b/uitest/ui_logger_dsl/example.ul @@ -0,0 +1,34 @@ +Start writer +Send UNO Command (".uno:UpdateInputFields") +Send UNO Command (".uno:FontDialog") +Open Modal CharacterPropertiesDialog +Choose Tab number 0 in 'tabcontrol' from CharacterPropertiesDialog +Select in 'westfontnamelb-cjk' ComboBox item number 66 from CharacterPropertiesDialog +Choose Tab number 1 in 'tabcontrol' from CharacterPropertiesDialog +Select element with position 3 in 'effectslb' fromCharacterPropertiesDialog +Toggle 'shadowcb' CheckBox from CharacterPropertiesDialog +Choose Tab number 2 in 'tabcontrol' from CharacterPropertiesDialog +Select '270deg' RadioButton from CharacterPropertiesDialog +Select '90deg' RadioButton from CharacterPropertiesDialog +Increase 'scalewidthsb' from CharacterPropertiesDialog +Increase 'scalewidthsb' from CharacterPropertiesDialog +Increase 'scalewidthsb' from CharacterPropertiesDialog +Decrease 'scalewidthsb' from CharacterPropertiesDialog +Decrease 'scalewidthsb' from CharacterPropertiesDialog +Decrease 'scalewidthsb' from CharacterPropertiesDialog +Toggle 'pairkerning' CheckBox from CharacterPropertiesDialog +Click on 'cancel' from CharacterPropertiesDialog +Close Dialog +Type on writer {"TEXT": "a"} +Type on writer {"TEXT": "n"} +Type on writer {"TEXT": "a"} +Type on writer {"TEXT": " "} +Type on writer {"TEXT": "a"} +Type on writer {"TEXT": "h"} +Type on writer {"TEXT": "m"} +Type on writer {"TEXT": "e"} +Type on writer {"TEXT": "d"} +Select from Pos 4 to Pos 9 +Open Modal QuerySaveDialog +Click on 'discard' from QuerySaveDialog +Close Dialog
\ No newline at end of file diff --git a/uitest/ui_logger_dsl/starter_commands.tx b/uitest/ui_logger_dsl/starter_commands.tx new file mode 100644 index 0000000000..b581cf7a34 --- /dev/null +++ b/uitest/ui_logger_dsl/starter_commands.tx @@ -0,0 +1,10 @@ +// This file is for the starter commands when you open any office application. +// Button clicks for opening an application are automatically translated as coming from a starter button. + +StarterCommand: + 'Start' program_name=Program +; + +Program: + "writer"|"calc"|"impress"|"draw"|"math"|"database" +; diff --git a/uitest/ui_logger_dsl/type_options.tx b/uitest/ui_logger_dsl/type_options.tx new file mode 100644 index 0000000000..b106e7f038 --- /dev/null +++ b/uitest/ui_logger_dsl/type_options.tx @@ -0,0 +1,9 @@ +Type_options: + char | KeyCode +; +char: + '{' '"TEXT":' input_char=STRING '}' +; +KeyCode: + '{' '"KEYCODE":' input_key_code=STRING '}' +; diff --git a/uitest/ui_logger_dsl/ui_logger_dsl_grammar.tx b/uitest/ui_logger_dsl/ui_logger_dsl_grammar.tx new file mode 100644 index 0000000000..717fb7172f --- /dev/null +++ b/uitest/ui_logger_dsl/ui_logger_dsl_grammar.tx @@ -0,0 +1,29 @@ +/* + This file is for defining the DSL grammar. + This file imports all grammar rules from all the other files. + The compiler works with this file. + Each imported file has comments related to its content. +*/ + +import dialog_commands +import starter_commands +import uno_commands +import UI_Object_commands +import Special_commands +import General_commands + +UILogger: + commands*=Command +; + +Command: + UNOCommand | StarterCommand | UIObjectCommand | DialogCommand | + SpecialCommand | GeneralCommand | Comment +; +/* + The Comment rule is for having an ability to write a comment, + if you want to write a test case in the DSL +*/ +Comment: + /\/\/.*$/ +; diff --git a/uitest/ui_logger_dsl/uno_commands.tx b/uitest/ui_logger_dsl/uno_commands.tx new file mode 100644 index 0000000000..597c855cb8 --- /dev/null +++ b/uitest/ui_logger_dsl/uno_commands.tx @@ -0,0 +1,20 @@ +/* + This file is for the grammar of the UNO commands. + It has two modes: one with parameters and one without. +*/ + +UNOCommand: + 'Send UNO Command' '(' uno_command_name=STRING ')' (parameters=parameter)? +; + +parameter: + '{' parameter_data *= data ','? '}' +; + +data: + ','? key=STRING ':' value= value_type +; + +value_type: + INT|ID +; diff --git a/uitest/uitest/bisecting.py b/uitest/uitest/bisecting.py new file mode 100644 index 0000000000..938baaa2f9 --- /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 0000000000..0bb6d3ecdb --- /dev/null +++ b/uitest/uitest/framework.py @@ -0,0 +1,79 @@ +# -*- 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, connection=None): + unittest.TestCase.__init__(self, test_name) + self.opts = opts + self.parent_connection = connection + self.connection = None + + def getConnection(self): + if self.parent_connection: + return self.parent_connection + return self.connection + + def setUp(self): + self.setSignalHandler() + if not self.getConnection(): + self.connection = PersistentConnection(self.opts) + self.connection.setUp() + self.xContext = self.getConnection().getContext() + self.xUITest = self.xContext.ServiceManager.createInstanceWithContext( + "org.libreoffice.uitest.UITest", self.xContext) + + self.ui_test = UITest(self.xUITest, self.xContext) + if self.parent_connection: + self.ui_test.set_use_dispose(False) + 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) + + if self.connection: + self.connection.tearDown() + finally: + self.resetSignalHandler() + if self.connection: + self.connection.kill() + + def signalHandler(self, signum, frame): + if self.getConnection(): + self.getConnection().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 0000000000..5a3aeff22c --- /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 0000000000..db2bc1828b --- /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 +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 + self.use_dispose = True + + def set_use_dispose(self, use_dispose): + self.use_dispose = use_dispose + + 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 get_default_sleep(self): + return DEFAULT_SLEEP + + 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) + + @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", load_props=()): + with EventListener(self._xContext, eventName) as event: + component = self.get_desktop().loadComponentFromURL(url, "_default", 0, load_props) + 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, load_props=()): + try: + yield self.load_component_from_url(url, "OnLoad", load_props) + finally: + self.close_doc() + + # Resets the setting to the old value at exit + @contextmanager + def set_config(self, path, new_value): + xChanges = self._xContext.ServiceManager.createInstanceWithArgumentsAndContext('com.sun.star.configuration.ReadWriteAccess', ("",), self._xContext) + try: + old_value = xChanges.getByHierarchicalName(path) + xChanges.replaceByHierarchicalName(path, new_value) + xChanges.commitChanges() + yield + finally: + xChanges.replaceByHierarchicalName(path, old_value) + xChanges.commitChanges() + + # 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() + + def wait_and_yield_dialog(self, event, parent, close_button): + while not event.executed: + time.sleep(DEFAULT_SLEEP) + dialog = self._xUITest.getTopFocusWindow() + if parent == dialog: + raise Exception("executing the action did not open the dialog") + try: + yield dialog + except: + if not close_button: + if 'cancel' in dialog.getChildren(): + self.close_dialog_through_button(dialog.getChild("cancel")) + raise + finally: + if close_button: + self.close_dialog_through_button(dialog.getChild(close_button)) + + # 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: + xDialogParent = self._xUITest.getTopFocusWindow() + if not self._xUITest.executeDialog(command): + raise Exception("Dialog not executed for: " + command) + yield from self.wait_and_yield_dialog(event, xDialogParent, close_button) + + @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() + + xDialogParent = self._xUITest.getTopFocusWindow() + with EventListener(self._xContext, event_name) as event: + ui_object.executeAction(action, parameters) + yield from self.wait_and_yield_dialog(event, xDialogParent, close_button) + + # 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: + 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 + if self.use_dispose: + component.dispose() + else: + if component.isModified(): + with self.execute_dialog_through_command('.uno:CloseDoc', close_button="discard") as xConfirmationDialog: + pass + else: + self._xUITest.executeCommand(".uno:CloseDoc") + 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 0000000000..e69de29bb2 --- /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 0000000000..e34304c690 --- /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 0000000000..17b682b0e0 --- /dev/null +++ b/uitest/uitest/uihelper/common.py @@ -0,0 +1,68 @@ +# -*- 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 +from contextlib import contextmanager +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() + +@contextmanager +def change_measurement_unit(UITestCase, unit): + try: + launch_option_dialog(UITestCase, unit) + yield + finally: + # change to default value + launch_option_dialog(UITestCase, 'Inch') + +def launch_option_dialog(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 0000000000..f2be76de40 --- /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 0000000000..488b202f60 --- /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.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: |