# 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)