简述
WinAppDriver是微软官方提供的一款用于做Window桌面应用程序的界面(UI)自动化测试工具,并且支持Appium,而Appium本身就支持多种编程语言,这样我们就可以借助于Appium-Python-Client(Appium的python客户端)使用python来编写测试windows桌面程序的自动化测试脚本。
开发者模式设置
1、右键windows操作系统【开始】菜单,点击【设置】,设置界面搜索框输入“开发者”,点击【开发者设置】
2、开发者选项界面勾选“开发人员模式”,等待系统配置完成
Appium连接WinAppDriver配置
1、直接点击WinAppDriver.exe,出现这样的画面,就是正常启动了,默认启动的服务器地址是 http://127.0.0.1:4723/
2、运行Appium,启动服务器界面,不点“启动服务器”按钮,点击【File】-> 【New Session Window...】,开启一个新的会话窗口
3、因为我们是不带参数(即默认配置)启动WinAppDriver的(服务器地址为http://127.0.0.1:4723/),【远程路径】修改为“/”,同时在JSON Reprresentation那里添加如下JSON配置参数(这配置表示创建Windows操作系统桌面会话),并保存,这样下次就不用重复添加了
{
"platformName": "Windows",
"app": "Root"
}
4、点击【启动会话】,可以看到WinAppDriver运行窗口打印连接成功的日志
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
关于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()