赞
踩
- def __getattribute__(self, attr_name):
- """
- Attribute access for this class
- If we already have criteria for both dialog and control then
- resolve the control and return the requested attribute.
- If we have only criteria for the dialog but the attribute
- requested is an attribute of DialogWrapper then resolve the
- dialog and return the requested attribute.
- Otherwise delegate functionality to :func:`__getitem__` - which
- sets the appropriate criteria for the control.
- """
- allow_magic_lookup = object.__getattribute__(self, "allow_magic_lookup") # Beware of recursions here!
- if not allow_magic_lookup:
- try:
- return object.__getattribute__(self, attr_name)
- except AttributeError:
- wrapper_object = self.wrapper_object() # 这里是重点,需要看对应函数的实现代码
- try:
- return getattr(wrapper_object, attr_name)
- except AttributeError:
- message = (
- 'Attribute "%s" exists neither on %s object nor on'
- 'targeted %s element wrapper (typo? or set allow_magic_lookup to True?)' %
- (attr_name, self.__class__, wrapper_object.__class__))
- raise AttributeError(message)
-
- if attr_name in ['__dict__', '__members__', '__methods__', '__class__', '__name__']:
- return object.__getattribute__(self, attr_name)
-
- if attr_name in dir(self.__class__):
- return object.__getattribute__(self, attr_name)
-
- if attr_name in self.__dict__:
- return self.__dict__[attr_name]
-
- # if we already have 2 levels of criteria (dlg, control)
- # this third must be an attribute so resolve and get the
- # attribute and return it
- if len(self.criteria) >= 2: # FIXME - this is surprising
-
- ctrls = self.__resolve_control(self.criteria)
-
- try:
- return getattr(ctrls[-1], attr_name)
- except AttributeError:
- return self.child_window(best_match=attr_name)
- else:
- # FIXME - I don't get this part at all, why is it win32-specific and why not keep the same logic as above?
- # if we have been asked for an attribute of the dialog
- # then resolve the window and return the attribute
- desktop_wrapper = self.backend.generic_wrapper_class(self.backend.element_info_class())
- need_to_resolve = (len(self.criteria) == 1 and hasattr(desktop_wrapper, attr_name))
- if hasattr(self.backend, 'dialog_class'):
- need_to_resolve = need_to_resolve and hasattr(self.backend.dialog_class, attr_name)
- # Probably there is no DialogWrapper for another backend
-
- if need_to_resolve:
- ctrls = self.__resolve_control(self.criteria)
- return getattr(ctrls[-1], attr_name)
-
- # It is a dialog/control criterion so let getitem
- # deal with it
- return self[attr_name]
- def __get_ctrl(self, criteria_):
- """Get a control based on the various criteria"""
- # make a copy of the criteria
- criteria = [crit.copy() for crit in criteria_]
- # find the dialog
- if 'backend' not in criteria[0]:
- criteria[0]['backend'] = self.backend.name
- if self.app is not None:
- # find_elements(...) accepts only "process" argument
- criteria[0]['process'] = self.app.process
- del criteria[0]['app']
- dialog = self.backend.generic_wrapper_class(findwindows.find_element(**criteria[0]))
-
- ctrls = []
- # if there is only criteria for a dialog then return it
- if len(criteria) > 1:
- # so there was criteria for a control, add the extra criteria
- # that are required for child controls
- previous_parent = dialog.element_info
- for ctrl_criteria in criteria[1:]:
- ctrl_criteria["top_level_only"] = False
- if "parent" not in ctrl_criteria:
- ctrl_criteria["parent"] = previous_parent
-
- if isinstance(ctrl_criteria["parent"], WindowSpecification):
- ctrl_criteria["parent"] = ctrl_criteria["parent"].wrapper_object()
-
- # resolve the control and return it
- if 'backend' not in ctrl_criteria:
- ctrl_criteria['backend'] = self.backend.name
- ctrl = self.backend.generic_wrapper_class(findwindows.find_element(**ctrl_criteria))
- previous_parent = ctrl.element_info
- ctrls.append(ctrl)
-
- if ctrls:
- return (dialog, ) + tuple(ctrls)
- else:
- return (dialog, )
-
- def __resolve_control(self, criteria, timeout=None, retry_interval=None):
- """
- Find a control using criteria
- * **criteria** - a list that contains 1 or 2 dictionaries
- 1st element is search criteria for the dialog
- 2nd element is search criteria for a control of the dialog
- * **timeout** - maximum length of time to try to find the controls (default 5)
- * **retry_interval** - how long to wait between each retry (default .2)
- """
- if timeout is None:
- timeout = Timings.window_find_timeout
- if retry_interval is None:
- retry_interval = Timings.window_find_retry
-
- try:
- ctrl = wait_until_passes(
- timeout,
- retry_interval,
- self.__get_ctrl, # 传入了一个函数接口
- (findwindows.ElementNotFoundError,
- findbestmatch.MatchError,
- controls.InvalidWindowHandle,
- controls.InvalidElement),
- criteria)
-
- except TimeoutError as e:
- raise e.original_exception
-
- return ctrl
-
- def wrapper_object(self):
- """Allow the calling code to get the HwndWrapper object"""
- ctrls = self.__resolve_control(self.criteria)
- return ctrls[-1]
- #=========================================================================
- 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
- #=========================================================================
- def find_element(**kwargs):
- """
- Call find_elements and ensure that only one element is returned
- Calls find_elements with exactly the same arguments as it is called with
- so please see :py:func:`find_elements` for the full parameters description.
- """
- elements = find_elements(**kwargs)
-
- if not elements:
- raise ElementNotFoundError(kwargs)
-
- if len(elements) > 1:
- exception = ElementAmbiguousError(
- "There are {0} elements that match the criteria {1}".format(
- len(elements),
- six.text_type(kwargs),
- )
- )
-
- exception.elements = elements
- raise exception
-
- return elements[0]
以下是我的理解:
1. 通过child_window()无条件的返回了一个pywinauto.application.WindowSpecification对象,这个对象只是封装了对应的查找条件,拿着这个返回对象还不能知道是否成功查找到窗口;
2. WindowSpecification对象获取其wapper_object()时才开始真正查找。wrapper_object() -> _resolve_control() -> wait_until_passes() -> _get_ctrl() -> findwindows.find_element()
3. wait_until_passes()中使用了总超时时间和重试间隔,当总时间超时时会抛出Timeout异常。如果__get_ctrl()返回了正常值就立即返回;
4.wait_until_passes会捕获_resolve_control()中指定的(findwindows.ElementNotFoundError,
findbestmatch.MatchError,
controls.InvalidWindowHandle,
controls.InvalidElement)几种类型的异常,当出现这些异常时会继续进行重试。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。