PS:UP主只是某双非一本的测开实习生,在开发脚本的时候发现的这个问题,如果有说的不对的地方和写文章不好的地方请多指教。
涉及的额外概念:
进程和线程的区别
进程的优先级
pywinauto库
python自动化
进程中的线程在 CPU 上执行
写在题头:请检查你的python代码中有没有Application.connect这个方法(别的语言暂未知道是哪个方法名称),如果有的话,那么请继续看下去
1、出错原因
Application(object).connect(self, **kwargs)与window资源管理器explore.exe造成的一些冲突。
2、解决办法
在**kwargs中加入预设的时间参数,或者在这个函数前time.sleep(5)即可
3、基本原理(通过举例说明,以下是一个具体的例子)
(1)背景阐述,介绍这个例子
该例子是开发自动化脚本,要求模拟人的开关WiFi,蓝牙,飞行模式,但是当我打开右下角菜单的时候,出现了找不到WiFi、蓝牙控件的错误。
错误提示:visible_only not found我自己设定要找的字符串比如wlan,bluetooth
(2)windows资源管理器操作进程的原理
首先先说明一点,像桌面上的这种图标,包括你的文件夹
都是由window资源管理器这个进程加载的
有些叫windows资源管理器,有些叫explorer.exe,都一样
这个进程作为实现诸多功能的集大成者,是在开机的时候就已经将这个主进程基本启动了,
但是我们要知道windows为了优化开机速度,从而让某些进程在开机的时候只执行主进程,但是关联的子进程在开机之后才准备就绪,windows资源管理器我想也是如此。
开机之后,大家可以点击这个图标看一下,是不是这个进程打开的时候时间会慢,甚至有卡顿,这就是windows资源管理器在运行子进程的时候,这个优先级没那么高的子进程被人为的打开调用了。增加了该进程的优先级。
我们把这个子进程称作in进程(internet图标相关进程)
进程的状态:
那么就可以知道,刚刚开机的时候,in进程是新建状态
当我们打开右下角WiFi图标的时候,in进程就是执行状态
当我们进行了足够多关于windows资源管理器子进程的调用时(非in线程),那么资源管理器就会把in进程的优先级降低,从线程池中挤兑出去,那么in线程就会进入阻塞状态。
所以in进程基本上的3个状态我们都知道了,从打开时间来说
执行态>阻塞态>新建态
(3)我们为什么会出现错误的原理
打开python中相关的代码段,我通过debug是这里出问题
title的值是字符串,是我自己设定的想找到的按钮
再Ctrl+单击查看connect函数内部
查看到函数内部代码,这里可以发现端倪
(3.1)以下是Application.connect函数
def connect(self, **kwargs):
"""Connect to an already running process
The action is performed according to only one of parameters
:param process: a process ID of the target
:param handle: a window handle of the target
:param path: a path used to launch the target
:param timeout: a timeout for process start (relevant if path is specified)
.. seealso::
:func:`pywinauto.findwindows.find_elements` - the keyword arguments that
are also can be used instead of **process**, **handle** or **path**
"""
timeout = Timings.app_connect_timeout
retry_interval = Timings.app_connect_retry
if 'timeout' in kwargs and kwargs['timeout'] is not None:
timeout = kwargs['timeout']
if 'retry_interval' in kwargs and kwargs['retry_interval'] is not None:
retry_interval = kwargs['retry_interval']
connected = False
if 'process' in kwargs:
self.process = kwargs['process']
try:
wait_until(timeout, retry_interval, self.is_process_running, value=True)
except TimeoutError:
raise ProcessNotFoundError('Process with PID={} not found!'.format(self.process))
connected = True
elif 'handle' in kwargs:
if not handleprops.iswindow(kwargs['handle']):
message = "Invalid handle 0x%x passed to connect()" % (
kwargs['handle'])
raise RuntimeError(message)
self.process = handleprops.processid(kwargs['handle'])
connected = True
elif 'path' in kwargs:
try:
self.process = timings.wait_until_passes(
timeout, retry_interval, process_from_module,
ProcessNotFoundError, kwargs['path'],
)
except TimeoutError:
raise ProcessNotFoundError('Process "{}" not found!'.format(kwargs['path']))
connected = True
elif kwargs:
kwargs['backend'] = self.backend.name
if 'visible_only' not in kwargs:
kwargs['visible_only'] = False
if 'timeout' in kwargs:
del kwargs['timeout']
self.process = timings.wait_until_passes(
timeout, retry_interval, findwindows.find_element,
exceptions=(findwindows.ElementNotFoundError, findbestmatch.MatchError,
controls.InvalidWindowHandle, controls.InvalidElement),
*(), **kwargs
).process_id
else:
self.process = findwindows.find_element(**kwargs).process_id
connected = True
if not connected:
raise RuntimeError(
"You must specify some of process, handle, path or window search criteria.")
if self.backend.name == 'win32':
self.__warn_incorrect_bitness()
if not handleprops.has_enough_privileges(self.process):
warning_text = "Python process has no rights to make changes " \
"in the target GUI (run the script as Administrator)"
warnings.warn(warning_text, UserWarning)
return self
我传进来的参数没有时间,只有字符串,没有process,所以是调用默认的time,而且非path非handle,于是数据直接进入了elif kwargs这里,然后就出现了我们的错误,没有找到该控件,于是我们怀疑时间问题
于是Ctrl+单击查看timings函数
(3.2)以下是Timings
继续Ctrl+单击TimeConfig()
(3.3)以下是TimeConfig()函数
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)
这个函数很长,但是我们只需要关注与我们项目相关的时间(具体看你自己要用到哪个tieout),就知道原来是因为时间设定太短,导致出错。
(4)两个错误之间的联系,也就是2为什么会导致3
1:in进程并没有在一开机就进入线程池,而是被人为打开的时候才被创建,时间超长加时了
2:in线程在开机之后一直没有打开,于是进入了阻塞,再次被调用时间相对慢,但是比开机要快,所以这种情况多出现于一些笔记本电脑,一个线程的执行速度比较慢。所以会出现这个情况。