
随着软件开发日趋复杂,许多桌面应用需要跨越 Windows、Linux 等多个操作系统提供一致的用户体验。如何在 Windows、Linux 等异构环境中高效地验证应用功能的一致性和稳定性,成为自动化测试流程中一个亟待解决的课题。传统的测试方法往往因环境隔离、界面差异和远程操作的复杂性而显得力不从心。
本文将通过一个具体的测试场景——使用开源文件传输工具 NitroShare——探讨如何构建一套可靠的远程自动化测试方案。我们将展示如何从一台 Windows 控制端,远程驱动一台 Linux 设备上的 Qt 桌面应用,并完成跨系统的功能验证,为处理类似场景提供一个可行的技术思路。
在规划远程桌面自动化测试时,我们通常会遇到以下几个技术难点:
要有效应对这些挑战,一个理想的测试方案应具备远程执行、跨平台控件识别以及统一的脚本与报告管理能力。
NitroShare 是一款基于 Qt 开发的开源局域网文件传输工具,支持 Windows 和 Linux,适合作为跨平台测试的案例。
为验证跨平台远程测试的可行性,我们设计了如下测试场景:

整个测试流程可以通过录制与编写脚本相结合的方式完成。
首先,在 Windows 上启动 NitroShare 并打开传输历史窗口,为接收文件做准备。
操作步骤:
CukeTest 支持对 Qt 应用和原生 Windows 控件的混合自动化。我们可以通过录制功能自动生成操作脚本。由于 NitroShare 启动后主界面默认隐藏,录制过程需要先操作应用本身(Qt),再切换到系统托盘(Windows)。

录制后生成的脚本片段如下,它记录了启动应用并从托盘打开窗口的过程:
const { RunSettings } = require("leanpro.common");
const { QtAuto } = require("leanpro.qt");
const { WinAuto } = require("leanpro.win");
(async () => {
await RunSettings.set({reportSteps: true});
let modelQt = QtAuto.loadModel(__dirname + "/recording.tmodel");
let modelWin = WinAuto.loadModel(__dirname + "/recording.tmodel");
// 启动本地NitroShare进程
await QtAuto.launchQtProcessAsync("C:\\Program Files\\NitroShare\\nitroshare.exe");
await modelQt.getApplication("nitroshare").exists(10);
// 操作 Windows 系统托盘,打开应用主界面
await modelWin.getButton("通知_V_形").click();
await modelWin.getButton("NitroShare1").click(15, 13, 2); // 使用右键点击
await modelQt.getMenuItem("View_Transfers...").click(51, 21);
})();
接下来,我们需要连接到远程 Linux 设备,控制其上的 NitroShare 应用发送一个文件。
操作步骤:
在执行此阶段脚本前,需先在远程 Linux 终端运行 cuketest worker 命令启动服务。该服务会提供一个 WebSocket 地址,用于本地 CukeTest 建立连接。

在本地录制时,配置好远程应用的路径,并启用“远程录制”功能,输入 Worker 地址即可。CukeTest 会将你在远程桌面上的操作自动翻译为脚本代码。

对于 Linux 系统下某些无法通过标准控件识别的托盘图标,可以借助 CukeTest 的图像识别能力作为补充。通过“添加图案对象”功能,将图标截图保存为识别模型,脚本即可通过图像点击来完成操作,增强了脚本的适应性。

补全后的远程操作脚本片段如下:
const { CukeTest } = require("cuketest");
(async () => {
// 连接到远程Linux设备上的Worker服务
let auto = await CukeTest.connect({
wsEndpoint: 'ws://<Your-Linux-IP>:3131/ws'
});
const { runSettings: RunSettings } = auto;
const { qtAuto: QtAuto } = auto;
await RunSettings.set({reportSteps: true});
let remoteModel = await QtAuto.loadModel(__dirname + "/recording_1.tmodel");
// 远程启动NitroShare
await QtAuto.launchQtProcessAsync("/usr/bin/nitroshare");
// 注意:某些Linux桌面环境可能需要通过图像识别点击托盘图标
await remoteModel.getPattern("pattern_nitroshare_icon").click();
await remoteModel.getPattern("pattern_view_transfers_menu").click();
// 执行文件发送操作
await remoteModel.getButton("sendFilesBtn").click();
await remoteModel.getEdit("fileNameEdit").set("/home/user/Documents/testfile.txt");
await remoteModel.getButton("Open").click();
await remoteModel.getTableItem("Windows_Host").click(); // 假设Windows主机名为"Windows_Host"
await remoteModel.getButton("OK").click();
// 操作完成,关闭远程连接
auto.close();
})();
说明: 在 Linux 不同桌面环境中,系统托盘的实现差异较大,直接进行控件识别可能不稳定。此时,可以利用 CukeTest 的图像识别能力作为补充,将托盘图标和菜单项添加为图像对象,增强脚本的通用性。
文件发送后,我们需要在本地 Windows 端的 NitroShare 窗口中验证传输状态是否为“成功”。

验证点:
CukeTest 提供了检查点(Checkpoint)方法,可以方便地对控件属性和文件系统进行验证。
例如,使用 checkProperty() 方法可以断言控件上的传输状态值是否正确,这比检查界面截图更为可靠和高效。
// 验证本地 NitroShare 接收状态
await modelQt.getTableItem("Succeeded").checkProperty("data", "Succeeded");
同时,可以结合 Node.js 的 fs 模块来验证文件是否真实存在于本地文件系统中,形成完整的闭环验证。
测试结束后,一个规范的流程应当包含环境清理步骤,即关闭所有由测试启动的应用程序,以确保下次测试的独立性。
可以通过调用应用对象自身的 quit() 方法,或 Util.stopProcess() 方法强制结束进程。
// 方式一:通过 quit() 方法关闭应用
await modelQt.getApplication("nitroshare").quit();
// 方式二:通过进程ID关闭应用(需在启动时保存进程ID)
let process = await QtAuto.launchQtProcessAsync("...");
await Util.stopProcess(process);
在完成各个操作模块的脚本录制后,为了提高测试用例的可读性和可维护性,我们通常会将其整合到一个结构化的测试框架中。这里,我们采用行为驱动开发(BDD)的模式,使用 Gherkin 语法来描述测试场景。
首先,在 feature 文件中用自然语言描述整个测试流程。这种方式让技术人员和非技术人员都能清晰地理解测试的目的和步骤。
# language: zh-CN
功能: 跨平台桌面应用的远程自动化测试实践
验证NitroShare跨平台自动化操作
场景: 用户点击退出菜单项后应用关闭并显示退出状态
假如启动Windows端NitroShare应用
当远程连接启动Linux端NitroShare应用并发送文件
而且验证Windows端文件传输成功
那么退出应用
接下来,我们将 Gherkin 中的每一个步骤与先前录制的自动化脚本代码进行绑定。CukeTest 可以为这些步骤自动生成函数框架,我们只需将对应的代码逻辑填充进去。
在整合过程中,可以对脚本进行优化,例如:
整合后的测试脚本代码如下:
const { Given, When, Then, setDefaultTimeout } = require('cucumber');
const { CukeTest } = require("cuketest");
const { QtAuto } = require("leanpro.qt");
const { WinAuto } = require('leanpro.win');
const { Util } = require('leanpro.common');
// 为异步操作设置合理的超时时间
setDefaultTimeout(60 * 1000);
// 加载用于本地自动化的UI模型文件
let localWinModel = WinAuto.loadModel(__dirname + "\\model1.tmodel");
let localQtModel = QtAuto.loadModel(__dirname + "\\model1.tmodel");
Given("启动Windows端NitroShare应用", async function () {
// 启动本地应用并打开传输界面
localAppProcessId = await QtAuto.launchQtProcessAsync("C:\\Program Files\\NitroShare\\nitroshare.exe");
await localWinModel.getButton("通知_V_形").click()
await localWinModel.getButton("NitroShare").click(0, 0, 2);
await localQtModel.getMenuItem("View_Transfers...").click();
// 截图并添加到报告中
screenshot = await localQtModel.getWindow("TransferWindow").takeScreenshot()
this.attach(screenshot, 'image/png')
});
When("远程连接启动Linux端NitroShare应用并发送文件", async function () {
let auto = await CukeTest.connect({
wsEndpoint: 'ws://10.0.2.15:3131/ws'
});
const { runSettings: RunSettings } = auto;
const { qtAuto: QtAuto } = auto;
await RunSettings.set({ reportSteps: true });
//启动Qt应用文件"nitroshare"
await QtAuto.launchQtProcessAsync("/usr/bin/nitroshare");
let remoteQtModel = await QtAuto.loadModel(__dirname + "\\model1.tmodel");
// 通过图像打开文件传输界面
await remoteQtModel.getPattern("pattern_nitroshare").click()
await remoteQtModel.getPattern("pattern_view_transfers").click()
let screenshot = await remoteQtModel.getWindow("TransferWindow").takeScreenshot()
this.attach(screenshot, 'image/png')
await remoteQtModel.getButton("sendFilesBtn").click();
await remoteQtModel.getEdit("fileNameEdit").set("/home/leanpro/Downloads/hello.txt")
screenshot = await remoteQtModel.getWindow("QFileDialog").takeScreenshot()
this.attach(screenshot, 'image/png')
await remoteQtModel.getButton("Open").click();
await remoteQtModel.getTableItem("Windows").click()
screenshot = await remoteQtModel.getWindow("DeviceDialog").takeScreenshot()
this.attach(screenshot, 'image/png')
await remoteQtModel.getButton("OK").click();
auto.close();
});
When("验证Windows端文件传输成功", async function () {
// 检查本地UI,确认接收状态为 "Succeeded"
await qtModel.getDataItem("Succeeded").checkProperty("data", "Succeeded");
let screenshot = await localQtModel.getWindow("TransferWindow").takeScreenshot()
this.attach(screenshot, 'image/png')
// 此处还可结合文件系统API,验证物理文件是否存在
});
Then("退出应用", async function () {
// 关闭本地 NitroShare
await localQtModel.getApplication("nitroshare").quit()
// 连接远程 Worker 并关闭远程 NitroShare
let auto = await CukeTest.connect({
wsEndpoint: 'ws://10.0.2.15:3131/ws'
});
const { qtAuto: QtAuto } = auto;
let remoteQtModel = await QtAuto.loadModel(__dirname + "\\model1.tmodel");
await remoteQtModel.getApplication("nitroshare").quit()
await auto.close();
});
完成脚本整合后,直接运行该项目。CukeTest将自动执行整个流程:连接远程设备、操作UI、执行验证,并最终生成一份包含详细步骤、截图和执行结果的可视化报告。


本文通过一个具体的跨平台桌面应用测试案例,完整展示了如何构建一个从远程操作到本地验证的闭环自动化流程。这个过程看似复杂,但通过合适的工具和方法论,可以被拆解为一系列清晰、可管理和可自动化的步骤。
这表明,借助合适的工具链,团队完全有能力应对多平台桌面应用的测试挑战。将自动化测试与 BDD 等方法结合,不仅能提高测试执行的效率和一致性,还能增强测试用例的透明度和可维护性,为保障复杂应用的功能质量提供了可靠的技术路径。