关于测试windows 桌面 app,不是桌面浏览器打开 url!
我的需求:自动登录某应用,点击指定按钮,打开新的窗口。
结论:虽然经历曲折,最终还是找到了解决方案。
涉及:win32api 、appium、selenium。
因为刚刚接触,找了很多资料,可能是一开始查询的姿势不对,一直想的是如何操作 windows 的 app(摔桌!),找到了两种方案,一种是 vs 自带的 CodedUI Test,一个是 win32 的库。但我要测试的 app 是个套壳的 web,再加上我用的是 python,前者我就直接略过了。然后开始研究 win32 的库。
这个库是 python 调用 Windows api 的库,有很多类,感觉基本所以的操作都能做。我用到的不多,因为没有想用这个做完整的自动化测试,就用到了几个基本的 win32api、win32process、win32gui。虽然我最后没有用这个,不过也算是收获了一些知识。
首先这个安装,我最开始安装的是 pywin32,用的时候遇到了一些问题,虽然找到解决办法了,但在最后网友们说建议用 pypiwin32,然后我就默默的卸载重装了,果然没那么多问题了...
<!-- old -->
$ pip install pywin32
<!-- new -->
$ pip install pypiwin32
主要使用 win32api 执行一些命令,获取系统信息等,可以打开一个桌面 app
win32api.ShellExecute(hwnd, op , file , params , dir , bShow)
# ShellExecute这个函数接收6个参数,
# hwnd:父窗口的句柄,如果没有父窗口,则为0。
# op:要进行的操作,为“open”、“print”或者空。
# file:要运行的程序,或者打开的脚本。
# params:要向程序传递的参数,如果打开的为文件,则为空。
# dir:程序初始化的目录。
# bShow:是否显示窗口。
获取 app 版本信息win32api.GetFileVersionInfo(app_name, os.sep)
在打开桌面 app 这一步,用 win32process 替代了 win32api.ShellExecute,win32process 能更好的管理打开的程序。
win32process.CreateProcess(appName, commandLine , processAttributes , threadAttributes , bInheritHandles ,dwCreationFlags , newEnvironment , currentDirectory , startupinfo )
# appName:可执行的文件名。
# commandLine:命令行参数。
# processAttributes:进程安全属性,如果为None,则为默认的安全属性。
# threadAttributes:线程安全属性,如果为None,则为默认的安全属性。
# bInheritHandles:继承标志。
# dwCreationFlags:创建标志。
# newEnvironment:创建进程的环境变量。
# currentDirectory:进程的当前目录。
# startupinfo :创建进程的属性。
CreateProcess 返回一个列表,分别是进程句柄、线程句柄、进程 ID,以及线程 ID,拿到句柄之后就可以对它做任何操作了。
比如关闭该程序win32process.TerminateProcess(process_handle, 0)
这个模块主要对图形界面进行操作,比如计算机中打开许多的应用程序;
我需要找到某一个程序,就可以根据窗口名称获取到对应的句柄win32gui.FindWindow(None, window_name)
,还可以获取窗口大小win32gui.GetWindowRect(handle)
。
spy++ 这个软件可以查看窗口控件名称。
不过呢,这个库对于原生的 window 应用来说比较友好,可以获取下拉菜单什么的,但是我需要操作的应用是 electron 写的,只能够勉强打开关闭应用,想要操作按钮输入啥的,得靠坐标定位,这就断了我的后路。
接着找解决方案的时候,有人提出来 appium 可以实现 windows 桌面的测试,我立马去研究了下 appium 怎么用。
这个上手还是挺简单的,对于操作 app 的元素来说,就是些前端的知识。而启动来说,就是启一个 appium server,然后就编写代码了。
desired_caps = {
'platformName': 'Windows',
'app': app,
'deviceName': 'WindowsPC'
}
driver = webdriver.Remote('http://{}:{}/wd/hub'.format('127.0.0.1', '4724'), desired_caps)
不过在我的 windows 上始终处于,driver 获取不到,但应用默默的打开了的情况,日志会报错,找遍了也只找到了个 WinAppDriver 的 issuehttps://github.com/microsoft/WinAppDriver/issues/1008,没有解决办法,只好放弃了。
停滞了几天之后,逛论坛的时候看到有人说 selenium 可以利用 “欺骗” 来达到运行 windows app 的目的,啪叽啪叽的就去试验了。
看了下官方文档,发现和 appium 的使用特别像,立马就上手了
options = webdriver.ChromeOptions()
options.add_argument("--remote-debugging-port=9222") # open devtools for operator element
options.add_experimental_option('w3c', False)
options.set_capability('platform', 'WINDOWS') # test windows app
options.set_capability('version', '10') # window version
options.binary_location = app # start up app path
try:
self.driver = webdriver.Remote(command_executor='http://{}:{}/wd/hub'.format(host, port), options=options)
except BaseException as e:
raise e
利用 binary_location 把要运行的 app 路径传进去,指定平台为 WINDOWS,可以成功的启动应用并且拿到 driver;
获取元素,执行 js 都没有问题
<!-- by class name -->
self.driver.find_elements_by_class_name('login')
<!-- js -->
app.execute_js('return window.location.href;')
不过在对输入框进行操作的时候,发现 send_keys() 输入的字符串,实际 app 上内容是不全的,一开始以为是 selenium 的原因,换成了 PyKeyboard 库测试,发现只要是输入一串字符都会出现这种问题,没有找出来是什么原因,只要简单粗暴的把字符串拆成列表一个一个写入了。
k = PyKeyboard()
for i in list(value):
k.tap_key(i)
time.sleep(0.1)
在使用的过程中发现还可以多机部署测试,在服务器起个 server,
java -jar selenium-server-standalone-3.141.59.jar -role hub -maxSession 10 -port 8088
多台测试机上面下载好 webdriver 和 selenium-server(grid),使用命令启动 hub 节点且连接到 server,还可以指定 webdriver 的位置等,特别方便。
java -Dwebdriver.chrome.driver="D:\software\chromedriver_win32\2.44_chrome69-71\chromedriver.exe" -jar selenium-server-standalone-3.141.59.jar -role node -port 5555 -hub "http://127.0.0.1:8088/grid/register/" -browser "browserName=chrome,seleniumProtocol=WebDriver,maxInstances=5,platform=WINDOWS,version=10" -maxSession 5
然后在测试客户端配置指定使用的 hub 地址端口就好,后期还可以把这些执行的操作写成脚本在服务器端远程调用。
这个功能对于我想要同时在多个真机上进行操作很有用,同样的操作不同的账号就可以一次登录了。
至此终于找到了最终解决方案,感谢广大的网友们!
实现了完整的代码,可以确定到目前为止是满足我的需求的,当然我的最终目的不是登录应用,不然光这些是远远不够的,这些操作对我来说只是辅助的条件,不过从这个过程中还是学到很多很多东西,为自己记录下来。