Repository: robocorp/rpaframework

Issue:
RPA.Windows 2.0.0 cannot find menu_items past a certain depth
![image](https://user-images.githubusercontent.com/1869202/151804379-514e66e2-267a-403b-89e8-d4a419707f80.png)



Discussion:
Possible bug, but needs to be investigated further.

Resolving patch:
diff --git a/invocations/analysis.py b/invocations/analysis.py
--- a/invocations/analysis.py
+++ b/invocations/analysis.py
@@ -32,7 +32,7 @@
 
 MAIN_README = MAIN_PACKAGE / "README.rst"
 
-EXCLUDE_ROBOT_TESTS = ["skip"]
+EXCLUDE_ROBOT_TESTS = ["skip", "manual"]
 if platform.system() == "Windows":
     EXCLUDE_ROBOT_TESTS.append("posix")
 else:
@@ -134,16 +134,20 @@ def test_python(ctx, asynchronous=None):
     aliases=["testrobot"],
     help={
         "robot": (
-            "Only run a specific .robot file, any selected file "
-            "must have the format 'test_<robot name>.robot'."
+            "Only run a specific .robot file, any selected file must have the format "
+            "'test_<robot name>.robot'. (excludes 'manual' tag only)"
         ),
         "test-name": (
-            "Run only a specific test from the available test suites or robot file."
+            "Run only a specific test from the available test suites or robot file. "
+            "(no exclusions)"
         ),
     },
 )
 def test_robot(ctx, robot=None, test_name=None, asynchronous=None):
-    """Run Robot Framework tests."""
+    """Run Robot Framework tests.
+
+    Skips the following tags by default: skip, manual. (usually with GH CI runs)
+    """
     if getattr(ctx, "is_meta", False):
         shell.invoke_each(ctx, f"code.test-robot{' -a' if asynchronous else ''}")
     else:
@@ -159,22 +163,27 @@ def test_robot(ctx, robot=None, test_name=None, asynchronous=None):
         )
         if robot_test_source.exists():
             exclude_list = EXCLUDE_ROBOT_TESTS[:]  # copy of the original list
+            if robot:
+                # Run even skipped tests (but not manual ones) when specifying a test
+                #  robot file explicitly. (during development only)
+                robot_test_source /= f"test_{robot}.robot"
+                exclude_list.remove("skip")
             if test_name:
-                # Run specific explicit task without exclusion. (during development)
+                # Run specific explicit test case without any exclusion. (during
+                #  development only)
+                robot_test = f'--test "{test_name}"'
                 exclude_list.clear()
-                robot_test = f' --test "{test_name}" '
             else:
-                # Run all tasks and take into account exclusions. (during CI)
-                robot_test = " "
+                # Run all tasks and take into account remaining exclusions. (during CI
+                #  gate in GitHub flow)
+                robot_test = ""
             exclude_str = " ".join(f"--exclude {tag}" for tag in exclude_list)
             arguments = (
                 f"--runemptysuite --loglevel TRACE --outputdir {robot_test_output} "
                 f"--pythonpath {robot_test_resources}"
             )
-            if robot:
-                robot_test_source /= f"test_{robot}.robot"
-            cmds = f"{arguments} {exclude_str}{robot_test}{robot_test_source}"
-            return shell.run_in_venv(ctx, "robot", cmds, asynchronous=asynchronous)
+            command = f"{arguments} {exclude_str} {robot_test} {robot_test_source}"
+            return shell.run_in_venv(ctx, "robot", command, asynchronous=asynchronous)
         else:
             return shell.run(
                 ctx,
diff --git a/packages/windows/src/RPA/Windows/keywords/elements.py b/packages/windows/src/RPA/Windows/keywords/elements.py
--- a/packages/windows/src/RPA/Windows/keywords/elements.py
+++ b/packages/windows/src/RPA/Windows/keywords/elements.py
@@ -80,7 +80,6 @@ def print_tree(
 
         :param locator: string locator or Control element
         :param max_depth: maximum depth level (defaults to 8)
-        :param encoding: defaults to "utf-8"
         :param capture_image_folder: if None images are not captured
         :param log_as_warnings: if set log messages are visible on the console
         """
diff --git a/packages/windows/src/RPA/Windows/keywords/locators.py b/packages/windows/src/RPA/Windows/keywords/locators.py
--- a/packages/windows/src/RPA/Windows/keywords/locators.py
+++ b/packages/windows/src/RPA/Windows/keywords/locators.py
@@ -3,15 +3,25 @@
 from RPA.core.windows.locators import (
     Locator,
     LocatorMethods,
+    MatchObject,
     WindowsElement,
 )
 
+from RPA.Windows import utils
 from RPA.Windows.keywords import keyword
 from RPA.Windows.keywords.context import with_timeout
 
+if utils.IS_WINDOWS:
+    import uiautomation as auto
+    from uiautomation import TreeNode
+
 
 class LocatorKeywords(LocatorMethods):
-    """Keywords for handling Windows locators"""
+    """Keywords for handling Windows locators."""
+
+    # NOTE(cmin764): Timeout is automatically set to `None` in the upper calls by the
+    #  `with_timeout` decorator, so we alter the behaviour (context timeout setting)
+    #  on the first call only.
 
     @keyword
     @with_timeout
@@ -20,38 +30,39 @@ def get_element(
         locator: Optional[Locator] = None,
         search_depth: int = 8,
         root_element: Optional[WindowsElement] = None,
-        timeout: Optional[float] = None,  # pylint: disable=unused-argument
+        timeout: Optional[float] = None,
     ) -> WindowsElement:
-        """Get Control element defined by the locator.
-
-        Returned element can be used instead of a locator string for
-        keywords accepting `locator`.
+        """Get a Control Windows element defined by the locator.
 
+        The returned element can be used instead of a locator string for other keywords
+        accepting the `locator` parameter.
         Keyword ``Get Attribute`` can be used to read element attribute values.
 
-        If `locator` is *None* then returned `element` will be in order of preference:
+        If `locator` is `None`, then the returned element will be in this priority:
 
-            1. anchor element if that has been set with `Set Anchor`
-            2. current active window if that has been set with `Control Window`
-            3. final option is the `Desktop`
+            1. `root_element` if provided.
+            2. Anchor element if that has been previously set with ``Set Anchor``.
+            3. Current active window if that has been set with ``Control Window``.
+            4. Last resort is the "Desktop" element.
 
-        :param locator: locator as a string or as an element
-        :param search_depth: how deep the element search will traverse (default 8)
-        :param root_element: can be used to set search root element
-        :param timeout: float value in seconds, see keyword
-         ``Set Global Timeout``
-        :return: WindowsElement object
+        :param locator: Locator as a string or as an element object.
+        :param search_depth: How deep the element search will traverse. (default 8)
+        :param root_element: Will be used as search root element object if provided.
+        :param timeout: After how many seconds (float) to give up on search. (see
+            keyword ``Set Global Timeout``)
+        :returns: The identified `WindowsElement` object.
 
-        Example:
+        **Example: Robot Framework**
 
         .. code-block:: robotframework
 
-            ${element} =    Get Element    name:"RichEdit Control"
-            Set Value    ${element}    note to myself
+            *** Tasks ***
+            Set Text Into Notepad Window
+                Windows Run    Notepad
+                Control Window      subname:Notepad
+                ${element} =    Get Element    regex:"Text (E|e)ditor"
+                Set Value    ${element}    note to myself
         """
-        # NOTE(cmiN): Timeout is automatically set to `None` in the upper call by the
-        #  `with_timeout` decorator, so we alter the behaviour (context timeout
-        #  setting) at this level only.
         return super().get_element(
             locator=locator,
             search_depth=search_depth,
@@ -59,6 +70,85 @@ def get_element(
             timeout=timeout,
         )
 
+    @classmethod
+    def _search_siblings(
+        cls, initial_element: WindowsElement, *, locator: Optional[Locator]
+    ) -> List[WindowsElement]:
+        elements = [initial_element]
+
+        element = initial_element
+        while True:
+            next_control = element.item.GetNextSiblingControl()
+            if not next_control:
+                break
+
+            element = WindowsElement(next_control, locator)
+            if initial_element.is_sibling(element):
+                # Every newly found matching element will inherit the offset set in the
+                #  original initially found element.
+                element.item.robocorp_click_offset = (
+                    initial_element.item.robocorp_click_offset
+                )
+                elements.append(element)
+
+        return elements
+
+    def _search_globally(
+        self,
+        initial_element: WindowsElement,
+        *,
+        locator: Optional[Locator],
+        search_depth: int,
+        root_element: Optional[WindowsElement],
+        timeout: Optional[float],
+    ) -> List[WindowsElement]:
+        def get_first_child(ctrl: TreeNode) -> TreeNode:
+            return ctrl.GetFirstChildControl()
+
+        def get_next_sibling(ctrl: TreeNode) -> TreeNode:
+            return ctrl.GetNextSiblingControl()
+
+        def compare(ctrl: TreeNode, _) -> bool:
+            element = WindowsElement(ctrl, locator)
+            return initial_element.is_sibling(element)
+
+        # Take all the elements (no matter their level) starting from a parent as
+        #  search tree root.
+        parent_locator = None
+        if locator:
+            branches = locator.rsplit(MatchObject.TREE_SEP, 1)
+            if len(branches) == 2:
+                # Full locator's parent becomes the root for the search.
+                parent_locator = branches[0]
+        top_element = self.get_element(
+            # If the locator doesn't have a parent (null `parent_locator`) then simply
+            #  rely on the resulting root resolution: root > anchor > window > Desktop.
+            parent_locator,
+            search_depth,
+            root_element,
+            timeout,
+        )
+        # Explore the entire subtree of elements starting with the resulted root above
+        #  and keep only the ones matching the strategies in the last locator branch.
+        tree_generator = auto.WalkTree(
+            top_element.item,
+            getFirstChild=get_first_child,
+            getNextSibling=get_next_sibling,
+            yieldCondition=compare,
+            includeTop=True,
+            maxDepth=search_depth,
+        )
+        elements = []
+        for control, _ in tree_generator:
+            element = WindowsElement(control, locator)
+            # Every newly found matching element will inherit the offset set in the
+            #  original initially found element.
+            element.item.robocorp_click_offset = (
+                initial_element.item.robocorp_click_offset
+            )
+            elements.append(element)
+        return elements
+
     @keyword
     @with_timeout
     def get_elements(
@@ -66,41 +156,56 @@ def get_elements(
         locator: Optional[Locator] = None,
         search_depth: int = 8,
         root_element: Optional[WindowsElement] = None,
-        timeout: Optional[float] = None,  # pylint: disable=unused-argument
+        timeout: Optional[float] = None,
+        siblings_only: bool = True,
     ) -> List[WindowsElement]:
-        """Get list of elements matching locator.
+        """Get a list of elements matching the locator.
+
+        By default, only the siblings (similar elements on the same level) are taken
+        into account. In order to search globally, turn `siblings_only` off, but be
+        aware that this will take more time to process.
+        For more details on the rest of parameters, take a look at the ``Get Element``
+        keyword.
 
-        :param locator: locator as a string or as an element
-        :param search_depth: how deep the element search will traverse (default 8)
-        :param root_element: can be used to set search root element
-        :param timeout: float value in seconds, see keyword
-         ``Set Global Timeout``
-        :return: list of WindowsElement objects
+        :param locator: Locator as a string or as an element object.
+        :param search_depth: How deep the element search will traverse. (default 8)
+        :param root_element: Will be used as search root element object if provided.
+        :param timeout: After how many seconds (float) to give up on search. (see
+            keyword ``Set Global Timeout``)
+        :param siblings_only: Filter for elements on the same level as the initially
+            found one. Turn it off for a global search. (`True` by default)
+        :returns: A list of matching `WindowsElement` objects.
 
-        Example:
+        **Example: Robot Framework**
 
         .. code-block:: robotframework
 
-            Set Anchor    id:DataGrid
-            ${elements}=    Get Elements    type:HeaderItem
-            FOR    ${el}    IN    @{elements}
-                Log To Console    ${el.Name}
-            END
+            *** Tasks ***
+            Get Headers On Same Level
+                Set Anchor      id:DataGrid
+                @{elements} =   Get Elements    type:HeaderItem
+                FOR    ${element}    IN    @{elements}
+                    Log To Console    ${element.name}
+                END
+
+            Get All Calculator Buttons Matching Expression
+                Windows Run    Calc
+                Control Window    subname:Calc
+                @{buttons} =    Get Elements    class:Button regex:.*o.*
+                ...     siblings_only=${False}
+                Log List    ${buttons}
+                ${length} =     Get Length      ${buttons}
+                Log To Console      Number of buttons: ${length}
         """
-        elements = []
-        initial_element = element = self.get_element(
-            locator, search_depth, root_element, timeout=timeout
+        initial_element = self.get_element(locator, search_depth, root_element, timeout)
+
+        if siblings_only:
+            return self._search_siblings(initial_element, locator=locator)
+
+        return self._search_globally(
+            initial_element,
+            locator=locator,
+            search_depth=search_depth,
+            root_element=root_element,
+            timeout=timeout,
         )
-        elements.append(initial_element)
-        while True:
-            next_control = element.item.GetNextSiblingControl()
-            if next_control:
-                element = WindowsElement(next_control, locator)
-                if initial_element.is_sibling(element):
-                    element.item.robocorp_click_offset = (
-                        initial_element.item.robocorp_click_offset
-                    )
-                    elements.append(element)
-            else:
-                break
-        return elements


Test patch:
diff --git a/packages/windows/tests/robot/test_windows.robot b/packages/windows/tests/robot/test_windows.robot
--- a/packages/windows/tests/robot/test_windows.robot
+++ b/packages/windows/tests/robot/test_windows.robot
@@ -1,47 +1,137 @@
 *** Settings ***
+Library           Collections
 Library           Process
 Library           RPA.Windows
 Library           String
-Task Setup        Set Wait Time    0.7
+
+Force Tags      skip
+Test Setup      Set Wait Time    0.7
+
 
 *** Variables ***
 # Not used as vars, but make sure you have these in PATH.
-${EXE_UIDEMO}     uidemo.exe
-${EXE_CALCULATOR}    calc.exe
-${EXE_NOTEPAD}    notepad.exe
-${EXE_SPOTIFY}    Spotify.exe
-${EXE_SAGE50}     sage.exe
-${SPOTIFY_PLAYCONTROL}    name:"Player controls" > control:ButtonControl and name:
-${SPOTIFY_PLAY}    ${SPOTIFY_PLAYCONTROL}Play
-${SPOTIFY_PAUSE}    ${SPOTIFY_PLAYCONTROL}Pause
-${SPOTIFY_NEXT}    ${SPOTIFY_PLAYCONTROL}Next
-${SPOTIFY_Prev}    ${SPOTIFY_PLAYCONTROL}Prev
+${EXE_UIDEMO}       UIDemo.exe
+${EXE_CALCULATOR}   calc.exe
+${EXE_SPOTIFY}      Spotify.exe
+
 ${TIMEOUT}        1
 
+
+*** Keywords ***
+Calculator Teardown
+    Send Keys    keys={Esc}
+    Sleep    1s
+    Send Keys    keys={Alt}1
+    Sleep    1s
+
+Calculator Temperature Converter View
+    Click    id:TogglePaneButton
+    Send Keys    keys={PAGEDOWN}    wait_time=0.1    # to see the Temperature view
+    ${temp} =    Set Variable    id:Temperature    # works on Windows 10 and lower
+    ${ver} =    Get OS Version
+    IF    "${ver}" == "11"
+        ${temp} =    Set Variable    name:"Temperature Converter"
+    END
+    Click    ${temp}
+
+Calculator Standard View
+    Click    id:TogglePaneButton
+    Send Keys    keys={PAGEUP}    wait_time=0.1    # to see the Standard view
+    ${std} =    Set Variable    id:Standard
+    ${ver} =    Get OS Version
+    IF    "${ver}" == "11"
+        ${std} =    Set Variable    name:"Standard Calculator"
+    END
+    Click    ${std}
+
+Select Temperature Unit
+    [Arguments]    ${unit}    ${degrees}
+    # Select Unit
+    Click    id:Units1
+    Click    id:Units1 > name:${unit}
+    Send Keys    keys=${degrees}
+    Log To Console    Set ${unit}: ${degrees}
+    Send Keys    keys={Esc}
+
+Get Temperature Values
+    [Arguments]    ${keys}
+    ${values} =    Split String    ${keys}    ,
+    FOR    ${val}    IN    @{values}
+        ${temperature} =    Get Attribute    id:UnitConverterRootGrid > type:TextControl and regex:.*\\s${val}    Name
+        Log To Console    ${temperature}
+    END
+
+Calculator button actions
+    Control Window    Calculator type:Window
+    Click    id:num9Button
+    Click    id:num6Button
+    Click    id:plusButton
+    Click    id:num4Button
+    Click    id:equalButton
+    ${result} =    Get Attribute    id:CalculatorResults    Name
+    Log To Console    \n${result}
+    Send Keys    keys={Esc}
+
+Calculator with keys
+    Control Window    Calculator
+    Click    id:clearButton
+    Send Keys    keys=96+4=    interval=0.1
+    ${result} =    Get Attribute    id:CalculatorResults    Name
+    Log To Console    ${result}
+    ${buttons} =    Get Elements    type:Group and name:"Number pad" > type:Button
+    FOR    ${button}    IN    @{buttons}
+        Log To Console    ${button}
+    END
+    Length Should Be    ${buttons}    11    msg=From 0 to 9 and decimlar separator
+
+Keep open a single Notepad
+    Set Global Timeout    ${TIMEOUT}
+    ${closed} =    Set Variable    0
+    ${run} =    Run Keyword And Ignore Error    Close Window    subname:Notepad control:WindowControl
+    IF    "${run}[0]" == "PASS"
+        ${closed} =    Set Variable    ${run}[1]
+    END
+    Log    Closed Notepads: ${closed}
+    Windows Run    Notepad
+
+Kill app by name
+    [Arguments]    ${app_name}
+    ${window_list} =    List Windows
+    FOR    ${win}    IN    @{window_list}
+        ${exists} =    Evaluate    re.match(".*${app_name}.*", """${win}[title]""")
+        IF    ${exists}
+            ${command} =    Set Variable    os.kill($win["pid"], signal.SIGTERM)
+            Log    Killing app: ${win}[title] (PID: $win["pid"])
+            Evaluate    ${command}    signal
+        END
+    END
+
+Close Current Window And Sleep
+    Close Current Window
+    Sleep    1s
+
+
 *** Test Cases ***
 Windows search Calculator by clicking buttons
-    [Tags]    skip    manual
     Windows Search    Calculator    wait_time=1
     Calculator button actions
     [Teardown]    Close Current Window And Sleep
 
 Calculator by clicking buttons already running
-    [Tags]    skip    manual
+    [Tags]  manual
     Calculator button actions
 
 Windows run Do some calculations
-    [Tags]    skip    manual
-    Windows Run    calc.exe    wait_time=1
+    Windows Run    ${EXE_CALCULATOR}    wait_time=1
     Calculator with keys
     [Teardown]    Close Current Window And Sleep
 
 Windows run Do some calculations already running
-    [Tags]    skip    manual
+    [Tags]  manual
     Calculator with keys
 
 Play Task Calculator
     [Documentation]    Checks if 34+6=40
-    [Tags]    skip    manual
     Windows Search    Calculator    wait_time=1
     Control Window    Calculator
     Click    id:clearButton
@@ -68,18 +158,10 @@ Play Task Calculator
     [Teardown]    Close Current Window
 
 Play Task Temperature
-    [Tags]    skip    manual
     Windows Search    Calculator    wait_time=1
     Control Window    Calculator
-    # Go to Temperature Converter view.
-    Click    id:TogglePaneButton
-    Send Keys    keys={PAGEDOWN}    wait_time=0.1    # to see the Temperature view
-    ${temp} =    Set Variable    id:Temperature    # works on Windows 10 and lower
-    ${ver} =    Get OS Version
-    IF    "${ver}" == "11"
-        ${temp} =    Set Variable    name:"Temperature Converter"
-    END
-    Click    ${temp}
+
+    Calculator Temperature Converter View
     Log To Console    \nGet temperatures
     Select Temperature Unit    Kelvin    225
     Get Temperature Values    Fahrenheit
@@ -87,19 +169,12 @@ Play Task Temperature
     Get Temperature Values    Kelvin
     Select Temperature Unit    Celsius    225
     Get Temperature Values    Kelvin,Fahrenheit
-    # Go back to Standard Calculator view.
-    Click    id:TogglePaneButton
-    Send Keys    keys={PAGEUP}    wait_time=0.1    # to see the Standard view
-    ${std} =    Set Variable    id:Standard
-    IF    "${ver}" == "11"
-        ${std} =    Set Variable    name:"Standard Calculator"
-    END
-    Click    ${std}
+
+    Calculator Standard View
     [Teardown]    Close Current Window
 
 Play Task UIDemo
-    [Tags]    skip    manual
-    Windows Run    UIDemo.exe
+    Windows Run    ${EXE_UIDEMO}
     Control Window    UiDemo
     Send Keys    id:user    admin
     Send Keys    id:pass    password
@@ -129,25 +204,31 @@ Play Task UIDemo
     [Teardown]    Close Current Window
 
 Resize window with Spotify
-    [Tags]    skip    manual
-    Windows Run    Spotify.exe
-    ${window} =    Control Window    executable:Spotify.exe
+    [Tags]  manual
+
+    Windows Run    ${EXE_SPOTIFY}
+    ${window} =    Control Window    executable:${EXE_SPOTIFY}
     Log To Console    Spotify window: ${window}
+
     Maximize Window
+    ${spotify_playcontrol} =    Set Variable
+    ...     name:"Player controls" > control:ButtonControl and name:
+    ${spotify_next} =   Set Variable    ${spotify_playcontrol}Next
     FOR    ${_}    IN RANGE    3
         Click    ${SPOTIFY_NEXT}
         Sleep    3s
     END
+
     Minimize Window
     Sleep    1s
     Maximize Window
     Sleep    2s
     Foreground Window
     Restore Window
+
     [Teardown]    Close Current Window
 
 Notepad write text into a file
-    [Tags]    skip    manual
     Windows Run    notepad
     Control Window    subname:"- Notepad"
     ${ver} =    Get OS Version
@@ -189,7 +270,6 @@ Notepad write text into a file
     [Teardown]    Close Current Window
 
 Control Window by handle
-    [Tags]    skip    manual
     Log To Console    \nList Windows
     ${win} =    List Windows
     FOR    ${w}    IN    @{win}
@@ -199,9 +279,10 @@ Control Window by handle
     Log To Console    Controlled window: ${win}
 
 Calculator result from recording
-    [Tags]    skip    manual
     Windows Run    Calc
     Control Window    Calculator    # Handle: 4066848
+
+    Calculator Standard View
     Click    name:"Seven"
     Click    name:"Eight"
     Click    name:"Nine"
@@ -210,35 +291,34 @@ Calculator result from recording
     Click    name:"Two"
     Click    name:"Three"
     Click    name:"Equals"
+
     [Teardown]    Close Current Window
 
 Write to Notepad in the background
-    [Tags]    skip    manual
-    Windows Run    Notepad
+    [Setup]     Windows Run    Notepad
+
     Windows Run    Calc
     Clear Anchor
     Control Window    subname:"- Notepad"    foreground=${False}
     # All the following keyword calls will use the set anchor element as root locator,
-    #    UNLESS they specify a locator explicitly or `Clear Anchor` is used.
-    ${text_edit} =    Set Variable    name:"Text Editor"
-    ${ver} =    Get OS Version
-    IF    "${ver}" == "11"
-        ${text_edit} =    Set Variable    name:"RichEdit Control"
-    END
+    #  UNLESS they specify a locator / root element explicitly or `Clear Anchor` is
+    #  used.
+    ${text_edit} =    Set Variable    regex:"Text (E|e)ditor"
     Set Anchor    ${text_edit}
+
     # Write in Notepad while having Calculator as active window.
     Control Window    Calculator
     # Clear Notepad edit window by writing initial text, then append rest of the text.
     ${time} =    Get Time
-    Set Value    value=time now is ${time}    # clears when append=False (default)
-    Set Value    value= and it's task run time    append=True    newline=True
-    Set Value    value=this will appear on the 2nd line    append=True
-    Set Value    value=${EMPTY}    append=True    enter=True
+    Set Value    value=time now is ${time}    # clears when append=${False} (default)
+    Set Value    value= and it's task run time    append=${True}    newline=${True}
+    Set Value    value=this will appear on the 2nd line    append=${True}
+    Set Value    value=${EMPTY}    append=${True}    enter=${True}
+
     Close Current Window    # this closes Calculator first (as active window)
     [Teardown]    Close Window    subname:Notepad    # finally Notepad is closed too
 
 Test getting elements
-    [Tags]    skip    manual
     Clear Anchor
     ${ver} =    Get OS Version
     ${desktop} =    Get Element
@@ -254,7 +334,6 @@ Test getting elements
     END
 
 Control window after closing linked root element
-    [Tags]    skip    manual
     [Setup]    Keep open a single Notepad
     ${window} =    Control Window    subname:Notepad control:WindowControl
     Log    Controlling Notepad window: ${window}
@@ -266,8 +345,7 @@ Control window after closing linked root element
     [Teardown]    Close Current Window    # closes Calculator (last active window)
 
 Tree printing and controlled anchor cleanup
-    [Tags]    skip    manual
-    Print Tree    #capture_image_folder=output${/}controls
+    Print Tree
     Windows Run    Calc
     ${win} =    Control Window    subname:Calc control:WindowControl    timeout=${TIMEOUT}
     Set Anchor    ${win}
@@ -279,9 +357,9 @@ Tree printing and controlled anchor cleanup
     ${elem} =    Get Element
     Should Be Equal    ${elem.name}    Desktop 1
 
-Click Buttons With Offset
-    [Tags]    skip    manual
+Click Calculator Numeric Buttons
     [Documentation]    Clicks all the numeric buttons in Calculator
+
     Windows Run    Calc
     Control Window    subname:Calc
     @{buttons} =    Get Elements    id:NumberPad > class:Button
@@ -292,75 +370,31 @@ Click Buttons With Offset
         END
     END
 
-*** Keywords ***
-Calculator Teardown
-    Send Keys    keys={Esc}
-    Sleep    1s
-    Send Keys    keys={Alt}1
-    Sleep    1s
+    [Teardown]  Close Current Window
 
-Select Temperature Unit
-    [Arguments]    ${unit}    ${degrees}
-    # Select Unit
-    Click    id:Units1
-    Click    id:Units1 > name:${unit}
-    Send Keys    keys=${degrees}
-    Log To Console    Set ${unit}: ${degrees}
-    Send Keys    keys={Esc}
+Log All Calculator Buttons Matching Expression
+    [Documentation]    Logs all the buttons in the controlled window if they contain
+    ...     an 'o' in their name.
+    [Setup]   Windows Run    Calc
 
-Get Temperature Values
-    [Arguments]    ${keys}
-    ${values} =    Split String    ${keys}    ,
-    FOR    ${val}    IN    @{values}
-        ${temperature} =    Get Attribute    id:UnitConverterRootGrid > type:TextControl and regex:.*\\s${val}    Name
-        Log To Console    ${temperature}
-    END
-
-Calculator button actions
-    Control Window    Calculator type:Window
-    Click    id:num9Button
-    Click    id:num6Button
-    Click    id:plusButton
-    Click    id:num4Button
-    Click    id:equalButton
-    ${result} =    Get Attribute    id:CalculatorResults    Name
-    Log To Console    \n${result}
-    Send Keys    keys={Esc}
+    Control Window    subname:Calc
+    @{buttons} =    Get Elements    class:Button regex:.*o.*
+    ...     siblings_only=${False}  # this will search globally for such buttons
+    Log List    ${buttons}
+    ${length} =     Get Length      ${buttons}
+    Log To Console      Number of buttons: ${length}
 
-Calculator with keys
-    Control Window    Calculator
-    Click    id:clearButton
-    Send Keys    keys=96+4=    interval=0.1
-    ${result} =    Get Attribute    id:CalculatorResults    Name
-    Log To Console    ${result}
-    ${buttons} =    Get Elements    type:Group and name:"Number pad" > type:Button
-    FOR    ${button}    IN    @{buttons}
-        Log To Console    ${button}
-    END
-    Length Should Be    ${buttons}    11    msg=From 0 to 9 and decimlar separator
+    [Teardown]  Close Current Window
 
-Keep open a single Notepad
-    Set Global Timeout    ${TIMEOUT}
-    ${closed} =    Set Variable    0
-    ${run} =    Run Keyword And Ignore Error    Close Window    subname:Notepad control:WindowControl
-    IF    "${run}[0]" == "PASS"
-        ${closed} =    Set Variable    ${run}[1]
-    END
-    Log    Closed Notepads: ${closed}
-    Windows Run    Notepad
+Retrieve Nested Notepad Elements
+    [Documentation]     Try to unfold menu items until being able to retrieve a deep
+    ...     element inside the tree as soon as it becomes visible.
+    [Setup]   Windows Run    Notepad
 
-Kill app by name
-    [Arguments]    ${app_name}
-    ${window_list} =    List Windows
-    FOR    ${win}    IN    @{window_list}
-        ${exists} =    Evaluate    re.match(".*${app_name}.*", """${win}[title]""")
-        IF    ${exists}
-            ${command} =    Set Variable    os.kill($win["pid"], signal.SIGTERM)
-            Log    Killing app: ${win}[title] (PID: $win["pid"])
-            Evaluate    ${command}    signal
-        END
-    END
+    Control Window      subname:Notepad
+    Click   View
+    Click   Zoom
+    ${zoom_in} =   Get Element     Zoom in
+    Log To Console      "Zoom in" item: ${zoom_in}
 
-Close Current Window And Sleep
-    Close Current Window
-    Sleep    1s
+    [Teardown]  Close Current Window