listview qt 选中内容_演练: 操作Qt应用中的QListView

演练:操作Qt应用中的List

背景

需要针对Qt的ListView组件开发的列表应用进行操作和自动化测试。ListView通常用于含有大量可选项的窗口,比如文件列表、清单等等。以下我们对QListView控件简称为List。

目标

本次自动化的目标是实现对List组件自动化的全面了解,使用CukeTest提供的方法,可以快速的完成自动化。而简单的了解Qt列表的实现方式、以及行为模式,有助于自动化其它表现出列表行为的控件(比如非标准的自绘制列表、下拉框中的选项等等)。

本次演练由浅入深的对List的自动化操作有个全面的认知。

直接对列表项ListItem的操作;

掌握列表的滚动操作;

掌握列表内容的检索与操作;

本次用于测试用的被测应用为Qt SDK中提供的Demo应用——FetchMore,它演示了一个简化的文件浏览工具,可以输入路径来检索路径下的文件/文件夹,界面如下:

d568a2b7a5774249802df978a6d87b88.png

为了便于管理和理解,以下将不同的操作归类为三个场景:

操作目标选项:单击目标项

选中目标项

滚动列表;使用滚动条按钮进行翻页

使用滚动条的方法进行滚动和翻页

使用列表方法进行滚动

搜索后选中目标 在搜索框中输入内容;

判断搜索结果中是否存在目标选项;

实际操作

由于Qt应用中,列表中的未显示的选项不会被直接识别到也无法被操作,也就是说,在模型管理器识别了应用中的选项,当它被滚动到不可见区域也会因为被隐藏而无法检测到。因此对于动态变化的选项作为识别对象是不理想的,CukeTest的建议与表格控件Table、树控件Tree一样,选择列表(List)这一父控件容器作为识别对象,通过容器上提供的方法操作和获取子控件。

建立项目

编辑剧本文件

新建项目后,按照行为驱动测试的最佳实践,首先编写剧本(*.feature文件),编写场景和步骤,然后生成代码模版,剧本文件可以切换到文本模式进行修改。

# language: zh-CN

功能: QtList自动化

针对Qt的ListView组件开发的列表窗口进行操作和自动化测试。

场景: 操作目标选项

假如单击目标项"."

假如选中目标项"."

场景: 滚动列表

假如使用列表方法进行滚动

当使用滚动条按钮进行翻页

当使用滚动条的方法进行滚动和翻页

场景: 搜索后选中目标

当在搜索框中输入路径"C:/Program Files"

那么判断搜索结果中是否存在目标项"WindowsPowerShell"

完成剧本文件和代码模版以后如下:

f6a262be9e18e604249ef78bb066ad97.png

识别控件

由于可访问的ListItem项会随着滚动操作而动态变化,所以我们就没必要识别到具体的ListItem控件,而只要识别到其父控件也就是外部的List控件即可,因此我们可以识别一下列表第一项的.,因为后面的操作中也会用到。如下红框所示的复选框可以不勾选:

45faaef657a363c2edd135738833b0dd.png

没有勾选的测试对象在点击“添加”按钮时不会添加到模型中。

接着再识别顶部的路径输入框,识别完成

执行完上述操作后,模型文件就可以满足对List和ListItem项的简单操作了,然而在后续进一步的操作中偶尔也需要添加新的控件到模型文件中。

编写脚本

单击目标项/选中目标项

单击与选中目标项的区别在于,前者使用click()也就是控件的鼠标点击方法,对于列表选项来说也可以选中,但存在隐患,具体是什么隐患我们后面再说;而选中目标项使用的是列表选项提供的select()方法,相对来说更可靠。

因为需要单击目标选项,我们复习一下click()方法的调用方法,click()方法可以传入三个参数,分别是点击的相对横坐标x、纵坐标y,以及点击的鼠标按钮: 1为左键,2为右键。所以我们调用点击控件,可以直接调用click(0,0,1)。而如果需要右键,只需要将click(0,0,1)改为click(0,0,2)即可。但这就结束了吗,不,我们上文中提到这种选中对列表项来说存在隐患,是因为CukeTest中调用click(0,0,1)等同于缺省调用点击方法,即不传任何参数的click()调用,默认是左键点击控件正中心。由于点击的是控件中心,而列表项有时候会出现只有不到一半的部分出现在可视范围内(如下图所示),这就有可能会导致点击操作落空,从而导致操作失败。

97c827a1899f2f20570eb76fbcccfaa3.png

脚本如下:

Given("单击目标项{string}", async function (itemName){

let targetItem = await model.getListItem(itemName);

await targetItem.click(1, 1, 1);

await Util.delay(500);

let isFocused = await targetItem.focused();

assert.strictEqual(isFocused, true, `Target item ${itemName} is not selected!`);

});

Given("选中目标项{string}", async function (itemName){

let targetItem = await model.getListItem(itemName);

await targetItem.select();

await Util.delay(500);

let isFocused = await targetItem.focused();

assert.strictEqual(isFocused, true, `Target item ${itemName} is not selected!`);

});

在点击/选中操作后的延时是考虑到应用的响应时间而加入的,否则获取目标控件选中状态在应用响应之前就完成的话,结果会是未选中。

滚动列表

由于Qt目前不支持使用通用控件方法vScroll()和hScroll进行垂直和水平滚动,但我们还可以采用其它的方法可以进行滚动,例如:模拟按键(方向键和PageUp/PageDown键)进行滚动和翻页、使用滚动条按钮进行翻页、使用drag&drop进行拖拽/滑屏操作。但这里我们仅介绍三种适合滚动列表视图的方式:

使用滚动条控件滚动

使用滚动条控件的方法滚动

使用列表控件的方法滚动

这里使用滚动条的按钮滚动的方式考虑的场景————只有一条垂直的滚动条,比较简单。有些时候,还会出现水平的滚动条,这个时候就要区分滚动条进行操作。由于Qt的组件唯一标识符较少,所以通常是在识别时加上index属性。因正因为可能出现的这种情况,CukeTest推荐的方法还是使用列表控件自身提供的滚动方法进行滚动。更多与滚动操作相关的内容可以点击如何滚动界面查看。

1. 使用滚动条控件滚动

滚动条也是一类可以操作的控件,识别以后甚至能看到它的完整结构,有上下滚动的按钮、有上下翻页的按钮,以及供拖拽的滚动滑块,这里将滚动条中的这五个控件全部侦测添加到模型管理器中,如下图所示:

ef16ad88e935fb50321fe5013e8d66f5.png

接着就可以使用click()方法点击这些控件完成滚动了。

需要注意的是,由于识别时应用中没有水平滚动条,因此仅能识别到唯一的一条滚动条就是垂直滚动条,如果应用后来出现了水平滚动条,则滚动条的控件操作可能会错误的发送到水平滚动条上,下面一个方法也一样。因此在这种情况可能发生的前提下,最好使用列表控件List自带的方法进行滚动。

```js

When("使用滚动条按钮进行翻页", async function () {

// 在模型文件中添加滚动条的测试对象

let lineUp = model.getButton("Line up");

let lineDown = model.getButton('Line down')

let pageUp = model.getButton('Page up');

let pageDown = model.getButton('Page down');

await lineDown.click();

await Util.delay(1000);

await pageDown.click();

await Util.delay(1000);

await lineUp.click();

await Util.delay(1000);

await pageUp.click();

await Util.delay(1000);

});

##### 2. 使用滚动条控件的方法滚动

上文提到,滚动条也是一种控件,叫做`ScrollBar`控件,因此它也提供了相当一部分的方法供用户调用,我们就可以通过调用这些方法来控制滚动条,从而完成相应页面的滚动,这里使用的是`lineUp()`、`lineDown()`、`pageUp()`和`pageDown()`方法。

```js

When("使用滚动条的方法进行滚动和翻页", async function () {

let scrollbar = model.getScrollBar('ScrollBar');

await scrollbar.lineDown()

await Util.delay(1000);

await scrollbar.lineUp();

await Util.delay(1000);

await scrollbar.pageDown();

await Util.delay(1000);

await scrollbar.pageUp();

await Util.delay(1000);

});

3. 使用列表控件的方法滚动

列表控件List提供的滚动方法有以下三个: scrollToTop()、scrollToBottom()与scrollTo()方法,分别能够滚动到顶部、滚动到底部以及滚动到指定位置,下面的脚本中演示了三种滚动的调用方式。其中ScrollTo()方法会滚动到目标项的位置。

Given("使用列表方法进行滚动", async function (){

let targetList = model.getList("List");

let count = await targetList.itemCount();

await targetList.scrollToBottom();

await Util.delay(1000);

await targetList.scrollTo(count);

await Util.delay(1000);

await targetList.scrollToTop();

await Util.delay(1000);

});

搜索后选中目标选项

本次演练中还有两个操作,一个是搜索框的输入,另一个是在搜索结果中检索是否有满足条件的项。

搜索框的输入

应用中的搜索框本质上是一个文本输入框,因此可以使用set()方法输入指定字符串,这个应用会自动的搜索,如果是需要另外输入ENTER回车键信号触发搜索的,可以通过在输入值后追加“~”符号来输入回车键,更多特殊按键的信息可以查阅附录:输入键对应表。

When("在搜索框中输入路径{string}", async function (path){

let searchBox = model.getEdit("Directory:");

await searchBox.click();

await searchBox.set(path);

assert.equal(await searchBox.value(), path);

});

检索结果

前面提到过,由于滚动视窗的原理,只能获取到当前可见的ListItem项,因此检索搜索结果,需要一边滚动一边判断当前页中是否有目标选项,如果要手动编写这样的脚本显得有点儿难度,因此CukeTest为列表控件提供了findItem()方法,可以在列表中自动的搜索第一个满足条件的列表项ListItem对象,当然findItem()方法还有更高级的用法,这里就先不介绍了。

由于返回了目标ListItem对象,因此我们直接调用该对象上的select()方法就可以完成选中操作了。

Then("判断搜索结果中是否存在目标项{string}", async function (itemName){

let targetItem = await model.getList('List').findItem(itemName);

await targetItem.select();

await Util.delay(3000);

});

添加Hook

完成了以上脚本的编写,几乎就完成了所有的工作,但是这里为了方便调试,将被测应用的启动和关闭也加入到脚本中,这样就不用手动的去做这些事情了,这也是生命周期(Hook)的工作了。通常来说,这些准备工作也可以写到场景步骤中,但是因为场景中的步骤通常是服务于业务流程与逻辑的,因此加入这些准备工作的脚本可能会有些不合适。

但是从另一个角度来说,如果过分依赖Hook脚本,可能会导致非专业人员的困扰,因为非专业人员主要是通过剧本文件(.feature文件)来了解步骤定义,而Hook是不会显示在剧本文件中的,可能会带来阅读上的障碍。

首先,常用的Hook可以在工具箱的Cucumber栏目中看到,这里我们拖拽BeforeAll和AfterAll这两个Hook到脚本编辑器中,BeforeAll会在任何操作运行前执行并且只会执行一次,比如运行项目、运行剧本、运行场景或者是运行步骤前;AfterAll与BeforeAll刚好相反,是在所有操作都完成后才会运行,AfterAll可以用来执行关闭被测应用的操作,这里为了观察操作结果将相关的脚本注释掉了,读者可以反注释掉,只在其中保留了一个恢复CukeTest客户端的脚本。脚本如下:

const cuketest = require('cuketest');

const path = require('path');

let pid = 0;

BeforeAll(async function (){

pid = await Util.launchProcess(path.join(

__dirname,

'..',

'fetchmore.exe'

));

await Util.delay(1000);

cuketest.minimize(); // CukeTest最小化

})

AfterAll(async function (){

// await Util.stopProcess(pid); // 在调试时可以注释这一行观察结束后的现象

cuketest.restore(); // CukeTest还原

})

以上就是Qt列表应用的自动化,完整的代码可以前往Github查看CukeTest Demos的Repo。


版权声明:本文为weixin_31910545原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。