testcafe (官网 ) 是一个基于 nodejs 的自动化测试框架,优点就先不多说了,我们直接进入正题!
testcafe 是一个 npm 包,它可以使用 npm 或 yarn 包管理器进行安装,这里我们使用 yarn 进行安装(因为它很快)
如果你的机器上没有安装 yarn,那么你可以使用 npm 来安装 yarn 😊
在命令行中运行以下命令
1 yarn global add testcafe
这样, testcafe 就安装在你本机上啦
我们新建一个目录,用于练习我们的自动化测试框架 testcafe
1 2 3 cd ~/wwwmkdir testcafe && cd testcafe
然后新建一个名为 myFirstTestcase.js
文件,用于编写我们的自动化测试脚本
如果你安装了 vscode,你还可以使用 code myFirstTestcase.js
来创建并编辑它
需要在 vscode 的控制它中输入 install command
来启用 code
命令
将以下内容粘贴到 myFirstTestcase.js
中
1 2 3 4 5 6 7 8 9 10 11 12 import { Selector } from 'testcafe' ;fixture ('Getting Started' ) .page ('http://devexpress.github.io/testcafe/example' ) test ('My first test' , async t => { });
到这里,我们的第一个 test script 就写好了,接下来我们来运行它看看
我们回到命令行中,执行以下命令来运行一个测试脚本
1 testcafe chrome myFirstTestcase.js
这个命令中,
testcafe
是我们使用包管理工具全局安装的依赖,testcafe 是它的可执行程序
chrome
是我们的测试平台,安装在本机的浏览器,也可以是 safari
firefox
等
myFirstTestcase.js
是我们编写的测试脚本
稍等片刻,我们的浏览器就会被自动打开然后运行测试脚本,最后浏览器被自动关闭,在终端上留下了测试结果。
刚才我们的测试脚本只是简单的打开了一个页面,它并没有执行任何动作。
在接下来的几个例子中,你可以打开测试目标网站 http://devexpress.github.io/testcafe/example 这个页面,然后打开控制台,观察其 DOM 结构,并试着模拟脚本的操作,以便方便的理解脚本的内容。
接下来我们简单的写两个动作,用来对页面进行操作。 打开 myFirstTestcase.js
,在 test
方法的回调函数中添加以下内容
1 2 3 4 5 6 test('My first test', async t => { - // Test code + await t + .typeText('#developer-name', 'John Smith') + .click('#submit-button'); });
t 是我们的测试用例的控制器,它又很多方法,在上面的例子中,我们调用了它的 typeText
和 click
两个方法,其中
typeText
方法用来键入文本,它接受两个参数:第一个参数是 selector 选择器(它的语法类似 jQuery 选择器的语法);第二个参数是你要键入的文本;
click
方法用来模拟鼠标点击,它接受一个参数,是一个 selector 选择器
有关这个控制器的更多方法,请先参考 TestCafe 官方 API 手册 (它是一个英文文档,稍后我会整理出这个文档的中文手册在本页下方)
这段代码的作用是:
首先找到 id 为 developer-name
的标签,输入值 ‘John Smith’
然后点击 id 为 submit-button
的按钮
上面一小节我们对页面进行了交互,接下来我们想知道页面进行了什么反应,也就是观察的页面变化。
我们对测试脚本进行修改
1 2 3 4 5 6 7 8 9 10 test('My first test', async t => { await t .typeText('#developer-name', 'John Smith') .click('#submit-button'); + + const articleHeader = await Selector('.result-content').find('h1'); + + // 获取 article header 的内容文本 + let headerText = await articleHeader.innerText; });
在我们点击页面上的“提交”按钮后,会打开一个“谢谢”页面。 如果我们要访问页面上的 DOM 元素,可以使用测试脚本顶部导入的 Selector 方法
L6,我们声明了一个 articleHeader
变量,这个变量的值是根据选择器 .result-content > h1
找到的 DOM 元素
L9,我们又声明了一个 headerText
变量,它的值是我们获取到的 DOM 元素的 innerText
属性(这个属性的值是 DOM 元素的内容文本)
我们拿到需要判断的值后,就可以对测试用例进行断言了。 它到底能否正确执行测试用例并输出我们期望的结果?
我们使用测试用例控制器的 t.expect()
方法来进行断言, 将测试脚本改写为以下内容
1 2 3 4 5 6 7 8 9 10 11 12 test('My first test', async t => { await t .typeText('#developer-name', 'John Smith') .click('#submit-button'); - const articleHeader = await Selector('.result-content').find('h1'); - - // 获取 article header 的内容文本 - let headerText = await articleHeader.innerText; + // 使用断言方法来判断我们获取到的值与我们期望的值是否相等 + .expect(Selector('#article-header').innerText).eql('Thank you, John Smith!'); });
L11 expect()
是一个 BDD(行为驱动开发)风格的断言方法, 它接受一个参数:需要进行断言的变量; 它返回一个断言类实例对象,后跟一个断言方法。
eql()
是一个断言方法,用于判断断言实例与期望值是否严格等于,它接受一个参数:期望值; 该类断言方法会在打印出相应的测试报告,如果相等则返回 pass,否则抛出一个 AssertionError
到这里,我们的第一个自动化测试脚本就完成了,如果你没有跑通的话,请检查一下你的测试脚本是否与以下内容一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { Selector } from 'testcafe' fixture ('Getting Started' ) .page ('http://devexpress.github.io/testcafe/example' ) test ('My first test' , async t => { await t .typeText ('#developer-name' , 'John Smith' ) .click ('#submit-button' ) .expect (Selector ('#article-header' ).innerText ).eql ('Thank you, John Smith!1' ) })
TestCafe 测试必须将一些测试组织起来,测试组 (Fixtures) 就像是一个文件夹,将同一类的测试包裹起来。
一个测试文件可以包含很多测试组。
要声明一个测试组,使用 fixture
方法
1 2 3 4 5 fixture ( fixtrueName )fixture `fixtureName`
参数
类型
描述
fixtureName
string
测试组的名称
它返回一个测试组对象,可以接测试组方法,有关这些方法,请参考下方相关 API。
请注意,测试方法 test
必须放在测试组声明后面。
你可以使用 test
方法声明一个测试用例。
参数
类型
描述
testName
string
测试用例的名称
fn
Function
包含测试代码的异步函数
t
Object
测试用例的测试控制器
1 2 3 4 5 6 7 8 9 fixture ('MyFixture' )test ('Test1' , async t => { }) test ('Test2' , async t => { })
TestCafe 测试在服务器端执行。
你可以使用测试动作 来操纵测试的网页。
要确定页面元素的状态或从客户端获取任何其他数据,请使用选择器 和客户端方法。
要检查页面状态是否与预期页面状态匹配,请使用断言 。
如果要使用测试动作 、断言 、或者等待 , 必须挂在在测试控制器上.
您可以指定 fixture 中所有测试启动时的 web 页面。
1 2 fixture.page ( url ) fixture.page `url`
类似的,你也可以为特定的测试用例置顶一个起始的 web 页面。
1 2 test.page ( url ) test.page `url`
参数
类型
描述
url
string
指定起始页面的 URL
1 2 3 4 5 6 7 8 9 10 11 fixture ('MyFixture' ) .page ('http://devexpress.github.io/testcafe/example' ) test ('Test1' , async t => { }) test.page ('http://devexpress.github.io/testcafe/blog/' ) ('Test2' , async t => { })
如果没有指定起始页面,则打开 about:blank
页面。
你也可以使用 file://
协议或相对路径
1 2 fixture ('MyFixture' ) .page ('file:///user/my-website/index.html' )
1 2 fixture ('MyFixture' ) .page ('../my-project/index.html' )
你可以使用键值对的方式为测试指定元数据,并在测试报告中展示这些元数据。
要定义元数据,请使用 meta
方法。
1 2 3 4 5 6 7 8 fixture .meta ('fixtureID' , 'f-0001' ) .meta ({ author : 'John' , creationDate : '05/03/2018' }) test .meta ('testID' , 't-0001' ) .meta ({ severity : 'critical' , testedAPIVersion : '1.0' }) ('MyTest' , async t => { })
你可以使用 custom reporter 来访问测试元数据。
报告器的 reportFixtureStart
和 reportTestDone
方法来访问测试元数据
在每个测试组开始前、结束后,都可以执行特定的方法,它叫做钩子。如果一个测试在多个浏览器中运行,则在每个浏览器中都会执行指定钩子。
你可以指定测试组开始前、结束后执行的钩子
1 2 3 4 5 6 7 8 fixture `My fixture` .page `http://example.com` .before ( async ctx => { }) .after ( async ctx => { });
在每次测试的开始前、结束后,也有相应的钩子。
1 2 3 4 5 fixture.beforeEach ( fn (t) ) fixture.afterEach ( fn (t) ) test.before ( fn (t) ) test.after ( fn (t) )
如果指定了 test.before()
或 test.after()
, 那么它会覆盖 fixture.beforeEach()
或 fixture.afterEach()
的钩子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 fixture `My fixture` .page `http://example.com` .beforeEach ( async t => { }) .afterEach ( async t => { }); test .before ( async t => { }) ('MyTest' , async t => { }) .after ( async t => { });
通过使用测试的上下文对象,来共享在钩子和测试代码之间的变量。
测试的上下文对象为 t.ctx
,使用它来代替全局变量。
1 2 3 4 5 6 7 8 9 10 11 12 fixture `Fixture1` .beforeEach (async t => { t.ctx .someProp = 123 ; }); test ('Test1' , async t => { console .log (t.ctx .someProp ); }) .after (async t => { console .log (t.ctx .someProp ); });
需要注意的是,每个测试都有自己的测试上下文对象。每次测试开始时,t.ctx
都是一个空的对象。
在测试组的钩子中,回调函数的参数为 ctx
,即为测试的上下文对象,在测试代码中可以使用 t.fixtureCtx
来访问它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 fixture `Fixture1` .before (async ctx => { ctx.someProp = 123 ; }) .after (async ctx => { console .log (ctx.newProp ); }); test ('Test1' , async t => { console .log (t.fixtureCtx .someProp ); }); test ('Test2' , async t => { t.fixtureCtx .newProp = 'abc' ; });
你在写测试用例时,可以跳过某个测试用例或者只执行某个测试用例
1 2 3 4 5 6 7 8 9 10 fixture.skip `Fixture1` ; test ('Fixture1Test1' , () => {});test ('Fixture1Test2' , () => {});fixture `Fixture2` ; test ('Fixture2Test1' , () => {});test.skip ('Fixture2Test2' , () => {}); test ('Fixture2Test3' , () => {});
1 2 3 4 5 6 7 8 9 10 11 fixture.only `Fixture1` ; test ('Fixture1Test1' , () => {});test ('Fixture1Test2' , () => {});fixture `Fixture2` ; test ('Fixture2Test1' , () => {});test.only ('Fixture2Test2' , () => {}); test ('Fixture2Test3' , () => {});
选择器是标识测试中的网页元素的方法。选择器 API 提供了选择页面上的元素并获取其状态的方法和属性。
要从 testcafe
模块导入 Selector
构造函数,调用此构造函数并将 CSS 选择器字符串作为参数传递。
1 2 3 import { Selector } from 'testcafe' ;const article = Selector ('.article-content' );
Selector
参数语法类似于 jQuery 选择器语法,他们都是使用的 CSS选择器 语法。
在上面的例子中,我们选择了一个 class 为 atricle-content
的元素。
然后我们就可以使用这个选择器对对元素进行操作了。
或者在断言方法中使用它
1 await t.expect (article.scrollHeight ).eql (1800 )
甚至还可以编写一个匹配多个页面元素的选择器,然后按文本、属性等对它们进行过滤。
下面这两个例子首先选择了一个 class 为 radio-button
的元素,并且其中的文本为 “Windows”,第二个是含有属性为 selected
的元素。
1 2 const windowsRadioButton = Selector ('.radio-button' ).withText ('Windows' );const selectedRadioButton = Selector ('.radio-button' ).withAttribute ('selected' );
如果需要在 DOM 树中查找特定元素,可以使用选择器 API 的搜索方法 查找它。
1 const buttonWrapper = Selector ('.article-content' ).find ('#share-button' ).parent ();
1 Selector ( init [, options] )
参数
类型
描述
init
Function | string | Selector | Snapshot | Promise
标识要选择的 DOM 节点
options
(可选的)
Object
选项, 有关选择器选项
使用一个 CSS 选择器
1 2 3 import { Selector } from 'testcafe' ;const usernameInput = Selector ('#username' );
使用一个在客户端执行的函数,必须返回一个 DOM node
,一组 DOM nodes
, NodeList
, HTMLCollection
, null
或者 undefined
,或者 Promise.resolve() 为以上内容的方法
请注意,它不能使用一些外部变量,因为该方法是在浏览器中运行的
1 2 3 4 5 6 import { Selector } from 'testcafe' ;const element = Selector (() => { const storedElementId = window .localStorage .storedElementId ; return document .querySelector (storedElementId); });
一个 Selector
构造器
1 2 3 4 import { Selector } from 'testcafe' ;const ctaButton = Selector ('.cta-button' );Selector (ctaButton, { visibilityCheck : true });
本主题描述如何标识DOM元素并使用选择器获取关于它们的信息。
选择器可能返回一个、多个或者不存在匹配的元素。您可以使用一下属性来检查元素是否存在,或者确定匹配元素的数量。
属性
类型
描述
exists
boolean
如果匹配到元素则返回 true
count
number
选择器匹配的节点数量
1 2 3 4 5 6 7 8 9 10 11 12 13 import { Selector } from 'testcafe' ;fixture `Example page` .page `http://devexpress.github.io/testcafe/example/` ; test ('My test' , async t => { const osCount = Selector ('.column.col-2 label' ).count ; const submitButtonExists = Selector ('#submit-button' ).exists ; await t .expect (osCount).eql (3 ) .expect (submitButtonExists).ok (); });
注意,选择器的 getter 是异步的
你也可以获取选择器匹配元素的状态(size、position、classes 等)。有关元素的状态
1 2 3 4 5 6 7 8 9 10 11 import { Selector } from 'testcafe' ;fixture `My fixture` .page ('http://devexpress.github.io/testcafe/example/' ); const windowsInput = Selector ('#windows' );test ('Obtain Element State' , async t => { await t.click (windowsInput); const windowsInputChecked = await windowsInput.checked ; });
如果你需要获取整个 DOM 元素实例的状态,需要使用 await
来匹配
1 2 3 4 5 6 7 8 9 10 11 12 import { Selector } from 'testcafe' ;fixture `My fixture` .page `http://devexpress.github.io/testcafe/example/` ; test ('DOM Node Snapshot' , async t => { const sliderHandle = Selector ('#slider' ).child ('span' ); const sliderHandleSnapshot = await sliderHandle (); console .log (sliderHandleSnapshot.hasClass ('ui-slider-handle' )); console .log (sliderHandleSnapshot.childElementCount ); });
你还可以将选择器的返回值作为测试控制器操作的目标。DOM 节点快照同样可以。
1 2 3 4 5 6 7 8 9 10 11 12 import { Selector } from 'testcafe' ;fixture `My fixture` .page `http://devexpress.github.io/testcafe/example/` ; const label = Selector ('#tried-section' ).child ('label' );test ('My Test' , async t => { const labelSnapshot = await label (); await t.click (labelSnapshot); });
如果匹配到多个目标元素,那么只有匹配到的第一个元素将会被操作。
你可以将选择器的返回值作为断言的目标。
1 2 3 4 5 6 7 8 9 10 11 12 13 import { Selector } from 'testcafe' ;fixture `My fixture` .page `http://devexpress.github.io/testcafe/example/` ; test ('Assertion with Selector' , async t => { const developerNameInput = Selector ('#developer-name' ); await t .expect (developerNameInput.value ).eql ('' ) .typeText (developerNameInput, 'Peter' ) .expect (developerNameInput.value ).eql ('Peter' ); });
在测试期间,testcafe 会一次又一次的检查,等待目标元素变得可见,如果超时还不可见则不会通过测试用例。
如何设置超时
Testcafe 会输出有关于测试报告运行失败时的详细信息。
如果你尝试使用不匹配任何DOM元素的选择器时,测试失败并抛出一个错误。错误消息指示哪个选择器失败。
如果选择器返回多个DOM节点,你可以对它们进行筛选,以选择一个最终将由选择器返回的节点。选择器提供了根据索引、文本、属性等过滤DOM节点的方法。
参数
返回值类型
描述
nth(index)
Selector
根据匹配集中的索引查找元素。index
参数为从 0
开始的索引,如果指定为负数,则从末尾开始计算索引。
1 2 3 4 5 Selector ('ul' ).nth (2 )Selector ('div' ).nth (-1 )
参数
返回值类型
描述
withText(text)
Selector
创建一个选择器,该选择器过滤指定文本 的匹配项,选择包含此文本的元素。text
参数区分大小写。
withExactText(text)
Selector
创建一个选择器,该选择器过滤指定文本 的匹配项,将会严格匹配等于该文本的元素。text
参数区分大小写。
withText(regexp)
Selector
创建一个选择器,该选择器过滤指定正则表达式 的匹配项。
1 2 3 4 5 6 7 Selector ('label' ).withText ('foo' )Selector ('div' ).withText (/a[b-e]/ )
需要注意, withText
不仅会留下立即包含指定文本的元素,还会留下它的祖先元素。比如
1 2 3 <div class ="container" > <div class ="child" > some text</div > </div >
1 2 Selector ('div' ).withText ('some text' )
参数
返回值类型
描述
withAttribute(attrName [, attrValue])
Selector
创建一个选择器,选择包含指定属性(或属性值)的元素。
参数
类型
描述
attrName
string | RegExp
属性名,区分大小写。
attrValue
(可选的)
string | RegExp
属性值,区分大小写。你可以省略它来匹配任何值。
1 2 3 4 5 6 7 8 Selector ('a' ).withAttribute ('href' )Selector ('img' ).withAttribute ('title' , '图片' )Selector ('img' ).withAttribute ('src' , /^https?:\/\/static\.mutoe\.com/ )
参数
返回值类型
描述
filterVisible()
Selector
创建一个选择器,只留下可见的元素。这些元素没有 display: none;
, visibility: hidden;
, width: 0;
或 height: 0;
这些属性
filterHidden()
Selector
创建一个选择器,只留下不可见的元素。这些元素含有 display: none;
, visibility: hidden;
, width: 0;
或 height: 0;
这些属性
1 2 3 4 5 Selector ('div' ).filterVisible ()Selector ('input' ).filterHidden ()
参数
返回值类型
描述
filter(cssSelector)
Selector
创建一个选择器,根据 css 选择器的语法过滤。
filter(filterFn(node, idx), dependencies)
Selector
创建一个选择器,根据节点的状态过滤。
参数
类型
描述
cssSelector
string
css 选择器
filterFn(node, idx)
Function
该方法将会在浏览器环境中执行
dependencies
Function
需要传递到客户端的方法,否则调用时不会出现在浏览器上下文环境中
node
Element
当前元素的 DOM node
idx
number
当前 DOM node 所处的索引
1 2 Selector ('li' ).filter ('.active' )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { Selector } from 'testcafe' fixture `Example page` .page `http://devexpress.github.io/testcafe/example/` test ('My test' , async t => { const secondCheckBox = Selector ('input' ) .withAttribute ('type' , 'checkbox' ) .nth (1 ) const checkedInputs = Selector ('input' ) .withAttribute ('type' , 'checkbox' ) .filter (node => node.checked ) const windowsLabel = Selector ('label' ) .withText ('Windows' ) await t .click (secondCheckBox) .expect (checkedInputs.count ).eql (1 ) .click (windowsLabel) })
如果所有的 DOM 都被过滤掉,filter()
选择器会返回 null
参数
返回值类型
描述
find(cssSelector)
Selector
返回当前元素匹配的所有子节点,并通过 css 选择器筛选它们。
find(filterFn, dependencies)
Selector
返回当前元素匹配的所有子节点,根据节点的状态过滤。
parent()
Selector
返回当前元素匹配的父节点。(第一个元素将会是最近的父元素)
parent(index)
Selector
返回当前元素匹配的父节点,并通过索引筛选它们。0
是最近的父元素,负数将会从根结点开始计算。
parent(cssSelector)
Selector
返回当前元素匹配的父节点,并通过 css 选择器筛选。
parent(filterFn, dependencies)
Selector
返回当前元素匹配的父节点,根据节点的状态过滤。
child()
Selector
返回当前元素匹配的子节点。(第一个元素将会是最近的子元素)
child(index)
Selector
返回当前元素匹配的子节点,并通过索引筛选它们。0
是最近的子元素,负数将会从根结点开始计算。
child(cssSelector)
Selector
返回当前元素匹配的子节点,并通过 css 选择器筛选。
child(filterFn, dependencies)
Selector
返回当前元素匹配的子节点,根据节点的状态过滤。
sibling()
Selector
返回当前元素匹配的兄弟元素。(第一个元素将会是最近的兄弟元素)
sibling(index)
Selector
返回当前元素匹配的兄弟元素,并通过索引筛选它们。以 0
开始,负数将会从末尾开始计算。
sibling(cssSelector)
Selector
返回当前元素匹配的兄弟元素,并通过 css 选择器筛选。
sibling(filterFn, dependencies)
Selector
返回当前元素匹配的兄弟元素,根据节点的状态过滤。
nextSibling()
Selector
返回当前元素之后匹配的兄弟元素。(第一个元素将会是之后最近的兄弟元素)
nextSibling(index)
Selector
返回当前元素之后匹配的兄弟元素,并通过索引筛选它们。以 0
开始,负数将会从末尾开始计算。
nextSibling(cssSelector)
Selector
返回当前元素之后匹配的兄弟元素,并通过 css 选择器筛选。
nextSibling(filterFn, dependencies)
Selector
返回当前元素之后匹配的兄弟元素,根据节点的状态过滤。
prevSibling()
Selector
返回当前元素之前匹配的兄弟元素。(第一个元素将会是之前最近的兄弟元素)
prevSibling(index)
Selector
返回当前元素之前匹配的兄弟元素,并通过索引筛选它们。以 0
开始,负数将会从末尾开始计算。
prevSibling(cssSelector)
Selector
返回当前元素之前匹配的兄弟元素,并通过 css 选择器筛选。
prevSibling(filterFn, dependencies)
Selector
返回当前元素之前匹配的兄弟元素,根据节点的状态过滤。
例子
1 Selector ('ul' ).find ('label' ).parent ('div.someClass' )
找到页面上的所有ul元素。然后,在每个已找到的ul元素中找到label元素。然后,为每个label元素找到一个匹配div.someClass选择器的父元素。
1 Selector ('.container' ).parent (1 ).nth (0 ).find ('.content' ).withText ('yo!' ).child ('span' )
这个例子做了以下事情:
找到 .container
元素的第二个父元素(父元素的父元素)
选择匹配集中的第 1 个元素
在该元素中,查找与 .content
选择器匹配的元素
找到文本包含 ‘yo!’ 的元素
在每个已过滤的元素中,搜索标记名为 span
的子项。
暂未更新
暂未更新
暂未更新
暂未更新
暂未更新
暂未更新
单击页面上的元素
1 t.click ( selector [, options] )
参数
类型
描述
selector
Function | string | Selector | Snapshot | Promise
selector 选择器,有关选择器
options
(可选的)
Object
选项, 有关点击动作选项
下面用一个例子来展示如何使用 t.click
动作来选择一个复选框元素。
1 2 3 4 5 6 test ('Click a check box and check its state' , async t => { const checkbox = Selector ('#testing-on-remote-devices' ); await t .click (checkbox) .expect (checkbox.checked ).ok (); });
下面一个例子使用 options
参数在输入框中设置光标位置
1 2 3 4 5 6 7 8 test ('Click Input' , async t => { const nameInput = Selector ('#developer-name' ); await t .typeText (nameInput, 'Peter Parker' ) .click (nameInput, { caretPos : 5 }) .pressKey ('backspace' ) .expect (nameInput.value ).eql ('Pete Parker' ); });
双击页面上的元素
1 t.doubleClick ( selector [, options] )
参数
类型
描述
selector
Function | string | Selector | Snapshot | Promise
selector 选择器,有关选择器
options
(可选的)
Object
选项, 有关点击动作选项
右击页面上的元素
1 t.rightClick ( selector [, options] )
参数
类型
描述
selector
Function | string | Selector | Snapshot | Promise
selector 选择器,有关选择器
options
(可选的)
Object
选项, 有关点击动作选项
1 t.drag ( selector, dragOffsetX, dragOffsetY [, options] )
参数
类型
描述
selector
Function | string | Selector | Snapshot | Promise
selector 选择器,有关选择器
dragOffsetX
Number
鼠标在 x 轴上需要拖拽的距离
dragOffsetY
Number
鼠标在 y 轴上需要拖拽的距离
options
(可选的)
Object
选项, 有关鼠标动作选项
下面一个例子来演示如何使用 t.drag
动作来拖拽元素
1 2 3 4 5 6 7 8 test ('Drag slider' , async t => { const slider = Selector ('#developer-rating' ); await t .click ('#i-tried-testcafe' ); .expect (slider.value ).eql (1 ) .drag ('.ui-slider-handle' , 360 , 0 , { offsetX : 10 , offsetY : 10 }) .expect (slider.value ).eql (7 ); });
1 t.dragToElement ( selector, destinationSelector [, options] )
参数
类型
描述
selector
Function | string | Selector | Snapshot | Promise
selector 选择器,有关选择器
destinationSelector
Function | string | Selector | Snapshot | Promise
selector 选择器, 拖拽目标元素,有关选择器
options
(可选的)
Object
选项, 有关拖拽到元素动作选项
下面这个例子演示了如何使用 t.dragToElement
将元素拖放到特定区域
1 2 3 4 5 6 test ('Drag an item from the toolbox' , async t => { const designSurfaceItems = Selector ('.design-surface' ).find ('.items' ); await t .dragToElement ('.toolbox-item.text-input' , '.design-surface' ) .expect (designSurfaceItems.count ).gt (0 ); });
将鼠标指针悬停在网页元素上
1 t.hover ( selector [, options] )
参数
类型
描述
selector
Function | string | Selector | Snapshot | Promise
selector 选择器,有关选择器
options
(可选的)
Object
选项, 有关鼠标动作选项
使用此操作可以调用弹出元素,例如悬停在其他元素上时出现的提示窗口、弹出菜单或下拉列表。
下面这例子演示如何将鼠标指针移动到组合框上显示下拉列表,然后选择一个项目并检查组合框的值。
1 2 3 4 5 6 7 test ('Select combo box value' , async t => { const comboBox = Selector ('.combo-box' ); await t .hover (comboBox) .click ('#i-prefer-both' ) .expect (comboBox.value ).eql ('Both' ); });
1 t.selectText ( selector [, startPos] [, endPos] [, options] )
参数
类型
描述
默认值
selector
Function | string | Selector | Snapshot | Promise
selector 选择器,有关选择器
startPos
(可选的)
Number
选择的起始位置,从 0 开始的整数
0
endPos
(可选的)
Number
选择的结束位置,从 0 开始的整数
可见文本的长度
options
(可选的)
Object
选项, 有关基本动作选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const developerNameInput = Selector ('#developer-name' );const getElementSelectionStart = ClientFunction (selector => selector ().selectionStart );const getElementSelectionEnd = ClientFunction (selector => selector ().selectionEnd );test ('Select text within input' , async t => { await t .typeText (developerNameInput, 'Test Cafe' , { caretPos : 0 }) .selectText (developerNameInput, 7 , 1 ); await t .expect (await getElementSelectionStart (developerNameInput)).eql (1 ) .expect (await getElementSelectionEnd (developerNameInput)).eql (7 ); });
如果 startPos
的值大于 endPos
的值,则动作将执行向前选择。
1 t.selectTextAreaContent ( selector [, startLine] [, startPos] [, endLine] [, endPos] [, options] )
参数
类型
描述
默认值
selector
Function | string | Selector | Snapshot | Promise
selector 选择器,有关选择器
startLine
(可选的)
Number
选择开始的行号,从 0 开始的整数
0
startPos
(可选的)
Number
选择的起始位置,从 0 开始的整数
0
endLine
(可选的)
Number
选择结束的行号,从 0 开始的整数
最后一行的索引
endPos
(可选的)
Number
选择的结束位置(基于 endline
),从 0 开始的整数
endLine
的最后一个字符
options
(可选的)
Object
选项, 有关基本动作选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const commentTextArea = Selector ('#comments' );const getElementSelectionStart = ClientFunction (selector => selector ().selectionStart );const getElementSelectionEnd = ClientFunction (selector => selector ().selectionEnd );test ('Select text within textarea' , async t => { await t .click ('#tried-test-cafe' ) .typeText (commentTextArea, [ 'Lorem ipsum dolor sit amet' , 'consectetur adipiscing elit' , 'sed do eiusmod tempor' ].join (',\n' )) .selectTextAreaContent (commentTextArea, 0 , 5 , 2 , 10 ); await t .expect (await getElementSelectionStart (commentTextArea)).eql (5 ) .expect (await getElementSelectionEnd (commentTextArea)).eql (67 ); });
如果 startLine
比 endLine
的值大,则执行向前选择。
1 t.selectEditableContent ( startSelector, endSelector [, options] )
参数
类型
描述
默认值
startSelector
Function | string | Selector | Snapshot | Promise
标识开始选择的元素 selector 选择器,有关选择器
endSelector
Function | string | Selector | Snapshot | Promise
标识结束选择的元素 selector 选择器,有关选择器
options
(可选的)
Object
选项, 有关基本动作选项
此方法适用于启用了 contentEditable
属性的 HTML 元素。
1 2 3 4 5 6 7 test ('Delete text within a contentEditable element' , async t => { await t .selectEditableContent ('#foreword' , '#chapter-3' ) .pressKey ('delete' ) .expect (Selector ('#chapter-2' ).exists ).notOk () .expect (Selector ('#chapter-4' ).exists ).ok (); });
1 t.typeText ( selector, text [, options] )
参数
类型
描述
selector
Function | string | Selector | Snapshot | Promise
selector 选择器,有关选择器
text
string
要在指定元素中输入的文本
options
(可选的)
Object
选项, 有关输入动作选项
如果要删除文本,请使用 t.selectText
和 t.pressKey
来实现
1 2 3 4 5 6 7 8 test ('Type and Replace' , async t => { const nameInput = Selector ('#developer-name' ); await t .typeText (nameInput, 'Peter' ) .typeText (nameInput, 'Paker' , { replace : true }) .typeText (nameInput, 'r' , { caretPos : 2 }) .expect (nameInput.value ).eql ('Parker' ); });
注意
某些类型的HTML5输入(如 DateTime , Color 或 Range)需要以特定格式输入值。
The following table lists value formats expected by these inputs.
输入类型
模式
例子
Date
yyyy-MM-dd
2018-10-25
Week
yyyy-Www
2018-W03
Month
yyyy-MM
2018-10
DateTime
yyyy-MM-ddThh:mm
2018-10-25T13:22
Time
hh:mm
13:22:28
Color
#rrggbb
#003500
Range
n
45
1 t.pressKey ( keys [, options] )
参数
类型
描述
keys
string
要在指定元素中输入的文本
options
(可选的)
Object
选项, 有关基本动作选项
下表显示了如何指定不同类型,键序列和组合的键。
按键类型
例子
字母和数字
a
, A
, 1
修饰键
shift
, alt
(⌥
), ctrl
, meta
(⌘
)
导航和操作键
backspace
, tab
, enter
, capslock
, esc
, space
, pageup
, pagedown
, end
, home
, left
, right
, up
, down
, ins
, delete
组合键
shift+a
, ctrl+d
一番操作
ctrl+a del
, ctrl+a ctrl+c
(自由组合按键,并使用空格分隔它们)
1 2 3 4 5 6 7 test ('Key Presses' , async t => { const nameInput = Selector ('#developer-name' ); await t .typeText (nameInput, 'Peter Parker' ) .pressKey ('home right . delete delete delete delete' ) .expect (nameInput.value ).eql ('P. Parker' ); });
参数
类型
描述
url
string
要导航到的 URL 地址。可以是绝对地址或相对地址
下面的例子来演示如何导航到一个绝对地址
1 2 3 4 5 test ('Navigate to the main page' , async t => { await t .click ('#submit-button' ) .navigateTo ('http://devexpress.github.io/testcafe' ); });
在重定向发生后,TestCafe 会自动等待服务器响应。如果服务器在 15s 内没有响应,测试将会被恢复(?)。
1 t.takeScreenshot ( [path] )
参数
类型
描述
path
(可选的)
string
截图文件保存的相对路径和名称,该相对路径是基于命令行制定的基本目录。
1 2 3 4 5 6 test ('Take a screenshot of a fieldset' , async t => { await t .typeText ('#developer-name' , 'Peter Parker' ) .click ('#submit-button' ) .takeScreenshot ('my-fixture/thank-you-page.png' ); });
1 t.takeScreenshot ( [path] )
参数
类型
描述
selector
Function | string | Selector | Snapshot | Promise
selector 选择器,有关选择目标元素
path
(可选的)
string
截图文件保存的相对路径和名称,该相对路径是基于命令行制定的基本目录。
options
(可选的)
Object
用于定义屏幕截图截取方式的选项。详情见下文。
1 2 3 4 5 6 test ('Take a screenshot of a fieldset' , async t => { await t .click ('#reusing-js-code' ) .click ('#continuous-integration-embedding' ) .takeElementScreenshot (Selector ('fieldset' ).nth (1 ), 'my-fixture/important-features.png' ); });
options
对象包含以下属性
属性
类型
描述
默认值
scrollTargetX
, scrollTargetY
number
该点的坐标,是相对于目标元素计算的。
元素的中心。
includeMargins
boolean
是否包含外边距 (margin)
false
includeBorders
boolean
是否包含边框 (border)
true
includePaddings
boolean
是否包含内边距 (padding)
true
crop
Object
是否允许裁剪屏幕截图上的目标元素。
裁剪到整个元素。如果它不适合窗口大小,则为其可见部分。
crop
对象具有以下字段
字段
类型
描述
top
number
裁剪矩形的上边缘。如果传递负数,则从元素的下边缘计算坐标。
left
number
裁剪矩形的左边缘。如果传递负数,则从元素的右边缘计算坐标。
bottom
number
裁剪矩形的下边缘。如果传递负数,则从元素的上边缘计算坐标。
right
number
裁剪矩形的右边缘。如果传递负数,则从元素的左边缘计算坐标。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 test ('Take a screenshot of my new avatar' , async t => { await t .click ('#change-avatar' ) .setFilesToUpload ('#upload-input' , 'img/portrait.jpg' ) .click ('#submit' ) .takeElementScreenshot ('#avatar' , { includeMargins : true , crop : { top : -100 , left : 10 , bottom : 30 , right : 200 } }); });
暂未更新
暂未更新
参数
类型
描述
默认值
speed
number
动作的速度,在 1
(最大速度) 和 0.01
(最小速度) 之间
1
基本动作选项用于 t.pressKey
,t.selectText
,t.selectTextAreaContent
和 t.selectEditableContent
动作。
1 2 3 4 5 6 test ('My Test' , async t => { const nameInput = Selector ('#developer-name' ); await t .typeText (nameInput, 'Peter' ) .typeText (nameInput, ' Parker' , { speed : 0.1 }); });
1 2 3 4 5 6 7 8 9 10 11 12 { modifiers : { ctrl : Boolean , alt : Boolean , shift : Boolean , meta : Boolean }, offsetX : Number , offsetY : Number , speed : Number }
参数
类型
描述
默认值
ctrl
alt
shift
meta
boolean
在鼠标操作期间要按下的修饰键
false
offsetX
offsetY
number
鼠标指针坐标,正整数从左上角计算,负整数从右下角计算
目标元素的中心
speed
number
动作的速度,在 1
(最大速度) 和 0.01
(最小速度) 之间
1
鼠标动作选项用于 t.drag
和 t.hover
动作。
1 2 3 4 5 6 7 8 9 10 test ('My Test' , async t => { await t .drag (sliderHandle, 360 , 0 , { offsetX : 10 , offsetY : 10 , modifiers : { shift : true } }); });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { modifiers : { ctrl : Boolean , alt : Boolean , shift : Boolean , meta : Boolean }, offsetX : Number , offsetY : Number , destinationOffsetX : Number , destinationOffsetY : Number , speed : Number }
参数
类型
描述
默认值
ctrl
alt
shift
meta
boolean
在鼠标操作期间要按下的修饰键
false
offsetX
offsetY
number
鼠标指针坐标,正整数从左上角计算,负整数从右下角计算
目标元素的中心
destinationOffsetX
destinationOffsetY
number
鼠标拖拽完成时的指针坐标,正整数从左上角计算,负整数从右下角计算
目标元素的中心
speed
number
动作的速度,在 1
(最大速度) 和 0.01
(最小速度) 之间
1
拖拽到元素动作选项用于 t.dragToElement
动作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 test ('My Test' , async t => { const fileIcon = Selector ('.file-icon' ); const directoryPane = Selector ('.directory' ); await t .dragToElement (fileIcon, directoryPane, { offsetX : 10 , offsetY : 10 , destinationOffsetX : 100 , destinationOffsetY : 50 , modifiers : { shift : true } }); });
1 2 3 4 5 6 7 8 9 10 11 12 13 { modifiers : { ctrl : Boolean , alt : Boolean , shift : Boolean , meta : Boolean }, offsetX : Number , offsetY : Number , caretPos : Number , speed : Number }
参数
类型
描述
默认值
ctrl
alt
shift
meta
boolean
在鼠标操作期间要按下的修饰键
false
offsetX
offsetY
number
鼠标指针坐标,正整数从左上角计算,负整数从右下角计算
目标元素的中心
caretPos
number
如果在输入元素上执行动作,则为初始插入符号位置,从零开始的整数。
文本长度
speed
number
动作的速度,在 1
(最大速度) 和 0.01
(最小速度) 之间
1
点击操作选项用于 t.click
,t.doubleClick
和 t.rightClick
动作。
1 2 3 4 5 6 7 test ('My Test' , async t => { const nameInput = Selector ('#developer-name' ); await t .typeText (nameInput, 'Pete Parker' ) .click (nameInput, { caretPos : 4 }) .pressKey ('r' ); });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { modifiers : { ctrl : Boolean , alt : Boolean , shift : Boolean , meta : Boolean }, offsetX : Number , offsetY : Number , caretPos : Number , replace : Boolean , paste : Boolean , speed : Number }
参数
类型
描述
默认值
ctrl
alt
shift
meta
boolean
在鼠标操作期间要按下的修饰键
false
offsetX
offsetY
number
鼠标指针坐标,正整数从左上角计算,负整数从右下角计算
目标元素的中心
caretPos
number
如果在输入元素上执行动作,则为初始插入符号位置,从零开始的整数。
文本长度
replace
boolean
是否替换原有文本
false
paste
boolean
是否使用粘贴的方式一次性键入文本
false
speed
number
动作的速度,在 1
(最大速度) 和 0.01
(最小速度) 之间
1
输入动作选项用于 t.typeText
动作。
1 2 3 4 5 test ('My Test' , async t => { await t .typeText (nameInput, 'Peter' ) .typeText (nameInput, 'Parker' , { replace : true }); });
你可以使用断言来检查测试的网页状态是否与预期状态匹配。 TestCafe 提供了一组基于行为驱动开发风格(BDD风格)的断言方法。
要构造一个断言,可以使用测试控制器 (t
) 的 expect 方法。
1 await t.expect ( actual )
这个构造方法接受一个实际值,可以是 selector 的 DOM 节点状态属性或者是一个从页面中侦听到的 promise 对象。
TestCafe 会自动等待节点状态值的变动。
接下来跟一个断言方法,他接受期望值和一些其他的可选参数。
1 await t.expect ( actual ).eql ( expected [, message] [, options ])
参数
类型
描述
actual
Any
比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
expected
Any
期望值
message
(optional)
string
如果测试失败,需要在测试报告中输出的字符串
options
(optional)
Object
参见断言选项
1 2 3 await t .expect ({ a : 'bar' }).eql ({ a : 'bar' }, '这个断言将会通过' ) .expect ({ a : 'bar' }).eql ({ a : 'foo' }, '这个断言将会失败,并且这句话会被打印出来' );
1 2 3 test ('My test' , async t => { await t.expect (Selector ('.className' ).count ).eql (3 ); });
1 await t.expect ( actual ).notEql ( unexpected [, message] [, options ])
参数
类型
描述
actual
Any
比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
unexpected
Any
不期望的值
message
(optional)
string
如果测试失败,需要在测试报告中输出的字符串
options
(optional)
Object
参见断言选项
1 2 3 await t .expect ({ a : 'bar' }).notEql ({ a : 'bar' }, '这个断言将会失败,并且这句话会被打印出来' ) .expect ({ a : 'bar' }).notEql ({ a : 'foo' }, '这个断言将会通过' );
1 2 3 test ('My test' , async t => { await t.expect (Selector ('.className' ).count ).notEql (2 ); });
1 await t.expect ( actual ).ok ( [ message ] [, options ])
参数
类型
描述
actual
Any
比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
message
(optional)
string
如果测试失败,需要在测试报告中输出的字符串
options
(optional)
Object
参见断言选项
1 2 3 await t .expect ('ok' ).ok ('这个断言将会通过' ) .expect (false ).ok ('这个断言将会失败,并且这句话会被打印出来' );
1 2 3 test ('My test' , async t => { await t.expect (Selector ('#element' ).exists ).ok (); });
1 await t.expect ( actual ).notOk ( [ message ] [, options ])
参数
类型
描述
actual
Any
比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
message
(optional)
string
如果测试失败,需要在测试报告中输出的字符串
options
(optional)
Object
参见断言选项
1 2 3 await t .expect ('ok' ).notOk ('这个断言将会失败,并且这句话会被打印出来' ) .expect (false ).notOk ('这个断言将会通过' );
1 2 3 test ('My test' , async t => { await t.expect (Selector ('#element' ).exists ).notOk (); });
1 await t.expect ( actual ).contains ( expected [, message ] [, options ])
参数
类型
描述
actual
Any
比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
expected
Any
期望值
message
(optional)
string
如果测试失败,需要在测试报告中输出的字符串
options
(optional)
Object
参见断言选项
1 2 3 4 await t .expect ('foo bar' ).contains ('bar' , '用例未通过:字符串 "foo bar" 里面不含有期望的 "bar" 子串' ) .expect ([1 , 2 , 3 ]).contains (2 , '用例未通过:数组中不含有期望的值' ) .expect ({ foo : 'bar' , hello : 'universe' }).contains ({ foo : 'bar' }, '用例未通过:对象中不含有期望的属性' )
1 2 3 4 test ('My test' , async t => { const getLocation = ClientFunction (() => document .location .href .toString ()) await t.expect (getLocation ()).contains ('example.com' , '用例未通过:网址不包含期望的值' ); });
1 await t.expect ( actual ).notContains ( unexpected [, message ] [, options ])
参数
类型
描述
actual
Any
比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
unexpected
Any
不期望的值
message
(optional)
string
如果测试失败,需要在测试报告中输出的字符串
options
(optional)
Object
参见断言选项
1 2 3 4 await t .expect ('foo bar' ).notContains ('bar' , '用例未通过:字符串中含有不期望的子串' ) .expect ([1 , 2 , 3 ]).notContains (2 , '用例未通过:数组中含有不期望的值' ) .expect ({ foo : 'bar' , hello : 'universe' }).notContains ({ buzz : 'abc' }, '用例未通过:对象中含有不期望的属性' )
1 2 3 4 test ('My test' , async t => { const getLocation = ClientFunction (() => document .location .href .toString ()) await t.expect (getLocation ()).notContains ('example.com' , '用例未通过:网址包含了不被期望的值' ); });
1 await t.expect ( actual ).typeOf ( typeName [, message ] [, options ])
参数
类型
描述
actual
Any
比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
typeName
string
期望的 actual
的类型
message
(optional)
string
如果测试失败,需要在测试报告中输出的字符串
options
(optional)
Object
参见断言选项
1 2 3 4 await t .expect ({ a : 'bar' }).typeOf ('object' , '用例未通过:比较值不是对象' ) .expect (/bar/ ).typeOf ('regexp' , '用例未通过:比较值不是正则表达式' ) .expect (null ).typeOf ('null' , '用例未通过:比较值不为null' )
1 2 3 test ('My test' , async t => { await t.expect (Selector ('#element' ).getAttribute ('attr' )).typeOf ('string' ); });
1 await t.expect ( actual ).notTypeOf ( typeName [, message ] [, options ])
参数
类型
描述
actual
Any
比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
typeName
string
不期望的 actual
的类型
message
(optional)
string
如果测试失败,需要在测试报告中输出的字符串
options
(optional)
Object
参见断言选项
1 2 await t .expect ('bar' ).notTypeOf ('number' , '用例未通过:比较值不是数字类型' )
1 2 3 test ('My test' , async t => { await t.expect (Selector ('#element' ).getAttribute ('attr' )).notTypeOf ('null' ); });
1 await t.expect ( actual ).gt ( expected [, message ] [, options ])
参数
类型
描述
actual
Any
比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
expected
Any
期望的值
message
(optional)
string
如果测试失败,需要在测试报告中输出的字符串
options
(optional)
Object
参见断言选项
1 await t.expect (5 ).gt (2 , '用例未通过:比较值应该比 2 大' )
1 2 3 test ('My test' , async t => { await t.expect (Selector ('#element' ).clientWidth ).gt (300 ); });
1 await t.expect ( actual ).gte ( expected [, message ] [, options ])
用法同 gt
1 await t.expect ( actual ).lt ( expected [, message ] [, options ])
用法同 gt
1 await t.expect ( actual ).lte ( expected [, message ] [, options ])
用法同 gt
1 await t.expect ( actual ).within ( start, finish [, message ] [, options ])
参数
类型
描述
actual
number
比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
start
number
范围下限(包含)
finish
number
范围上限(包含)
message
(optional)
string
如果测试失败,需要在测试报告中输出的字符串
options
(optional)
Object
参见断言选项
1 await t.expect (5 ).within (3 , 10 , '这个断言将会通过' );
1 2 3 test ('My test' , async t => { await t.expect (Selector ('#element' ).scrollTop ).within (300 , 400 ); });
1 await t.expect ( actual ).notWithin ( start, finish [, message ] [, options ])
用法同 within
1 await t.expect ( actual ).match ( regexp [, message ] [, options ])
参数
类型
描述
actual
string
比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
regexp
RegExp
用来匹配 actual
的正则表达式
message
(optional)
string
如果测试失败,需要在测试报告中输出的字符串
options
(optional)
Object
参见断言选项
1 await t.expect ('foobar' ).match (/^f/ , '这个断言将会通过' );
1 2 3 4 test ('My test' , async t => { const getLocation = ClientFunction (() => document .location .href .toString ()); await t.expect (getLocation ()).match (/\.com/ ); });
1 await t.expect ( actual ).notMatch ( regexp [, message ] [, options ])
用法同 match
1 2 3 4 { timeout : Number , allowUnawaitedPromise : Boolean }
参数
类型
描述
默认值
timeout
number
如果在断言中使用了属性选择器或客户端函数,可等待的最大时间 (单位:ms)
allowUnawaitedPromise
boolean
如果你要断言一个常规的 promise 方法,请将该选项设置为 true
1 await t.expect (Selector ('#elementId' ).innerText ).eql ('text' , '在 500ms 内检查元素的文本' , { timeout : 500 });
1 await t.expect (doSomethingAsync ()).ok ('检查异步函数是否含回了 promise 对象' , { allowUnawaitedPromise : true });
暂未更新
无论出于什么原因,让测试暂停一小会
参数
类型
描述
timeout
number
暂停的持续时间,单位 ms
1 2 3 4 await t .click ('#play-1-sec-animation' ) .wait (1000 ) .expect (header.getStyleProperty ('opacity' )).eql (0 );