Appium: Windows系统桌面应用自动化测试(四) 【辅助工具】

@[TOC](Appium: Windows系统桌面应用自动化测试(四) 辅助工具)

文件批量上传

文件批量上传和文件单个上传原理是相同的,单个上传直接传入文件路径即可,批量上传需要进入批量上传的文件所在目录,然后观察选中多个文件时【文件路径输入框】读取的批量文件写入规则,如图7-12所示,可以看到规则是:"file_name" "file_name"。

我们只需要根据规则将批量文件组合成字符串输入到【文件路径输入框】中,然后点击【插入】即可完成批量上传。

例如打开Word程序,新建一个空白文档,然后依次点击图片>>此设备...,打开插入图片对话框,并在【文件路径输入框】中输入批量上传的文件,最后点击【插入】将多个图片插入到word文档中。代码实现如下:

python 复制代码
# upload_files.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 EC
from selenium.webdriver.common.keys import Keys


def upload_files(driver, file_path, files: list):
    # 定位文件路径输入框
    file_input = WebDriverWait(driver, 60, 0.5).until(EC.visibility_of_element_located((By.XPATH, "//Edit[@Name='文件名(N):']")))
    # 输入框输入 Shift,由中文切换到英文
    file_input.send_keys(Keys.SHIFT)
    # 先输入批量文件所在的目录,进入该目录下
    file_input.send_keys(file_path + Keys.ENTER)

    # 生成多个文件上传时的字符串
    mult_file_str = ''
    for file in files:
        mult_file_str += " \"" + file + "\""
    # 文件上传
    file_input.send_keys(mult_file_str)
    file_insert_btn = WebDriverWait(driver, 60, 0.5).until(EC.visibility_of_element_located((By.NAME, "插入(S)")))
    file_insert_btn.click()

# 添加启动参数
desired_caps = {}
desired_caps['app'] = r"C:\Program Files\Microsoft Office\root\Office16\WINWORD.EXE"
# 客户端连接 Server,启动 Session 会话
driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', desired_capabilities=desired_caps)
driver.set_window_size(1000, 600)
time.sleep(1)
# 新建空白文档
driver.find_element(by=By.NAME, value="空白文档").click()
time.sleep(1)
# 依次点击 插入 >> 图片 >> 此设备...,打开文件上传窗口
driver.find_element(by=By.NAME, value="插入").click()
driver.find_element(by=By.NAME, value="图片").click()
driver.find_element(by=By.NAME, value="此设备...").click()

# 文件上传
upload_files(driver, r"D:", ['ty.png', 'ty2.png'])

# 关闭程序和会话
driver.close()
driver.find_element(by=By.NAME, value="不保存").click()
driver.quit()

运行上面脚本,可以观察到先启动Word程序,然后点击【空白文档】新建了一个文档,再然后依次点击图片>>此设备...打开了上传文件窗口,上传文件窗口中先是在【文件路径输入框】中输入了"D:"进入到了D盘根目录,接着就在【文件路径输入框】中输入了"ty.png" "ty2.png",并点击了【插入】按钮,此时可以看到Word文档中已经成功插入了两张图片,如图所示,关闭写字板程序,会话关闭。

获取通知栏信息

Windows系统屏幕右下角(任务栏最右侧)的区域被称为通知区域,通知区域最后一个图标为通知栏图标,点击可打开通知栏,通知栏中可以查看所有被允许的通知信息。Windows也提供了快捷键迅速开启通知栏,例如Win10系统上快捷键就是Win+a。

通知信息如Windows系统更新、截图、安全通知、备份。例如关闭防火墙后通知栏会出现如图所示的消息。
自动化脚本实现获取通知栏信息的操作步骤是设置appTopLevelWindow为Root,定位桌面任意元素并发送快捷键打开通知栏,然后定位通知信息元素,获取到通知元素后便可使用text接口得到文本内容。示例代码如下:

python 复制代码
# get_notification_message.py
import time
from appium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys


desired_caps = {}
desired_caps['app'] = "Root"
driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', desired_capabilities=desired_caps)
time.sleep(1)

# 通过快捷键打开 通知栏
driver.find_element(by=By.NAME, value="任务栏").send_keys(Keys.COMMAND + 'a' + Keys.COMMAND)
time.sleep(1)

# 定位【来自 Windows 安全中心 的通知】通知元素
notification_element = driver.find_element(by=By.NAME, value="来自 Windows 安全中心 的通知")
# 获取【来自 Windows 安全中心 的通知】下所有 Tag 为 Text 的元素文本,并且打印
text_elements = notification_element.find_elements(by=By.TAG_NAME, value="Text")
texts = [text_element.text for text_element in text_elements]
print(texts)

# 通过快捷键关闭 通知栏
driver.find_element(by=By.NAME, value="任务栏").send_keys(Keys.COMMAND + 'a' + Keys.COMMAND)
# 关闭会话
driver.quit()

运行上面脚本后控制台输出内容如下:

python 复制代码
['Windows 安全中心', '防火墙和网络保护', '启用 Windows 防火墙', 'Windows 防火墙已关闭。点击或单击以启用。', '22:17']

运行上面脚本后,可以观察到通知栏先开启然后关闭。

Windows系统上,不止通知栏,许多操作都支持快捷键,例如打开文件资源管理器(Win+e)、打开反馈中心(Win+f)、打开剪贴板(Win+v),我们都可以通过此方法对其操作。

滚动条操作

滚动条是常见的一个控件,当内容超过显示区域时便会出现滚动条,通过滑动滚动条可查看所有的内容。

WinAppDriver本身也实现了scroll(xoffset, yoffset)方法操作滚动条,使用方法为

  • TouchActions(driver).scroll(0, 100).perform()。

除此之外,我们还可以接着键盘键PageUp和PageDown达到滑动滚动条的目的,下面通过发送键盘键滑动滚动条,选择写字板上的字体。打开字体选择下拉列表后,在循环体中重复接下来的操作,首先查找预期的字体元素,如果出现则点击字体元素,如果不出现则定位任意下拉列表框中的元素发送PageUp或PageDown键,如此循环,直到预期字体元素出现。代码实现如下:

python 复制代码
# scrollbar_operation.py
import time
from appium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


# 添加启动参数
desired_caps = {}
desired_caps['app'] = r"C:\Program Files\Windows NT\Accessories\wordpad.exe"
driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', desired_capabilities=desired_caps)
driver.set_window_size(1000, 600)
time.sleep(1)

# 字体选择
combox_btn = WebDriverWait(driver, 30, 1).until(
    EC.visibility_of_element_located((By.XPATH, "//ComboBox[@Name='字体系列']/Button")))
combox_btn.click()

i = 0
while i < 100:
    try:
        el = WebDriverWait(driver, 1, 1).until(EC.visibility_of_element_located((By.XPATH, "//ListItem[@Name='幼圆']")))
        el.click()
        break
    except:
        i += 1
    temp_element = WebDriverWait(driver, 1, 1).until(EC.visibility_of_element_located((By.XPATH, "//List[@Name='字体系列']")))
    temp_element.send_keys(Keys.PAGE_DOWN)

time.sleep(1)
# 关闭程序和会话
driver.close()
driver.quit()

运行上面脚本,可以观察到打开字体选择下拉列表后,相隔一定的时间滚动条就下滑一定的距离,当查找的幼圆字体后,点击了幼圆字体,下来列表消失。

其他操作

运行在Windows系统上的应用程序,也有下拉框、单选、多选等控件,这些控件的操作与我们开发其它内容自动化脚本的思路是一致的。都是先熟悉手动操作步骤,然后封装成测试方法,使用时调用封装的方法即可。

例如写字板中选择字体下拉框。封装思路为:先定位打开下拉按钮并点击,使下拉列表显示出来,然后在下来列表中根据字体名查找字体元素,然后点击该字体元素。代码实现如下:

python 复制代码
# combox_select.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 EC


def combox_select(combox: str, value):
    combox_btn = WebDriverWait(driver, 30, 1).until(EC.visibility_of_element_located((By.XPATH, f"//ComboBox[@Name='{combox}']/Button")))
    combox_btn.click()
    value_item = WebDriverWait(driver, 30, 1).until(EC.visibility_of_element_located((By.XPATH, f"//ListItem[@Name='{value}']")))
    value_item.click()

# 添加启动参数
desired_caps = {}
desired_caps['app'] = r"C:\Program Files\Windows NT\Accessories\wordpad.exe"
driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', desired_capabilities=desired_caps)
time.sleep(1)

# 字体选择
combox_select('字体系列', '黑体')

time.sleep(1)
# 关闭程序和会话
driver.close()
driver.quit()

运行上面的脚本,会观察到启动写字板后首先打开了字体选择列表,然后点击黑体字体选择了字体,最后关闭写字板,结束会话。

注意:选择字体时要确保字体在下列列表的可视区域。

对于其他结构相同的下拉框,该方法也同样适用,例如字体大小选择20号字体,使用该方法时就可以写成combox_select('字体大小', 20)。

辅助工具Pywin32

PythonWin32模块是一个非常受欢迎的模块,它提供了从Python访问许多Windows API的功能。自动化测试中使用它可以更方便地实现某些功能,例如窗口截图。PythonWin32模块作为一个成熟的模块。可以辅助自动化测试项目更好地开展,提高工作效率。

Pywin32简介

Pywin32模块是一个第三方模块库,提供了很多访问Windows系统的API,该项目是一个开源项目,在GitHub上可以看到项目源码 Pywin32

Pywin32的安装和Python的其它第三方库安装方式一样,在命令行工具中输入pip install pywin32即可完成安装。完成后在Python安装路径下~\Lib\site-packages\win32可以看到所有API支撑模块,

下面对部分模块做以介绍:

  • win32api:封装Windows Win32 API的模块。
  • win32console:Windows控制台函数的接口,用于处理字符模式应用程序。
  • win32event:提供win32事件/等待API接口的模块。
  • win32file:win32文件API的接口,包括Vista引入的事务性NTFS操作。
  • win32gui:提供本机win32 GUI API的接口。
  • win32net:封装Windows Network API的模块。
  • win32process:提供win32进程和线程API的接口。
  • win32security:提供win32安全API的接口。
  • win32service:提供Windows NT Service API的接口。
  • win32ui:封装Microsoft Foundation类的模块。

其中win32api、win32gui和win32ui是较为重要的三个模块,还有一个消息常量模块win32con。通过这些API,我们可以获取Windows窗口的相关信息,并做简单的操作。

从pywin32所有API支撑模块可以看出,Pywin32库非常强大,提供了丰富的windows系统上的API接口。但是在WinApp自动化中,作为一个辅助工具,我们只用到他的边角料功能就能满足我们的需求。本节将会介绍自动化中使用Pywin32更好地帮助我们完成测试。

常用方法

win32 提供的API非常丰富,下面介绍一些常用的方法:

  • win32gui.FindWindow(ClassName, Title):获取当前窗口句柄,句柄是窗口的唯一标识。参数ClassName是窗口的类名,Title是窗口标题。
  • win32gui. FindWindowEx(hld, Child, ClassName, Title):获取父窗口下第一个窗口类为ClassName控件的窗口。参数hld是目标窗口的父窗口,Child是目标窗口的子窗口,ClassName是目标窗口的类名,Title是目标窗口的标题。
  • win32api.GetCursorPos():获取当前窗口坐标,返回值是tuple类型,例如(721, 550)。
  • win32gui.GetWindowRect(handle):获取窗口边框矩形的左上角和右下角坐标,返回值是tuple类型,例如(-25600, -25600, -25441, -25573)。
  • win32gui.FindWindow(None, title):根据窗口名查找 Title窗口,返回值是窗口句柄。
  • win32gui.GetWindowText(handle):获取窗口标题。
  • win32gui.GetClassName(handle):获取窗口类名。
  • win32gui.BringWindowToTop(handle):将窗口放在最前面。
  • win32gui.SetForegroundWindow(handle):将窗口激活并放在最前面。
  • win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0):鼠标左键按下。
  • win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0):鼠标左键释放。
  • win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0):鼠标右键按下。
  • win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0):鼠标右键释放。
  • win32api.mouse_event(win32con.MOUSEEVENTF_WHEEL, 0, 0, -1):滑动界面,-1表示向下移动一个单位。
  • win32api.keybd_event(val, 0, 0, 0):按下键盘某个键。
  • win32api.keybd_event(val, 0, win32con.KEYEVENTF_KEYUP, 0):松开键盘的某个键。

获取所有窗口句柄

获取所有窗口句柄我们需要用到win32gui.EnumWindows(callBack,none) 方法,该方法是获取windows所有窗口,无论窗口是否激活。当找到一个窗口就会执行callback一次,传入 当前窗口句柄。将其封装成方法,代码如下:

python 复制代码
def get_all_windows():
    handle_list = []
    win32gui.EnumWindows(lambda handle, param: param.append(handle), handle_list)
    return handle_list

# 打印所有窗口 handle
print(get_all_windows())
# 打印窗口 title
print([win32gui.GetWindowText(handle) for handle in get_all_windows()])

键盘按键输入

我们可以通过win32api.keybd_event(bVk, bScan, dwFlags, dwExtraInfo)方法实现键盘事件,因此可以利用它实现键盘按键输入。

keybd_event(bVk, bScan, dwFlags, dwExtraInfo)方法有四个参数,bVk是虚拟键码, bScan是硬件扫描码,一般设置为0即可,dwFlags是函数操作的一个标志位,如果值为KEYEVENTF_EXTENDEDKEY则该键被按下,也可设置为0即可,如果值为KEYEVENTF_KEYUP则该按键被释放,dwExtraInfo是定义与击键相关的附加的32位值,一般设置为0即可。封装方法如下:

python 复制代码
def send_keyboard(*args):
    for arg in args:
        win32api.keybd_event(arg, 0, 0, 0)
    for arg in args:
        win32api.keybd_event(arg, 0, win32con.KEYEVENTF_KEYUP, 0)

例如 ctrl 键对应的键码是 17,A键对应的键码是65,那么使用封装的方法发送ctrl+A组合键就可写成send_keyboard([17, 65])。

键盘键码请查看附件Ⅰ 键码对照表。

窗口截图

窗口截图的思路是根据窗口句柄获取窗口 DC 和窗口的位置信息及宽和高,然后创建一个新的 DC,再使用新创建的 DC 创建一个兼容设备内容的 DC,最后创建 bitmap,根据 DC 获取图像信息,保存成图片文件。

DC在pywin32中是一个重要概念。windows不允许程序直接访问硬件,所有的操作都需要通过一个设备上下文环境。屏幕上的每个窗口都对应一个DC。DC相当于一个视频缓冲区,对这个缓冲区的操作,会表现在这个缓冲区对应的屏幕窗口上。除了窗口对应的DC外,还可以自己创建DC,然后在创建的DC上面建立数据拷贝到窗口的DC上,就相当于刷新窗口的DC。

示例:打开写字板程序,并对写字板当前窗口截图。根据窗口截图思路可实现截图函数 screenshots_windows。因此实现截图写字板窗口的代码如下:

python 复制代码
# win32_screenshots.py
import time

import win32gui, win32ui, win32con
from appium import webdriver


def screenshots_windows(windows_name):
    handle = win32gui.FindWindow(None, windows_name)   # 获取窗口句柄
    win32gui.SetForegroundWindow(handle)                # 将窗口放在前台,激活该窗口
    hdDC = win32gui.GetWindowDC(handle)                 # 获取窗口 DC
    newhdDC = win32ui.CreateDCFromHandle(hdDC)         # 根据句柄创建一个DC
    saveDC = newhdDC.CreateCompatibleDC()               # 创建一个兼容设备内存的 DC
    saveBitmap = win32ui.CreateBitmap()                 # 创建 bitmap 保存图片

    # 获取窗口的位置信息
    left, top, right, bottom = win32gui.GetWindowRect(handle)
    width = right -- left
    height = bottom -- top
    # bitmap 初始化
    saveBitmap.CreateCompatibleBitmap(newhdDC, width, height)
    saveDC.SelectObject(saveBitmap)
    saveDC.BitBlt((0, 0), (width, height), newhdDC, (0, 0), win32con.SRCCOPY)
    saveBitmap.SaveBitmapFile(saveDC, windows_name + ".png")


desired_caps = {}
desired_caps['app'] = r"C:\Program Files\Windows NT\Accessories\wordpad.exe"
driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', desired_capabilities=desired_caps)
time.sleep(3)
screenshots_windows("文档 - 写字板")
# 关闭会话
driver.quit()

注:此截屏函数screenshots_windows来自网络。

运行上面代码后,在当前目录下多了一个"文档 - 写字板.png"文件

脚本录制

脚本录制是通过工具记录用户的操作,并将操作生成脚本,已达到回放的目的。WinAppDriver社区也推出了一个开源的工具WinAppDriver UI Recorder,该工具可以让用户轻松地创建自动化的UI测试。下面我们就来学习WinAppDriver UI Recorder的使用。

  • 1.下载WinAppDriver UI Recorder项目,下载地址:https://github.com/Microsoft/WinAppDriver/tree/master/Tools。例如笔者直接下载的是ZIP包,下载并解压后会得到WinAppDriver-master一个文件夹。

  • 2.安装Visual Studio 2017及其以上版本的VS工具,例如笔者安装的是VS 2022。

  • 3.以管理员身份启动VS,并且打开WinAppDriver UI Recorder项目。启动VS后点击【打开项目或解决方案§】,选择~WinAppDriver-master\Tools\UIRecorder\WinAppDriverUIRecorder.sln文件,

  • 4.编译项目。在【解决方法资源管理器】下找到WinAppDriverUIRecorder,右键点击后在弹窗的菜单中选择【生成(U)】开始编译项目,,如果没有控制台没有报错则编译成功。

    如果【解决方法资源管理器】没有显示,可在视图菜单下点击使其显示。

  • 5.点击VS工具菜单中的【启动】图标:

    运行项目,项目启动成功后会打开一个【WAD UIRecorder】窗口,窗口结果(图片来源WinAppDriver UI Recorder官方介绍文档https://blogs.windows.com/windowsdeveloper/2018/06/20/introducing-winappdriver-ui-recorder/)。

  • 6.点击【Record】激活录制,然后在正文区域输入一个"s",如图

    从WAD UIRecorder界面上面的面板中可以看到内容区域的xPath定位语法是:

python 复制代码
"/Pane[@ClassName=\"#32769\"][@Name=\"桌面 1\"]/Window[@ClassName=\"WordPadClass\"][@Name=\"文档 - 写字板\"]/Document[@ClassName=\"RICHEDIT50W\"][@Name=\"多信息文本窗口\"]"

从下面的C# Code面板中可以看到操作生成的C#代码,代码如下:

python 复制代码
// KeyboardInput VirtualKeys=""s"" CapsLock=False NumLock=True ScrollLock=False
Console.WriteLine("KeyboardInput VirtualKeys=\"\"s\"\" CapsLock=False NumLock=True ScrollLock=False");
System.Threading.Thread.Sleep(100);
winElem_LeftClickDocument多信息文本窗口_24_70.SendKeys("s");

使用WinAppDriver UI Recorder工具,用户可以通过更简单、更直观的方法来为WinAppDriver编写自动化脚本。

虽然生成的代码是C#,但是通过该工具可以帮助我们迅速生成测试脚本,对于一些难以用元素识别工具获得元素属性的元素,也可以快速得到定位语法,这也是UI Recorder初始版本最先支持的两个场景。

相关推荐
cpsvps_net5 小时前
美国服务器环境下Windows容器工作负载智能弹性伸缩
windows
甄超锋6 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
cpsvps8 小时前
美国服务器环境下Windows容器工作负载基于指标的自动扩缩
windows
网硕互联的小客服11 小时前
Apache 如何支持SHTML(SSI)的配置方法
运维·服务器·网络·windows·php
etcix11 小时前
implement copy file content to clipboard on Windows
windows·stm32·单片机
许泽宇的技术分享12 小时前
Windows MCP.Net:基于.NET的Windows桌面自动化MCP服务器深度解析
windows·自动化·.net
非凡ghost13 小时前
AMS PhotoMaster:全方位提升你的照片编辑体验
windows·学习·信息可视化·软件需求
mortimer14 小时前
一次与“顽固”外部程序的艰难交锋:subprocess 调用exe踩坑实录
windows·python·ai编程
gameatp16 小时前
从 Windows 到 Linux 服务器的全自动部署教程(免密登录 + 压缩 + 上传 + 启动)
linux·服务器·windows
穷人小水滴16 小时前
在 windows 运行 flatpak 应用 (WSL)
linux·windows·ubuntu