-->

pywinauto: Iterate through all controls in a windo

2019-07-15 07:39发布

问题:

I'm trying to write a general test script to find errors in new software builds. My idea is to iterate through the controls in the window and interact with each one, logging any errors that are caused and restarting the software if it crashes.

I'm looking for a way to dynamically find control identifiers, a bit like print_control_identifiers() but with the output being a list or similar structure which I can iterate through.

On a GitHub question about control identifiers this was mentioned:

it's possible to walk the hierarchy by using .children() (immediate children only) and .descendants() (the whole subtree as a plain list)

I assumed I could just iterate through my Application object's descendants() list and call a relavant interaction method for each, however I can't work out how to get this list. I assumed I could do something like this, but I haven't had any success:

def test(application):
    for child in application.descendants():
        #interact with child control

software = Application(backend='uia').start(cmd_line=FILE_PATH)
test(software)

AttributeError: Neither GUI element (wrapper) nor wrapper method 'descendants' were found (typo?)


EDIT


I resorted to looking through the code and found the print_control_identifiers method:

class Application(object):

    def print_control_identifiers(self, depth=None, filename=None):
        """
        Prints the 'identifiers'
        Prints identifiers for the control and for its descendants to
        a depth of **depth** (the whole subtree if **None**).
        .. note:: The identifiers printed by this method have been made
               unique. So if you have 2 edit boxes, they won't both have "Edit"
               listed in their identifiers. In fact the first one can be
               referred to as "Edit", "Edit0", "Edit1" and the 2nd should be
               referred to as "Edit2".
        """
        if depth is None:
            depth = sys.maxsize
        # Wrap this control
        this_ctrl = self.__resolve_control(self.criteria)[-1]

        # Create a list of this control and all its descendants
        all_ctrls = [this_ctrl, ] + this_ctrl.descendants()

        # Create a list of all visible text controls
        txt_ctrls = [ctrl for ctrl in all_ctrls if ctrl.can_be_label and ctrl.is_visible() and ctrl.window_text()]

        # Build a dictionary of disambiguated list of control names
        name_ctrl_id_map = findbestmatch.UniqueDict()
        for index, ctrl in enumerate(all_ctrls):
            ctrl_names = findbestmatch.get_control_names(ctrl, all_ctrls, txt_ctrls)
            for name in ctrl_names:
                name_ctrl_id_map[name] = index

        # Swap it around so that we are mapped off the control indices
        ctrl_id_name_map = {}
        for name, index in name_ctrl_id_map.items():
            ctrl_id_name_map.setdefault(index, []).append(name)

This shows that .descendants() isn't a method of the Application class, but belongs to the control. I was wrong there it seems. Is it possible to create my own version of print_control-identifiers() which returns a list of control objects that can be iterated through?

回答1:

Correct method to list top-level windows is application.windows(). Then you can call .descendants() for every listed window. In the most cases application has only one top-level window. Particularly for backend="uia" even new dialogs are children of the main window (for backend="win32" every dialog is a top-level window).