简述


WinAppDriver是微软官方提供的一款用于做Window桌面应用程序的界面(UI)自动化测试工具,并且支持Appium,而Appium本身就支持多种编程语言,这样我们就可以借助于Appium-Python-Client(Appium的python客户端)使用python来编写测试windows桌面程序的自动化测试脚本。

 

开发者模式设置


1、右键windows操作系统【开始】菜单,点击【设置】,设置界面搜索框输入“开发者”,点击【开发者设置】

python桌面端自动化框架 python桌面程序自动化_python桌面端自动化框架

 

2、开发者选项界面勾选“开发人员模式”,等待系统配置完成

python桌面端自动化框架 python桌面程序自动化_测试类型_02

 

 

 

Appium连接WinAppDriver配置


1、直接点击WinAppDriver.exe,出现这样的画面,就是正常启动了,默认启动的服务器地址是 http://127.0.0.1:4723/

python桌面端自动化框架 python桌面程序自动化_软件测试_03

2、运行Appium,启动服务器界面,不点“启动服务器”按钮,点击【File】-> 【New Session Window...】,开启一个新的会话窗口

python桌面端自动化框架 python桌面程序自动化_软件测试_04

3、因为我们是不带参数(即默认配置)启动WinAppDriver的(服务器地址为http://127.0.0.1:4723/),【远程路径】修改为“/”,同时在JSON Reprresentation那里添加如下JSON配置参数(这配置表示创建Windows操作系统桌面会话),并保存,这样下次就不用重复添加了

{
  "platformName": "Windows",
  "app": "Root"
}

python桌面端自动化框架 python桌面程序自动化_python_05

4、点击【启动会话】,可以看到WinAppDriver运行窗口打印连接成功的日志

python桌面端自动化框架 python桌面程序自动化_windows_06

 

WinAppDriver支持的查找元素定位方式


Client API

Locator Strategy

Matched Attribute in inspect.exe

Example

FindElementByAccessibilityId

accessibility id

AutomationId

AppNameTitle

FindElementByClassName

class name

ClassName

TextBlock

FindElementById

id

RuntimeId (decimal)

42.333896.3.1

FindElementByName

name

Name

Calculator

FindElementByTagName

tag name

LocalizedControlType (upper camel case)

Text

FindElementByXPath

xpath

Any

//Button[0]

上图方法是C#的版本,python版本就是对应的把大写变小写,单词间以下划线(即_)隔开, 如FindElementByAccessibilityId 对应的就是好find_element_by_accessibility_id。

【Matched Attribute in inspect.exe】列表示的是相应查找方法的定位器参数匹配的是inspect.exe(元素侦测工具)显示的元素中的哪个属性,比如accessibility id 匹配的就是inspect.exe中元素的AutomationId属性。inspect.exe这个工具是用来查看运行软件(uwp、win32、win form、wpf)的 UI 元素的 Name、ID、Text 等等。包含在 Windows SDK 中。最新的Microsoft Visual Studio版本默认包含Windows SDK,可以在Windows SDK文件夹下找到该inspect.exe工具,该文件夹通常是C:\Program Files (x86)\Windows Kits\10\bin\x86

python桌面端自动化框架 python桌面程序自动化_windows_07

关于FindElementByXPath方法的示例,我看的时候感觉有摸不清怎么写xpath,就算配合上面inspect.exe侦测元素图,也还是蒙,我们可以利用调试代码建立会话后,调用driver的page_source属性打印出xml格式的元素树,以上图为例:

self.driver.page_source
<?xml version="1.0" encoding="utf-16"?>
<Window AcceleratorKey="" AccessKey="" AutomationId="" ClassName="#32770" FrameworkId="Win32" HasKeyboardFocus="True" HelpText="" IsContentElement="True" IsControlElement="True" IsEnabled="True" IsKeyboardFocusable="True" IsOffscreen="False" IsPassword="False" IsRequiredForForm="False" ItemStatus="" ItemType="" LocalizedControlType="对话框" Name="VNC Viewer : Authentication [No Encryption]" Orientation="None" ProcessId="13344" RuntimeId="42.854442" x="0" y="0" width="364" height="102" CanMaximize="False" CanMinimize="False" IsModal="False" WindowVisualState="Normal" WindowInteractionState="ReadyForUserInteraction" IsTopmost="False" CanRotate="False" CanResize="False" CanMove="True" IsAvailable="True">
    <Edit AcceleratorKey="" AccessKey="" AutomationId="1005" ClassName="Edit" FrameworkId="Win32" HasKeyboardFocus="False" HelpText="" IsContentElement="True" IsControlElement="True" IsEnabled="False" IsKeyboardFocusable="True" IsOffscreen="False" IsPassword="False" IsRequiredForForm="False" ItemStatus="" ItemType="" LocalizedControlType="编辑" Name="" Orientation="None" ProcessId="13344" RuntimeId="42.657824" x="129" y="36" width="150" height="23" />
    <Edit AcceleratorKey="" AccessKey="" AutomationId="1000" ClassName="Edit" FrameworkId="Win32" HasKeyboardFocus="True" HelpText="" IsContentElement="True" IsControlElement="True" IsEnabled="True" IsKeyboardFocusable="True" IsOffscreen="False" IsPassword="True" IsRequiredForForm="False" ItemStatus="" ItemType="" LocalizedControlType="编辑" Name="" Orientation="None" ProcessId="13344" RuntimeId="42.2885588" x="129" y="67" width="150" height="24" />
    <Button AcceleratorKey="" AutomationId="1" ClassName="Button" FrameworkId="Win32" HasKeyboardFocus="False" IsContentElement="True" IsControlElement="True" IsEnabled="True" IsKeyboardFocusable="True" IsOffscreen="False" IsPassword="False" IsRequiredForForm="False" ItemStatus="" ItemType="" LocalizedControlType="按钮" Name="OK" Orientation="None" ProcessId="13344" RuntimeId="42.854288" x="286" y="36" width="68" height="23" />
    <Button AcceleratorKey="" AutomationId="2" ClassName="Button" FrameworkId="Win32" HasKeyboardFocus="False" IsContentElement="True" IsControlElement="True" IsEnabled="True" IsKeyboardFocusable="True" IsOffscreen="False" IsPassword="False" IsRequiredForForm="False" ItemStatus="" ItemType="" LocalizedControlType="按钮" Name="Cancel" Orientation="None" ProcessId="13344" RuntimeId="42.985372" x="286" y="67" width="68" height="24" />
    <Image AcceleratorKey="" AutomationId="101" ClassName="Static" FrameworkId="Win32" HasKeyboardFocus="False" IsContentElement="True" IsControlElement="True" IsEnabled="True" IsKeyboardFocusable="False" IsOffscreen="False" IsPassword="False" IsRequiredForForm="False" ItemStatus="" ItemType="" LocalizedControlType="图像" Orientation="None" ProcessId="13344" RuntimeId="42.1444034" x="12" y="36" width="48" height="48" />
    <Text AcceleratorKey="" AutomationId="65535" ClassName="Static" FrameworkId="Win32" HasKeyboardFocus="False" IsContentElement="True" IsControlElement="True" IsEnabled="True" IsKeyboardFocusable="False" IsOffscreen="False" IsPassword="False" IsRequiredForForm="False" ItemStatus="" ItemType="" LocalizedControlType="文本" Name="Username:" Orientation="None" ProcessId="13344" RuntimeId="42.1640712" x="69" y="36" width="53" height="23" />
    <Text AcceleratorKey="" AutomationId="65535" ClassName="Static" FrameworkId="Win32" HasKeyboardFocus="False" IsContentElement="True" IsControlElement="True" IsEnabled="True" IsKeyboardFocusable="False" IsOffscreen="False" IsPassword="False" IsRequiredForForm="False" ItemStatus="" ItemType="" LocalizedControlType="文本" Name="Password:" Orientation="None" ProcessId="13344" RuntimeId="42.854286" x="69" y="67" width="53" height="24" />
    <TitleBar AcceleratorKey="" AutomationId="TitleBar" ClassName="" FrameworkId="" HasKeyboardFocus="False" IsContentElement="False" IsControlElement="True" IsEnabled="True" IsKeyboardFocusable="True" IsOffscreen="False" IsPassword="False" IsRequiredForForm="False" ItemStatus="" ItemType="" LocalizedControlType="标题栏" Orientation="None" ProcessId="13344" RuntimeId="42.854442.3.-2147483647.854442.-2.0" x="1" y="3" width="362" height="23" />
</Window>

 

VNC Viewer 连接远程电脑桌面 UI自动化测试的示例代码


 

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@Author: 思文伟
@Date: 2021/03/30 15:49:32
'''
import io
import os
import sys
from appium import webdriver

sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding='utf-8')
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(BASE_DIR)

from WinAppUITest.utils.base_testcase import BaseTestCase
from WinAppUITest.utils.test_wrapper import testcase


class VNCViewerTest(BaseTestCase):
    """VNC Viewer 连接远程电脑桌面"""
    @classmethod
    def setUpClass(cls):
        desired_caps = {}
        desired_caps["app"] = r"C:\Users\cfgdc-pc 98\Desktop\vnc-4_1_2-x86_win32_viewer.exe"  # vnc viewer 的执行路径
        server_url = "http://127.0.0.1:4723"
        cls.driver = webdriver.Remote(server_url, desired_caps)

    def setUp(self):
        pass

    @testcase(priority=1, enabled=True, description='使用正确密码连接远程电脑桌面')
    def connect_remote_pc_desktop(self):

        ip = "10.201.15.253"
        pwd = "123456"
        self.sleep(5)  # 等待vnc viewer界面显示出来
        el_ok = self.driver.find_element_by_name("OK")
        el_ip = self.driver.find_element_by_accessibility_id('1001')  # 这个查找方法慎用,AutomationId 这个很有可能是动态值
        el_ip.clear()
        el_ip.send_keys(ip)
        el_ok.click()

        self.sleep(20)  # 上面点击ok后,到下一个界面显示出来需要时间,所以这里设置延时等待
        # for window_handle in self.driver.window_handles: #调试代码
            # self.driver.switch_to.window(window_handle)
            # print("self.driver.title=", self.driver.title)
            # print("self.driver.page_source=", self.driver.page_source)

        # 因为上面点击ok按钮后,界面消失后到弹出下一个界面后,需要切换到下一个界面的窗口,否则直接执行后续的代码会报错
        # 查找并切换到输入密码控件所在的窗口 代码 start -------
        vnc_title = "VNC Viewer : Authentication [No Encryption]"
        for window_handle in self.driver.window_handles:
            self.driver.switch_to.window(window_handle)
            if self.driver.title == vnc_title:
                break
        # 查找并切换到输入密码控件所在的窗口 代码 end   -------

        childrens = self.driver.find_elements_by_xpath("./*")  # 获取当前窗口下的所有子元素
        for c in childrens:
            # print("c.get_attribute("IsEnabled")=", c.get_attribute("IsEnabled"))
            if c.get_attribute("IsEnabled") == "true":  # 通过界面我们知道 只有输入密码框是可编辑的,所以使用该条件来判断是否密码输入框元素
                c.send_keys(pwd)
                break
        self.sleep(10)  # 这里延时是为了 显示输入密码的过程,否则就会执行下面的点击ok按钮,一闪而过
        self.driver.find_element_by_name("OK").click()

    def tearDown(self):
        pass

    @classmethod
    def tearDownClass(cls):
        cls.sleep(2)
        cls.driver.quit()


if __name__ == '__main__':
    VNCViewerTest.run_test()

完整示例代码https://github.com/hotswwkyo/WinAppUITest