GSoC'20 final report & Project Documentation
Overview
The idea was about The UI testing framework in LibreOffice. The UITesting Framework is based on introspection code in c++ interacting with a testing framework in python through a simple UNO interface. To identify objects we use the ids that we introduced for loading dialogs from UI files. We were having unsupported items list in LibreOffice UITesting Framework. So The project mainly goals is to Extend the ability of the existing UI testing framework to support the unsupported items that exist now. So the work done on this list to decrease number of items in it. As we also have a domain specific language
that we used to log the events in its syntax and a Logger system that
supports the UI elements of Visual Component Library and some
application-specific events can also logged with the DSL syntax. Then to Increase the
ability of the DSL that used for testing by Be able to log more complex
events and the events of the new added items that were unsupported
before. Also Be able to generate more meaningful test cases with the DSL
not just replaying the user action from the Logger.
Background
Background
To understand how the project works you should know 3 things:
- How UITesting Framework works?
- The current UI testing framework is built using unittest which is Unit testing framework existing in python libraries. It supports test automation.
- So as we can see here that our UITestCase class inherited from (unittest.TestCase) so mainly it has all the methods that unittest.TestCase has.
- If we want to add support for new items in the UITesting framework that we have we should first add the introspection code then we should add functions that use this code in the python UITesting framework code then we should make sure that this code is working well.
- The introspection code interacts with a testing framework in python through a simple mostly un-typed UNO interface.
- So first let's discuss how we could extend the introspection part. There are two types of elements. First one is the one that UI elements that inherit from vcl::Window. And the other type is the non-vcl::Window UI objects.
- For the objects that inherit from vcl::Window, so basically most of our GUI elements. Most of them are already done. But understanding how it works makes it easy to plan what to do for other items. On the introspection side the corresponding class is WindowUIObject, and all classes inheriting from vcl::Window provide the virtual method in this search GetUITestFactory that returns a factory function for the introspection library. So adding support requires three steps:
- adding a GetUITestFactory method to the UI object class.
- adding the corresponding factory method.
- implementing the introspection wrapper class.
- for the objects that are non-vcl::Window UI objects that need to be wrapped which makes everything a bit more complicated. The basic idea behind supporting such pseudo objects is to hold a pointer to the corresponding vcl::Window based UI object through a VclPtr and a way to get the correct property that represents the object we want to cover. Then we can add the functions in C++ that will be called from the python code that will be written to test this item like for example what we have here.
- After this point we need to add the functions in the python part to be able to use it and it will call the functions that we add in the introspection code in the C++ part. So we can check when replaying if the action is done so that means the item was added successfully.
- How the DSL Works?
- Domain-Specific-Language which is DSL is a way of defining new grammar for a new language to be used later by interpreting this langauge and generate another code or execute the code on specific hardware.
- In my last year GSoC project with LibreOffice I implemented a DSL that is used now to log all the user action in it's syntax and convert these logs to a test case using the implemented interpreter. This vide talk more about the project.
- Note that to run the project you need to have a python2.7 with textX library installed into it. We will descripe later how to use but this video will be very helpful.
- If you need to understand how the project is written and mainly the part in this folder: https://opengrok.libreoffice.org/xref/core/uitest/ui_logger_dsl/ I think you can easily see the tutorials here of TextX. The basics of this tutorial that you can understand with 30 mins can let you understand a lot of the project. Also The grammar descriped here.
- How The Logger Works?
- Logging in LibreOffice is mainly handled by a class called UITestLogger, defined here.The
logger class logs the user actions only if the member flag mbValid is
set to true. The flag can be turned on by setting the environment
variable LO_COLLECT_UIINFO to a file name where the logs should be
collected (see the constructor of UITestLogger, defined here). we make it equal test.log in last section.
- Their is a pointer to an instance of UITestLogger class here. To use the logger object, the static function getInstance can be used to get access to the pointer.
- The function logAction, defined in the same class, is used to log events
from the classes which extend the class vcl::Control. The log
statements corresponding to a particular class can be found in the
function get_action of the UI wrapper classes. The log statements get
generated when VCL events get broadcasted.
- For other classes, we have functions named collectUIInformation spread in different places of the code (This OpenGrok search might be helpful) and these different places are the functions that execute the supported events in different applications. These functions collect information about an event and store it in a struct EventDescription (defined here) and pass it to another function of UITestLogger, called logEvent. The logEvent function constructs a log string using the information provided by the given event description, and finally logs the statement using the file stream maStream.
Achievements
The work was done by taking items from the list one by one. The time of working on each item vary so maybe an item takes 3 days another takes one week another one takes 3 weeks. So the Achievements was taking as much items as I could from the list. After adding an item I used to write a test case to test this item to make sure that the support working well now. Also if we have a bug that related to this part of the code I try to help on writing it's test case. I will provide here examples for each item how to use it and how to test it.
The added Items:
- Calc - Set Zoom
used by:- gridwin.executeAction("SET", mkPropertyValues({"ZOOM": "100"}))
- self.assertEqual(get_state_as_dict(gridwin)["Zoom"], "100")
- Calc / Format cell / background color selector
used by:- color_selector.executeAction("CHOOSE", mkPropertyValues({"POS": "2"}))
- self.assertEqual(get_state_as_dict(color_selector)["CurrColorId"], "2")
- self.assertEqual(get_state_as_dict(color_selector)["CurrColorPos"], "1")
- self.assertEqual(get_state_as_dict(color_selector)["ColorsCount"], "12")
- self.assertEqual(get_state_as_dict(color_selector)["ColCount"], "12")
- self.assertEqual(get_state_as_dict(color_selector)["ColorText"], "Chart 2")
- self.assertEqual(get_state_as_dict(color_selector)["RGB"], "(255,66,14)")
- ComboBoxUIObject - Select by Text - Set - Clear
used by:- xpaletteselector.executeAction("SELECT", mkPropertyValues({"TEXT": "chart-palettes"}))
- xpaletteselector.executeAction("SET", mkPropertyValues({"TEXT": "chart-palettes"}))
- xpaletteselector.executeAction("CLEAR", mkPropertyValues({}))
- Writer - comments
used by:- xComment1.executeAction("TYPE", mkPropertyValues({"TEXT": "any Comment"}))
- xComment1.executeAction("LEAVE", mkPropertyValues({}))
- xComment1.executeAction("RESOLVE", mkPropertyValues({}))
- xComment1.executeAction("SELECT", mkPropertyValues({"FROM": "0", "TO": "4"}))
- xComment1.executeAction("HIDE", mkPropertyValues({}))
- xComment1.executeAction("SHOW", mkPropertyValues({}))
- xComment1.executeAction("DELETE", mkPropertyValues({}))
- self.assertEqual(get_state_as_dict(xComment1)["Text"], "any Comment")
- self.assertEqual(get_state_as_dict(xComment1)["SelectedText"], "any Comment")
- self.assertEqual(get_state_as_dict(xComment1)["Resolved"], "false" )
- self.assertEqual(get_state_as_dict(xComment1)["Author"], "Unknown Author" )
- self.assertEqual(get_state_as_dict(xComment1)["ReadOnly"], "false" )
- self.assertEqual(get_state_as_dict(xComment1)["Visible"], "true" )
- Calc - Dropdown items
used by:- gridwin.executeAction("LAUNCH", mkPropertyValues({"SELECTMENU": "", "COL": "2", "ROW": "9"})) Then Select the TreeList UI Object
- xWin = self.xUITest.getTopFocusWindow()
- xlist = xWin.getChild("list")
- xListItem = xlist.getChild('0')
- xListItem.executeAction("DOUBLECLICK" , mkPropertyValues({}) )
- SvxNumValueSet
used by:- obj_name.executeAction("CHOOSE", mkPropertyValues({"POS": "pos_num"}))
- self.assertEqual(get_state_as_dict(color_selector)["ItemsCount"], "2")
- self.assertEqual(get_state_as_dict(color_selector)["SelectedItemPos"], "1")
- self.assertEqual(get_state_as_dict(color_selector)["SelectedItemId"], "12")
- self.assertEqual(get_state_as_dict(color_selector)["ItemText"], "any")
- Vertical TabControl
used by:- xtab.executeAction("SELECT", mkPropertyValues({"POS": "0"}))
- self.assertEqual(get_state_as_dict(xtab)["CurrPageTitel"], "Mail")
- self.assertEqual(get_state_as_dict(xtab)["CurrPagePos"], "1")
- self.assertEqual(get_state_as_dict(xtab)["PageCount"], "4")
- Calc - Comments
used by:- gridwin.executeAction("COMMENT",mkPropertyValues({"OPEN":""}))
- gridwin.executeAction("TYPE",mkPropertyValues({"TEXT":"any"}))
- gridwin.executeAction("COMMENT",mkPropertyValues({"CLOSE":""}))
- gridwin.executeAction("COMMENT",mkPropertyValues({"SETTEXT":"any"}))
- get_state_as_dict(gridwind)["CurrentCellCommentText"]
- ToolBox Objects
used by:- variable_name.executeAction("CLICK", mkPropertyValues({"POS": poition_x }))
- self.assertEqual(get_state_as_dict(xfind_bar)["CurrSelectedItemID"], "5")
- self.assertEqual(get_state_as_dict(xfind_bar)["CurrSelectedItemText"], "Find All")
- self.assertEqual(get_state_as_dict(xfind_bar)["CurrSelectedItemCommand"], ".uno:FindAll")
- self.assertEqual(get_state_as_dict(xfind_bar)["ItemCount"], "14")
- Menu Button objects
used by:- var_name.executeAction("OPENLIST", mkPropertyValues({}))
- var_name.executeAction("CLOSELIST", mkPropertyValues({}))
- var_name.executeAction("OPENFROMLIST", mkPropertyValues({"POS": "0" }))
- get_state_as_dict(var_name)["Label"]
- ValueSet
used by:- obj_name.executeAction("CHOOSE", mkPropertyValues({"POS": "4"}))
- self.assertEqual(get_state_as_dict(FontWorkSelector)["ItemsCount"], "36")
- self.assertEqual(get_state_as_dict(FontWorkSelector)["SelectedItemId"], "3")
- self.assertEqual(get_state_as_dict(FontWorkSelector)["SelectedItemPos"], "2")
The Support is already merged for all these types of objects and also I added a test case for each object in this list and Also the uiLogger and DSL support is added for all items in this list.
Code
all my merged patches can be found here : merged patches
For more details :
For more details :
- uitest: Fix small issue in UI Logger DSL grammar
- uitest: Fix some issue in the UI Logger DSL core
- uitest: avoid defining the same object multiple time in in math
- uitest: Add support for Calc - Set Zoom
- uitest: Add support for Calc / Format cell / background color selector
- uitest: Add support for Writer comments
- uitest: Add demo for Writer-comments
- uitest : Add support for Dropdown items in grid window
- uitest: Fix small issue in UI Logger DSL grammar "EditUIObject"
- uitest : Add Support for SvxNumValueSet
- uitest : Add demo for SvxNumValueSet support
- uitest: Add support for vertical TabControl Object
- uitest : Add support to Calc - comments
- uitest : Add demo for Calc-comments
- uitest : Fix the old demo of writer comments
- uitest : Add support for "ToolBox" Objects for example" "bottom find bar"
- uitest : Add demo for "bottom find bar" using ToolBox support
- uitest : extend the ComboBox UIObject
- uitest : sw: Add UItest for Hyperlink Dialog
- uitest : Avoid any timing issue in test_insert_hyperlink
- uitest : Add support for Menu Button objects
- uitest : Add demo for gear button menu in Tools->Customize
- uitest : Add support for Normal ValueSet
- Add demo for FontWork Selector
- uilogger : Add support in the Logger and DSL for the LAUNCHMENU in CALC
- uilogger : Add support in the Logger and DSL for the ToolBox
- uilogger : Add support in the Logger and DSL for ZOOM in CALC
- uilogger : Add support in the Logger and DSL for ValueSet
- uitest : Change all the ValueSet uitest statments to CHOOSE
- uilogger : Add support in the Logger and DSL for Calc-Comments
- uilogger : Add support in the Logger and DSL for Writer-Comments
- uilogger : Add support in the Logger and DSL for MenuBtn
- uilogger : Add support in the Logger and DSL for Vertical Tab
When This project will be Helpful?
This project will be really helpfull if:
- You are tring to write a test case that test a specific part in LibreOffice and you don't know where to start.
- You can open the logger.
- start logging all the actions that you did around the part you will test.
- run the compiler and get an pre-test that you can build your work on it.
- You want to know the ids of the dialog member and search with it in the code.
- Log actions in this dialog and use OpenGrok for search.
- You want to know all the called uno command when you press on anything.
- You can do this by find all the uno commands in the generated pre-test.
The extention added this year by ading support for more items let us has more coverage on most of the UIObjects and more flexibility and power in using our UITest Framework also it let us make better test cases for all the open bugs in the future.
How to Use the logger?
First you should update the master branch:
1) use these lines in .bashrc or .cshrc:
2) Launch LibreOffice like
3) Simulate what you want to do with the mouse
- export PYTHONPATH=/"Path of LibreOffice repo"/instdir/program/
- export URE_BOOTSTRAP=file:///"Path of LibreOffice repo"/instdir/program/fundamentalrc
- export SAL_USE_VCLPLUGIN=gen
2) Launch LibreOffice like
LO_COLLECT_UIINFO="test.log" SAL_USE_VCLPLUGIN=gen instdir/program/soffice
3) Simulate what you want to do with the mouse
4) Close LibreOffice
5) Open the resulting file in instdir/uitest/test.log
6) Enter the UI logger directory with this Command:
cd uitest/ui_logger_dsl/
7) make sure that your python has textX library installed:
8) Use the following Command
python dsl_core.py <path_to_log_file> <path_to_a_new_python_file>
and <path_to_a_new_python_file> can be a location of your choice where you would like to see the generated code.
You can follow all these steps in this youtube video.
Remaining Tasks
We still have some unfinished items in the unsupported item list and it maybe increased in the future. So some of these items need more work and investigation. i will try to work on my free time to empty all the items in this list.
Thank You!
Thank you for giving me the opportunity to work on this project. I
learned a lot from this project. I will definitely like to spend more
time with LibreOffice community in future. I like to thank also my
mentors they help me and guide me to learn a lot and the community was
always active and helpfull.
Comments
Post a Comment