Reputation: 319
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?
Upvotes: 5
Views: 10757
Reputation: 10000
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).
Upvotes: 6