You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
787 lines
30 KiB
787 lines
30 KiB
6 years ago
|
# GUI Application automation and testing library
|
||
|
# Copyright (C) 2006-2018 Mark Mc Mahon and Contributors
|
||
|
# https://github.com/pywinauto/pywinauto/graphs/contributors
|
||
|
# http://pywinauto.readthedocs.io/en/latest/credits.html
|
||
|
# All rights reserved.
|
||
|
#
|
||
|
# Redistribution and use in source and binary forms, with or without
|
||
|
# modification, are permitted provided that the following conditions are met:
|
||
|
#
|
||
|
# * Redistributions of source code must retain the above copyright notice, this
|
||
|
# list of conditions and the following disclaimer.
|
||
|
#
|
||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||
|
# this list of conditions and the following disclaimer in the documentation
|
||
|
# and/or other materials provided with the distribution.
|
||
|
#
|
||
|
# * Neither the name of pywinauto nor the names of its
|
||
|
# contributors may be used to endorse or promote products derived from
|
||
|
# this software without specific prior written permission.
|
||
|
#
|
||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
||
|
"""Basic wrapping of UI Automation elements"""
|
||
|
|
||
|
from __future__ import unicode_literals
|
||
|
from __future__ import print_function
|
||
|
|
||
|
import six
|
||
|
import time
|
||
|
import warnings
|
||
|
import comtypes
|
||
|
|
||
|
from .. import backend
|
||
|
from ..timings import Timings
|
||
|
from ..base_wrapper import BaseWrapper
|
||
|
from ..base_wrapper import BaseMeta
|
||
|
|
||
|
from ..uia_defines import IUIA
|
||
|
from .. import uia_defines as uia_defs
|
||
|
from ..uia_element_info import UIAElementInfo, elements_from_uia_array
|
||
|
|
||
|
# region PATTERNS
|
||
|
AutomationElement = IUIA().ui_automation_client.IUIAutomationElement
|
||
|
DockPattern = IUIA().ui_automation_client.IUIAutomationDockPattern
|
||
|
ExpandCollapsePattern = IUIA().ui_automation_client.IUIAutomationExpandCollapsePattern
|
||
|
GridItemPattern = IUIA().ui_automation_client.IUIAutomationGridItemPattern
|
||
|
GridPattern = IUIA().ui_automation_client.IUIAutomationGridPattern
|
||
|
InvokePattern = IUIA().ui_automation_client.IUIAutomationInvokePattern
|
||
|
ItemContainerPattern = IUIA().ui_automation_client.IUIAutomationItemContainerPattern
|
||
|
LegacyIAccessiblePattern = IUIA().ui_automation_client.IUIAutomationLegacyIAccessiblePattern
|
||
|
MultipleViewPattern = IUIA().ui_automation_client.IUIAutomationMultipleViewPattern
|
||
|
RangeValuePattern = IUIA().ui_automation_client.IUIAutomationRangeValuePattern
|
||
|
ScrollItemPattern = IUIA().ui_automation_client.IUIAutomationScrollItemPattern
|
||
|
ScrollPattern = IUIA().ui_automation_client.IUIAutomationScrollPattern
|
||
|
SelectionItemPattern = IUIA().ui_automation_client.IUIAutomationSelectionItemPattern
|
||
|
SelectionPattern = IUIA().ui_automation_client.IUIAutomationSelectionPattern
|
||
|
SynchronizedInputPattern = IUIA().ui_automation_client.IUIAutomationSynchronizedInputPattern
|
||
|
TableItemPattern = IUIA().ui_automation_client.IUIAutomationTableItemPattern
|
||
|
TablePattern = IUIA().ui_automation_client.IUIAutomationTablePattern
|
||
|
TextPattern = IUIA().ui_automation_client.IUIAutomationTextPattern
|
||
|
TogglePattern = IUIA().ui_automation_client.IUIAutomationTogglePattern
|
||
|
TransformPattern = IUIA().ui_automation_client.IUIAutomationTransformPattern
|
||
|
ValuePattern = IUIA().ui_automation_client.IUIAutomationValuePattern
|
||
|
VirtualizedItemPattern = IUIA().ui_automation_client.IUIAutomationVirtualizedItemPattern
|
||
|
WindowPattern = IUIA().ui_automation_client.IUIAutomationWindowPattern
|
||
|
# endregion
|
||
|
|
||
|
# =========================================================================
|
||
|
_friendly_classes = {
|
||
|
'Custom': None,
|
||
|
'DataGrid': 'ListView',
|
||
|
'DataItem': 'DataItem',
|
||
|
'Document': None, # TODO: this is RichTextBox
|
||
|
'Group': 'GroupBox',
|
||
|
'Header': None,
|
||
|
'HeaderItem': None,
|
||
|
'Hyperlink': None,
|
||
|
'Image': None,
|
||
|
'List': 'ListBox',
|
||
|
'ListItem': 'ListItem',
|
||
|
'MenuBar': 'Menu',
|
||
|
'Menu': 'Menu',
|
||
|
'MenuItem': 'MenuItem',
|
||
|
'Pane': None,
|
||
|
'ProgressBar': 'Progress',
|
||
|
'ScrollBar': None,
|
||
|
'Separator': None,
|
||
|
'Slider': None,
|
||
|
'Spinner': 'UpDown',
|
||
|
'SplitButton': None,
|
||
|
'Tab': 'TabControl',
|
||
|
'Table': None,
|
||
|
'Text': 'Static',
|
||
|
'Thumb': None,
|
||
|
'TitleBar': None,
|
||
|
'ToolBar': 'Toolbar',
|
||
|
'ToolTip': 'ToolTips',
|
||
|
'Tree': 'TreeView',
|
||
|
'TreeItem': 'TreeItem',
|
||
|
'Window': 'Dialog',
|
||
|
}
|
||
|
|
||
|
|
||
|
# =========================================================================
|
||
|
class LazyProperty(object):
|
||
|
|
||
|
"""
|
||
|
A lazy evaluation of an object attribute.
|
||
|
|
||
|
The property should represent immutable data, as it replaces itself.
|
||
|
Provided by: http://stackoverflow.com/a/6849299/1260742
|
||
|
"""
|
||
|
|
||
|
def __init__(self, fget):
|
||
|
"""Init the property name and method to calculate the property"""
|
||
|
self.fget = fget
|
||
|
self.func_name = fget.__name__
|
||
|
|
||
|
def __get__(self, obj, cls):
|
||
|
"""Replace the property itself on a first access"""
|
||
|
if obj is None:
|
||
|
return None
|
||
|
value = self.fget(obj)
|
||
|
setattr(obj, self.func_name, value)
|
||
|
return value
|
||
|
lazy_property = LazyProperty
|
||
|
|
||
|
|
||
|
# =========================================================================
|
||
|
class UiaMeta(BaseMeta):
|
||
|
|
||
|
"""Metaclass for UiaWrapper objects"""
|
||
|
control_type_to_cls = {}
|
||
|
|
||
|
def __init__(cls, name, bases, attrs):
|
||
|
"""Register the control types"""
|
||
|
|
||
|
BaseMeta.__init__(cls, name, bases, attrs)
|
||
|
|
||
|
for t in cls._control_types:
|
||
|
UiaMeta.control_type_to_cls[t] = cls
|
||
|
|
||
|
@staticmethod
|
||
|
def find_wrapper(element):
|
||
|
"""Find the correct wrapper for this UIA element"""
|
||
|
# Set a general wrapper by default
|
||
|
wrapper_match = UIAWrapper
|
||
|
|
||
|
# Check for a more specific wrapper in the registry
|
||
|
if element.control_type in UiaMeta.control_type_to_cls:
|
||
|
wrapper_match = UiaMeta.control_type_to_cls[element.control_type]
|
||
|
|
||
|
return wrapper_match
|
||
|
|
||
|
|
||
|
# =========================================================================
|
||
|
@six.add_metaclass(UiaMeta)
|
||
|
class UIAWrapper(BaseWrapper):
|
||
|
|
||
|
"""
|
||
|
Default wrapper for User Interface Automation (UIA) controls.
|
||
|
|
||
|
All other UIA wrappers are derived from this.
|
||
|
|
||
|
This class wraps a lot of functionality of underlying UIA features
|
||
|
for working with windows.
|
||
|
|
||
|
Most of the methods apply to every single element type. For example
|
||
|
you can click() on any element.
|
||
|
"""
|
||
|
|
||
|
_control_types = []
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
def __new__(cls, element_info):
|
||
|
"""Construct the control wrapper"""
|
||
|
return super(UIAWrapper, cls)._create_wrapper(cls, element_info, UIAWrapper)
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def __init__(self, element_info):
|
||
|
"""
|
||
|
Initialize the control
|
||
|
|
||
|
* **element_info** is either a valid UIAElementInfo or it can be an
|
||
|
instance or subclass of UIAWrapper.
|
||
|
If the handle is not valid then an InvalidWindowHandle error
|
||
|
is raised.
|
||
|
"""
|
||
|
BaseWrapper.__init__(self, element_info, backend.registry.backends['uia'])
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
def __hash__(self):
|
||
|
"""Return a unique hash value based on the element's Runtime ID"""
|
||
|
return hash(self.element_info.runtime_id)
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_expand_collapse(self):
|
||
|
"""Get the element's ExpandCollapse interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "ExpandCollapse")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_selection(self):
|
||
|
"""Get the element's Selection interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "Selection")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_selection_item(self):
|
||
|
"""Get the element's SelectionItem interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "SelectionItem")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_invoke(self):
|
||
|
"""Get the element's Invoke interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "Invoke")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_toggle(self):
|
||
|
"""Get the element's Toggle interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "Toggle")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_text(self):
|
||
|
"""Get the element's Text interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "Text")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_value(self):
|
||
|
"""Get the element's Value interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "Value")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_range_value(self):
|
||
|
"""Get the element's RangeValue interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "RangeValue")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_grid(self):
|
||
|
"""Get the element's Grid interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "Grid")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_grid_item(self):
|
||
|
"""Get the element's GridItem interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "GridItem")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_table(self):
|
||
|
"""Get the element's Table interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "Table")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_table_item(self):
|
||
|
"""Get the element's TableItem interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "TableItem")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_scroll_item(self):
|
||
|
"""Get the element's ScrollItem interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "ScrollItem")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_scroll(self):
|
||
|
"""Get the element's Scroll interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "Scroll")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_transform(self):
|
||
|
"""Get the element's Transform interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "Transform")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_transformV2(self):
|
||
|
"""Get the element's TransformV2 interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "TransformV2")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_window(self):
|
||
|
"""Get the element's Window interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "Window")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_item_container(self):
|
||
|
"""Get the element's ItemContainer interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "ItemContainer")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@lazy_property
|
||
|
def iface_virtualized_item(self):
|
||
|
"""Get the element's VirtualizedItem interface pattern"""
|
||
|
elem = self.element_info.element
|
||
|
return uia_defs.get_elem_interface(elem, "VirtualizedItem")
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
@property
|
||
|
def writable_props(self):
|
||
|
"""Extend default properties list."""
|
||
|
props = super(UIAWrapper, self).writable_props
|
||
|
props.extend(['is_keyboard_focusable',
|
||
|
'has_keyboard_focus',
|
||
|
'automation_id',
|
||
|
])
|
||
|
return props
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
def legacy_properties(self):
|
||
|
"""Get the element's LegacyIAccessible control pattern interface properties"""
|
||
|
elem = self.element_info.element
|
||
|
impl = uia_defs.get_elem_interface(elem, "LegacyIAccessible")
|
||
|
property_name_identifier = 'Current'
|
||
|
|
||
|
interface_properties = [prop for prop in dir(LegacyIAccessiblePattern)
|
||
|
if (isinstance(getattr(LegacyIAccessiblePattern, prop), property)
|
||
|
and property_name_identifier in prop)]
|
||
|
|
||
|
return {prop.replace(property_name_identifier, '') : getattr(impl, prop) for prop in interface_properties}
|
||
|
|
||
|
# ------------------------------------------------------------
|
||
|
def friendly_class_name(self):
|
||
|
"""
|
||
|
Return the friendly class name for the control
|
||
|
|
||
|
This differs from the class of the control in some cases.
|
||
|
class_name() is the actual 'Registered' window class of the control
|
||
|
while friendly_class_name() is hopefully something that will make
|
||
|
more sense to the user.
|
||
|
|
||
|
For example Checkboxes are implemented as Buttons - so the class
|
||
|
of a CheckBox is "Button" - but the friendly class is "CheckBox"
|
||
|
"""
|
||
|
if self.friendlyclassname is None:
|
||
|
if self.element_info.control_type not in IUIA().known_control_types.keys():
|
||
|
self.friendlyclassname = self.element_info.control_type
|
||
|
else:
|
||
|
ctrl_type = self.element_info.control_type
|
||
|
if (ctrl_type not in _friendly_classes) or (_friendly_classes[ctrl_type] is None):
|
||
|
self.friendlyclassname = ctrl_type
|
||
|
else:
|
||
|
self.friendlyclassname = _friendly_classes[ctrl_type]
|
||
|
return self.friendlyclassname
|
||
|
|
||
|
#------------------------------------------------------------
|
||
|
def automation_id(self):
|
||
|
"""Return the Automation ID of the control"""
|
||
|
return self.element_info.automation_id
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def is_keyboard_focusable(self):
|
||
|
"""Return True if the element can be focused with keyboard"""
|
||
|
return self.element_info.element.CurrentIsKeyboardFocusable == 1
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def has_keyboard_focus(self):
|
||
|
"""Return True if the element is focused with keyboard"""
|
||
|
return self.element_info.element.CurrentHasKeyboardFocus == 1
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def set_focus(self):
|
||
|
"""Set the focus to this element"""
|
||
|
try:
|
||
|
if self.is_minimized():
|
||
|
if self.was_maximized():
|
||
|
self.maximize()
|
||
|
else:
|
||
|
self.restore()
|
||
|
except uia_defs.NoPatternInterfaceError:
|
||
|
pass
|
||
|
try:
|
||
|
self.element_info.element.SetFocus()
|
||
|
except comtypes.COMError as exc:
|
||
|
warnings.warn('The window has not been focused due to ' \
|
||
|
'COMError: {}'.format(exc), RuntimeWarning)
|
||
|
|
||
|
return self
|
||
|
|
||
|
# TODO: figure out how to implement .has_focus() method (if no handle available)
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def close(self):
|
||
|
"""
|
||
|
Close the window
|
||
|
|
||
|
Only a control supporting Window pattern should answer.
|
||
|
If it doesn't (menu shadows, tooltips,...), try to send "Esc" key
|
||
|
"""
|
||
|
try:
|
||
|
name = self.element_info.name
|
||
|
control_type = self.element_info.control_type
|
||
|
|
||
|
iface = self.iface_window
|
||
|
iface.Close()
|
||
|
|
||
|
if name and control_type:
|
||
|
self.actions.log("Closed " + control_type.lower() + ' "' + name + '"')
|
||
|
except(uia_defs.NoPatternInterfaceError):
|
||
|
self.type_keys("{ESC}")
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def minimize(self):
|
||
|
"""
|
||
|
Minimize the window
|
||
|
|
||
|
Only controls supporting Window pattern should answer
|
||
|
"""
|
||
|
iface = self.iface_window
|
||
|
if iface.CurrentCanMinimize:
|
||
|
iface.SetWindowVisualState(uia_defs.window_visual_state_minimized)
|
||
|
return self
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def maximize(self):
|
||
|
"""
|
||
|
Maximize the window
|
||
|
|
||
|
Only controls supporting Window pattern should answer
|
||
|
"""
|
||
|
iface = self.iface_window
|
||
|
if iface.CurrentCanMaximize:
|
||
|
iface.SetWindowVisualState(uia_defs.window_visual_state_maximized)
|
||
|
return self
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def restore(self):
|
||
|
"""
|
||
|
Restore the window to normal size
|
||
|
|
||
|
Only controls supporting Window pattern should answer
|
||
|
"""
|
||
|
iface = self.iface_window
|
||
|
iface.SetWindowVisualState(uia_defs.window_visual_state_normal)
|
||
|
return self
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def get_show_state(self):
|
||
|
"""Get the show state and Maximized/minimzed/restored state
|
||
|
|
||
|
Returns values as following
|
||
|
|
||
|
window_visual_state_normal = 0
|
||
|
window_visual_state_maximized = 1
|
||
|
window_visual_state_minimized = 2
|
||
|
"""
|
||
|
iface = self.iface_window
|
||
|
ret = iface.CurrentWindowVisualState
|
||
|
|
||
|
return ret
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def is_minimized(self):
|
||
|
"""Indicate whether the window is minimized or not"""
|
||
|
return self.get_show_state() == uia_defs.window_visual_state_minimized
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def is_maximized(self):
|
||
|
"""Indicate whether the window is maximized or not"""
|
||
|
return self.get_show_state() == uia_defs.window_visual_state_maximized
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def is_normal(self):
|
||
|
"""Indicate whether the window is normal (i.e. not minimized and not maximized)"""
|
||
|
return self.get_show_state() == uia_defs.window_visual_state_normal
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def invoke(self):
|
||
|
"""An interface to the Invoke method of the Invoke control pattern"""
|
||
|
name = self.element_info.name
|
||
|
control_type = self.element_info.control_type
|
||
|
|
||
|
self.iface_invoke.Invoke()
|
||
|
|
||
|
if name and control_type:
|
||
|
self.actions.log("Invoked " + control_type.lower() + ' "' + name + '"')
|
||
|
# Return itself to allow action chaining
|
||
|
return self
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def expand(self):
|
||
|
"""
|
||
|
Displays all child nodes, controls, or content of the control
|
||
|
|
||
|
An interface to Expand method of the ExpandCollapse control pattern.
|
||
|
"""
|
||
|
self.iface_expand_collapse.Expand()
|
||
|
|
||
|
# Return itself to allow action chaining
|
||
|
return self
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def collapse(self):
|
||
|
"""
|
||
|
Displays all child nodes, controls, or content of the control
|
||
|
|
||
|
An interface to Collapse method of the ExpandCollapse control pattern.
|
||
|
"""
|
||
|
self.iface_expand_collapse.Collapse()
|
||
|
|
||
|
# Return itself to allow action chaining
|
||
|
return self
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def get_expand_state(self):
|
||
|
"""
|
||
|
Indicates the state of the control: expanded or collapsed.
|
||
|
|
||
|
An interface to CurrentExpandCollapseState property of the ExpandCollapse control pattern.
|
||
|
Values for enumeration as defined in uia_defines module:
|
||
|
expand_state_collapsed = 0
|
||
|
expand_state_expanded = 1
|
||
|
expand_state_partially = 2
|
||
|
expand_state_leaf_node = 3
|
||
|
"""
|
||
|
return self.iface_expand_collapse.CurrentExpandCollapseState
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def is_expanded(self):
|
||
|
"""Test if the control is expanded"""
|
||
|
state = self.get_expand_state()
|
||
|
return state == uia_defs.expand_state_expanded
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def is_collapsed(self):
|
||
|
"""Test if the control is collapsed"""
|
||
|
state = self.get_expand_state()
|
||
|
return state == uia_defs.expand_state_collapsed
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def get_selection(self):
|
||
|
"""
|
||
|
An interface to GetSelection of the SelectionProvider pattern
|
||
|
|
||
|
Retrieves a UI Automation provider for each child element
|
||
|
that is selected. Builds a list of UIAElementInfo elements
|
||
|
from all retrieved providers.
|
||
|
"""
|
||
|
ptrs_array = self.iface_selection.GetCurrentSelection()
|
||
|
return elements_from_uia_array(ptrs_array)
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def selected_item_index(self):
|
||
|
"""Return the index of a selected item"""
|
||
|
# Go through all children and look for an index
|
||
|
# of an item with the same text.
|
||
|
# Maybe there is another and more efficient way to do it
|
||
|
selection = self.get_selection()
|
||
|
if selection:
|
||
|
for i, c in enumerate(self.children()):
|
||
|
if c.window_text() == selection[0].name:
|
||
|
return i
|
||
|
return None
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def select(self):
|
||
|
"""Select the item
|
||
|
|
||
|
Only items supporting SelectionItem pattern should answer.
|
||
|
Raise NoPatternInterfaceError if the pattern is not supported
|
||
|
|
||
|
Usually applied for controls like: a radio button, a tree view item
|
||
|
or a list item.
|
||
|
"""
|
||
|
self.iface_selection_item.Select()
|
||
|
|
||
|
name = self.element_info.name
|
||
|
control_type = self.element_info.control_type
|
||
|
if name and control_type:
|
||
|
self.actions.log("Selected " + control_type.lower() + ' "' + name + '"')
|
||
|
|
||
|
# Return itself so that action can be chained
|
||
|
return self
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def is_selected(self):
|
||
|
"""Indicate that the item is selected or not.
|
||
|
|
||
|
Only items supporting SelectionItem pattern should answer.
|
||
|
Raise NoPatternInterfaceError if the pattern is not supported
|
||
|
|
||
|
Usually applied for controls like: a radio button, a tree view item,
|
||
|
a list item.
|
||
|
"""
|
||
|
return self.iface_selection_item.CurrentIsSelected
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def children_texts(self):
|
||
|
"""Get texts of the control's children"""
|
||
|
return [c.window_text() for c in self.children()]
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def can_select_multiple(self):
|
||
|
"""
|
||
|
An interface to CanSelectMultiple of the SelectionProvider pattern
|
||
|
|
||
|
Indicates whether the UI Automation provider allows more than one
|
||
|
child element to be selected concurrently.
|
||
|
"""
|
||
|
return self.iface_selection.CurrentCanSelectMultiple
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def is_selection_required(self):
|
||
|
"""
|
||
|
An interface to IsSelectionRequired property of the SelectionProvider pattern.
|
||
|
|
||
|
This property can be dynamic. For example, the initial state of
|
||
|
a control might not have any items selected by default,
|
||
|
meaning that IsSelectionRequired is FALSE. However,
|
||
|
after an item is selected the control must always have
|
||
|
at least one item selected.
|
||
|
"""
|
||
|
return self.iface_selection.CurrentIsSelectionRequired
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def _select(self, item=None):
|
||
|
"""
|
||
|
Find a child item by the name or index and select
|
||
|
|
||
|
The action can be applied for dirrent controls with items:
|
||
|
ComboBox, TreeView, Tab control
|
||
|
"""
|
||
|
if isinstance(item, six.integer_types):
|
||
|
item_index = item
|
||
|
title = None
|
||
|
elif isinstance(item, six.string_types):
|
||
|
item_index = 0
|
||
|
title = item
|
||
|
else:
|
||
|
err_msg = u"unsupported {0} for item {1}".format(type(item), item)
|
||
|
raise ValueError(err_msg)
|
||
|
|
||
|
list_ = self.children(title=title)
|
||
|
if item_index < len(list_):
|
||
|
wrp = list_[item_index]
|
||
|
wrp.iface_selection_item.Select()
|
||
|
else:
|
||
|
raise IndexError("item not found")
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def is_active(self):
|
||
|
"""Whether the window is active or not"""
|
||
|
ae = IUIA().get_focused_element()
|
||
|
focused_wrap = UIAWrapper(UIAElementInfo(ae))
|
||
|
return (focused_wrap.top_level_parent() == self.top_level_parent())
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def is_dialog(self):
|
||
|
"""Return true if the control is a dialog window (WindowPattern interface is available)"""
|
||
|
try:
|
||
|
return self.iface_window is not None
|
||
|
except uia_defs.NoPatternInterfaceError:
|
||
|
return False
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
def menu_select(self, path, exact=False, ):
|
||
|
"""Select a menu item specified in the path
|
||
|
|
||
|
The full path syntax is specified in:
|
||
|
:py:meth:`pywinauto.menuwrapper.Menu.get_menu_path`
|
||
|
|
||
|
There are usually at least two menu bars: "System" and "Application"
|
||
|
System menu bar is a standard window menu with items like:
|
||
|
'Restore', 'Move', 'Size', 'Minimize', e.t.c.
|
||
|
This menu bar usually has a "Title Bar" control as a parent.
|
||
|
Application menu bar is often what we look for. In most cases,
|
||
|
its parent is the dialog itself so it should be found among the direct
|
||
|
children of the dialog. Notice that we don't use "Application"
|
||
|
string as a title criteria because it couldn't work on applications
|
||
|
with a non-english localization.
|
||
|
If there is no menu bar has been found we fall back to look up
|
||
|
for Menu control. We try to find the control through all descendants
|
||
|
of the dialog
|
||
|
"""
|
||
|
self.verify_actionable()
|
||
|
|
||
|
cc = self.children(control_type="MenuBar")
|
||
|
if not cc:
|
||
|
cc = self.descendants(control_type="Menu")
|
||
|
if not cc:
|
||
|
raise AttributeError
|
||
|
menu = cc[0]
|
||
|
menu.item_by_path(path, exact).select()
|
||
|
|
||
|
# -----------------------------------------------------------
|
||
|
_scroll_types = {
|
||
|
"left": {
|
||
|
"line": (uia_defs.scroll_small_decrement, uia_defs.scroll_no_amount),
|
||
|
"page": (uia_defs.scroll_large_decrement, uia_defs.scroll_no_amount),
|
||
|
},
|
||
|
"right": {
|
||
|
"line": (uia_defs.scroll_small_increment, uia_defs.scroll_no_amount),
|
||
|
"page": (uia_defs.scroll_large_increment, uia_defs.scroll_no_amount),
|
||
|
},
|
||
|
"up": {
|
||
|
"line": (uia_defs.scroll_no_amount, uia_defs.scroll_small_decrement),
|
||
|
"page": (uia_defs.scroll_no_amount, uia_defs.scroll_large_decrement),
|
||
|
},
|
||
|
"down": {
|
||
|
"line": (uia_defs.scroll_no_amount, uia_defs.scroll_small_increment),
|
||
|
"page": (uia_defs.scroll_no_amount, uia_defs.scroll_large_increment),
|
||
|
},
|
||
|
}
|
||
|
|
||
|
def scroll(self, direction, amount, count=1, retry_interval=Timings.scroll_step_wait):
|
||
|
"""Ask the control to scroll itself
|
||
|
|
||
|
**direction** can be any of "up", "down", "left", "right"
|
||
|
**amount** can be only "line" or "page"
|
||
|
**count** (optional) the number of times to scroll
|
||
|
**retry_interval** (optional) interval between scroll actions
|
||
|
"""
|
||
|
def _raise_attrib_err(details):
|
||
|
control_type = self.element_info.control_type
|
||
|
name = self.element_info.name
|
||
|
msg = "".join([control_type.lower(), ' "', name, '" ', details])
|
||
|
raise AttributeError(msg)
|
||
|
|
||
|
try:
|
||
|
scroll_if = self.iface_scroll
|
||
|
if direction.lower() in ("up", "down"):
|
||
|
if not scroll_if.CurrentVerticallyScrollable:
|
||
|
_raise_attrib_err('is not vertically scrollable')
|
||
|
elif direction.lower() in ("left", "right"):
|
||
|
if not scroll_if.CurrentHorizontallyScrollable:
|
||
|
_raise_attrib_err('is not horizontally scrollable')
|
||
|
|
||
|
h, v = self._scroll_types[direction.lower()][amount.lower()]
|
||
|
|
||
|
# Scroll as often as we have been asked to
|
||
|
for _ in range(count, 0, -1):
|
||
|
scroll_if.Scroll(h, v)
|
||
|
time.sleep(retry_interval)
|
||
|
|
||
|
except uia_defs.NoPatternInterfaceError:
|
||
|
_raise_attrib_err('is not scrollable')
|
||
|
except KeyError:
|
||
|
raise ValueError("""Wrong arguments:
|
||
|
direction can be any of "up", "down", "left", "right"
|
||
|
amount can be only "line" or "page"
|
||
|
""")
|
||
|
|
||
|
return self
|
||
|
|
||
|
|
||
|
backend.register('uia', UIAElementInfo, UIAWrapper)
|