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 除了页面 中的元素之外,还能获取 信息,比如页面 Selector('title').textContent