前端测试工具 TestCafe 中的 Selector

发表时间 ·

TestCafe 中的 Selector 是功能丰富的模块,封装了许多查找元素的功能,并且提供了额外的函数式查找、而且具有获取抽象动态元素和静态元素状态的功能,

上节 所说,测试代码运行在服务侧的 Test Controller 中。当需要在测试代码中指代客户端侧的 DOM 元素时,需要一种通信实体,连接服务侧和客户端侧的代码。Selector 就是其中的一种,另外还有 ClientFunction.

与常规的测试工具对比

首先回顾 selenium 中寻找页面元素的方法:

#selenium

driver.find_element_by_id()
driver.find_element_by_name()
driver.find_element_by_xpath()
driver.find_element_by_link_text()
driver.find_element_by_partial_link_text()
driver.find_element_by_tag_name()
driver.find_element_by_class_name()
driver.find_element_by_css_selector()

puppeteer 中寻找页面元素的方法(使用了类似jQuery的形式):

//puppeteer

page.$()
page.$x()
page.$$()
page.waitFor()
page.waitForSelector()
page.waitForXPath()

上面两种语言里寻找元素的方法都是通过函数的方法查找,有下面一些缺点:

  • 往往一种方法不足以精确定位到意向中的元素,需要多种函数组合使用,比如:这篇文章里讲的做法
  • 仅能用来定位DOM元素,需要编写函数进一步获取元素的信息,比如这篇文章里讲的做法
  • 函数返回类型不同,有的是单个元素(比如 page.$() ),有的是 array(比如 page.$x() );
  • 查找到多个元素后的筛选等操作,需要编写代码;
  • 需要记忆多种方法函数的名称。

在 TestCafe 中,上面这些进一步操作,都由 Test Controller 使用 Selector 实体来完成,将许多待处理的事物提供为API,成为开箱即用的功能。

基本用法

基本用法如下面代码,已经简单得几乎不能再简化了:

import { Selector } from 'testcafe';

const article = Selector('.article-content');

生成了 Selector 实体后(即上述代码中的 article),可以作为参数传递给 服务端 代码(动作或者断言)

await t.click(article);
await t.expect(article.scrollHeight).eql(1800);

创建 Selector 的几种方法

可以通过多种方法创建 Selector,如下:

  • jQuery 形式的字符串
Selector('img');
Selector('#submit-button');
Selector('.button');
  • 运行在浏览器端的函数片段,这个函数必须返回 DOM
Selector(id => {
    return document.getElementById(id);
});
  • 从其他 Selector 实例中派生
Selector(Selector('.cta-button'), { visibilityCheck: true });
const visibleTopMenu = Selector(await Selector('#top-menu')(), {
        visibilityCheck: true
    });

动态和静态地获取元素

使用 Selector 获取元素信息,还有动态和静态两种,这是之前的工具 selenium 和 Puppeteer 所不具备的。

动态方法获取

这种模式下,元素的信息是异步获得的。创建 Selector 实体时,Selector 并不是对元素的引用,而是一种在服务侧代码中的表达(representation)。创建 Selector 的代码仅仅返回一个 Promise,当需要用到该元素时,使用 await 关键字才能得到元素的状态信息。比如:

import { Selector } from 'testcafe';

const windowsInput = Selector('#windows');    // 服务侧代码的表达,并不立即获取元素信息

test('Obtain Element State', async t => {
    await t.click(windowsInput);

    const windowsInputChecked = await windowsInput.checked; // 发出 click 动作后,才检查状态
});

动态的 Selector 实体可以直接传递给服务端的动作(action)或者断言(expect)代码,正如 上节 中的例子, clickexpect 都直接接收 Selector 实体及其对外api

获取元素静态信息

如果在创建 Selector 实体时就使用 await 关键字 将 Selector 返回的 Promise resolve 出结果,就能直接得到 DOM 节点的静态信息(SnapShot),这些信息通过 selector 实体的属性或者方法获得。比如下面的方法获取元素的 computed style:

import { Selector } from 'testcafe';

test('DOM Node Snapshot', async t => {
    const logo = await (Selector('#logo'))();

    console.log(logo.style);
});

Puppeteer 获取页面元素的样式 的方法相比,上面的代码非常简单易读。

Selector 返回元素的数量

Selector 定位到的元素有可能是多个、一个、0 个等,可以通过 Selector 的两个属性来确定:

import { Selector } from 'testcafe';

test('properties', async t => {
    const logo = Selector('#logo');

    console.log(logo.count);    // 数量
    console.log(logo.exists);   // 至少有1个时为 true
});

Selector 获取节点Node信息

正如 上节 所说,Selector 能获取节点的信息,而且封装为方便使用的 api,比如:

适用于所有类型节点( 节点类型列表 ):

node.childElementCount  //子元素类型
node.childNodeCount  //子节点类型
node.hasChildElements  //是否有子元素
node.hasChildNodes  // 是否有子节点
node.nodeType   //节点类型
node.textContent   //节点包含的文字
node.hasClass(className)  // 是否包含某种 class 名称

Selector 获取元素Element信息

对于DOM元素,除了上面普适 Node 的方法以外,还有下面 api 供使用:
  • 外观状态
elem.checked    // 复选框和单选框的状态
elem.focused    // 当前是否位于焦点
elem.selected   // 下拉框的状态
elem.selectedIndex  //下拉框所选中的选项的序号
elem.visible    // 是否显示状态
  • 属性信息
elem.attributes    // 全部 attribute,等效于 getAttribute
elem.classNames    // 全部的 class 名称
elem.id    // 元素的 id
elem.innerText    // 元素包含的文字
elem.style    // 元素的 computed style
elem.tagName    // 元素名称,比如 img 等
elem.value    // input 元素的值
  • 尺寸信息
elem.boundingClientRect   //返回元素的宽高和位置信息
elem.clientHeight    //返回 clientHeight,下同
elem.clientLeft
elem.clientTop
elem.clientWidth
elem.namespaceURI
elem.offsetHeight
elem.offsetLeft
elem.offsetTop
elem.offsetWidth
elem.scrollHeight
elem.scrollLeft
elem.scrollTop
elem.scrollWidth

上面的尺寸信息参考 元素的属性

进一步筛选元素

如果需要从 selector 初步得到的元素集合中进一步筛选元素,可以使用函数式的 api,仍然返回 selector 类型。

  • 按位置顺序过滤
Selector('label').nth(n)   // 返回前一次筛选结果集中的第 n 个元素,n=0表示第一个,n=-1表示最后一个
  • 按包含文字过滤
Selector('label').withText('关键字')   // 筛选出真包含关键字的元素
Selector('label').withText(/re/)   // 筛选出符合 re 正则表达式的元素
Selector('label').withExactText('关键字')   // 筛选出全等关键字的元素
  • 按元素属性过滤
Selector('label').withAttribute('attrName')   // 筛选出包含attrName属性的元素
Selector('label').withAttribute('attrName', 'attrValue')   // 筛选出 attrName = attrValue 的元素
  • 根据是否可见过滤
Selector('label').filterVisible()   // 筛选出可见元素
Selector('label').filterHidden()   // 筛选出不可见的元素
  • 按元素样式过滤
Selector('label').filter('selected')   // 筛选 class=selected 的元素
  • 按元素满足条件过滤
Selector('label').filter((node, idx)=>{})   // 筛选符合特定函数描述的元素
  • 在元素所在 DOM 树中查找
Selector('label').find('selected')   // 从所有后代节点 node 中,筛选class=selected 的 node
Selector('label').parent('selected')   // 从所有父代 node 中,筛选class=selected 的 node
Selector('label').child('selected')   // 从所有后代元素 element 中,筛选class=selected 的元素
Selector('label').sibling('selected')   // 从所有同级元素 element 中,筛选class=selected 的元素
Selector('label').nextSibling('selected')   // 从所有同级后续元素 element 中,筛选class=selected 的元素
Selector('label').prevSibling('selected')   // 从所有同级前置元素 element 中,筛选class=selected 的元素

获取页面 title

除了页面 <body> 中的元素之外,还能获取 <head> 信息,比如页面 <title>

Selector('title').textContent

相关文章   欢迎到 留言板 写下你的看法。
  本页面内容采用 署名协议 CC-BY 授权。欢迎转载,请保留原文链接