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.
465 lines
15 KiB
465 lines
15 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.
|
||
|
|
||
|
"""Global timing settings for all of pywinauto
|
||
|
|
||
|
This module has one object that should be used for all timing adjustments:
|
||
|
|
||
|
* timings.Timings
|
||
|
|
||
|
There are a couple of predefined settings:
|
||
|
|
||
|
* ``timings.Timings.fast()``
|
||
|
* ``timings.Timings.defaults()``
|
||
|
* ``timings.Timings.slow()``
|
||
|
|
||
|
The Following are the individual timing settings that can be adjusted:
|
||
|
|
||
|
* window_find_timeout (default 5)
|
||
|
* window_find_retry (default .09)
|
||
|
|
||
|
* app_start_timeout (default 10)
|
||
|
* app_start_retry (default .90)
|
||
|
|
||
|
* app_connect_timeout (default 5.)
|
||
|
* app_connect_retry (default .1)
|
||
|
|
||
|
* cpu_usage_interval (default .5)
|
||
|
* cpu_usage_wait_timeout (default 20)
|
||
|
|
||
|
* exists_timeout (default .5)
|
||
|
* exists_retry (default .3)
|
||
|
|
||
|
* after_click_wait (default .09)
|
||
|
* after_clickinput_wait (default .09)
|
||
|
|
||
|
* after_menu_wait (default .1)
|
||
|
|
||
|
* after_sendkeys_key_wait (default .01)
|
||
|
|
||
|
* after_button_click_wait (default 0)
|
||
|
|
||
|
* before_closeclick_wait (default .1)
|
||
|
* closeclick_retry (default .05)
|
||
|
* closeclick_dialog_close_wait (default 2)
|
||
|
* after_closeclick_wait (default .2)
|
||
|
|
||
|
* after_windowclose_timeout (default 2)
|
||
|
* after_windowclose_retry (default .5)
|
||
|
|
||
|
* after_setfocus_wait (default .06)
|
||
|
* setfocus_timeout (default 2)
|
||
|
* setfocus_retry (default .1)
|
||
|
|
||
|
* after_setcursorpos_wait (default .01)
|
||
|
|
||
|
* sendmessagetimeout_timeout (default .01)
|
||
|
|
||
|
* after_tabselect_wait (default .05)
|
||
|
|
||
|
* after_listviewselect_wait (default .01)
|
||
|
* after_listviewcheck_wait default(.001)
|
||
|
* listviewitemcontrol_timeout default(1.5)
|
||
|
|
||
|
* after_treeviewselect_wait default(.1)
|
||
|
|
||
|
* after_toobarpressbutton_wait default(.01)
|
||
|
|
||
|
* after_updownchange_wait default(.1)
|
||
|
|
||
|
* after_movewindow_wait default(0)
|
||
|
* after_buttoncheck_wait default(0)
|
||
|
* after_comboboxselect_wait default(.001)
|
||
|
* after_listboxselect_wait default(0)
|
||
|
* after_listboxfocuschange_wait default(0)
|
||
|
* after_editsetedittext_wait default(0)
|
||
|
* after_editselect_wait default(.02)
|
||
|
|
||
|
* drag_n_drop_move_mouse_wait default(.1)
|
||
|
* before_drag_wait default(.2)
|
||
|
* before_drop_wait default(.1)
|
||
|
* after_drag_n_drop_wait default(.1)
|
||
|
* scroll_step_wait default(.1)
|
||
|
|
||
|
"""
|
||
|
|
||
|
import six
|
||
|
import time
|
||
|
import operator
|
||
|
from functools import wraps
|
||
|
|
||
|
from . import deprecated
|
||
|
|
||
|
|
||
|
#=========================================================================
|
||
|
class TimeConfig(object):
|
||
|
|
||
|
"""Central storage and manipulation of timing values"""
|
||
|
|
||
|
__default_timing = {
|
||
|
'window_find_timeout': 5.,
|
||
|
'window_find_retry': .09,
|
||
|
|
||
|
'app_start_timeout': 10.,
|
||
|
'app_start_retry': .90,
|
||
|
|
||
|
'app_connect_timeout': 5.,
|
||
|
'app_connect_retry': .1,
|
||
|
|
||
|
'cpu_usage_interval': .5,
|
||
|
'cpu_usage_wait_timeout': 20.,
|
||
|
|
||
|
'exists_timeout': .5,
|
||
|
'exists_retry': .3,
|
||
|
|
||
|
'after_click_wait': .09,
|
||
|
'after_clickinput_wait': .09,
|
||
|
|
||
|
'after_menu_wait': .1,
|
||
|
|
||
|
'after_sendkeys_key_wait': .01,
|
||
|
|
||
|
'after_button_click_wait': 0,
|
||
|
|
||
|
'before_closeclick_wait': .1,
|
||
|
'closeclick_retry': .05,
|
||
|
'closeclick_dialog_close_wait': 2.,
|
||
|
'after_closeclick_wait': .2,
|
||
|
|
||
|
'after_windowclose_timeout': 2,
|
||
|
'after_windowclose_retry': .5,
|
||
|
|
||
|
'after_setfocus_wait': .06,
|
||
|
'setfocus_timeout': 2,
|
||
|
'setfocus_retry': .1,
|
||
|
|
||
|
'after_setcursorpos_wait': .01,
|
||
|
|
||
|
'sendmessagetimeout_timeout': .01,
|
||
|
|
||
|
'after_tabselect_wait': .05,
|
||
|
|
||
|
'after_listviewselect_wait': .01,
|
||
|
'after_listviewcheck_wait': .001,
|
||
|
'listviewitemcontrol_timeout': 1.5,
|
||
|
|
||
|
'after_treeviewselect_wait': .1,
|
||
|
|
||
|
'after_toobarpressbutton_wait': .01,
|
||
|
|
||
|
'after_updownchange_wait': .1,
|
||
|
|
||
|
'after_movewindow_wait': 0,
|
||
|
'after_buttoncheck_wait': 0,
|
||
|
'after_comboboxselect_wait': 0.001,
|
||
|
'after_listboxselect_wait': 0,
|
||
|
'after_listboxfocuschange_wait': 0,
|
||
|
'after_editsetedittext_wait': 0,
|
||
|
'after_editselect_wait': 0.02,
|
||
|
'drag_n_drop_move_mouse_wait': 0.1,
|
||
|
'before_drag_wait': 0.2,
|
||
|
'before_drop_wait': 0.1,
|
||
|
'after_drag_n_drop_wait': 0.1,
|
||
|
'scroll_step_wait': 0.1,
|
||
|
|
||
|
'app_exit_timeout': 10.,
|
||
|
'app_exit_retry': .1,
|
||
|
}
|
||
|
|
||
|
assert(__default_timing['window_find_timeout'] >=
|
||
|
__default_timing['window_find_retry'] * 2)
|
||
|
|
||
|
_timings = __default_timing.copy()
|
||
|
_cur_speed = 1
|
||
|
|
||
|
def __getattribute__(self, attr):
|
||
|
"""Get the value for a particular timing"""
|
||
|
if attr in ['__dict__', '__members__', '__methods__', '__class__']:
|
||
|
return object.__getattribute__(self, attr)
|
||
|
|
||
|
if attr in dir(TimeConfig):
|
||
|
return object.__getattribute__(self, attr)
|
||
|
|
||
|
if attr in self.__default_timing:
|
||
|
return self._timings[attr]
|
||
|
else:
|
||
|
raise AttributeError("Unknown timing setting: {0}".format(attr))
|
||
|
|
||
|
def __setattr__(self, attr, value):
|
||
|
"""Set a particular timing"""
|
||
|
if attr == '_timings':
|
||
|
object.__setattr__(self, attr, value)
|
||
|
elif attr in self.__default_timing:
|
||
|
self._timings[attr] = value
|
||
|
else:
|
||
|
raise AttributeError("Unknown timing setting: {0}".format(attr))
|
||
|
|
||
|
def fast(self):
|
||
|
"""Set fast timing values
|
||
|
|
||
|
Currently this changes the timing in the following ways:
|
||
|
timeouts = 1 second
|
||
|
waits = 0 seconds
|
||
|
retries = .001 seconds (minimum!)
|
||
|
|
||
|
(if existing times are faster then keep existing times)
|
||
|
"""
|
||
|
|
||
|
for setting in self.__default_timing:
|
||
|
# set timeouts to the min of the current speed or 1 second
|
||
|
if "_timeout" in setting:
|
||
|
self._timings[setting] = \
|
||
|
min(1, self._timings[setting])
|
||
|
|
||
|
if "_wait" in setting:
|
||
|
self._timings[setting] = self._timings[setting] / 2
|
||
|
|
||
|
elif setting.endswith("_retry"):
|
||
|
self._timings[setting] = 0.001
|
||
|
|
||
|
#self._timings['app_start_timeout'] = .5
|
||
|
|
||
|
def slow(self):
|
||
|
"""Set slow timing values
|
||
|
|
||
|
Currently this changes the timing in the following ways:
|
||
|
timeouts = default timeouts * 10
|
||
|
waits = default waits * 3
|
||
|
retries = default retries * 3
|
||
|
|
||
|
(if existing times are slower then keep existing times)
|
||
|
"""
|
||
|
for setting in self.__default_timing:
|
||
|
if "_timeout" in setting:
|
||
|
self._timings[setting] = max(
|
||
|
self.__default_timing[setting] * 10,
|
||
|
self._timings[setting])
|
||
|
|
||
|
if "_wait" in setting:
|
||
|
self._timings[setting] = max(
|
||
|
self.__default_timing[setting] * 3,
|
||
|
self._timings[setting])
|
||
|
|
||
|
elif setting.endswith("_retry"):
|
||
|
self._timings[setting] = max(
|
||
|
self.__default_timing[setting] * 3,
|
||
|
self._timings[setting])
|
||
|
|
||
|
if self._timings[setting] < .2:
|
||
|
self._timings[setting] = .2
|
||
|
|
||
|
def defaults(self):
|
||
|
"""Set all timings to the default time"""
|
||
|
self._timings = self.__default_timing.copy()
|
||
|
|
||
|
Fast = deprecated(fast)
|
||
|
Slow = deprecated(slow)
|
||
|
Defaults = deprecated(defaults)
|
||
|
|
||
|
|
||
|
Timings = TimeConfig()
|
||
|
|
||
|
|
||
|
#=========================================================================
|
||
|
class TimeoutError(RuntimeError):
|
||
|
pass
|
||
|
|
||
|
|
||
|
#=========================================================================
|
||
|
if six.PY3:
|
||
|
_clock_func = time.perf_counter
|
||
|
else:
|
||
|
_clock_func = time.clock
|
||
|
|
||
|
|
||
|
def timestamp():
|
||
|
"""Get a precise timestamp"""
|
||
|
return _clock_func()
|
||
|
|
||
|
|
||
|
#=========================================================================
|
||
|
def always_wait_until(timeout,
|
||
|
retry_interval,
|
||
|
value=True,
|
||
|
op=operator.eq):
|
||
|
"""Decorator to call wait_until(...) every time for a decorated function/method"""
|
||
|
def wait_until_decorator(func):
|
||
|
"""Callable object that must be returned by the @always_wait_until decorator"""
|
||
|
@wraps(func)
|
||
|
def wrapper(*args, **kwargs):
|
||
|
"""pre-callback, target function call and post-callback"""
|
||
|
return wait_until(timeout, retry_interval,
|
||
|
func, value, op, *args, **kwargs)
|
||
|
return wrapper
|
||
|
return wait_until_decorator
|
||
|
|
||
|
|
||
|
#=========================================================================
|
||
|
def wait_until(timeout,
|
||
|
retry_interval,
|
||
|
func,
|
||
|
value=True,
|
||
|
op=operator.eq,
|
||
|
*args, **kwargs):
|
||
|
r"""
|
||
|
Wait until ``op(function(*args, **kwargs), value)`` is True or until timeout expires
|
||
|
|
||
|
* **timeout** how long the function will try the function
|
||
|
* **retry_interval** how long to wait between retries
|
||
|
* **func** the function that will be executed
|
||
|
* **value** the value to be compared against (defaults to True)
|
||
|
* **op** the comparison function (defaults to equality)\
|
||
|
* **args** optional arguments to be passed to func when called
|
||
|
* **kwargs** optional keyword arguments to be passed to func when called
|
||
|
|
||
|
Returns the return value of the function
|
||
|
If the operation times out then the return value of the the function
|
||
|
is in the 'function_value' attribute of the raised exception.
|
||
|
|
||
|
e.g. ::
|
||
|
|
||
|
try:
|
||
|
# wait a maximum of 10.5 seconds for the
|
||
|
# the objects item_count() method to return 10
|
||
|
# in increments of .5 of a second
|
||
|
wait_until(10.5, .5, self.item_count, 10)
|
||
|
except TimeoutError as e:
|
||
|
print("timed out")
|
||
|
"""
|
||
|
start = timestamp()
|
||
|
|
||
|
func_val = func(*args, **kwargs)
|
||
|
# while the function hasn't returned what we are waiting for
|
||
|
while not op(func_val, value):
|
||
|
|
||
|
# find out how much of the time is left
|
||
|
time_left = timeout - (timestamp() - start)
|
||
|
|
||
|
# if we have to wait some more
|
||
|
if time_left > 0:
|
||
|
# wait either the retry_interval or else the amount of
|
||
|
# time until the timeout expires (whichever is less)
|
||
|
time.sleep(min(retry_interval, time_left))
|
||
|
func_val = func(*args, **kwargs)
|
||
|
else:
|
||
|
err = TimeoutError("timed out")
|
||
|
err.function_value = func_val
|
||
|
raise err
|
||
|
|
||
|
return func_val
|
||
|
|
||
|
# Non PEP-8 alias
|
||
|
WaitUntil = deprecated(wait_until)
|
||
|
|
||
|
|
||
|
#=========================================================================
|
||
|
def always_wait_until_passes(timeout,
|
||
|
retry_interval,
|
||
|
exceptions=(Exception)):
|
||
|
"""Decorator to call wait_until_passes(...) every time for a decorated function/method"""
|
||
|
def wait_until_passes_decorator(func):
|
||
|
"""Callable object that must be returned by the @always_wait_until_passes decorator"""
|
||
|
@wraps(func)
|
||
|
def wrapper(*args, **kwargs):
|
||
|
"""pre-callback, target function call and post-callback"""
|
||
|
return wait_until_passes(timeout, retry_interval,
|
||
|
func, exceptions, *args, **kwargs)
|
||
|
return wrapper
|
||
|
return wait_until_passes_decorator
|
||
|
|
||
|
|
||
|
#=========================================================================
|
||
|
def wait_until_passes(timeout,
|
||
|
retry_interval,
|
||
|
func,
|
||
|
exceptions=(Exception),
|
||
|
*args, **kwargs):
|
||
|
"""
|
||
|
Wait until ``func(*args, **kwargs)`` does not raise one of the exceptions
|
||
|
|
||
|
* **timeout** how long the function will try the function
|
||
|
* **retry_interval** how long to wait between retries
|
||
|
* **func** the function that will be executed
|
||
|
* **exceptions** list of exceptions to test against (default: Exception)
|
||
|
* **args** optional arguments to be passed to func when called
|
||
|
* **kwargs** optional keyword arguments to be passed to func when called
|
||
|
|
||
|
Returns the return value of the function
|
||
|
If the operation times out then the original exception raised is in
|
||
|
the 'original_exception' attribute of the raised exception.
|
||
|
|
||
|
e.g. ::
|
||
|
|
||
|
try:
|
||
|
# wait a maximum of 10.5 seconds for the
|
||
|
# window to be found in increments of .5 of a second.
|
||
|
# P.int a message and re-raise the original exception if never found.
|
||
|
wait_until_passes(10.5, .5, self.Exists, (ElementNotFoundError))
|
||
|
except TimeoutError as e:
|
||
|
print("timed out")
|
||
|
raise e.
|
||
|
"""
|
||
|
start = timestamp()
|
||
|
|
||
|
# keep trying until the timeout is passed
|
||
|
while True:
|
||
|
try:
|
||
|
# Call the function with any arguments
|
||
|
func_val = func(*args, **kwargs)
|
||
|
|
||
|
# if no exception is raised then we are finished
|
||
|
break
|
||
|
|
||
|
# An exception was raised - so wait and try again
|
||
|
except exceptions as e:
|
||
|
|
||
|
# find out how much of the time is left
|
||
|
time_left = timeout - (timestamp() - start)
|
||
|
|
||
|
# if we have to wait some more
|
||
|
if time_left > 0:
|
||
|
# wait either the retry_interval or else the amount of
|
||
|
# time until the timeout expires (whichever is less)
|
||
|
time.sleep(min(retry_interval, time_left))
|
||
|
|
||
|
else:
|
||
|
# Raise a TimeoutError - and put the original exception
|
||
|
# inside it
|
||
|
err = TimeoutError()
|
||
|
err.original_exception = e
|
||
|
raise err
|
||
|
|
||
|
# return the function value
|
||
|
return func_val
|
||
|
|
||
|
# Non PEP-8 alias
|
||
|
WaitUntilPasses = deprecated(wait_until_passes)
|