Qt对象操作API

对象操作API的方法分为两类,操作和属性。操作对控件做实际的动作,属性是获得控件的运行时属性。因为控件访问是异步的,所以获取属性也是以方法的形式,即调用时需要加上括号”()”,并且返回目标值的Promise对象——Promise<T>,需要在调用前加上await关键字来获取Promise中的结果。

API语法说明

Qt的API文档建立在leanpro.qt的类型文件上(比如可以进入leanpro.qt.d.ts类型定义文件中查看API的调用方式),因此采用的是TypeScript语法,下面会根据阅读文档所需知识进行简单的介绍,如果希望进一步了解,可以前往TypeScript中文介绍了解更多相关语法。

首先是当前类的名称,在这里是IQtControl,全称可以理解为Interface of Qt Control,代表广义的Qt控件的接口定义。

其次是基类,代表当前类扩展自哪个基类,扩展代表会继承基类中的属性和方法。这里扩展自容器类IQtContainer,容器包含了各种获取对象API,这样的扩展可以实现链式的对象获取。

比如获取“Table OverView”窗口中的”sheet1”表格里的”Family”单元格的完整调用可以写作:

JavaScript
Python
model.getWindow("Table Overview").getTable("sheet1").getTableItem("Family");
model.getWindow("Table Overview").getTable("sheet1").getTableItem("Family")
CukeTest在实际生成控件调用代码时会进行优化,所以我们平时不会看到这么长的调用。

最后就是当前类的操作方法和属性方法,操作方法和属性方法之间空了一行表示区分。()中代表了传入的参数个数,参数的格式为[argName]:[argType],如果:前有?,代表该参数有缺省值,即使为空也不影响运行。方法的尾部(即最后一个:后面)代表方法的返回值,Promise<T>表示该方法是一个异步方法,只有T则代表是同步方法。

这里的T表示类型名称,比如voidstringnumber等。

各个类的关系

在CukeTest中,几个类之间的扩展关系大致如下:

  1. 最底层基类,容器类IQtContainer,包含获取自动化对象的方法;
  2. 第二层基类,基本控件类IQtControl,包含了各个控件通用的操作和属性方法;
  3. 具体控件类,有IQWindowIQLabel...等等一系列具体Qt控件,除了扩展而来的方法外,大部分控件都包含自己特有的操作和属性方法,来实现针对不同控件的自动化。

API 概览

操作方法

方法名 描述
click 模拟鼠标点击控件,可指定点击的位置和使用的鼠标键。
dblClick 模拟鼠标双击控件,可指定双击的位置和使用的鼠标键。
moveMouse 移动鼠标到控件的指定位置,可指定速度。
touch 模拟在控件上的触摸操作,支持单点或多点触摸。
wheel 滚动鼠标滚轮。
drag 模拟拖拽操作的第一步,鼠标按下,开始拖拽操作。
drop 模拟拖拽操作的第二步,松开鼠标。
pressKeys 输入组合按键或字符串。
exists 检查控件是否存在,可指定最长等待时间。
highlight 突出显示控件,用于调试和验证。
takeScreenshot 对控件截图,可指定文件路径。
invoke 调用控件的指定方法,并返回执行结果。
scrollIntoView 滚动到控件的可视位置。

属性方法

方法名 描述
rect 获取控件的位置和大小。
winId 获取控件的窗口句柄ID。
enabled 控件是否可用。
visible 控件是否可见。
modelProperties 获取控件在模型中缓存的属性。
modelImage 获取控件在模型中缓存的截图快照。
allProperties 获取目标控件的所有运行时属性。
property 获取控件的指定属性值。
waitProperty 等待控件的指定属性达到某个值。
checkProperty 校验控件的属性是否符合预期值。
checkImage 校验控件的图像是否与预期的参考图像匹配。

控件导航方法

方法名 描述
firstChild 获取控件的第一个子控件。
lastChild 获取控件的最后一个子控件。
next 获取控件的下一个兄弟控件。
previous 获取控件的上一个兄弟控件。
parent 获取控件的父控件。
findControls 根据条件查找控件。
all 获取所有匹配的同级控件。

通用的API

不同类型的对象操作有不同的操作和属性。它们都有一些共用的操作和属性,无论是哪个控件都可以调用的,如下:

JavaScript
Python
export interface IQtControl extends IQtContainer {
    // 通用操作方法
    click(x?: number, y?: number, mousekey?: number): Promise<void>;
    dblClick(x?: number, y?: number, mousekey?: number): Promise<void>;
    exists(seconds?: number): Promise<boolean>;
    moveMouse(x?: number, y?: number, seconds?: number): Promise<void>;
    moveMouse(path: [], seconds?: number): Promise<void>;
    touch(path: number[][][]): Promise<void>;
    wheel(value: number): Promise<void>;
    drag(x?: number, y?: number, mouseKey: number): Promise<void>;
    drop(x?: number, y?: number, seconds?: number): Promise<void>;
    drop(x?: number, y?: number, {seconds?: number, mouseKey?: number}): Promise<void>;
    pressKeys(keys: string, options?: PressKeysOptions | number): Promise<void>;
    highlight(milliseconds?: number);
    takeScreenshot(filePath?: string): Promise<void | string>;
    invoke(methodName: string, args?: (string | number | boolean)[]): Promise<object>;
    all(): Promise<IQtControl[]>;
    scrollIntoView(): Promise<void>;
    
    // 通用属性方法
    rect(): Promise<Rect>;
    winId(): Promise<number>;
    enabled(): Promise<boolean>;
    visible(): Promise<boolean>;
    modelProperties(): {[x: string]: any};
    modelImage(options?: {encoding: 'buffer' | 'base64'}): Promise<Buffer | string>;  //base64 is the default
    allProperties(): Promise<object>
    property(propertyName: string): Promise<string | boolean | number | object>;
    waitProperty(propertyName: QtPropertyIds, value: string | number | boolean, timeOutSeconds: number): Promise<boolean>
    checkProperty(propertyName: QtPropertyIds, expectedValue: string | number | boolean | RegExp, message: string): Promise<void>;
    checkProperty(propertyName: QtPropertyIds, expectedValue: string | number | boolean | RegExp, options: {message: string, operation: any}): Promise<void>;
    checkImage(options?: CheckImageCompareOptions | string): Promise<void>;

    // 通用导航方法
    firstChild(): Promise<IQtControl>;
    lastChild(): Promise<IQtControl>;
    next(): Promise<IQtControl>;
    previous(): Promise<IQtControl>;
    parent(): Promise<IQtControl>;

    // 通用控件搜索方法
    findControls(...conditions: ConditionFilter[]): Promise<IQtControl[]>; 
}
class QtControl(QtContainer):
    def click(x: Optional[int]=None, y: Optional[int]=None, mousekey: Optional[int]=None) -> None
    def dblClick(x: Optional[int]=None, y: Optional[int]=None, mousekey: Optional[int]=None) -> None
    def exists(seconds: Optional[int]=None) -> bool
    def moveMouse(x: Optional[int]=None, y: Optional[int]=None, seconds: Optional[int]=None) -> None
    def moveMouse(path: List[Any], seconds: Optional[int]=None) -> None
    def touch(paths: List[List[List[int]]]) -> None
    def wheel(value: int) -> None
    def drag(x: Optional[int]=None, y: Optional[int]=None, mouseKey: int) -> None
    def drop(x: Optional[int]=None, y: Optional[int]=None, options: Optional[Union[int, TypedDict]]=None) -> None
    def pressKeys(keys: str, opt: Optional[Union[int, PressKeysOptions]]=None) -> None
    def highlight(milliseconds: Optional[int]=None) -> None
    def takeScreenshot(filePath: Optional[str]=None) -> Union[str, None]
    def invoke(methodName: str, args: Optional[List[str]]=None) -> "any"
    def all() -> List["QtControl"]
    def scrollIntoView() -> None

    def rect() -> "Rect"
    def winId() -> int
    def enabled() -> bool
    def visible() -> bool
    def modelProperties(all: Optional[bool]=None) -> TypedDict
    def modelImage(options: Optional[TypedDict]=None) -> Union[str, bytearray]
    def allProperties() -> TypedDict
    def property(propertyIds: str) -> Union[str, int, bool, TypedDict]
    def waitProperty(propertyName: str, value: Union[str, int, bool], timeOutSeconds: Optional[int]=None) -> bool
    def checkProperty(propertyName: str, expectedValue: Union[str, int, bool], optionsOrMessage: Optional[Union[str, TypedDict]]=None) -> None
    def checkImage(options: Optional[any]=None) -> None

    def firstChild() -> "QtControl"
    def lastChild() -> "QtControl"
    def next() -> "QtControl"
    def previous() -> "QtControl"
    def parent() -> "QtControl"

    def findControls(*conditions: Union[str, Dict, List[Union[str, Dict]]]) -> List[QtControl]

操作方法

click(x, y, mousekey)

模拟鼠标点击控件,可以通过传入参数修改点击的相对位置(偏移量)和使用的鼠标键,缺省为左键点击目标控件中心位置。

参数:

  • x: number类型,点击位置相对控件左上角的水平像素点,0代表控件的水平中点位置,缺省值为0
  • y: number类型,点击位置相对控件左上角的垂直像素点,0代表控件的垂直中点位置,缺省值为0
  • mouseKey: MouseKey 类型,操作的鼠标键,缺省为1,即鼠标左键。

返回值:

  • 不返回任何值的异步方法。

使用示例

最简单的调用方式是不传入任何参数,直接点击控件的正中心:

JavaScript
Python
// 点击按钮的中心位置
await model.getButton("Button").click();
# 点击按钮的中心位置
model.getButton("Button").click()

如果需要点击控件的指定坐标位置,可以传入相对控件左上角的水平像素值(x)和垂直像素值(y):

JavaScript
Python
// 点击按钮上距离左上角水平590像素、垂直10像素的位置
await model.getButton("Button").click(590, 10);
# 点击按钮上距离左上角水平590像素、垂直10像素的位置
model.getButton("Button").click(590, 10)

注意:click(0, 0)不会点击控件左上角原点,而是点击控件正中心;如果期望点击左上角,请用click(1, 1)

如果需要右击控件,则需要传入第三个参数并设置为2:

JavaScript
Python
// 右键点击按钮的指定位置
await model.getButton("Button").click(590, 10, 2);
# 右键点击按钮的指定位置
model.getButton("Button").click(590, 10, 2)

如果需要右键点击控件正中心,可以使用以下方式:

JavaScript
Python
// 右键点击按钮中心
await model.getButton("Button").click(0, 0, 2);
await model.getButton("Button").click(null, null, 2);
# 右键点击按钮中心
model.getButton("Button").click(0, 0, 2)
model.getButton("Button").click(None, None, 2)

dblClick(x, y, mousekey)

模拟鼠标双击控件。与click()方法的调用方式一致。

参数:

  • x: number类型,双击位置相对控件左上角的水平像素点,缺省值为0
  • y: number类型,双击位置相对控件左上角的垂直像素点,缺省值为0
  • mouseKey: MouseKey 类型,操作的鼠标键,缺省为1

返回值:

  • 不返回任何值的异步方法。

使用示例

双击控件的正中心:

JavaScript
Python
// 双击按钮中心
await model.getButton("Button").dblClick();
# 双击按钮中心
model.getButton("Button").dblClick()

moveMouse(x, y, seconds)

移动鼠标光标到控件的指定位置。

参数:

  • x: number类型,移动位置相对控件左上角的水平像素点,缺省值为0
  • y: number类型,移动位置相对控件左上角的垂直像素点,缺省值为0
  • seconds: number类型,鼠标移动的持续时间,单位为秒。

返回值:

  • 不返回任何值的异步方法。

使用示例

移动鼠标到控件的中心位置:

JavaScript
Python
// 移动鼠标到Pane控件中心
await model.getPane("Pane").moveMouse();
# 移动鼠标到Pane控件中心
model.getPane("Pane").moveMouse()

如果需要移动鼠标到控件的指定位置并控制速度:

JavaScript
Python
// 在5秒内移动鼠标到指定位置(500, 70)
await model.getPane("Pane").moveMouse(500, 70, 5);
# 在5秒内移动鼠标到指定位置(500, 70)
model.getPane("Pane").moveMouse(500, 70, 5)

touch(paths)

模拟在控件上的触摸操作,可以模拟单点或多点触摸以及触摸轨迹。

参数:

  • paths: Array<Array<Array<number>>>类型,一个包含触摸路径的数组。每个触摸路径是一个数组,包含了一系列表示触摸点相对控件左上角坐标[x, y]的数组。可以传入多个路径来模拟多点触摸。

返回值:

  • 不返回任何值的异步方法。

使用示例

模拟一个单点触摸滑动的轨迹:

JavaScript
Python
// 模拟单点触摸滑动,从(60,70)开始,然后沿着指定路径移动
await modelQt.getGeneric("TouchWidget").touch([[[60,70],[0,1],[0,1],[0,2],[0,4],[0,5]]]);
# 模拟单点触摸滑动,从(60,70)开始,然后沿着指定路径移动
modelQt.getGeneric("TouchWidget").touch([[[60,70],[0,1],[0,1],[0,2],[0,4],[0,5]]])

模拟多点触摸,每个数组代表一个触摸点的移动轨迹:

JavaScript
Python
// 模拟三个触摸点同时进行操作
await modelQt.getGeneric("MultiTouchWidget").touch([
  [[10,20],[-5,3],[-3,2],[-3,2],[-2,1],[-2,2]],  // 第一个触摸点的轨迹
  [[20,30],[0,1],[0,1],[0,1],[0,1],[0,1]],        // 第二个触摸点的轨迹
  [[30,40],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1]]  // 第三个触摸点的轨迹
]);
# 模拟三个触摸点同时进行操作
modelQt.getGeneric("MultiTouchWidget").touch([
  [[10,20],[-5,3],[-3,2],[-3,2],[-2,1],[-2,2]],  # 第一个触摸点的轨迹
  [[20,30],[0,1],[0,1],[0,1],[0,1],[0,1]],        # 第二个触摸点的轨迹
  [[30,40],[0,1],[0,1],[0,1],[0,1],[0,1],[0,1]]  # 第三个触摸点的轨迹
])

wheel(value)

滚动鼠标滚轮,输入参数为滚轮滚动的刻度。

参数:

  • value: number类型,滚轮刻度。正值向前滚动,负值向后滚动。

返回值:

  • 不返回任何值的异步方法。

使用示例

向上滚动鼠标滚轮一个刻度:

JavaScript
Python
// 在Pane控件上向上滚动一个刻度
await model.getPane("Pane").wheel(1);
# 在Pane控件上向上滚动一个刻度
model.getPane("Pane").wheel(1)

drag(x, y, mouseKey)

拖拽操作的第一步操作,在目标位置按住鼠标不松开,xy为相对控件位置的水平像素和垂直像素,缺省为中心位置。

参数:

  • x: number类型,拖拽的起点相对控件左上角的水平像素点。
  • y: number类型,拖拽的起点相对控件左上角的垂直像素点。
  • mouseKey: MouseKey 类型,使用的鼠标键,缺省为1

返回值:

  • 不返回任何值的异步方法。

使用示例

从控件中心开始拖拽:

JavaScript
Python
// 在按钮中心按下鼠标,开始拖拽
await model.getButton("sourceButton").drag(0, 0);
# 在按钮中心按下鼠标,开始拖拽
model.getButton("sourceButton").drag(0, 0)

可以参考拖拽控件的方式选择

drop(x, y, options)

拖拽操作的第二步操作,将鼠标位置移至指定位置并松开鼠标,xy为相对控件位置的水平像素和垂直像素。第三个参数用于控制鼠标移动的速度和使用的鼠标键。

可以在A控件上执行drag()方法,在B控件上执行drop()方法,实现将A控件拖拽到B控件上的效果,具体文档可以查看拖拽控件的方式选择

参数:

  • x: number类型,释放鼠标的位置相对控件左上角的水平像素点,缺省为0
  • y: number类型,释放鼠标的位置相对控件左上角的垂直像素点,缺省为0
  • options: object类型,自定义拖拽行为的配置对象。支持的选项包括:
    • duration: number类型,鼠标移动到目标位置的持续时间,单位为秒。默认值为1,即1秒。
    • mouseKeys: number类型,在拖拽操作中使用的鼠标按键。1表示左键,2表示右键。默认值为左键(1)。

返回值:

  • 不返回任何值的异步方法。

使用示例

完成拖拽操作:

JavaScript
Python
// 将按钮拖拽到另一个控件上
await model.getButton("sourceButton").drag(0, 0);
await model.getButton("targetButton").drop(10, 10);
# 将按钮拖拽到另一个控件上
model.getButton("sourceButton").drag(0, 0)
model.getButton("targetButton").drop(10, 10)

可以参考拖拽控件的方式选择

pressKeys(keys, options?)

通过模拟键盘信号来实现字符串输入或组合键操作,因此不仅可以用于简单的文本输入,还可以进行复杂的键盘操作,如模拟登录、导航、剪切、粘贴等操作。

需要注意,字符串中的某些特殊字符(如 ^+~%{}())会被解析为控制键(如 Shift、Ctrl),而非作为字符输入。具体的键指令请参考附录: 输入键对应表。如果希望输入纯文本,无视这些控制键符号,请使用 {textOnly: true} 选项,这样调用:pressKeys(str, {textOnly: true})

参数:

  • keys: string 类型,要输入的按键、组合键或是字符串,最大支持 1024 个字符。
  • options: PressKeysOptions | number(可选)
    控制输入模式的可选参数,包含以下内容:

    • textOnly: boolean 类型,设为 true 时,将所有字符作为普通文本输入,忽略控制键指令。
    • cpm: number 类型,即每分钟输入的字符数,用于控制输入速度。建议 cpm 值设置在 200 以上。由于不同系统及应用对输入速度的处理方式不同,实际的输入速度可能与设置值有所偏差。

    如果直接传入数字作为 options,该值将被视为 cpm 参数。

返回值:

  • 不返回任何值的异步方法。

使用示例

输入纯文本:

JavaScript
Python
// 在输入框中输入文本,忽略特殊字符的控制键功能
await model.getEdit("Edit").pressKeys("Hello, World!", { textOnly: true });
# 在输入框中输入文本,忽略特殊字符的控制键功能
model.getEdit("Edit").pressKeys("Hello, World!", { "textOnly": True })

更多相关说明或样例,请参照模拟按键输入pressKeys方法

exists(seconds)

检查控件是否存在,输入参数为检查等待的最长时间(超时时间),当控件出现时立刻返回true,而当控件匹配结果出错或等待到超时返回false。该方法默认只会检查一次,不会重试或等待。

参数:

  • seconds: (可选)number类型,等待控件出现的最大秒数。如果省略,默认等待0秒,也就是只检查一次。

返回值:

  • 异步的返回控件是否存在,true为存在,false为不存在。

使用示例

  1. 不指定等待时间

JavaScript
Python
// 立即检查控件是否存在
let exists = await model.getApplication("dirview").exists();
# 立即检查控件是否存在
exists = model.getApplication("dirview").exists()
这段代码会立即检查一次,如果找到了控件,exists 的值为 true,否则为 false

  1. 指定等待时间

JavaScript
Python
// 等待最多10秒,直到控件出现
let bool = await model.getApplication("dirview").exists(10);
assert.strictEqual(bool, true);
# 等待最多10秒,直到控件出现
bool = model.getApplication("dirview").exists(10)
assert bool == True
这段代码会等待最多 10 秒钟,如果在这 10 秒内找到了控件,bool 的值为 true,否则为 false

highlight(duration)

控件高亮,用于调试和验证,可以传入高亮框的持续时间(单位为毫秒)。默认值为1000,即持续1秒。

参数:

  • duration: (可选)number类型,高亮的持续时间,单位为毫秒。

返回值:

  • 不返回任何值的异步方法。

使用示例

JavaScript
Python
// 高亮按钮2秒钟,用于调试验证
await model.getButton("Button").highlight(2000);
# 高亮按钮2秒钟,用于调试验证
model.getButton("Button").highlight(2000)

takeScreenshot(filePath?)

对控件截图,可以传入路径来将截图保存到路径位置。

参数:

  • filePath:(可选) string类型,截图文件的保存路径,如"./images/screenshot1.png"

返回值:

  • 异步的返回截图的base64字符串。

使用示例

JavaScript
Python
// 对按钮控件截图并保存到文件
await model.getButton("Button").takeScreenshot("./screenshots/button.png");
# 对按钮控件截图并保存到文件
model.getButton("Button").takeScreenshot("./screenshots/button.png")

takeScreenshot()方法返回的base64编码图片,可以直接作为图片插入到运行报告中,详细请参考报告附件

invoke(methodName, args?)

调用控件的指定方法,并返回方法的执行结果。

参数:

  • methodName: string类型,要执行的方法的名称。
  • args: (可选)字符串、数字或布尔值的数组,传递给方法的参数列表。

返回值:

  • 异步的返回方法调用后的执行结果。

使用示例

JavaScript
Python
// 获取表格行数
const result = await table.invoke("model.rowCount", []);
// 获取表格第1行第1列单元格的值
const cellValue = await table.invoke("model.data", [0, 0]);
# 获取表格行数
result = table.invoke("model.rowCount", [])
# 获取表格第1行第1列单元格的值
cellValue = table.invoke("model.data", [0, 0])

根据不同的控件和方法,invoke方法可以接受不同的参数。例如,在table控件中,可以使用invoke方法获取表格的行数和列数,甚至是直接获取表格内部的所有数据,如上述示例所示。根据具体的需求,可以传递不同的参数来调用不同的方法。

请注意,根据具体的应用场景和控件,invoke方法的参数和返回值可能会有所不同。请参考文档、源代码或API文档中对该类的方法的描述,以获取准确的参数和返回值信息。

属性方法

rect()

获取控件的位置和大小。

返回值:

  • 返回一个Rect对象,包含控件的坐标信息xy,宽度和高度信息widthheight

使用示例

JavaScript
Python
// 获取Button控件的矩形信息(位置和尺寸)
let rect = await model.getButton("Button").rect();
console.log(`Button位置: (${rect.x}, ${rect.y}) 尺寸: ${rect.width}x${rect.height}`);
# 获取Button控件的矩形信息(位置和尺寸)
rect = model.getButton("Button").rect()
print(f"Button位置: ({rect['x']}, {rect['y']}) 尺寸: {rect['width']}x{rect['height']}")

winId()

获取控件的窗口句柄ID。

返回值:

  • 异步的返回一串代表窗口句柄ID的数字。

使用示例

JavaScript
Python
// 获取窗口句柄ID
let id = await model.getWindow("Window").winId();
console.log("窗口句柄ID:", id);
# 获取窗口句柄ID
id = model.getWindow("Window").winId()
print(f"窗口句柄ID: {id}")

enabled()

控件是否可用。

返回值:

  • 异步的返回控件是否启用的布尔值。

使用示例

JavaScript
Python
// 检查Button控件是否启用,返回布尔值
let isEnabled = await model.getButton("Button").enabled();
console.log(`Button控件是否启用: ${isEnabled}`);
# 检查Button控件是否启用,返回布尔值
is_enabled = model.getButton("Button").enabled()
print(f"Button控件是否启用: {is_enabled}")

visible()

控件是否可见。

返回值:

  • 异步的返回控件是否可见的布尔值。

使用示例

JavaScript
Python
// 检查控件是否可见
let isVisible = await model.getButton("Button").visible();
console.log(`Button控件是否可见: ${isVisible}`);
# 检查控件是否可见
is_visible = model.getButton("Button").visible()
print(f"Button控件是否可见: {is_visible}")

modelProperties()

获取目标控件的模型属性,也即对应模型对象的属性(包括识别属性和其它属性)。

返回值:

  • 异步的返回目标模型对象的属性组成的对象object

使用示例

获取窗口控件的模型属性:

JavaScript
Python
// 获取窗口的模型属性
let modelProperties = await model.getWindow("Standard_Dialogs").modelProperties();
console.log(modelProperties);
// 验证控件类型
assert.equal('Window', modelProperties.type);
# 获取窗口的模型属性
modelProperties = model.getWindow("Standard_Dialogs").modelProperties()
print(modelProperties)
# 验证控件类型
assert modelProperties['type'] == 'Window'

输出的 modelProperties 为:

{ type: 'Window', className: 'Dialog', text: 'Standard Dialogs' }

通过调用modelProperties()方法,可以获取目标控件的模型属性,包括识别属性和其他属性。返回的modelProperties对象包含了目标模型对象的各种属性信息,例如类型(type)、类名(className)和文本(text)等。

modelImage(options?)

获取目标控件在模型中的截屏文件内容。

参数:

  • options: object类型,可选参数,包含以下字段:
    • encoding: 'buffer' | 'base64'类型,返回值的格式。默认为'base64'

返回值:

  • 异步的返回目标控件在模型中的截屏文件流(base64编码或是Buffer)。

使用示例

获取控件的模型截图(默认base64格式):

JavaScript
Python
// 获取按钮的模型截图(base64格式)
let imageBase64 = await model.getButton("Button").modelImage();
console.log("图像数据长度:", imageBase64.length);
# 获取按钮的模型截图(base64格式)
image_base64 = model.getButton("Button").modelImage()
print(f"图像数据长度: {len(image_base64)}")

获取Buffer格式的截图:

JavaScript
Python
// 获取按钮的模型截图(Buffer格式)
let imageBuffer = await model.getButton("Button").modelImage({ encoding: 'buffer' });
console.log("Buffer大小:", imageBuffer.length);
# 获取按钮的模型截图(Buffer格式)
image_buffer = model.getButton("Button").modelImage({ "encoding": "buffer" })
print(f"Buffer大小: {len(image_buffer)}")

allProperties()

获取目标控件的所有运行时属性,以对象的形式返回。

返回值:

  • 异步的返回目标模型对象的属性组成的对象object

使用示例

获取按钮控件的所有运行时属性:

JavaScript
Python
// 获取按钮的所有运行时属性
let properties = await model.getButton("Button").allProperties();
console.log(properties);
// 只获取其中的一个属性,如objectName
console.log('objectName=' + properties['objectName']);
# 获取按钮的所有运行时属性
properties = model.getButton("Button").allProperties()
print(properties)
# 只获取其中的一个属性,如objectName
print('objectName=' + properties['objectName'])

会获取按钮的所有属性,并打印:

{
  "acceptDrops": false,
  "accessibleDescription": "",
  "accessibleName": "",
  "arrowType": 0,
  "autoExclusive": false,
  ...
  "objectName": "按钮1",
  "x": 11,
  "y": 11
}

上面的代码会打印某一个属性:

objectName=按钮1

property(name)

获取目标控件的指定运行时属性。支持传入各种控件的属性名,同时也支持直接使用allProperties()方法中的字段名。

如果传入了一个不存在的属性名,则会返回undefined,而不会出错。

参数:

返回值:

  • 异步的返回各种属性值。

使用示例

直接获取指定属性值:

JavaScript
Python
// 获取按钮的objectName属性
let objectName = await model.getButton("Button").property('objectName');
console.log('对象名称:', objectName);
# 获取按钮的objectName属性
object_name = model.getButton("Button").property('objectName')
print(f'对象名称: {object_name}')

相比使用allProperties()再取某个字段,property()方法更加简洁高效:

JavaScript
Python
// 使用allProperties()方法
let properties = await model.getButton("Button").allProperties();
console.log(properties['objectName']);

// 使用property()方法更简洁
let objectName = await model.getButton("Button").property('objectName');
# 使用allProperties()方法
properties = model.getButton("Button").allProperties()
print(properties['objectName'])

# 使用property()方法更简洁
object_name = model.getButton("Button").property('objectName')

waitProperty(propertyName, value, timeOutSeconds)

等待控件的指定属性变为某个特定的值。你可以使用它来判断控件的状态是否符合预期,比如等待一个按钮变为可用状态。

参数:

  • propertyName: string类型,属性名,支持的属性名有enabled、text、value等,详见可选属性字段
  • value: string | number | boolean类型,要等待的属性值。
  • timeOutSeconds: number类型,等待时间,单位为秒,缺省为5秒,-1将无限等待。

返回值:

  • boolean类型,等待结果,true表示成功等到该值,false表示超出等待时间。如果报错则代表没有匹配到该控件,有可能是该控件未出现,或者是对象名称有问题。

使用示例

等待控件的文本内容变为指定值:

JavaScript
Python
// 等待按钮文本变为"OK",最多等待30秒
let isReady = await model.getButton("Button").waitProperty("text", "OK", 30);
if (isReady) {
    console.log("按钮文本已变为OK");
} else {
    console.log("等待超时");
}
# 等待按钮文本变为"OK",最多等待30秒
is_ready = model.getButton("Button").waitProperty("text", "OK", 30)
if is_ready:
    print("按钮文本已变为OK")
else:
    print("等待超时")

等待控件变为可用状态:

JavaScript
Python
// 等待按钮变为可用状态,最多等待10秒
let isEnabled = await model.getButton("SubmitButton").waitProperty("enabled", true, 10);
# 等待按钮变为可用状态,最多等待10秒
is_enabled = model.getButton("SubmitButton").waitProperty("enabled", True, 10)

checkProperty(propertyName, expectedValue, optionsOrMessage)

校验控件的属性值是否符合预期。如果指定的属性值不匹配期望值,则会抛出错误。

你可以在录制过程中快捷生成基于此方法的属性检查脚本,详情请参考录制中添加属性检查点

参数:

  • propertyName: string类型,需要检查的属性名称。支持的属性名包括enabledtextvalue等,以及所有由allProperties()方法返回的字段。
  • expectedValue: string | number | boolean | RegExp类型,期望的属性值。当控件的属性值与期望值匹配时,测试将通过;否则会抛出错误。
  • optionsOrMessage: string | object类型,可选参数。如果是字符串,它将被用作自定义错误信息;如果是对象,则可包含以下字段:
    • message: string类型,自定义的错误信息。
    • operation: any类型,指定用于比较控件属性与期望值的运算符。默认值为equal,即验证属性值是否等于期望值。

返回值:

  • 不返回任何值的异步方法。

使用示例

有关在checkProperty()中使用正则表达式的示例,请参考通过正则表达式检查

验证文本框控件的text属性是否为"Hello World":

JavaScript
Python
// 验证文本框的文本内容
await model.getEdit("textEdit").checkProperty('text', 'Hello World', 'Text does not match expected value');
# 验证文本框的文本内容
model.getEdit("textEdit").checkProperty('text', 'Hello World', 'Text does not match expected value')

如果文本不符合预期,将抛出一个错误,错误信息为"Text does not match expected value"。

验证按钮是否可用:

JavaScript
Python
// 验证按钮是否处于启用状态
await model.getButton("SubmitButton").checkProperty('enabled', true, '按钮应该是可用的');
# 验证按钮是否处于启用状态
model.getButton("SubmitButton").checkProperty('enabled', True, '按钮应该是可用的')

checkImage(options?)

用于验证控件的当前图像是否与指定的参考图像匹配。此方法是自动化测试中进行视觉校验的关键工具,确保 UI 控件的视觉表现符合预期。

checkImage()方法支持多种参数配置,更多关于此方法的说明及使用示例,请参考Virtual.checkImage()方法介绍

参数:

它支持多种参数配置,允许您通过以下方式提供参考图像:

  • 直接传递图像文件的路径字符串
  • 使用一个配置对象,在该对象中可以指定:
    • image: Base64 编码的图像数据。
    • imagePath: 图像文件的路径。
    • 各种比较选项(如 pixelPercentTolerance, colorTolerance 等)来调整比较的灵敏度。
  • 若不提供具体的图像源,则默认与模型中该控件预存的参考图像进行比较。

返回值:

  • Promise<void>: 不返回任何值的异步方法。如果比较失败,则会抛出错误。

使用示例

假设我们有一个控件 Button,我们可以使用 checkImage() 来验证其当前显示的图像是否与预期相符。例如:

JavaScript
Python
await model.getButton("Button").checkImage();
model.getButton("Button").checkImage()

CukeTest提供了一系列的控件导航方法,使得用户可以使用导航方法检索到目标控件的父控件、子控件以及兄弟控件。通过导航方法可以借助一个锚点控件索引到整棵控件树中所有的控件,常用于输入框定位、控件遍历等场景。

导航方法都是异步方法,也就是需要一定时间在应用里面检索到被导航的控件并返回,并不是直接在模型管理器的树中进行导航,这点需要区分不要混淆。

有关控件导航的使用,可以参考附录H:CukeTest自带样例中的"文件浏览器遍历"。

firstChild()

获取控件在应用中的第一个子控件,如果没有任何满足条件的子控件则返回null

注意,firstChild()仅代表直系的子控件,不会检索到孙控件。如果没有在子控件中检索到满足条件的控件,会直接返回null而不是继续在孙控件中搜索。如果仍希望获得某个控件下的所有控件,可以使用findControls方法。

返回值:

  • 异步的任意控件类型,具体类型取决于导航到的控件类型。没有导航到任何控件时返回null

使用示例

获取容器控件的第一个子控件:

JavaScript
Python
// 获取Pane控件的第一个子控件
let firstChild = await model.getPane("Container").firstChild();
if (firstChild) {
    console.log("找到第一个子控件");
    await firstChild.highlight(1000);
}
# 获取Pane控件的第一个子控件
first_child = model.getPane("Container").firstChild()
if first_child:
    print("找到第一个子控件")
    first_child.highlight(1000)

注意,如果某些控件是不可见或还未加载的,那么是不会被计入的。准确来说,firstChild()方法只是拿到可以拿到的子对象中的第一个,下面的lastChild()方法同样。

lastChild()

获取控件在应用中的最后一个子控件,如果没有任何满足条件的子控件则返回null

返回值:

  • 异步的任意控件类型,具体类型取决于导航到的控件类型。没有导航到任何控件时返回null

使用示例

获取容器控件的最后一个子控件:

JavaScript
Python
// 获取Pane控件的最后一个子控件
let lastChild = await model.getPane("Container").lastChild();
if (lastChild) {
    console.log("找到最后一个子控件");
    await lastChild.highlight(1000);
}
# 获取Pane控件的最后一个子控件
last_child = model.getPane("Container").lastChild()
if last_child:
    print("找到最后一个子控件")
    last_child.highlight(1000)

next()

获取控件在应用的下一个兄弟控件,通常为相邻的右侧/下方节点,取决于容器的布局方向。如果没有任何满足条件的兄弟控件则返回null,比如当控件作为父控件的lastChild时会返回null

返回值:

  • 异步的任意控件类型,具体类型取决于导航到的控件类型。没有导航到任何控件时返回null

使用示例

获取按钮的下一个兄弟控件:

JavaScript
Python
// 获取当前按钮的下一个兄弟控件
let nextButton = await model.getButton("Button1").next();
if (nextButton) {
    console.log("找到下一个兄弟控件");
    await nextButton.click();
}
# 获取当前按钮的下一个兄弟控件
next_button = model.getButton("Button1").next()
if next_button:
    print("找到下一个兄弟控件")
    next_button.click()

previous()

next()方法相反,获取控件在应用的上一个兄弟控件,通常为相邻的左侧/上方节点,取决于容器的布局方向。如果没有任何满足条件的兄弟控件则返回null,比如当控件作为父控件的firstChild时会返回null

返回值:

  • 异步的任意控件类型,具体类型取决于导航到的控件类型。没有导航到任何控件时返回null

使用示例

获取按钮的上一个兄弟控件:

JavaScript
Python
// 获取当前按钮的上一个兄弟控件
let prevButton = await model.getButton("Button2").previous();
if (prevButton) {
    console.log("找到上一个兄弟控件");
    await prevButton.click();
}
# 获取当前按钮的上一个兄弟控件
prev_button = model.getButton("Button2").previous()
if prev_button:
    print("找到上一个兄弟控件")
    prev_button.click()

parent()

firstChild()lastChild()方法相反,获取控件的父控件,如果控件为最顶层控件则返回null

返回值:

  • 异步的任意控件类型,具体类型取决于导航到的控件类型。没有导航到任何控件时返回null

使用示例

获取按钮的父控件:

JavaScript
Python
// 获取按钮的父控件
let parentControl = await model.getButton("Button").parent();
if (parentControl) {
    console.log("找到父控件");
    await parentControl.highlight(1000);
}
# 获取按钮的父控件
parent_control = model.getButton("Button").parent()
if parent_control:
    print("找到父控件")
    parent_control.highlight(1000)

findControls(...conditions)

搜索方法可以让用户更精细、自主的在被测应用中使用用户给定的搜索条件匹配控件。可以理解为类似children的导航方法,会匹配目标控件中的所有子控件,并根据筛选条件进行筛选。因此可以直接传递一个筛选条件对象Criteria;另外还可以选择传入一个模型对象名称objectName,这会自动的使用该模型对象的识别属性作为筛选条件。

它以数组形式返回所有满足条件的控件对象。它能够根据传入的条件,实时获得应用上所有匹配的控件,并返回对象数组的Promise。该API可以用于有界面多个相似控件的时候,同时返回这一组对象,以便于对所有这些对应控件进行操作。

参数:

  • conditions: ConditionFilter[]类型,一个或多个筛选条件,可以是Criteria对象或模型对象名称。

返回值:

  • 异步的返回满足条件的控件对象数组。

使用示例

使用findControls()方法能够完成一些特殊情况下的任务:

场景一:批量操作

借助findControls()方法能够返回多个控件对象的特性,对于应用中的大量相似控件进行遍历操作。比如要选中名为"Dock"的容器控件中所有的复选框,可以通过如下脚本完成:

JavaScript
Python
// 查找Dock容器中所有的复选框控件
let checkboxes = await model.getGeneric("Dock").findControls({type: "CheckBox"});
console.log(`找到${checkboxes.length}个复选框`);
// 遍历并选中所有复选框
for (let index in checkboxes) {
    await checkboxes[index].check();
}
# 查找Dock容器中所有的复选框控件
checkboxes = model.getGeneric("Dock").findControls({"type": "CheckBox"})
print(f"找到{len(checkboxes)}个复选框")
# 遍历并选中所有复选框
for checkbox in checkboxes:
    checkbox.check()

场景二:按多个条件查找

可以传入多个筛选条件来精确定位控件:

JavaScript
Python
// 查找所有文本包含"Submit"的按钮
let buttons = await model.getPane("MainPane").findControls({
    type: "Button",
    "text~": "Submit"
});
# 查找所有文本包含"Submit"的按钮
buttons = model.getPane("MainPane").findControls({
    "type": "Button",
    "text~": "Submit"
})

all()

all()方法返回与当前控件的"标识属性"相匹配的所有同级控件数组,它简化了在测试中对同一层级多个控件的批量操作,尤其适用于处理表格、列表或一组按钮等场景。

返回值:

  • 异步的任意控件类型数组,具体类型取决于导航到的控件类型。没有导航到任何控件时返回空数组。

findControls()相比,all()更适用于无需特定筛选条件就要操作或检查当前控件同级所有控件的情况。

使用示例

假设在一个应用界面中有多个按钮排列在一起,您可能需要验证这些按钮的可用性或者依次进行点击。使用all()方法,可以轻松获取这一层级的所有按钮控件,并进行遍历操作。

JavaScript
Python
// 获取所有同级的按钮控件
let controls = await model.getButton("Normal").all();
console.log('控件数量:', controls.length); // 输出匹配到的控件数量
// 遍历所有控件并打印名称
for (let control of controls) {
    console.log(await control.name()); // 打印每个控件的名称
    // 执行其他操作...
}
# 获取所有同级的按钮控件
controls = model.getButton("Normal").all()
print("控件数量:", len(controls))  # 输出匹配到的控件数量
# 遍历所有控件并打印名称
for control in controls:
    print(control.name())  # 打印每个控件的名称
    # 执行其他操作...

在这个示例中,model.getButton("Normal").all()调用会返回当前模型下所有按钮控件的数组。然后,通过遍历这个数组,可以实现对每个按钮的文本打印和点击操作。这种方法简化了对同级多个控件的操作流程,提高了测试脚本的效率和可读性。

可能用到的类

可选属性字段PropertyName

下面是Qt自动化支持的属性名,可以用于property()waitProperty()以及checkProperty()方法中。

控件类型 属性字段
QtControl winId
QtControl enabled
QtControl rect
QtControl visible
QTab count
QTab activeIndex
QTab activeName
QTab tabNames
QTabItem text
QTabItem itemIndex
QButton text
QLabel text
QCalendar date
QTabBar count
QTabBar activeIndex
QTabBar activeName
QTabBar tabNames
QItemView data
QItemView selectedIndex
QItemView rowCount
QItemView columnCount
QMenuBarItem itemIndex
QMenuBarItem text
QMenu itemNames
QTable columnHeaders
QTable rowHeaders
QTable data
QTable rowCount
QTable columnCount
QTableItem value
QTableItem rowIndex
QTableItem columnIndex
QTree columnHeaders
QTree childCount
QTree columnCount
QTreeItem value
QTreeItem rowData
QTreeItem expandable
QTreeItem expanded
QTreeItem childCount
QTreeItem rowIndex
QTreeItem columnIndex
QTreeItem itemPath
QList data
QList selectedIndex
QList itemCount
QListItem value
QListItem itemIndex
QRadioButton checked
QCheckBox checkState
QComboBox selectedIndex
QComboBox selectedName
QDial value
QEdit value

除了以上列出来的属性外,各个控件还各自维护了一些特有的内部属性名,可以参考allProperties()方法的返回结果对象中的字段。

Rect类型

用于描述控件形状与位置信息的对象。在CukeTest中,所有控件都可以用一个矩形(Bounding Rectangle)来描述位置和形状信息,Rect对象包含以下属性:

  • x: number类型,相对坐标的水平像素;
  • y: number类型,相对坐标的垂直像素;
  • width: number类型,水平宽度,单位为像素;
  • height: number类型,垂直高度,单位为像素;

本篇只介绍了对象操作的基本概念和两个基类的方法,更多关于API介绍的文档可以继续阅读下一篇:基础类型对象的API

另外,具体每个操作和属性的定义都可以在模型管理器中找到,也可以打开CukeTest中自带的Qt自动化API库的类型文件——leanpro.qt的类型文件进行查找,具体方法可以查看更多API介绍一节的介绍。

Qt控件类型枚举类: QtControlType

包含了所有Qt控件类型,通常用于findControls()方法或是导航方法获取指定类型的控件对象。

JavaScript
enum QtControlType {
    Button = 'Button',
    Calendar = 'Calendar',
    CheckBox = 'CheckBox',
    ComboBox = 'ComboBox',
    Dial = 'Dial',
    ItemView = 'ItemView',
    ViewItem = 'ViewItem',
    Label = 'Label',
    List = 'List',
    ListItem = 'ListItem',
    GraphicsView  = 'GraphicsView',
    GroupBox = 'GroupBox',
    Window = 'Window',
    Menu = 'Menu',
    MenuItem = 'MenuItem',
    MenuBar = 'MenuBar',
    MenuBarItem = 'MenuBarItem',
    Edit = 'Edit',
    Pane = 'Pane',
    ProgressBar = 'ProgressBar',
    RadioButton = 'RadioButton',
    ScrollArea = 'ScrollArea',
    Slider = 'Slider',
    SpinBox = 'SpinBox',
    StatusBar = 'StatusBar',
    Tab = 'Tab',
    TabBar = 'TabBar',
    TabItem = 'TabItem',
    Table = 'Table',
    TableItem = 'TableItem',
    ToolBar = 'ToolBar',
    ToolBox = 'ToolBox',
    Tree = 'Tree',
    TreeItem = 'TreeItem',
    QuickItem = 'QuickItem',
    Custom = 'Custom',
    Application = 'Application',
}

控件筛选条件类: Criteria

JavaScript
Python
export interface Criteria {
    appName?: string,
    type?: QtControlType | string,
    className?: string,
    name?: string,
    index?: number,
    itemPath?: ItemPath,
    itemIndex?: number,
    text?: string,
    toolTip?: string,
    'text~'?: string,
    'name~'?: string,
    'toolTip~'?: string
}
class Criteria():
  appName: str
  type: str
  className: str
  name: str
  index: int
  itemPath: TypedDict
  itemIndex: int
  text: str
  toolTip: str

控件路径

用于描述列表项控件ListItem、树视图项控件TreeItem和表格单元格控件TableItem所处位置的对象。

JavaScript
export type ItemPath = (number | [number, number])[];

API调用技巧

同步方法与异步方法的区分技巧

IQContainer作为容器类,提供了获取对象的API,如下:

JavaScript
Python
export interface IQtContainer {
    parent: IQtContainer;
    getGeneric(...conditions: ConditionFilter[]): IQtControl;
    getWindow(...conditions: ConditionFilter[]): IQWindow;
    getButton(...conditions: ConditionFilter[]): IQButton;
    ......
    getGraphicsItem(...conditions: ConditionFilter[]): IQGraphicsItem
    getQuick(...conditions: ConditionFilter[]): IQuickItem
    ......
    getTable(...conditions: ConditionFilter[]): IQTable;
    getTableItem(...conditions: ConditionFilter[]): IQTableItem;
    getTree(...conditions: ConditionFilter[]): IQTree;
    getTreeItem(...conditions: ConditionFilter[]): IQTreeItem;
    getVirtual(...conditions: ConditionFilter[]): IVirtual;
}
class QtContainer():
  def getApplication(*conditions: List[Union[str, Dict]]) -> "QApplication"
  def getGeneric(*conditions: List[Union[str, Dict]]) -> "QtControl"
  def getWindow(*conditions: List[Union[str, Dict]]) -> "QWindow"
  def getButton(*conditions: List[Union[str, Dict]]) -> "QButton"
  def getCalendar(*conditions: List[Union[str, Dict]]) -> "QtControl"
  def getCheckBox(*conditions: List[Union[str, Dict]]) -> "QCheckBox"
  def getComboBox(*conditions: List[Union[str, Dict]]) -> "QComboBox"
  def getEdit(*conditions: List[Union[str, Dict]]) -> "QEdit"
  def getGraphicsItem(*conditions: List[Union[str, Dict]]) -> "QGraphicsItem"
  def getQuick(*conditions: List[Union[str, Dict]])-> "QuickItem"
  def getItemView(*conditions: List[Union[str, Dict]]) -> "QItemView"
  def getViewItem(*conditions: List[Union[str, Dict]]) -> "QItemViewItem"
  def getList(*conditions: List[Union[str, Dict]]) -> "QList"
  def getListItem(*conditions: List[Union[str, Dict]]) -> "QListItem"
  def getMenu(*conditions: List[Union[str, Dict]]) -> "QMenu"
  def getMenuBar(*conditions: List[Union[str, Dict]]) -> "QMenuBar"
  def getMenuBarItem(*conditions: List[Union[str, Dict]]) -> "QMenuBarItem"
  def getMenuItem(*conditions: List[Union[str, Dict]]) -> "QMenuItem"
  def getRadioButton(*conditions: List[Union[str, Dict]]) -> "QRadioButton"
  def getSpinBox(*conditions: List[Union[str, Dict]]) -> "QSpinBox"
  def getTab(*conditions: List[Union[str, Dict]]) -> "QTab"
  def getTabBar(*conditions: List[Union[str, Dict]]) -> "QTabBar"
  def getTabItem(*conditions: List[Union[str, Dict]]) -> "QTabItem"
  def getTable(*conditions: List[Union[str, Dict]]) -> "QTable"
  def getTableItem(*conditions: List[Union[str, Dict]]) -> "QTableItem"
  def getTree(*conditions: List[Union[str, Dict]]) -> "QTree"
  def getTreeItem(*conditions: List[Union[str, Dict]]) -> "QTreeItem"
  def getVirtual(*conditions: List[Union[str, Dict]]) -> "Virtual"
  def getPattern(*conditions: List[Union[str, Dict]]) -> "Pattern"

对于所有常用的控件"Widget",会有容器方法"get[Widget]"返回Widget对应的对象。例如Button控件,有提供getButton返回按钮的对象。

如果和上节中的对象操作API的返回值比较可以立刻注意到,获取对象API的返回值都是直接的数据类型,而不是Promise<T>,这代表这些都是同步方法,即不需要添加await关键字即可取到返回结果。而对象操作API中也有一个特别的同步方法,就是属于TableListTree控件的getItem方法,它也是对象操作API中唯一一个同步方法。

CukeTest中同步方法和异步方法从命名上就有区别,含get前缀的方法是同步方法,无需await关键字;而没有这个前缀的是异步方法,不加await的话只会返回一个Promise对象,加上await才是正确的返回值。

使用模糊匹配(正则匹配)获取控件

观察获取对象使用的Criteria对象可以发现里面有几个特殊属性,它们以~作为后缀,同时在Criteria对象中有同名的属性。
这些带~后缀的属性代表这些属性允许使用模糊匹配,或者也叫作正则匹配。可以在模型管理器中修改匹配模式。也可以在脚本中使用如下方式进行匹配:

JavaScript
Python
// 全文匹配
await model.getButton({"name": "数字5"}).click();
// 匹配所有name属性包含“数字”的控件
await model.getButton({"name~": "数字"}).click();
// 匹配所有name属性以“5”结尾的控件
await model.getButton({"name~": "数字$"}).click();
# 全文匹配
model.getButton({"name": "数字5"}).click()
# 匹配所有name属性包含“数字”的控件
model.getButton({"name~": "数字"}).click()
# 匹配所有name属性以“5”结尾的控件
model.getButton({"name~": "数字$"}).click()

这些属性都可以在模型管理器里面通过“[复制节点属性]”功能直接生成,不一定需要自己手动构造。

更多API介绍

更多的控件的操作和属性的帮助可以在模型管理器中找到,或者在拖拽模型文件生成的脚本中,按住Ctrl键点击脚本头部的leanpro.qt引用跳转到类型定义文件。

results matching ""

    No results matching ""