GSoC'20 final report & Project Documentation



    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.


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: 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.


    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"}))
    tested by:
    • self.assertEqual(get_state_as_dict(gridwin)["Zoom"], "100")

  • Calc / Format cell / background color selector
    used by:
    • color_selector.executeAction("CHOOSE", mkPropertyValues({"POS": "2"}))
    tested by:
    • 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({}))
    tested by:
    • 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"}))
    tested by:
    • 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"}))
    tested by:
    • 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"}))  
    tested by:
    • get_state_as_dict(gridwind)["CurrentCellCommentText"]

  • ToolBox Objects
    used by:
    • variable_name.executeAction("CLICK", mkPropertyValues({"POS": poition_x }))
    tested by:
    • 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" })) 
    tested by:
    • get_state_as_dict(var_name)["Label"]

  • ValueSet
    used by:
    • obj_name.executeAction("CHOOSE", mkPropertyValues({"POS": "4"}))
    tested by:
    • 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.


all my merged patches can be found here : merged patches

For more details :
  1.   uitest: Fix small issue in UI Logger DSL grammar  
  2.   uitest: Fix some issue in the UI Logger DSL core  
  3.   uitest: avoid defining the same object multiple time in in math  
  4.   uitest: Add support for Calc - Set Zoom  
  5.   uitest: Add support for Calc / Format cell / background color selector  
  6.   uitest: Add support for Writer comments  
  7.   uitest: Add demo for Writer-comments  
  8.   uitest : Add support for Dropdown items in grid window 
  9.   uitest: Fix small issue in UI Logger DSL grammar "EditUIObject"  
  10.   uitest : Add Support for SvxNumValueSet  
  11.   uitest : Add demo for SvxNumValueSet support  
  12.   uitest: Add support for vertical TabControl Object  
  13.   uitest : Add support to Calc - comments  
  14.   uitest : Add demo for Calc-comments  
  15.   uitest : Fix the old demo of writer comments
  16.   uitest : Add support for "ToolBox" Objects for example" "bottom find bar"  
  17.   uitest : Add demo for "bottom find bar" using ToolBox support  
  18.   uitest : extend the ComboBox UIObject
  19.   uitest : sw: Add UItest for Hyperlink Dialog  
  20.   uitest : Avoid any timing issue in test_insert_hyperlink  
  21.   uitest : Add support for Menu Button objects  
  22.   uitest : Add demo for gear button menu in Tools->Customize  
  23.   uitest : Add support for Normal ValueSet
  24.   Add demo for FontWork Selector  
  25.   uilogger : Add support in the Logger and DSL for the LAUNCHMENU in CALC  
  26.   uilogger : Add support in the Logger and DSL for the ToolBox  
  27.   uilogger : Add support in the Logger and DSL for ZOOM in CALC  
  28.   uilogger : Add support in the Logger and DSL for ValueSet  
  29.   uitest : Change all the ValueSet uitest statments to CHOOSE
  30.   uilogger : Add support in the Logger and DSL for Calc-Comments  
  31.   uilogger : Add support in the Logger and DSL for Writer-Comments  
  32.   uilogger : Add support in the Logger and DSL for MenuBtn  
  33.   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:
  • 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 <path_to_log_file> <path_to_a_new_python_file>
<path_to_log_file> should be replaced with something like SourceDirectory/core/instdir/uitest/test.log
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.


Popular posts from this blog

Week 8 Report

Week 9 Report