Appium: Windows系统桌面应用自动化测试 【脚本操作】
- 一、常用操作
- 1、添加被测程序
- 1.1示例一:通过程序路径指定应用程序,例如指定写字板程序路径。
- 1.2示例二:通过程序ID指定应用程序,例如指定计算器ID。
- 1.3 应用程序ID(AppId)
- 2、启动参数
- 3、元素定位
- 4、窗口操作
- 5、Root窗口
- 5.1、根据示例描述编写如下代码:
- 6、切换新窗口
- 6.1、实现代码如下:
- 7、文件操作
- 8、切换输入法
- 二、脚本实战
一、常用操作
WinApp自动化测试脚本是使用Appium客户端完成开发的,一些基本的操作方法可参考Appium的API。本节主要介绍WinApp在脚本编写时与移动端App脚本开发上不同的地方。
1、添加被测程序
设置启动程序是在启动参数中添加key为app的参数,值可以是应用程序的ID,也可以是应用程序的完整路径。
1.1示例一:通过程序路径指定应用程序,例如指定写字板程序路径。
desired_caps = {}
desired_caps['app'] = r"C:\Program Files\Windows NT\Accessories\wordpad.exe"
1.2示例二:通过程序ID指定应用程序,例如指定计算器ID。
desired_caps = {}
desired_caps["app"] = "Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"
1.3 应用程序ID(AppId)
应用程序ID(AppId)是应用程序用户模型 ID (AppUserModelID),简称 AUMID。在Windows PowerShell中输入命令Get-StartApps便可看到当前用户安装的所有应用名称和 AUMID
PS C:\Users\pc> Get-StartAppsName AppID
---- -----
Python 3.9 (64-bit) C:\Users\pc\AppData\Local\Programs\Python\Python39\python.exe
Google Chrome Chrome.JOYETIBVDRAA6DX32OHIYXNRNI
uc-devtools com.squirrel.uc-devtools.uc-devtools
SakuraCat com.tidalab.client
微信 D:\Program Files (x86)\Tencent\WeChat\WeChat.exe
Internet Explorer Microsoft.InternetExplorer.Default
闹钟和时钟 Microsoft.WindowsAlarms_8wekyb3d8bbwe!App
计算器 Microsoft.WindowsCalculator_8wekyb3d8bbwe!App***************************************************
***************** 省略部分内容 *****************
***************************************************
手机连接 Microsoft.YourPhone_8wekyb3d8bbwe!App
Microsoft Store Microsoft.WindowsStore_8wekyb3d8bbwe!App
天气 Microsoft.BingWeather_8wekyb3d8bbwe!App
Solitaire Collection Microsoft.MicrosoftSolitaireCollection_8wekyb3d8bbwe!AppPS C:\Users\pc>
从上内容可以看到已经安装的所有应用AUMID,例如闹钟和时钟应用的AUMID就是Microsoft.WindowsAlarms_8wekyb3d8bbwe!App。
2、启动参数
WinApp自动化测试中,在创建Windows应用程序驱动会话时,可以使用的启动参数是有限的
appTopLevelWindow值是窗口句柄,用于设置应用程序顶层窗口,如图
UIA树中就可以知道打开的写字板程序的顶层窗口为【窗格 ‘文档-写字板’】。appTopLevelWindow值是一个16进制,可以通过许多方法获取,
例如
- hex(win32gui.FindWindow()
- hex(int(driver.find_element(By.XX,‘XX’).get_attribute(‘NativeWindowHandle’)))
3、元素定位
WinAppDriver支持许多方式查找元素。如表7-2列出了支持的定位方式及在Accessibility Insights For Windows工具和Inspect.exe工具中显示的对应元素属性。
//Button[0]
4、窗口操作
我们可以使用Selenium提供的一些API操作WinApp窗口,例如窗口最大化、设置窗口位置、设置窗口大小、关闭窗口等。
一些Selenium提供的API同样适用于WinApp窗口的方法。
5、Root窗口
如果在启动参数中app参数的值给定的是一个具体的应用程序ID或完整的路径,那么启动会话后appTopLevelWindow默认就是该应用的根节点。
例如指定的应用程序是写字板,那么启动后会话的appTopLevelWindow默认就是【窗格 ‘文档-写字板’】,后续操作都会将【窗格 ‘文档-写字板’】作为开始节点,例如通过XPATH方式查找元素,就会从该节点开始查找,窗口切换时窗口查找也是查找该节点下的窗口。
但是从图
UIA树中可以看到,实则还有一个节点【窗格 ‘桌面 1’】,即Root窗口,此节点才是Windows桌面最顶层的窗口,此节点的子节点才是应用程序的根节点。
如果将启动参数app的值设置为‘Root’,那么启动会话后appTopLevelWindow就是Root窗口,表示我们的系统桌面。例如将启动参数app的值设置为‘Root’,然后依次桌面应用程序控制面板和此电脑。
使用Accessibility Insights For Windows工具查看桌面上的元素,如图
所示。从图中可以知道,控制面板和此电脑都有Name属性,因此可以使用NAME定位方式定位元素。
5.1、根据示例描述编写如下代码:
# window_root.py
import time
from appium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains# 添加启动参数
desired_caps = {}
desired_caps['app'] = "Root"
# 客户端连接 Server,启动 Session 会话
driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', desired_capabilities=desired_caps)
time.sleep(3)control_panel_element = driver.find_element(By.NAME, '控制面板')
# 双击桌面上的控制面板
ActionChains(driver).double_click(control_panel_element).perform()
time.sleep(1)my_computer_element = driver.find_element(By.NAME, '此电脑')
# 双击桌面上的此电脑
ActionChains(driver).double_click(my_computer_element).perform()
time.sleep(1)# 关闭会话
driver.quit()
运行脚本后,会观察到鼠标先双击桌面上的控制面板并打开,然后双击桌面上的回收站并打开。
注意:此操作是点击桌面上的应用程序,运行脚本时不能有窗口遮挡控制面板和回收站图标,否则会点击到对应位置最上面的窗口上。另一种解决方法是在点击桌面上的应用程序之前,通过脚本先发送快捷键 Win+d,将桌面完全显示出来。
6、切换新窗口
窗口切换与浏览器中的窗口切换相同,如果想操作新窗口,就需要先切换进新窗口。Appium能识别到的窗口是appTopLevelWindow窗口的子窗口,如果是切换appTopLevelWindow窗口的子窗口,则可以通过window_handles、current_window_handle、switch_to.window(window_name)三个API完成切换。
- window_handles:获取所有的appTopLevelWindow下的窗口句柄。
- current_window_handle:获得当前窗口句柄。
- switch_to.window(window_name):切换窗口,参数window_name为窗口句柄。
提示:WinAppDriver、Appium、Selenium三者之间的兼容性处理并不是很好,所以句柄的获取会存在很多问题,因此直接使用window_handles获取窗口句柄会有些吃力的。但相信在不久的将来,该问题会得到改善。
因此,如果打开了一个新窗口,而新窗口不在当前会话的appTopLevelWindow节点下,我们就需要根据新窗口句柄启动一个新的会话,开启的新会话就将会话绑定到新窗口上。例如打开写字板程序后,点击插入绘图打开画图窗口,然后关闭画图窗口,最后关闭写字板。
6.1、实现代码如下:
# switch_window.py
import time
import win32gui
from appium import webdriver
from selenium.webdriver.common.by import By# 添加启动参数
desired_caps = {}
desired_caps['app'] = r"C:\Program Files\Windows NT\Accessories\wordpad.exe"
# 客户端连接 Server,启动 Session 会话
driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', desired_capabilities=desired_caps)
time.sleep(1)
driver.set_window_size(1000, 600)# 打开绘图窗口
driver.find_element(by=By.NAME, value="绘图").click()
time.sleep(1)# 获取画图窗口句柄
paint_handle = win32gui.FindWindow(None, '位图图像 在 文档 中 - 画图')
# 配置启动参数,并且启动一个新会话
paint_session = webdriver.Remote(command_executor='http://127.0.0.1:4723',desired_capabilities={"appTopLevelWindow": hex(paint_handle)})# 点击画图窗口中的 关闭 按钮
paint_session.find_element(by=By.NAME, value="关闭").click()
# 关闭会话
paint_session.quit()# 点击写字板窗口中的 关闭 按钮
driver.close()
time.sleep(1)
driver.find_element(by=By.NAME, value="不保存(N)").click()
# 关闭会话
driver.quit()
7、文件操作
WinApp自动化上使用代码实现文件上传、文件下载、文件保存或文件另存为都是非常简单的,操作也是相同的,因为都是操作Windows系统应用程序上的窗口,我们只需要根据行为进行元素定位,元素操作即可。下面我们以文件上传为例讲解代码的实现。
由于文件上传的操作步骤都是一致的,因此可以将上传文件场景进行封装,然后调用。例如在写字板中上传图片,当打开上传图片窗口后,先定位文件路径输入框元素并且输入文件路径 ,然后点击打开按钮即可将文件上传到写字板中。实现代码如下:
# upload_file.py
import time
from appium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ECdef upload_file(driver, file):# 定位文件路径输入框并输入文件路径file_input = WebDriverWait(driver, 60, 0.5).until(EC.visibility_of_element_located((By.XPATH, "//Edit[@Name='文件名(N):']")))file_input.send_keys(file)# 定位打开按钮并点击file_open_btn = WebDriverWait(driver, 60, 0.5).until(EC.visibility_of_element_located((By.XPATH, "//Button[@Name='打开(O)']")))file_open_btn.click()# 添加启动参数
desired_caps = {}
desired_caps['app'] = r"C:\Program Files\Windows NT\Accessories\wordpad.exe"
# 客户端连接 Server,启动 Session 会话
driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', desired_capabilities=desired_caps)
time.sleep(1)
driver.set_window_size(1000, 600)
time.sleep(1)
# 点击图片,上传文件
(driver.find_elements(by=By.XPATH, value="//Group"))[4].click()# 文件上传
upload_file(driver, r"D:\ty.png")# 关闭程序和会话
driver.close()
driver.find_element(by=By.NAME, value="不保存(N)").click()
driver.quit()
注意:运行上面脚本时输入法需要切换到英文,如果是中文输入法,文件路径输入的结果会与预期不一致。
运行上面脚本,可以观察到先启动写字板程序,然后点击了图片图标打开了上传文件窗口,接着文件上传输入框中输入了文件路径,并且点击了打开按钮,此时可以看到写字板中已经成功插入了图片,最后关闭写字板程序,会话关闭。
8、切换输入法
自动化脚本在输入内容时会受到输入法的影响,例如我们原本想输入内容“D2”,但由于输入法是中文,因此得到的内容将是输入拼音D后列出的第二个汉字,完全与期望相差甚远。
我们在Windows系统上打开一个新程序后,新程序的输入法会是默认语言,例如输入法默认是中文,在Word文件中切换到了英文,当打开记事本后输入法会是默认的中文,当再切回到Word文件时输入法也会跟着切换回英文。
因此我们可以通过设置输入法的默认值达到预期的结果,操作步骤是:Windows10系统下右键点击右下角输入法,然后依次选择【设置】>>【常规】,在默认模式下将【选择输入法默认模式】下拉框选择为预取语言即可,如图标记处,就是将输入法的默认值设置为了英文。
但是项目自动化中我们还是会遇到需要切换输入法的场景。此时我们可以通过输入法切换的快捷键快速实现。例如笔者的默认输入法是中文,中英文切换的快捷键是Shift,改造上面代码中的文件上传函数,在文件路径输入框输入内容前先发送一个Shift快捷键,使输入法切换到英文,然后再输入文件路径。改造后的文件上传函数如下:
def upload_file(driver, file):# 定位文件路径输入框并输入文件路径file_input = WebDriverWait(driver, 60, 0.5).until(EC.presence_of_element_located((By.XPATH, "//Edit[@Name='文件名(N):']")))# 输入框输入 Shift,由中文切换到英文file_input.send_keys(Keys.SHIFT)file_input.send_keys(file)# 定位打开按钮并点击file_open_btn = WebDriverWait(driver, 60, 0.5).until(EC.presence_of_element_located((By.XPATH, "//Button[@Name='打开(O)']")))file_open_btn.click()
提示:Windows10系统下设置输入法切换的快捷键步骤是:右键点击右下角输入法,然后依次选择【设置】>>【按键】,即可在页面模式切换下找到【中/英文模式切换】、【全/半角切换】等快捷键设置项。
二、脚本实战
以操作 PC 端的微信为例,聊聊自动化的常见步骤
- 首先,我们在本机打开 WinAppDriver 服务,让它在后台运行
然后,我们使用 Python 编写自动化脚本
通过 ip 地址、端口号及 PC 版微信的绝对路径,使用 Appium 打开微信
import time, os
from appium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
from time import sleepclass Auto():def open_weixin(self, host='localhost', port=4723):# 打开WinAppDriver服务# 注意:如果手动开启,则可以注释掉# os.system(r'start "" /d "C:\Program Files\Windows Application Driver\" "WinAppDriver.exe"')# 配置信息# 包含:平台名、系统、应用程序绝对路径desired_caps = {'platformName': 'Windows', 'deviceName': 'WindowsPC','app': r"D:\Program Files (x86)\Tencent\WeChat\WeChat.exe"}try:# 连接WinAppDriver服务,打开目标软件self.driver = webdriver.Remote('http://{}:{}'.format(host, port), desired_caps)except Exception as e:raise AssertionError(e)
- 接着,通过「 组件元素识别工具 」拿到界面元素的属性值,执行常见的点击、移动、滑动等操作
比如:点击「 文件传输助手 」,发送一条信息
# 给文件传输助手发送一条信息
def send_msg(self, element_name, msg):""":param element_name:元素name值:param msg::return:"""# 通过name属性,找到目标元素chat_element = self.weixin_driver.find_element_by_name(target_name)# 点击元素,进入聊天界面chat_element.click()# 找到输入框,并输入self.weixin_driver.find_element_by_name("输入").send_keys(msg)# 点击右下角的发送,发送消息出去self.weixin_driver.find_element_by_name("发送(S)").click()
- 需要注意的是,如果涉及界面的滑动,可以使用「 ActionChains 」移动鼠标,然后使用 win32api 和 win32con 模拟屏幕滑动即可
import win32api
import win32con
from appium import webdriver
from selenium.webdriver import ActionChains# 模拟屏幕滑动
# 1、移动到某个元素区域
ActionChains(self.weixin_driver).move_to_element(self.weixin_driver.find_element_by_name("element_name")).perform()# 2、滑动界面
# 比如,向上滚动,模拟滑动
win32api.mouse_event(win32con.MOUSEEVENTF_WHEEL, 0, 0, -500)
- 完成自动化操作后,就可以主动释放资源、关闭 WinAppDriver 服务
# 释放资源及关闭服务
def tearDownFunc(self):print("准备退出")sleep(2)# 1、释放资源self.weixin_driver.quit()# 2、关闭WinAppDriver应用程序os.system(' @taskkill /f /im WinAppDriver.exe')
- 在实际使用过程中,可能会遇到复杂的桌面应用程序,这时我们可以通过打印驱动对象的「 page_source」元素控制树值,以此来帮助我们进行快速定位元素,进而完善自动化脚本