描述模式
描述模式是一种通过直接在代码中使用属性构造测试对象的方式。这种方式与传统的使用对象模型加载测试对象的方法不同。在描述模式中,可以通过WinAuto或QtAuto等对象调用get[ControlType]方法(如WinAuto.getWindow()、WinAuto.getButton()等)。这些get[ControlType]方法需要一个包含一组属性的对象作为参数,这些属性以逻辑“AND”的形式组合,定义了用于查找界面上控件的查询条件。
描述模式的优点在于无需依赖于对象模型,可以动态构建测试对象,或访问界面上动态创建的控件。其缺点是不能集中管理测试对象,也不能使用对象管理器的编辑界面修改或验证测试对象,维护成本较高。因此,您可以根据具体场景选择是否使用描述模式。
从属性构造测试对象
下面的示例展示了如何通过描述模式直接从属性构造测试对象,而无需加载对象模型:
const { WinAuto } = require('leanpro.win');
await WinAuto.getWindow({ "className": "ApplicationFrameWindow", "title": "计算器" })
.getWindow({ "className": "Windows.UI.Core.CoreWindow", "title": "计算器" })
.getGeneric({ "type": "Group", "automationId": "NumberPad", "name": "数字键盘" })
.getButton({ "automationId": "num5Button" }).click();from leanproAuto import WinAuto
WinAuto.getWindow({"className": "ApplicationFrameWindow","title": "计算器"}).getWindow({"className": "Windows.UI.Core.CoreWindow","title": "计算器"}).getGeneric({"type": "Group","automationId": "NumberPad","name": "数字键盘"}).getButton({"automationId": "num5Button"}).click()在上述示例中,每个方法调用都传递了一个对象参数,其中包含多个键值对。这些键值对组合起来形成了用于查找测试对象的过滤条件。
省略中间层次结构
如果您要操作的对象具有唯一标识,可以省略中间的层次结构,直接使用顶层窗口对象和目标操作对象级联,如下所示:
const { WinAuto } = require('leanpro.win');
await WinAuto.getWindow({ "className": "ApplicationFrameWindow", "title": "计算器" })
.getButton({ "automationId": "num5Button" }).click();from leanproAuto import WinAuto
WinAuto.getWindow({ "className": "ApplicationFrameWindow", "title": "计算器" }).getButton({ "automationId": "num5Button" }).click()在这个示例中,由于按钮“5”具有唯一的识别属性automationId,因此无需通过中间对象也能直接定位到该按钮,从而简化了代码。
批量传递属性
在使用描述模式时,您可以通过 getGeneric() 方法一次性传入属性数组,从而避免多层级调用。这种方式适合需要连续查找多级控件的场景。
💡 提示:在模型管理器的 对象属性 标签页中,点击 “复制级联节点属性”,即可自动复制当前控件及其父级的属性数组,直接用于脚本中。
示例代码
以下示例展示了三种方式(对象识别、图案控件、虚拟控件)在描述模式中批量传递属性并点击“数字 5”按钮的写法:

const { WinAuto } = require('leanpro.win');
// 方式一:对象识别
await WinAuto.getGeneric([
{ "type": "Window", "className": "ApplicationFrameWindow", "title": "计算器" },
{ "type": "Window", "className": "Windows.UI.Core.CoreWindow", "title": "计算器" },
{ "type": "Group", "automationId": "NumberPad", "name": "数字键盘" },
{ "type": "Button", "automationId": "num5Button", "name": "五" }
]).click();
// 方式二:图案控件 Pattern
let parent_widget = WinAuto.getGeneric([
{ "type": "Window", "className": "ApplicationFrameWindow", "title": "计算器" },
{ "type": "Window", "className": "Windows.UI.Core.CoreWindow", "title": "计算器" },
{ "type": "Group", "automationId": "NumberPad", "name": "数字键盘" }
]);
await parent_widget.getPattern({ "imagePath": "path_to_pattern_5.png" }).click();
// 方式三:虚拟控件 Virtual
await parent_widget.getVirtual({ "type": "Virtual", "boundingRectangle": "80,50,78,48", "align": 0 }).click();from leanproAuto import WinAuto
# 方式一:对象识别
WinAuto.getGeneric([
{ "type": "Window", "className": "ApplicationFrameWindow", "title": "计算器" },
{ "type": "Window", "className": "Windows.UI.Core.CoreWindow", "title": "计算器" },
{ "type": "Group", "automationId": "NumberPad", "name": "数字键盘" },
{ "type": "Button", "automationId": "num5Button", "name": "五" }
]).click()
# 方式二:图案控件 Pattern
parent_widget = WinAuto.getGeneric([
{ "type": "Window", "className": "ApplicationFrameWindow", "title": "计算器" },
{ "type": "Window", "className": "Windows.UI.Core.CoreWindow", "title": "计算器" },
{ "type": "Group", "automationId": "NumberPad", "name": "数字键盘" }
])
parent_widget.getPattern({ "imagePath": "path_to_pattern_5.png" }).click()
# 方式三:虚拟控件 Virtual
parent_widget.getVirtual({ "type": "Virtual", "boundingRectangle": "80,50,78,48", "align": 0 }).click()注意事项
- 在
getGeneric()方法中传入的属性必须属于同一种技术类型(如 Windows、Qt、Java 等)。 - 若目标控件下包含 图案控件(Pattern) 或 虚拟控件(Virtual),应先通过
getGeneric()获取父控件,再分别调用对应的方法。
利用模型中保存的对象属性
在默认情况下,第一个参数表示对象名,第二个参数表示识别属性。识别属性的可选值参考 Criteria 类。
覆盖已有标识属性
以下示例展示了如何覆盖识别属性:
await model.getButton("五", { automationId: "num6Button" }).click();model.getButton("五", { "automationId": "num6Button" }).click()在这个示例中,脚本首先获取模型中名为 五 的按钮的识别属性,然后将 automationId 替换为新的值 num6Button。因此,这行代码点击的按钮是计算器上的“6”而不是“5”,因为 automationId 被覆盖了。
动态添加属性
以下示例展示了如何为已知控件添加额外的识别属性:
await model.getButton("Button", { index: 2 }).click();model.getButton("Button", { "index": 2 }).click()此代码会点击匹配到的第3个 Button 控件(假设至少有3个按钮控件符合条件)。这种方法类似于对象继承,通过添加新的属性来扩展已有对象的定义。
动态构建叶子节点
你还可以将模型对象与描述模式结合使用,以简化代码结构。这种方法适用于在测试过程中动态生成的子控件,如 treeItem、tableItem 等。
await modelWin.getList("ListBox").getListItem({
"type": "ListItem",
"className": "ListBoxItem",
"name": "First Normal Item"
}).click();modelWin.getList("ListBox").getListItem({
"type": "ListItem",
"className": "ListBoxItem",
"name": "First Normal Item"
}).click()这段代码首先获取 ListBox 控件,然后通过描述模式获取 ListItem 控件,最后点击名为 First Normal Item 的项。
虚拟控件的描述模式
在某些情况下,控件的子元素可能无法被识别或动态生成,此时可以结合描述模式与虚拟控件进行操作。
例如,在一个 List 控件中,每个条目右侧都有“删除”按钮,但该按钮是程序动态绘制的,无法被识别为真实控件。
此时可以通过描述模式获取 listItem 对象后,再在其区域中定义一个虚拟控件来点击“删除”按钮。
await model.getListItem({"name":"group1"})
.getVirtual({"boundingRectangle":"0,0,16,16","align":1})
.click();model.getListItem({"name":"group1"}) \
.getVirtual({"boundingRectangle":"0,0,16,16","align":1}) \
.click()上述代码说明:
getListItem():通过描述模式获取列表项,支持属性{ "name": "group1" }。getVirtual():在该listItem区域内定义一个 16×16 的虚拟控件区域。align参数控制区域的对齐方式:0左上1右上2左下3右下
虚拟控件结合描述模式可用于以下场景:
- 动态控件:列表项、表格项等内容会动态变化,提前建模不便。
- 不可识别子控件:某些自绘 UI 或图形按钮无法通过对象识别,可通过虚拟控件定位。
- 多平台兼容:虚拟控件可与 Windows、Qt 等技术的控件结合使用。
这种方式在需要精准点击控件内部特定区域时非常有效,同时比纯坐标点击更具可维护性。
代码生成
描述模式的代码可以手动编写,但使用模型管理器生成代码更为便捷。
生成操作代码
- 选择控件:在模型管理器中,选择要操作的控件。
- 切换标签页:切换到对应的“控件操作”标签页。
- 开启描述模式:点击描述模式开关
(按钮按下表示开启)。

此时,复制的控件操作代码即为描述模式代码。
生成对象查找代码
节点属性
在模型管理器的“对象属性”标签页,点击 复制节点属性 按钮,会将控件节点的信息复制,并以对象形式呈现。复制得到的属性可以直接作为 get[ControlType] 方法的参数。
例如,复制后会得到如下内容:
{"type": "Button", "automationId": "plusButton", "name": "加"}
级联节点属性
在对象属性界面,点击 复制级联节点属性 按钮,将控件节点及其所有父节点的信息一并复制,并以数组形式呈现。复制得到的属性可直接作为 getGeneric() 方法的参数在批量传递属性时使用。
例如,复制后会得到如下内容:
[
{"type": "Window", "className": "ApplicationFrameWindow", "appName": "ApplicationFrameHost"},
{"type": "Group", "automationId": "StandardOperators", "name": "标准运算符"},
{"type": "Button", "automationId": "plusButton", "name": "加"}
]
在侦测控件时生成描述代码
点击“添加对象”并检测到控件后,在弹出的“添加对象”对话框中点击“复制代码”,即可将描述模式代码复制到剪贴板(默认会生成 click() 方法的描述模式代码)。