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 });
- 从其他 Selector 定位的 静态元素快照 中派生
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)代码,正如 上节 中的例子, click
和 expect
都直接接收 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-NC 授权。
欢迎转载,请保留原文链接 https://www.lfhacks.com/tech/testcafe-selector/ ,且不得用于商业用途。