背景
在 基于pywebview搭建企业级桌面端 这篇中记录了多种webview桌面端的框架,并记录了pywebview的使用和相关的坑。本篇记录如何在pywebview中实现自动化测试。
由于pywebview没有提供测试套件,只有几个基础的操作接口,所以需要结合一些测试框架来实现。
如果pywebview中不涉及bridge通信,那可以当做普通浏览器来测试,以上框架都支持。但pywebview中涉及bridge通信,那有特定接口无法模拟,比如自实现的系统文件操作或者窗体调整等超出了浏览器的功能,只能考虑变通的方式,比如CDP(chrome devtools protocol) 或者 BiDi (browser-to-browser protocol) 或者图形化识别。
CDP和BiDi
两者都是用过websocket通信,差异体现在:
- 设计目标:BiDi 协议旨在成为一种通用的、标准化的浏览器自动化和调试协议,注重跨浏览器的兼容性和简洁性。CDP 最初是为 Chrome 浏览器的开发者工具调试功能设计的,更侧重于满足 Chrome 浏览器的特定调试需求,功能丰富且细致,但相对复杂,并且主要适用于基于 Chromium 内核的浏览器。
- 功能范围:BiDi 协议提供了一些高级的、通用的功能,如页面导航、元素交互、脚本执行、监听 DOM 事件、捕获 JavaScript 错误和控制台消息、记录网络流量等。CDP 除了包含上述功能外,还提供了对浏览器渲染引擎、缓存策略、Cookie 管理等更底层的调试功能。
在常见基础操作上命名稍微有点区别:
功能 | BiDi | CDP |
---|---|---|
导航 | browsingContext.navigate | Page.navigate |
执行js | script.evaluate | Runtime.evaluate |
让pywebview支持CDP或BiDi
首先官网web engine 列举了pywebview在不同平台下的web engine
Platform | Code | Renderer | Provider | Browser compatibility |
---|---|---|---|---|
Android | WebKit | Ever - green Chromium | ||
GTK | gtk | WebKit | WebKit2 (minimum version >2.2) | |
macOS | WebKit | WebKit.WKWebView (bundled with OS) | ||
QT | qt | WebKit | QtWebEngine / QtWebKit | |
Windows | edgechromium | Chromium | > .NET Framework 4.6.2 and Edge Runtime installed | Ever - green Chromium |
Windows | cef | CEF | CEF Python | Chrome 66 |
Windows | mshtml | MSHTML | DEPRECATED Internet Explorer MSHTML | IE11 (Windows 10/8/7) |
查看changelog 发现 pywebview 5.4增加特性:
windows下支持 EdgeChromium通过添加远程调试支持设置webview.settings['REMOTE_DEBUGGING_PORT'] 来打开cdp.源码位置
#https://github.com/r0x0r/pywebview/blob/master/webview/platforms/edgechromium.py if webview_settings['REMOTE_DEBUGGING_PORT'] is not None: props.AdditionalBrowserArguments += f' --remote-debugging-port={webview_settings["REMOTE_DEBUGGING_PORT"]}'
但是实测发现即使设置了也没有效果,端口并不通。
随后查找其他方式,避免直接修改库源码。发现支持通过环境变量来设置,回退到pywebivew 4.3.3并启动前执行
os.environ["WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS"] = "--remote-debugging-port=9222"
至此selenium,playwright,pyppeteer都可以使用cdp操作了。
代码示例
各大框架比对见附录。考虑到playwright支持浏览器广,上层sdk支持多种语言,且测试报告,调试,录制,vscode插件等都支持,这里以playwright为例。
线性执行
playwright默认是并发执行的,假设应用场景是这个桌面应用不支持多开,或者测试场景是线性执行,比如登录,操作,退出。需要做如下配置:
- playwright.config.js配置
workers: 1
使得单个线程执行 多个test用serial 标记,使得test之间串行执行
test.describe.serial('测试集合',()=>{ test.describe('测试A',()=>{}) test.describe('测试B',()=>{}) })
用一个page实例,避免每次重连
let page; test.beforeAll(async () => { const browser = await playwright.chromium.connectOverCDP('http://127.0.0.1:9222'); const defaultContext = browser.contexts()[0]; page = defaultContext.pages()[0]; });
文件
获取本地路径
const directory = path.join(__dirname, `../fixtures/test.json`);
模拟pywebview.api
执行脚本覆盖pywebview的bridge通信
注意变量只能通过函数入参的形式传入,因为可执行的脚本作用域在浏览器而不是测试脚本,两者是隔离的
await page.evaluate((directory) => { // @ts-ignore window.pywebview = { api: { choosePath: () => Promise.resolve(JSON.stringify({ status: 'ok', directory })), getFileSize: () => Promise.resolve(1024) } }; }, directory);
操作行为
常见操作
await page.goto('http://localhost:5000/'); await page.getByText('选择文件').click() await page.waitForTimeout(4000); await page.getByRole('radio', { name: config.radioValue, exact: true }).check(); await page.locator('select').selectOption('value') await page.locator('.col:last-child').textContent();//querySelector await page.locator('//div[@class=".btn" and text()="确定"]').click();//xpath
可选的操作
try { // 等待 1 秒,如果元素出现则点击,超时则跳过 await page.waitForSelector(':text("×")', { timeout: 1000 }); await page.getByText('×').click(); } catch (error) { }
断言
@playwright/test
是 Playwright 官方提供的测试框架,它自带的 expect 断言风格简洁,与 Playwright 的 API 配合良好。
const { test, expect } = require('@playwright/test');
test('测试页面标题', async ({ page }) => {
// 导航到页面
await page.goto('https://www.example.com');
// 断言页面标题是否包含指定字符串
await expect(page).toHaveTitle(/Example/);
// 获取页面上某个元素(假设存在一个id为myElement的元素)
const element = page.locator('#myElement');
// 断言元素是否可见
await expect(element).toBeVisible();
// 断言元素的文本内容
const text = await element.textContent();
await expect(text).toContain('Some text');
});
测试报告
playwright提供了丰富的报告选项,包括 HTML、JSON等格式,可以自定义报告的样式和内容。
// playwright.config.js
reporter: [
['json', { outputFile: 'test-results.json' }],
['html',{open: 'never'}],
]
上述配置会生成一个包含测试结果的 HTML 报告,以及一个 JSON 文件,其中包含测试结果的详细信息。
如果接入CI,可以通过json获取关键信息,便于判断计数。这边给出关键部分代码
// 初始化计数器
let allCount = 0;
let passedCount = 0;
let failedCount = 0;
let flakyCount = 0;
let skippedCount = 0;
// 递归函数,用于遍历所有测试用例
function traverseSuites(suites) {
suites.forEach((suite) => {
if (suite.specs) {
suite.specs.forEach((spec) => {
spec.tests.forEach((test) => {
allCount++;
const result = test.results[0];
if (result.status === "passed") {
passedCount++;
} else if (
result.status === "timedOut" ||
result.status === "failed"
) {
failedCount++;
} else if (result.status === "flaky") {
flakyCount++;
} else if (result.status === "skipped") {
skippedCount++;
}
});
});
}
if (suite.suites) {
traverseSuites(suite.suites);
}
});
}
录制脚本
官方提供的录制是弹出浏览器,不能直接在cdp的场景下录制。所以不适用。但能通过 --ui的方式审查元素,比纯手动简单一些。
关于图像识别的自动化测试
此外还调研了sikuli 基于图像识别的自动化测试框架,直接执行sikuli的jar包很方便,但基于图像识别不同操作系统会有差异,识别率要微调,不能做到一端开发,多端执行。但好处是跨平台,可以操作任何桌面应用。
附录
框架对比
特性/方面 | Playwright | Puppeteer | Selenium | Cypress | Sikuli |
---|---|---|---|---|---|
开发者 | 微软 | 谷歌 | 最初是内部测试工具,后发展为广泛使用项目 | Cypress.io团队 | Ramanathan Guha和Tobias Grosser |
发布时间 | 2020年1月 | 2017年 (旨在弥补 Selenium 的不足) | 2004年 | 2017年 | 2009年 |
流行度 | 近期发展势头强劲,但之前不如 Puppeteer | 过去一年更受欢迎,但 Playwright 正在赶超 | 拥有庞大且活跃的社区 | 在前端开发者中较受欢迎 | 相对小众,主要用于特定场景 |
核心功能 | 自动化网页交互,提取目标数据 | 自动化网页交互,提取目标数据 | 跨浏览器测试、自动化、网页抓取 | 端到端测试、集成测试等 | 基于图像识别的自动化操作 |
自动等待功能 | 具备,模仿人类用户行为,避免被检测为机器人 | 缺乏便利性,需要手动设置计时器 (例如 Page.waitForSelector() ) | - | 自动等待元素可交互,无需手动设置等待时间 | 基于图像识别,等待目标图像出现 |
反爬机制绕过 | 需要与第三方服务集成 (例如代理、AI解决方案) | 需要与第三方服务集成 (例如代理、AI解决方案) | - | - | 可绕过部分基于元素定位的反爬机制 |
客户端支持 | 异步和同步客户端 | 仅异步客户端 | - | 仅支持JavaScript编写测试用例 | 支持Java、Python等 |
跨浏览器支持 | 强大,支持 Chrome/Chromium, WebKit (Safari), Firefox | 主要支持 Chrome/Chromium,对 Firefox/Edge 的支持处于实验阶段 | 多种浏览器选项,但需要为每个浏览器安装特定的 WebDriver (Selenium Manager 尝试解决,仍在 Beta 阶段) | 主要支持Chrome、Firefox等主流浏览器 | 跨平台,可在Windows、Mac、Linux上运行 |
语言支持 | Python, Java, JavaScript, TypeScript, .NET | JavaScript (有非官方的 Python 端口 Pyppeteer) | Java, Python, Ruby, C#, JavaScript,以及通过客户端语言绑定支持 Go, Haskell, PHP, Perl, R, Dart 等 | JavaScript | Java、Python等 |
浏览器功能 | 使用修补的浏览器版本,支持多版本浏览器测试,多上下文浏览,支持浏览器扩展 | 浏览器功能相对落后 | - | 支持调试工具,可实时查看测试步骤 | 不依赖浏览器,可操作任何桌面应用 |
社区支持 | 相对较新,不如 Puppeteer 广泛 | 成熟的社区,出色的文档,更成熟的生态系统 | 拥有庞大且活跃的社区,以及大量深入的文档 | 有活跃的社区和丰富的插件 | 社区相对较小 |
网络抓取支持 | 功能上略有优势,补充功能多 | 官方支持,有网络抓取教程 | - | 不专注于网络抓取 | - |
设备模拟 | 开箱即用的设备模拟跨浏览器测试 | 未明确提及,可能需要额外配置 | - | 支持设备模拟 | 可模拟鼠标键盘操作任何设备上的应用 |
维护 | 多版本支持可能增加维护难度,未来可能出现重大变化 | 相对稳定 | - | 易于维护,测试用例编写简洁 | 图像识别可能因界面变化需要更新图像模板 |
速度 | 通常比 Selenium 快 | - | 相对较慢,更适合小型到中型的抓取项目 | 测试执行速度快 | 图像识别有一定性能开销 |
架构 | 基于响应事件的解耦系统 (事件驱动架构),异步通信 | - | 通过交换 JSON 有效负载使用 HTTP 与 Web 驱动程序交互 (JSON Wire 协议),可能产生延迟 | 基于Node.js,与浏览器直接通信 | 基于图像识别算法 |
WebDriver 管理 | 内置驱动程序,实现更简单 | - | 需要为每个浏览器安装特定的 WebDriver (Selenium Manager 正在尝试解决这个问题) | 自动管理浏览器驱动 | 不涉及WebDriver |
无头浏览器能力 | 支持无头浏览,可以模拟用户操作,解决动态页面抓取问题 | - | 支持无头浏览,可以模拟用户操作,解决动态页面抓取问题 | 支持无头模式运行测试 | 可操作无头应用(如命令行工具) |
易用性 | 更容易实现 | - | 相对复杂,需要配置 WebDriver | 测试用例编写简单,易于上手 | 图像识别配置相对复杂 |
更新程度 | 较新 | - | 较老 | 更新频繁,功能不断完善 | 更新相对较慢 |
适用场景 | 需要跨浏览器支持,需要多种语言支持,需要更强大的通用网络自动化解决方案;项目需求可通过其支持的语言和浏览器满足时,可获得快速、高效且易于实现的无头浏览器 | 需要大量同行指导,只使用 Chrome,时间有限,需要成熟的社区和文档 | 需要灵活性,希望采用非常具体的浏览器和编程语言组合;考虑到在线可访问的资源范围,可能是学习使用无头浏览器进行网页抓取的有用工具 | 前端项目的快速测试,注重开发调试体验 | 需要对桌面应用进行自动化操作,尤其是界面元素难以用代码定位的情况 |
决策因素 | 先前的熟悉程度在决策中起着重要作用 | 先前的熟悉程度在决策中起着重要作用 | - | 项目前端技术栈和对测试速度的要求 | 应用类型和自动化操作的定位方式 |
定义 | 跨浏览器的 Web 自动化库,旨在简化端到端测试 | - | 专用于跨浏览器测试和自动化的开源框架 | 用于现代Web应用的快速、简单、可靠的测试框架 | 基于图像识别的自动化测试和操作工具 |
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。