首页/文章列表/文章详情

PC端自动化测试实战教程-6-pywinauto 打印和保存控件菜单树结构之ElementNotFoundError(详细教程)

编程知识582025-05-20评论

1.简介

其实前边的文章宏哥已经在控制台打印过控件菜单树结构,只是没有将其保存到文件中。只需要一个方法即可。在pywinauto中可以使用 print_control_identifiers() 方法打印控件菜单树结构,这对我们查找控件非常方便。宏哥今天将其单独拎出来是因为Windows10系统和Windows11系统会有一个坑,而且宏哥掉里边了,查了好多资料都没有找到解决办法,最后还好通过自己各种尝试将坑填平,成功爬出来了。其实前边已经遇到了打开记事本最后替换成了notepad++。今天跟随宏哥一步步入坑,然后再一步步填坑,最后成功解决。

2.控件操作

程序窗口中的内容,把它称之为控件,我们要对这个窗口的内容进行操作,就需要选择到对应的控件,获取所有控件我们可以通过print_control_identifiers()这个方法,来获取这个窗口下的直接子控件。因此我们为了清楚可以将控件的菜单结构树打印出来,一目了然。

3.起因

宏哥的台式电脑是Windows10系统的,但是宏哥的笔记本却是Windows11系统的。宏哥在学习和演示打印控件菜单树结构的时候,宏哥首先是在台式电脑(Win10系统)上操作和演示(打印记事本控件结构树)的,但是文章就写了一半,没有写完。这时候刚好由于出差宏哥只能被迫背上笔记本电脑(Win11系统),于是宏哥想要完成剩下的文章就继续将Windows10系统运行成功的代码,直接在Windows11系统上拷贝运行演示操作,结果报错了。。。。运行失败了,一时很懵,不知道如何解决,查了好多资料发现好多人都遇到同样的问题,但是就是没有给出解决办法,有的是提一句如何如何做,宏哥都一一试过了,都不行。就是这样就调入坑中了,要是一直在Windows10系统上操作演示或许就不会有这一篇文章,这一回事了。一切都是命啊,万般不由人,但是臣妾做到了。由于宏哥写文章的时候,手头还是没有Windows10,就网上找了一台免费微软提供类似win10系统,然后简单的搭建了一个环境给小伙伴或者童鞋们进行演示,有兴趣的自己可以试一下:实验 - 使用 Microsoft Office 集成 - Training | Microsoft Learn

4.Windows10系统

4.1代码设计

4.2参考代码

# -*- coding:utf-8 -*-#1.先设置编码,utf-8可支持中英文,如上,一般放在第一行#2.注释:包括记录创建时间,创建人,项目名称。'''Created on 2025-02-12@author: 北京-宏哥北京宏哥(微信搜索:北京宏哥,关注宏哥,提前解锁更多测试干货!)Project: PC端自动化测试实战教程-6-pywinauto打印和保存控件菜单树结构(详细教程)'''#3.导入模块frompywinautoimportApplicationimporttime#通过窗口打开app = Application('uia').start("notepad.exe")win= app['Untitled - Notepad']print(win)print(app.process)win.print_control_identifiers()

4.3运行代码

1.运行代码,右键Run'Test',就可以看到控制台输出,如下图所示:

2.运行代码后电脑端的动作(启动记事本)。如下图所示:

5.Windows11系统

1.宏哥出差了,然后想也没想就将上边在Windows10系统运行成功的代码拷贝到笔记本Windows11系统上的Pycharm中进行运行,结果报错了:pywinauto.findwindows.ElementNotFoundError: {'best_match': 'Untitled - Notepad', 'backend': 'uia', 'process': 31680}

5.1运行代码

1.运行代码,右键Run'Test',就可以看到控制台输出,如下图所示:

2.运行代码后电脑端的动作(启动记事本)。如下图所示:

5.2报错分析

1.宏哥眼睁睁地看记事本启动了,报错却告诉我找不到元素。这不是前后矛盾啊。因为代码中宏哥打印了启动记事本进程号是:28192,如下图所示:

2.然后宏哥查看笔记本电脑的任务管理器的记事本的进程号是:24196 ,如下图所示:

 3.宏哥再次用工具查看,进程号是:24196,如下图所示:

总结:工具查看和任务管理器查看的进程号(24196)相同,但是代码运行启动的进程号(28192)与它们的进程号(24196)不一样,所有才会报错找不到元素,这就可以说通了为啥报这个错。

6.填坑实践

6.1加等待

1.开始填坑,查了好多资料网上说,可能是由于代码运行的快,而PC端程序启动慢导致的,需要加等待,换句话说:应用程序可能需要一段时间才能完全初始化其窗口和UI元素。即便start()方法在内部尝试连接,但如果UI还未完全加载,后续立即进行窗口或控件查找可能失败。于是宏哥就加了等待的代码。

6.1.1代码设计

6.1.2参考代码
# -*- coding:utf-8 -*-#1.先设置编码,utf-8可支持中英文,如上,一般放在第一行#2.注释:包括记录创建时间,创建人,项目名称。'''Created on 2025-02-12@author: 北京-宏哥北京宏哥(微信搜索:北京宏哥,关注宏哥,提前解锁更多测试干货!)Project: PC端自动化测试实战教程-6-pywinauto 打印和保存控件菜单树结构(详细教程)'''#3.导入模块frompywinautoimportApplicationimporttime#通过窗口打开app = Application('uia').start("notepad.exe")time.sleep(3)win= app['无标题 - Notepad']print(win)print(app.process)win.print_control_identifiers()
6.1.3运行代码

1.运行代码,右键Run'Test',就可以看到控制台输出,如下图所示:

2.运行代码后电脑端的动作(启动记事本)。如下图所示:

6.2改路径

1.从上边看到我们失败了,然后宏哥继续查资料,又发现说是将start括号里写成路径的格式就可以。结果仍然是报一样的错误。如下图所示:

6.2.1参考代码
# -*- coding:utf-8 -*-#1.先设置编码,utf-8可支持中英文,如上,一般放在第一行#2.注释:包括记录创建时间,创建人,项目名称。'''Created on 2025-02-12@author: 北京-宏哥北京宏哥(微信搜索:北京宏哥,关注宏哥,提前解锁更多测试干货!)Project: PC端自动化测试实战教程-6-pywinauto 打印和保存控件菜单树结构(详细教程)'''#3.导入模块frompywinautoimportApplicationimporttime#通过窗口打开app = Application('uia').start("C:/Windows/notepad.exe")time.sleep(3)win= app['无标题 - Notepad']print(win)print(app.process)win.print_control_identifiers()

6.3 connect()

手动调用connect()给予额外的时间缓冲,可能恰好让UI准备就绪。结果仍然是报一样的错误。

6.3.1参考代码
# -*- coding:utf-8 -*-#1.先设置编码,utf-8可支持中英文,如上,一般放在第一行#2.注释:包括记录创建时间,创建人,项目名称。'''Created on 2025-02-12@author: 北京-宏哥北京宏哥(微信搜索:北京宏哥,关注宏哥,提前解锁更多测试干货!)Project: PC端自动化测试实战教程-6-pywinauto 打印和保存控件菜单树结构(详细教程)'''#3.导入模块frompywinautoimportApplicationimporttime#通过窗口打开app = Application('uia').start("notepad.exe")app= Application('uia').connect(class_name="Notepad")win= app['无标题 - Notepad']print(win)print(app.process)win.print_control_identifiers()

6.4 connect()和visible_only参数

手动调用connect()给予额外的时间缓冲,然后加上visible_only参数,这是宏哥自己想到的,因为在上边的报错中宏哥看到了visible_only参数,于是宏哥决定加上参数试一下。如下图所示:

6.4.1代码设计

6.4.2参考代码
# -*- coding:utf-8 -*-#1.先设置编码,utf-8可支持中英文,如上,一般放在第一行#2.注释:包括记录创建时间,创建人,项目名称。'''Created on 2025-02-12@author: 北京-宏哥北京宏哥(微信搜索:北京宏哥,关注宏哥,提前解锁更多测试干货!)Project: PC端自动化测试实战教程-6-pywinauto 打印和保存控件菜单树结构(详细教程)'''#3.导入模块frompywinautoimportApplicationimporttime#通过窗口打开app = Application('uia').start("notepad.exe")app= Application('uia').connect(class_name="Notepad",visible_only=False)win= app['无标题 - Notepad']print(win)print(app.process)win.print_control_identifiers()
6.4.3运行代码

1.运行代码,右键Run'Test',就可以看到控制台输出,如下图所示:

2.运行代码后电脑端的动作(启动记事本)。如下图所示:

6.5 connect()和等待

这个也是宏哥在一次偶然运行代码中发现的,因为宏哥忘记将等待的代码段注释掉,结果运行代码成功!哈哈~~,坑一下子就这样跳出来了,要问宏哥是什么原因,宏哥也是一脸懵,一头问号,反正不管怎么说,问题就这样得到解决了。

6.5.1代码设计

6.5.2参考代码
# -*- coding:utf-8 -*-#1.先设置编码,utf-8可支持中英文,如上,一般放在第一行#2.注释:包括记录创建时间,创建人,项目名称。'''Created on 2025-02-12@author: 北京-宏哥北京宏哥(微信搜索:北京宏哥,关注宏哥,提前解锁更多测试干货!)Project: PC端自动化测试实战教程-6-pywinauto 打印和保存控件菜单树结构(详细教程)'''#3.导入模块frompywinautoimportApplicationimporttime#通过窗口打开app = Application('uia').start("notepad.exe")time.sleep(3)app= Application('uia').connect(class_name="Notepad")win= app['无标题 - Notepad']print(win)print(app.process)win.print_control_identifiers()
6.5.3运行代码

1.运行代码,右键Run'Test',就可以看到控制台输出,如下图所示:

2.运行代码后电脑端的动作(启动记事本)。如下图所示:

好了到此打印控件菜单结构树,就大功告成了,下一步我们只需要将其保存就可以了,灰常简单哦!!!

7.保存控件菜单结构树

7.1print_control_identifiers()源码

忙着解决问题,都没有来得及查看一下print_control_identifiers()的源码,如下:

def print_control_identifiers(self, depth=None, filename=None):""" Prints the 'identifiers' Prints identifiers for the control and for its descendants to a depth of **depth** (the whole subtree if **None**). .. note:: The identifiers printed by this method have been made unique. So if you have 2 edit boxes, they won't both have"Edit" listed in their identifiers. In fact the first one can be referred to as"Edit","Edit0","Edit1" and the 2nd should be referred to as"Edit2". """ifdepthis None: depth =sys.maxsize# Wrap this control this_ctrl = self.__resolve_control(self.criteria)[-1]# Create a list of this control and all its descendants all_ctrls = [this_ctrl, ] +this_ctrl.descendants()# Create a list of all visible text controls txt_ctrls = [ctrl forctrlinall_ctrlsifctrl.can_be_labelandctrl.is_visible()andctrl.window_text()]# Build a dictionary of disambiguated list of control names name_ctrl_id_map =findbestmatch.UniqueDict()for index, ctrl in enumerate(all_ctrls): ctrl_names = findbestmatch.get_control_names(ctrl, all_ctrls, txt_ctrls) fornamein ctrl_names: name_ctrl_id_map[name] =index# Swap it around so that we are mapped off the control indices ctrl_id_name_map ={}for name, index in name_ctrl_id_map.items(): ctrl_id_name_map.setdefault(index, []).append(name) def print_identifiers(ctrls, current_depth=1, log_func=print):"""Recursively print ids for ctrls and their descendants in a tree-like format"""if len(ctrls) == 0 or current_depth >depth:returnindent= (current_depth - 1) * u"|"forctrlinctrls:try: ctrl_id =all_ctrls.index(ctrl)exceptValueError:continuectrl_text=ctrl.window_text()ifctrl_text:# transform multi-line text to one liner ctrl_text = ctrl_text.replace('\n', r'\n').replace('\r', r'\r') output = indent + u'\n'output+= indent + u"{class_name} - '{text}' {rect}\n"\"".format(class_name=ctrl.friendly_class_name(), text=ctrl_text, rect=ctrl.rectangle()) output += indent + u'{}'.format(ctrl_id_name_map[ctrl_id]) title = ctrl_text class_name = ctrl.class_name() auto_id = None control_type =Noneifhasattr(ctrl.element_info,'automation_id'): auto_id =ctrl.element_info.automation_idifhasattr(ctrl.element_info,'control_type'): control_type =ctrl.element_info.control_typeif control_type: class_name = None # no need for class_name if control_type existselse: control_type = None # if control_type is empty, still use class_name instead criteria_texts =[]if title: criteria_texts.append(u'title="{}"'.format(title))if class_name: criteria_texts.append(u'class_name="{}"'.format(class_name))if auto_id: criteria_texts.append(u'auto_id="{}"'.format(auto_id))if control_type: criteria_texts.append(u'control_type="{}"'.format(control_type))iftitleorclass_nameor auto_id: output += u'\n' + indent + u'child_window(' + u','.join(criteria_texts) + u')'if six.PY3: log_func(output) else: log_func(output.encode(locale.getpreferredencoding(), errors='backslashreplace')) print_identifiers(ctrl.children(), current_depth + 1, log_func) iffilenameisNone:print("Control Identifiers:") print_identifiers([this_ctrl, ]) else: log_file = codecs.open(filename, "w", locale.getpreferredencoding()) def log_func(msg): log_file.write(str(msg) + os.linesep) log_func("Control Identifiers:") print_identifiers([this_ctrl, ], log_func=log_func) log_file.close() print_ctrl_ids = print_control_identifiers dump_tree = print_control_identifiers

print_ctrl_ids 和 dump_tree 实现的功能与print_control_identifiers等价,都是调用的print_control_identifiers 方法。
用2个参数:

    • depth 查找框架深度,默认全部查找
    • filename 保存本地文件名称

7.2保存到本地文件

1.我们把打印的控件结构树内容保存到本地txt,这样查看更方便,直接CTRL+F查找即可。

7.2.1代码设计

7.2.2参考代码
# -*- coding:utf-8 -*-#1.先设置编码,utf-8可支持中英文,如上,一般放在第一行#2.注释:包括记录创建时间,创建人,项目名称。'''Created on 2025-02-12@author: 北京-宏哥北京宏哥(微信搜索:北京宏哥,关注宏哥,提前解锁更多测试干货!)Project: PC端自动化测试实战教程-6-pywinauto 打印和保存控件菜单树结构(详细教程)'''#3.导入模块frompywinautoimportApplicationimporttime#通过窗口打开app = Application('uia').start("notepad.exe")time.sleep(3)app= Application('uia').connect(class_name="Notepad")win= app['无标题 - Notepad']print(win)print(app.process)win.print_control_identifiers()win.print_ctrl_ids(filename="bjhg.txt")
7.2.3运行代码

1.运行代码,右键Run'Test',就可以看到控制台输出,如下图所示:

2.运行代码后电脑端的动作(启动记事本)。如下图所示:

3.在windows上运行后文件写入的中文内容有乱码,如下图所示:

4.重新设保存文件默认编码可以解决此问题。

7.2.4代码设计

7.2.5参考代码
# -*- coding:utf-8 -*-#1.先设置编码,utf-8可支持中英文,如上,一般放在第一行#2.注释:包括记录创建时间,创建人,项目名称。'''Created on 2025-02-12@author: 北京-宏哥北京宏哥(微信搜索:北京宏哥,关注宏哥,提前解锁更多测试干货!)Project: PC端自动化测试实战教程-6-pywinauto 打印和保存控件菜单树结构(详细教程)'''#3.导入模块frompywinautoimportApplicationimporttimeimportlocaledef getpreferredencoding(do_setlocale =True):return"utf-8"# 设置保存文件编码"utf-8"locale.getpreferredencoding =getpreferredencodingprint(locale.getpreferredencoding())#通过窗口打开app = Application('uia').start("notepad.exe")time.sleep(3)app= Application('uia').connect(class_name="Notepad")win= app['无标题 - Notepad']print(win)print(app.process)win.print_control_identifiers()win.print_ctrl_ids(filename="bjhg.txt")
7.2.6运行代码

1.运行代码,右键Run'Test',就可以看到控制台输出,如下图所示:

2.运行代码后电脑端的动作(启动记事本)。如下图所示:

8.小结

 今天主要讲解和分享的是打印控件菜单结构树的方法:print_control_identifiers()在Windows10系统和Windows11系统上遇到的问题:pywinauto.findwindows.ElementNotFoundError: {'best_match': 'Untitled - Notepad', 'backend': 'uia', 'process': 31680} ,如何遇到,然后宏哥是怎么一步一步解决的,但是其中的原理宏哥还是有点懵,好在是问题暂时解决了。其实回过头来看走过的路,其中有一些走弯路了,当时由于宏哥急于解决问题,没有仔细思考,第一步已经发现进程号都不一样了,什么等待啊、改路径等等全是白扯,根本解决不了问题。现在问题解决了,按照解决问题思路宏哥倒退一下是原理:PC端程序启动后,慢慢在后台加载界面程序,这时宏哥加了等待,然后等待程序加载完成,宏哥然后连接这个加载好的应用程序,这样就确保PC端启动的程序和连接的程序一样(进程号一致),然后执行控件结构树的打印,就不会找不到了。因此等待和连接二者缺一不可,前边宏哥也将而这分开实践了,解决不了问题,仍然报错。二者结合问题解决。

好了,关于打印和保存控件菜单结构树以及不同操作系统遇到的问题,都得到完美解决,仅供参考学习,小伙伴或者童鞋们,有其他更好的解决办法可以给宏哥留言评论哈!时间不早了今天就分享到这里,感谢你耐心地阅读!

神弓

北京-宏哥

这个人很懒...

用户评论 (0)

发表评论

captcha