Python教程:Pywinauto库使用指南(持续更新)

1- pywinauto 中的uia是什么意思?

pywinauto库中,uia指的是UI Automation,这是Windows操作系统提供的一种技术框架,用于实现用户界面(UI)的自动化测试和辅助功能访问。UI Automation是微软从Windows Vista开始引入的核心技术,旨在为所有UI元素提供一致的编程接口,无论这些元素是由何种技术(如Win32、WPF、UWP等)实现的。

pywinauto中,通过指定backend="uia",用户可以选择使用UI Automation作为底层引擎来与应用程序的UI元素进行交互。这为自动化测试提供了更广泛的支持,尤其是在处理现代Windows应用(尤其是使用WPF和UWP技术构建的应用)时更为有效。UI Automation后端相比传统的win32后端,提供了更丰富和高级的元素属性、控制模式(Control Patterns)以及事件支持,使得自动化脚本可以更加灵活和强大。

使用UI Automation后端,开发者可以更容易地定位和操作UI元素,如获取元素的属性、模拟鼠标和键盘操作、监听UI事件等,这对于创建复杂的自动化测试场景尤其有用。

2-pywinauto 中main_window.child_window()方法及参数详细介绍

pywinauto中,child_window()方法是用于定位父窗口中的一个特定子窗口或控件。这对于自动化测试中准确地找到并操作特定的UI元素至关重要。下面是对child_window()方法及其参数的详细说明:

基本用法

child_window()方法属于窗口对象,用于在当前窗口的所有子窗口中搜索符合指定条件的第一个子窗口。基本语法如下:

Python

1parent_window.child_window(arguments)

其中,parent_window是你已经获取到的窗口对象,arguments是用于筛选子窗口的一系列关键字参数。

参数详解

child_window()方法接受多个关键字参数来精确地定位子窗口,常见的参数包括但不限于:

  • title: 字符串,控件的标题或文本。
  • class_name: 字符串,控件的类名。
  • control_type: 字符串,控件的类型(仅在UIA backend下有效)。
  • auto_id: 字符串,控件的自动化ID(AutomationId)。
  • name: 字符串,控件的名称属性,可能与标题或自动化ID不同。
  • backend: 字符串,指定后端类型,如 'win32' 或 'uia',默认自动选择。
  • process: 整数,指定目标窗口所在的进程ID。
  • enabled: 布尔值,是否只查找启用的控件。
  • visible: 布尔值,是否只查找可见的控件。
  • top_level_only: 布尔值,是否只在顶级窗口中查找。
  • found_index: 整数,当找到多个匹配项时,可以选择特定索引的匹配项。
  • control_id: 整数,控件的ID(Windows API中的控件ID)。
  • handle: 整数或ctypes.c_void_p,直接指定控件的句柄。
  • regex_title: 正则表达式对象或字符串,用于匹配标题的正则表达式。
  • regex_class_name: 同上,但用于匹配类名的正则表达式。
  • best_match: 字符串,用于best_match()逻辑,尝试匹配最接近的控件。
  • 示例

    假设你想在记事本应用的主窗口中找到名为“文件”(File)的菜单项,可以这样做:

    Python

    from pywinauto import Application
    
    app = Application(backend="uia").start("notepad.exe")
    main_window = app.window(title="无标题 - 记事本")
    
    # 使用class_name和title定位子窗口
    menu_item = main_window.child_window(class_name="MenuItem", title="文件")

    注意事项

  • 参数组合:通常,为了精确匹配,你需要结合多个参数一起使用。比如,同时使用class_nametitle可以更准确地定位目标控件。
  • 性能考量:如果条件设置得过于宽泛,可能会导致搜索缓慢或找到错误的控件。尽量使用具体的属性来缩小搜索范围。
  • 异常处理:如果找不到匹配的子窗口,child_window()会抛出异常。可以使用try-except结构来妥善处理这种情况。
  • 通过灵活运用child_window()方法及其参数,你可以高效地定位并操作UI自动化中的各种元素。

    3-child_window()    children()   descendants()方法介绍

    如果要获取父窗口下的所有直接子窗口(而不只是单个子窗口),可以使用children()方法,它会返回一个包含所有直接子元素的列表。例如:

    #此定位元素的方式,需要window类型为:WindowSpecification
    

    Python

    all_direct_children = parent_window.children()

    而如果你需要递归地获取所有子孙窗口(子窗口以及子窗口的子窗口等),可以使用descendants()方法:

    #此定位元素的方式,需要window类型为:UiaWrapper

    Python

    all_descendants = parent_window.descendants()

    总结来说,parent_window.child_window()是用于定位单个满足特定条件的子窗口,而不是获取所有子内容。 若要获取所有子内容,请根据需要使用children()descendants()

    4-pywinauto打开程序时,程序启动的比较慢,或点击按钮弹窗比较慢,或者打开新窗口窗口渲染的比较慢,怎么添加合理的等待时间

    在使用pywinauto进行自动化操作时,合理地添加等待时间是非常重要的,尤其是在程序启动慢、按钮点击后弹窗延迟或新窗口渲染速度较慢的情况下。这能确保脚本在继续执行下一步操作前,等待相关的UI元素准备就绪。以下是几种添加等待时间的方法:

    1. 显式等待(推荐)

    使用wait方法是最灵活且推荐的等待方式,它允许你指定等待的条件和超时时间。

    等待窗口可见:

    Python

    app.Window(title="窗口标题").wait("visible", timeout=10)
    等待控件可用:

    Python

    app.Dialog.child_window(title="按钮标题", control_type="Button").wait("enabled", timeout=20)

    2. 静态等待(time.sleep)

    虽然不如显式等待灵活,但在某些简单场景下可以使用time.sleep来简单地暂停脚本执行。

    Python

    import time
    time.sleep(5)  # 等待5秒

    3. 动态等待

    对于某些不确定何时完成的操作,可以在循环中结合检查条件和短暂停顿来实现动态等待。

    Python

    import time
    
    timeout = 30  # 总超时时间
    interval = 1  # 检查间隔
    
    start_time = time.time()
    while time.time() - start_time < timeout:
        if app.Window(title="窗口标题").exists():
            break
        time.sleep(interval)
    else:
        raise TimeoutError("窗口未在指定时间内出现")

    4. 使用backend的特性

    某些后端(如uia)提供了更高级的等待逻辑,例如在启动应用程序时可以利用Application().start(..., timeout=...)来指定启动超时时间。

    5-pywinauto 打开的app有多个不同的窗口时,怎么定位具体的某个窗口

    pywinauto中,如果一个应用程序打开了多个不同的窗口,要定位到具体的某个窗口,你可以根据窗口的特征来精确筛选。以下是一些常用的筛选方法:

    1. 根据窗口标题(Title)

    窗口标题通常是区分不同窗口最直观的属性。你可以使用窗口标题的精确文本或正则表达式来定位窗口。

    Python

    specific_window = app.window(title="窗口的精确标题")
    # 或者使用正则表达式来模糊匹配
    specific_window = app.window(title_re="窗口标题的一部分.*")

    2. 结合类名(Class Name)

    如果多个窗口的标题相似,但类名不同,你可以结合类名一起筛选。

    Python

    specific_window = app.window(title="窗口标题", class_name="窗口类名")

    3. 控件类型(Control Type)和自动化ID(Automation ID)

    在使用UIA后端时,如果窗口内有特定类型的控件或者控件具有唯一的Automation ID,也可以作为定位依据。

    Python

    # 假设窗口内有特定的控件类型或ID
    specific_window = app.window(control_type="Window", automation_id="特定的自动化ID")

    4. 使用子窗口或后代窗口

    如果需要定位的窗口是另一个窗口的子窗口或后代,可以先定位到父窗口,再通过child_window()descendants()方法找到目标窗口。

    Python

    parent_window = app.window(title="父窗口标题")
    specific_child_window = parent_window.child_window(title="子窗口标题")
    # 或者遍历所有后代窗口找到匹配的
    specific_descendant_window = parent_window.descendants(title="后代窗口标题")[0]  # 注意这会返回一个列表

    5. 通过句柄(Handle)

    如果你已经知道了窗口的句柄,可以直接通过句柄连接到窗口。

    Python

    from pywinauto.windows import win32functions
    
    handle = win32functions.FindWindowEx(None, None, "窗口类名", "窗口标题")
    specific_window = app.window(handle=handle)

    注意事项

  • 在使用wait方法确保窗口已经加载和可见,特别是在定位动态弹出的窗口时,这可以避免因窗口未准备好而导致的错误。
  • 当有多个窗口匹配相同的筛选条件时,考虑使用更具体的筛选条件或结合多个属性来定位。
  • 如果窗口是在不同进程中创建的,可能需要使用Application.connect()方法来单独连接到那个窗口。
  • 6-pywinauto 中element_info 和BaseWrapper的用法

    pywinauto中,element_infoBaseWrapper是两个核心概念,它们在自动化测试和界面操作中扮演着重要角色。

    element_info (pywinauto.element_info.ElementInfo)

    element_info是一个底层的、面向元素信息的抽象类,它提供了对UI元素属性的访问和描述,但不直接支持操作。它是一个轻量级的包装,用于存储和传递关于UI元素的信息,如类名、标题、位置等,而不涉及实际的UI操作。element_info是所有控件信息的基础,无论控件是通过哪个后端(如win32, uia)来识别的。

    当你使用pywinauto的方法(如child_window())来定位一个元素时,实际上就是在创建或查找一个element_info对象。element_info对象可以被用来创建更高级的包装对象,如BaseWrapper

    BaseWrapper (pywinauto.base_wrapper.BaseWrapper)

    BaseWrapper是基于element_info的更高层次的包装,它不仅包含了元素的信息,还提供了对元素进行操作的方法,如点击、发送文本、获取属性等。当你需要与UI元素进行交互时,比如点击一个按钮或填写一个文本框,你应该使用BaseWrapper对象。

    BaseWrapperelement_info的直接扩展,它将底层的元素信息转换为可操作的对象。这意味着,你可以通过BaseWrapper直接执行如.click(), .set_text(), .texts(), .exists()等操作,使得自动化脚本编写更为直观和便利。

    使用示例

    假设你要操作一个按钮,可以按照以下步骤:

    Python

    from pywinauto import Application
    
    app = Application().start("your_app.exe")
    main_win = app.window(title="Main Window Title")
    
    # 使用child_window()方法找到一个按钮,得到的是一个BaseWrapper实例
    button = main_win.child_window(title="Click Me", control_type="Button")
    
    # 然后,你可以直接调用BaseWrapper的方法来操作这个按钮
    button.click()

    在这个过程中,虽然没有直接使用element_info,但实际上child_window()内部处理了从element_infoBaseWrapper的转换,让你能够直接与UI元素进行交互。

    7-pywinauto 的BaseWrapper类还有哪些方法,详细说明

    根据搜索到的内容总结如下

    pywinautoBaseWrapper类提供了许多方法来操作和查询UI元素。这些方法覆盖了从基本的属性访问到复杂的UI交互,下面是BaseWrapper类中一些常用方法的概览和简要说明:

    常用属性和方法

    1. .window_text(): 获取控件的文本内容。
    2. .texts(): 返回一个包含控件所有文本内容的列表,适用于包含多行文本的控件。
    3. .class_name(): 返回控件的类名。
    4. .control_type(): 使用UIA后端时,返回控件的类型。
    5. .enabled(): 检查控件是否启用。
    6. .visible(): 检查控件是否可见。
    7. .rectangle(): 获取控件的矩形区域,包括坐标和大小。
    8. .set_focus(): 将焦点设置到控件上。
    9. .click_input(): 模拟鼠标点击控件。
    10. .double_click_input(): 双击控件。
    11. .right_click_input(): 右键点击控件。
    12. .drag_drop(target): 拖拽控件到指定的目标。
    13. .type_keys(keys, with_spaces=True, set_foreground=True): 向控件发送键盘输入。
    14. .set_text(text): 设置文本控件的内容。
    15. .texts(): 获取控件的所有文本内容,适用于可能有多行文本的控件。

    控件定位和操作扩展

    1. .child_window(**kwargs): 在当前控件的子控件中查找并返回匹配条件的BaseWrapper对象。
    2. .descendants(**kwargs): 返回当前控件的所有子孙控件的列表,这些控件满足给定的筛选条件。
    3. .parent(): 返回当前控件的直接父控件的BaseWrapper对象。
    4. .top_parent(): 返回当前控件的最顶级父控件的BaseWrapper对象。

    属性获取与设置

  • .get_properties(): 获取控件的所有属性信息。
  • .set_property(name, value): 设置控件的属性值。
  • 图像操作

  • .capture_as_image(): 截取控件的图像,返回一个PIL图像对象。
  • 其他

  • .wait('exists', timeout=timedelta(seconds=5)): 等待控件存在,直到超时。
  • .wait('visible', timeout=timedelta(seconds=5)): 等待控件变得可见。
  • .wait_not('visible', timeout=timedelta(seconds=5)): 等待控件不再可见。
  • 以上只列出了部分方法,实际上BaseWrapper类包含更多功能,涵盖了大多数自动化测试和UI操作的需求。具体的使用方法和参数细节,建议参考pywinauto的官方文档或直接查看源代码注释,以获取最全面和最新的信息。

  • 尝试显式类型转换:如果适用,可以尝试将找到的元素转换为具体的控件类型,然后调用 .click_input() 方法。
  • Python

    from pywinauto.controls.win32_controls import ButtonWrapper
    
    button = ButtonWrapper(element)
    button.click_input()

    如果以上方法都不能解决问题,建议查阅 pywinauto 的官方文档或访问其GitHub页面查看是否有相关的问题报告或更新日志。同时,确保你的Python环境是最新的,并考虑升级 pywinauto 到最新稳定版。

    8-pywinauto 都可以转换成什么控件类型Wrapper

    pywinauto 提供了一系列针对不同控件类型的Wrapper类,这些类封装了对特定类型控件的操作方法,使得自动化操作更加直观和方便。以下是部分常用的控件类型及其对应的Wrapper类:

    1. ButtonButtonWrapper

    2. 用于操作按钮,支持点击等操作。
    3. EditEditWrapper

    4. 用于编辑框,支持文本的设置、获取等操作。
    5. ComboBoxComboBoxWrapper

    6. 用于组合框(下拉列表),支持选择项、获取选项列表等。
    7. ListBoxListBoxWrapper

    8. 用于列表框,支持选择项、获取项列表等。
    9. CheckBoxCheckBoxWrapper

    10. 用于复选框,支持勾选、取消勾选及状态检查。
    11. RadioButtonRadioButtonWrapper

    12. 用于单选按钮,支持选择及状态检查。
    13. MenuMenuWrapper

    14. 用于菜单项,支持展开菜单、选择菜单项等。
    15. TreeViewTreeViewWrapper

    16. 用于树状视图控件,支持节点的展开、选择等。
    17. TabItemTabWrapper

    18. 用于选项卡控件中的单个选项卡。
    19. DialogDialogWrapper

    20. 对话框窗口的特殊处理,虽然也是窗口,但可能包含特定于对话框的操作方法。
    21. WindowWindowWrapper

    22. 通用窗口类,几乎所有窗口都可以用这个类来操作,但针对特定控件类型使用特定的Wrapper会更方便。

    请注意,直接将element_info转换为特定的Wrapper类(如从element_infoButtonWrapper)并不总是直接支持,通常你不需要手动进行这样的转换。当你通过正确的筛选条件使用child_window()等方法时,pywinauto会自动为你提供最适合的Wrapper实例。如果需要更具体的控件操作,可以通过访问BaseWrapper实例的属性或方法来实现,因为大多数Wrapper类都是从BaseWrapper继承而来,已经包含了基础的交互方法。

    在实际使用中,直接利用child_window()方法根据控件的属性(如标题、类名、控制类型等)来定位并操作控件即可,大多数情况下无需关心底层的Wrapper类转换。如果需要更高级或特定的功能,查阅pywinauto的文档或源码,了解特定Wrapper类的使用方法会更有帮助。

    9-pywinauto打印获取的windows对象,一个是加了等待,一个是没加等待。获取的window对象使用print(window),打印出来分别是uiawrapper.UIAWrapper – 'New mail', Dialog,另一个是<pywinauto.application.WindowSpecification object at 0x0000016B0DE556A0> 有什么区别

    在使用pywinauto时,你观察到的区别反映了两种不同状态的窗口对象处理方式。

    未使用等待(直接获取)

    Python

    window = app.window(title="New mail")
    print(window)

    输出类似于 <pywinauto.application.WindowSpecification object at 0x0000016B0DE556A0>,这表明你获得的是一个WindowSpecification对象。这个对象实际上是一个窗口的描述符或者说规范,它定义了你要找的窗口应该满足的条件(在这个例子中是标题为"New mail"的窗口)。但是,此时pywinauto尚未实际找到并实例化这个窗口,这意味着打印出来的信息是一个内存地址,代表了这个待查找窗口描述的实例。

    使用等待后获取

    Python

    window = app.window(title="New mail").wait('visible')
    print(window)

    或者

    Python

    window = app.window(title="New mail").wait('exists', timeout=10)

    输出为 uiawrapper.UIAWrapper - 'New mail', Dialog,这表示你成功获取到了一个实际的UI元素对象,它已经不是一个简单的查找规范,而是窗口实际存在的、可操作的对象。uiawrapper.UIAWrapperpywinauto内部用于包装UIA(UI Automation)元素的类,这意味着窗口不仅被找到了,而且已经准备好了进行进一步的交互操作,比如点击、输入文本等。

    区别总结

  • 未使用等待:你得到的是一个窗口的查找描述符,它定义了要找什么,但不代表窗口已被找到或实例化。
  • 使用等待:你得到的是一个实际的UI元素对象,它代表了已经找到的窗口,可以直接用于执行各种操作。
  • 在实际应用中,使用等待(如.wait('visible').wait('exists'))通常更为推荐,因为它确保了操作不会在窗口还没准备好时进行,提高了脚本的稳定性和可靠性。

    10-我的pywinauto程序,Application.start()后,还需要执行application.connect() 后的app ,才能正常使用。否则此app获得的windows不可用,为什么?

    pywinauto中,理论上,一旦你使用Application.start()方法启动了一个应用程序,它应该自动连接到这个新启动的应用实例,并且你能够直接通过返回的app实例进行窗口和控件的查找及操作,无需再次调用app.connect()

    如果遇到必须先执行app.connect()后才能正常使用app获得的窗口或控件,这可能由几个原因引起:

    1. 启动时间问题:应用程序可能需要一段时间才能完全初始化其窗口和UI元素。即便start()方法在内部尝试连接,但如果UI还未完全加载,后续立即进行窗口或控件查找可能失败。此时,手动调用connect()给予额外的时间缓冲,可能恰好让UI准备就绪。

    2. 后台启动或隐藏窗口:某些应用程序可能在启动时没有立即显示窗口,或者窗口最小化到托盘/后台运行,导致初次连接时无法正确识别窗口。稍后调用connect()可能窗口已变为可见或可交互状态。

    3. UI自动化框架的兼容性或bug:尽管少见,但不同版本的pywinauto或目标应用程序的UI框架(如WPF、UWP等)可能存在兼容性问题,导致自动连接行为不如预期。

    4. 多实例问题:如果你的应用程序允许或意外启动了多个实例,start()可能连接到了不同的实例,而你期望操作的窗口位于另一个实例中。再次手动指定连接条件确保连接到正确的窗口实例。

    解决方法建议:

  • 增加启动后等待:在start()后,加入适当的等待时间(如time.sleep()),让应用程序有足够的时间初始化窗口和UI元素。

  • 精确连接:确保connect()时使用足够的参数(如窗口标题、类名等)来精确匹配你想要连接的窗口,避免连接到错误的实例或窗口。

  • 检查pywinauto版本:确保你使用的pywinauto版本是最新的,以获取最佳的兼容性和稳定性。

  • 日志和调试:使用pywinauto的日志功能或打印调试信息,检查启动和连接过程中可能出现的任何异常或警告,帮助定位问题所在。

  • 如果问题持续,考虑查看pywinauto的官方文档或社区,可能有针对特定应

    用或类似问题的解决方案。

    我的问题其实应该是兼容问题,我增加start()后的等待时间,无效。

    必须使用Application.connect()命令,重新获取app后,只有的操作才有效。

    app = Application(backend="uia").start(outlook_path, timeout=30)
    # 等待也是必须的
    time.sleep(15)
    
    # 必须使用connect()重新生成
    app = Application(backend="uia").connect(class_name="Olk Host")
    print("outlook第一次启动,打开outlook成功!, app=", app)

    11-Application(backend="uia").start()参数中有timeout参数吗?

    没有。

    因为在标准的pywinauto文档和通常的使用中,直接的start()方法调用并不直接接受一个timeout参数用于等待应用程序启动完成。

    通常,如果你想控制应用程序启动后的等待逻辑或处理超时,你需要采取一些额外的步骤,而不是直接通过start()方法的timeout参数(因为这个参数在标准用法中不存在)。以下是一些建议:

    1. 手动等待:在start()调用之后,你可以使用time.sleep()或更复杂的逻辑来等待一段预估的合理时间,确保应用程序有足够的时间启动并加载完毕。

    2. 动态等待窗口:利用pywinauto的等待机制,比如top_window()wait()方法,来动态等待直到应用程序的某个窗口变得可用或满足特定条件。例如,如果你知道应用程序启动后主窗口的标题,可以这样做:

      Python

      app = Application(backend="uia").start("your_app_path")
      main_window = app.window(title="Your Main Window Title").wait("visible", timeout=30)

      这里,timeout=30就是等待窗口变为可见的最大时间,单位为秒。

    3. 异常处理:在尝试访问应用程序窗口或执行操作时,使用try-except结构来捕获可能的超时或失败异常,从而实现间接的超时控制。

    4. 自定义逻辑:根据应用程序的具体行为,你可能需要更定制化的逻辑来确定何时应用程序已成功启动并准备好进行自动化操作,这可能涉及检查特定进程的状态、窗口属性变化等。

    综上所述,虽然直接在Application.start()方法中设置timeout参数不是标准用法,但通过上述方法你可以有效地控制和管理应用程序启动后的等待逻辑及超时处理。

    12-Application(backend="uia").connect()参数中有timeout参数吗?

    没有。

    pywinauto提供了灵活的等待和超时控制机制来确保连接操作不会无限期地阻塞。

    自动化等待与超时控制

    虽然直接在connect()方法签名中没有公开timeout参数,但pywinautowait系列方法和自动化操作通常允许你指定一个超时限制,例如在尝试连接到一个窗口时:

    Python

    app = Application(backend="uia")
    main_window = app.window(title="Your App Title").wait("exists", timeout=30)

    在这个例子中,wait("exists", timeout=30)是尝试等待窗口存在最多30秒。如果窗口在指定时间内变得可访问,则继续执行;如果超时,则会抛出异常,允许你的脚本据此作出反应。

    定制等待逻辑

    对于更复杂的场景,你可能需要自定义等待逻辑,比如检查窗口是否不仅存在还满足其他条件(如“visible”、“enabled”等),并结合自定义的循环和超时处理。

    总结

    尽管Application(backend="uia").connect()直接调用时不带有一个明确的timeout参数,pywinauto提供的wait方法和自动化操作的灵活性让你能够有效控制连接和等待窗口出现的时间限制,实现类似超时处理的逻辑。通过这种方式,确保了自动化脚本既能够稳健地等待必要的窗口准备就绪,又能避免不必要的阻塞。

    13-弹窗操作

    pywinauto中,当需要连接并操作一个应用程序新弹出的窗口时,通常需要先确保新窗口已经出现,然后通过窗口的标题或其他属性来定位并操作它。以下是处理新弹窗的基本步骤:

    1. 等待弹窗出现

    使用pywinauto的等待功能确保弹窗已经打开。这可以通过循环检查窗口是否存在,或者使用更高级的等待方法来实现。

    Python

    from pywinauto import Application
    from pywinauto.timings import wait_until_passes
    
    app = Application().connect(title="主应用程序的标题")  # 首先连接到主应用程序
    
    def is_popup_opened():
        try:
            app.window(title="弹窗的标题")
            return True
        except Exception:
            return False
    
    # 等待弹窗出现,最多等待10秒
    wait_until_passes(10, 1, is_popup_opened)

    2. 定位并操作弹窗

    一旦确认弹窗已经打开,就可以通过其标题或其它属性来定位并进行操作。

    Python

    popup_window = app.window(title="弹窗的标题")
    
    # 例如,点击弹窗上的一个按钮
    button_on_popup = popup_window.child_window(title="按钮的标题或自动化ID", control_type="Button")
    button_on_popup.click()

    3. 异常处理和重试机制

    在实际操作中,可能需要添加适当的异常处理逻辑,以防窗口标题变化或打开速度过慢等情况。

    4. 关闭弹窗

    如果操作完成后需要关闭弹窗,可以调用窗口的关闭方法:

    Python

    popup_window.close()

    注意事项:

  • 确保窗口标题是唯一的,或者足够精确以避免误操作其他窗口。
  • 使用control_typeclass_name等属性可以帮助更精确地定位控件。
  • 如果弹窗属于同一个应用程序,通常不需要再次调用Application().connect(),直接在已连接的应用实例上操作即可。
  • 根据弹窗出现的时机和应用的具体行为,可能需要微调等待逻辑。
  • 14-AttributeError: 'UIAWrapper' object has no attribute 'print_control_identifiers'

    pywinauto中,如果你遇到AttributeError: 'UIAWrapper' object has no attribute 'print_control_identifiers'错误,这意味着你尝试在一个基于UIA(UI Automation)的控件上使用了print_control_identifiers方法,但这个方法实际上并不直接存在于UIAWrapper对象中。

    print_control_identifiers方法主要用于经典的Windows应用程序自动化(win32应用程序),它在Win32Window对象中可用。然而,对于基于UI Automation(UIA)的应用,如现代的UWP应用或使用了UIA框架构建的桌面应用,直接使用print_control_identifiers会报错。

    对于UIAWrapper对象,你可以使用.legacy_properties()方法来获取控件的部分信息,但这通常不如print_control_identifiers那样详尽。

    解决方案:

    1. 检查应用类型:确认你的应用是否是现代UIA应用还是传统的win32应用。如果是UIA应用,继续阅读下文。

    2. 使用UIA属性:对于UIAWrapper对象,你可以利用.properties属性来查看控件的属性。例如:

    Python

    1control = app.window(title="Your Window Title").child_window(auto_id="Your Control AutoId")
    2print(control.properties)

    这将打印出控件的属性字典,其中包含了控制类型、名称、位置等信息。

    1. 自定义打印函数:如果需要类似print_control_identifiers的详细输出,你可能需要自己编写一个函数来遍历.properties或使用UIA的其他方法来获取更详细的控件信息。

    2. 降级为win32:在某些情况下,如果应用同时支持UIA和win32,你可以尝试降级到win32模式来使用print_control_identifiers。但这取决于应用的具体实现和支持情况,且不是所有UIA应用都能这么做。

    记住,UI Automation和经典Windows应用程序自动化在pywinauto中有一些差异,因此在处理不同类型的窗口和控件时,要相应地调整你的方法。

    15-AttributeError: 'UIAWrapper' object has no attribute 'child_window'

    UIAWrapper 窗口类型, 定位元素需要使用如下方法定位:
    ele = setWindow.descendants(title="Remove")[0]
    

    当你在使用pywinauto遇到AttributeError: 'UIAWrapper' object has no attribute 'child_window'错误时,这意味着你尝试在一个基于UIA(UI Automation)的控件(UIAWrapper对象)上调用了child_window方法,但这个方法在UIAWrapper对象中并不可用。

    UIAWrapper对象主要对应于使用UI Automation技术的应用程序,与传统的Win32应用程序使用的API有所不同。在UIAWrapper上,直接查找子窗口或控件的方法不是child_window,而是使用更符合UIA查询方式的属性和方法。

    解决方案:

    1. 使用.descendants方法:对于UIAWrapper对象,可以使用.descendants方法来查找所有子孙控件,然后通过条件过滤找到目标控件。

    Python

    controls = window.descendants(control_type="ControlType")  # 替换"ControlType"为实际控件类型,如"Button", "Edit",
    1. 直接属性访问:如果已知某些属性(如 namecontrol_type等),可以直接通过属性筛选。

    Python

    target_control = window.child_window(auto_id="目标控件的AutomationId")

    注意:上述child_window方法是针对经典的win32应用程序,对于UIA,你应该使用更符合其查询机制的方法,如.descendants或直接属性筛选。

    1. 构造查询条件:使用更复杂的查询条件来精确定位控件,例如使用.filter()方法。

    Python

    1from pywinauto.controls.uia_controls import UIAWrapper
    2control = UIAWrapper(handle=你的窗口句柄).filter(control_type="ControlType")[0]  # 替换"ControlType"和条件

    确保理解你的应用是基于UI Automation还是传统的Win32,然后选择相应的API和方法来操作和查找控件。对于UIA应用,利用.descendants和属性筛选通常是最直接有效的方式。

    16-pywinauto,已经找到窗口了,但使用window的方法print_control_identifiers()方法,会提示匹配到多个元素呢?There are 5 elements that match the criteria {'backend': 'uia', 'process': 22188}

    当您已经成功定位到一个窗口,但在尝试使用 window.print_control_identifiers() 方法时遇到提示匹配到多个元素,这通常意味着您调用 print_control_identifiers() 的对象实际上是一个窗口集合或者是基于某些宽泛条件的窗口规范(WindowSpecification),而不是一个特定的窗口实例。

    为了解决这个问题,确保您在调用 print_control_identifiers() 之前,已经准确地指定了一个唯一的窗口。这里有几个步骤可以帮助您:

    1. 重新检查定位窗口的代码:确认您是否确实已经通过足够精确的条件定位到单一窗口。比如,使用窗口的标题、类名、进程ID等属性来精确筛选。

    2. 使用更具体的选择器:如果可能,增加筛选条件,比如使用正则表达式匹配窗口标题 (title_re) 或是直接使用窗口的自动化ID (auto_id)。

    3. 确保调用链正确:如果通过一系列操作到达某个窗口,确认每一步都正确返回了预期的窗口对象。有时,错误的调用顺序可能导致返回的是窗口集合而非单个窗口。

    4. 检查是否使用了模糊匹配:避免使用可能匹配到多个窗口的过于宽泛的条件,如只使用 backendprocess 作为筛选条件。

    5. 显式地获取窗口实例:在确定了窗口后,可以尝试显式地通过索引或其他方法获取具体的窗口实例。例如,如果您知道目标窗口是匹配条件下的第一个,可以尝试:

    Python

    specific_window = windows[0]  # 假设'windows'是一个包含多个匹配窗口的列表
    specific_window.print_control_identifiers()
    1. 使用 top_level_only=False:如果目标窗口不是顶级窗口,尝试设置 top_level_only=False 来搜索所有级别的窗口,然后进一步筛选。

    2. 调试与分析:如果以上方法都无法解决问题,可以先使用 print_control_identifiers()depth=-1 参数来打印所有窗口的信息,帮助您识别哪些窗口与您的预期不符,并据此调整选择条件。

    确保每个步骤都尽可能具体和精确,以避免匹配到多个窗口的情况发生。

    17-pywinauto, 使用Desktop(backend="uia")获取窗口

    注意点1:
    Desktop(backend="uia").windows()/window(),两个方法,只能查询所有的主窗口,不支持查询主窗口下都子窗口。
    
    注意点2:
    使用Desktop(backend="uia").windows()/window(),如果条件不能定位到窗口的情况下。
    
    Desktop(backend="uia").windows()方法,结果返回空列表。
    如下:
    wins = Desktop(backend="uia").windows(title="Work or school account", class_name="Windows.UI.Core.CoreWindow")
    print(wins)
    结果:[]
    
    
    Desktop(backend="uia").window()方法,结果返回WindowSpecification对象。
    如下:
    win = Desktop(backend="uia").window(title="Work or school account", class_name="Windows.UI.Core.CoreWindow")
    print(win)
    结果:<pywinauto.application.WindowSpecification object at 0x000001524BE87790>,不能使用返回的window()的返回值做判断。
    
    
    注意点3:
    如果窗口类型为:WindowSpecification,使用parent_window.child_window() 方式查询窗口元素。
    如果窗口类型为:UIAWrapper,使用parent_window.parent_window.descendants() 方式查询窗口元素。
    

    18-pywinauto ,通过元素的 element.rectangle() 方法,使用pyautogui,移动鼠标到元素位置,实现。

    具体写法如下:

    # 假设你想对窗口内的某个元素进行操作,首先找到该元素
    element = main_window.child_window(title="Your Element Title", control_type="ControlTypeHere")  # 请替换为实际的标题和控件类型
    
    # 获取元素的矩形信息
    element_rectangle = element.rectangle()
    
    # 使用 pyautogui 将鼠标移动到该位置
    x, y = element_rectangle.left + (element_rectangle.width() // 2), element_rectangle.top + (element_rectangle.height() // 2)
    pyautogui.moveTo(x, y)  # 移动到元素中心位置,也可以直接移到左上角,去掉加法部分即可

    注意:element_rectangle.width 和element_rectangle.height返回的类型都是method;计算位置坐标是应使用element_rectangle.width() 和element_rectangle.height()形式。

    否则报错:TypeError: unsupported operand type(s) for //: 'method' and 'int'(不支持这两两种类型使用//语法计算)

    19-编码过程中,注意循环引用问题

    E   ImportError: cannot import name 'assertTrue' from partially initialized module 'assertTool' (most likely due to a circular import)  –很可能是循环导入

    问题:“文件a” 导入了 “文件b” 中的方法;“文件b” 也导入了“文件a”中的方法,导致循环导入问题。

    20-pywinauot, 定位uiaWrapper 元素时,增加等待有时候不能解决问题。但是窗口最大化后,就能查找到元素了。

    可能的原因:窗口布局变化

    应用程序窗口在最大化之前和之后,其内部元素的布局可能会有细微的变化。这种布局变化可能会影响到元素的可访问性或者导致元素的属性(如坐标位置)发生变化。最大化窗口可能使得元素稳定地出现在预期的位置或状态,从而变得可被pywinauto识别。

    21-pywinauto + pytest 执行的测试用例时,提示错误: Windows fatal exception: code 0x8001010d。 但是如果把此用例的内容,都放到一个文件中执行,不通过pytest,就没有这个错误提示。应该怎么解决

    错误代码 0x8001010d 通常与COM(Component Object Model)错误相关,特别是“RPC服务器不可用”或“接口调用中出现错误”。在使用pywinauto进行GUI自动化测试时遇到这个问题,可能的原因有几个方面,尤其是在结合pytest使用时,可能与测试环境的初始化、资源释放、并发执行测试等方面有关。以下是一些可能的解决方案:

    1. 确保单例模式: 确保在整个测试套件中,对应用程序实例的使用是单例的,避免多个测试用例同时操作同一个应用程序实例,这可能导致资源冲突。可以在conftest.py中使用fixture来管理应用实例的生命周期。

    2. 资源清理: 确保每个测试用例结束后,正确地关闭和清理所有打开的窗口和资源。使用try...finally块或者pytest.fixtureyield语句来确保即使测试失败也能执行清理操作。

    3. 并发执行问题: 如果你的测试套件配置为并行执行(pytest默认可能不会,但通过 -n NUM 参数可以设置),那么可能需要禁用并行执行,因为某些GUI操作或COM对象可能不支持多线程或并发访问。可以在运行pytest时加上-n 1参数来禁用并行执行。

    4. COM对象生命周期管理: 确保所有的COM对象在使用完毕后都被适当释放。在pywinauto中,通常这会自动处理,但如果涉及到自定义COM调用,请确保遵循正确的释放流程。

    5. 增加异常处理: 在测试代码中增加对特定异常的捕获和处理逻辑,尤其是与COM相关的异常,可以帮助诊断问题所在。可以尝试捕获并打印详细的错误信息,以便于进一步分析。

    6. 检查环境差异: 确认直接运行文件和通过pytest运行时的环境(如环境变量、权限等)是否一致。有时候,环境差异也会导致此类问题。

    如果以上建议不能解决问题,可能需要更详细的日志来定位问题。考虑在pytest运行时增加日志级别(通过-vvv参数或配置logging模块),并查看是否有其他相关的警告或错误信息输出,这有助于更准确地定位问题所在。

    作者:大卫软件测试

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python教程:Pywinauto库使用指南(持续更新)

    发表回复