testcafe -- 一个基于 nodejs 自动化测试框架

简介

testcafe (官网) 是一个基于 nodejs 的自动化测试框架,优点就先不多说了,我们直接进入正题!

安装

testcafe 是一个 npm 包,它可以使用 npm 或 yarn 包管理器进行安装,这里我们使用 yarn 进行安装(因为它很快)

如果你的机器上没有安装 yarn,那么你可以使用 npm 来安装 yarn 😊

1
npm install -g yarn

在命令行中运行以下命令

1
yarn global add testcafe

这样, testcafe 就安装在你本机上啦

小试牛刀

创建一个测试项目

我们新建一个目录,用于练习我们的自动化测试框架 testcafe

1
2
3
# 首先切换到你用于管理 web 项目的根目录,我本机是 "~/www"
cd ~/www
mkdir testcafe && cd testcafe

编写第一个测试脚本

然后新建一个名为 myFirstTestcase.js 文件,用于编写我们的自动化测试脚本

1
vim myFirstTestcase.js # 用 vim 打开这个文件(如果文件不存在就创建它)

如果你安装了 vscode,你还可以使用 code myFirstTestcase.js 来创建并编辑它
需要在 vscode 的控制它中输入 install command 来启用 code 命令

将以下内容粘贴到 myFirstTestcase.js

1
2
3
4
5
6
7
8
9
10
11
12
// ES6 导包语法
import { Selector } from 'testcafe';

// 声明一个 fixture 测试项目
fixture('Getting Started')
// 打开一个 web 页面用于接下来的测试
.page('http://devexpress.github.io/testcafe/example')

// 创建一个测试用例
test('My first test', async t => {
// Test code
});

到这里,我们的第一个 test script 就写好了,接下来我们来运行它看看

运行测试脚本

我们回到命令行中,执行以下命令来运行一个测试脚本

1
testcafe chrome myFirstTestcase.js

这个命令中,

  • testcafe 是我们使用包管理工具全局安装的依赖,testcafe 是它的可执行程序
  • chrome 是我们的测试平台,安装在本机的浏览器,也可以是 safari firefox
  • myFirstTestcase.js 是我们编写的测试脚本

稍等片刻,我们的浏览器就会被自动打开然后运行测试脚本,最后浏览器被自动关闭,在终端上留下了测试结果。

test report

编写测试代码

刚才我们的测试脚本只是简单的打开了一个页面,它并没有执行任何动作。

在接下来的几个例子中,你可以打开测试目标网站 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 是我们的测试用例的上下文对象,它又很多方法,在上面的例子中,我们调用了它的 typeTextclick 两个方法,其中

  • typeText 方法用来键入文本,它接受两个参数:第一个参数是 selector 选择器(它的语法类似 jQuery 选择器的语法);第二个参数是你要键入的文本;
  • click 方法用来模拟鼠标点击,它接受一个参数,是一个 selector 选择器

有关这个上下文对象的更多方法,请先参考 TestCafe 官方 API 手册 (它是一个英文文档,稍后我会整理出这个文档的中文手册在本页下方)

这段代码的作用是:

  1. 首先找到 id 为 developer-name 的标签,输入值 ‘John Smith’
  2. 然后点击 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 方法

  1. L6,我们声明了一个 articleHeader 变量,这个变量的值是根据选择器 .result-content > h1 找到的 DOM 元素
  2. 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

    assertion error

小结

到这里,我们的第一个自动化测试脚本就完成了,如果你没有跑通的话,请检查一下你的测试脚本是否与以下内容一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ES6 导包语法
import { Selector } from 'testcafe'

// 声明一个 fixture 测试项目
fixture('Getting Started')
// 打开一个 web 页面用于接下来的测试
.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')
})

API 文档 (unfinished)

测试代码结构

测试组 Fixtures

TestCafe 测试必须将一些测试组织起来,测试组 (Fixtures) 就像是一个文件夹,将同一类的测试包裹起来。
一个测试文件可以包含很多测试组。

要声明一个测试组,使用 fixture 方法

1
2
3
4
fixture( fixtrueName )

// 或下面这种用法
fixture `fixtureName`

参数类型描述
fixtureNamestring测试组的名称

它返回一个测试组对象,可以接测试组方法,有关这些方法,请参考下方相关 API。

请注意,测试方法 test 必须放在测试组声明后面。

测试用例 Tests (no)

暂未更新

指定起始页面 (no)

暂未更新

测试元数据 (no)

暂未更新

钩子 (no)

暂未更新

跳过测试 (no)

暂未更新

页面元素选择

选择器 selector

选择器是标识测试中的网页元素的方法。选择器 API 提供了选择页面上的元素并获取其状态的方法和属性。

要从 testcafe 模块导入 Selector 构造函数,调用此构造函数并将 CSS 选择器字符串作为参数传递。

1
2
3
import { Selector } from 'testcafe';

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

Selector 参数语法类似于 jQuery 选择器语法。
在上面的例子中,我们选择了一个 class 为 atricle-content 的元素。
然后我们就可以使用这个选择器对对元素进行操作了。

1
await t.click(article)

或者在断言方法中使用它

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();

创建选择器 (no)

1
Selector( init [, options] )
参数类型描述
initFunction | string | Selector | Snapshot | Promise标识要选择的 DOM 节点
options (可选的)Object选项, 有关选择器选项
1
2
3
import { Selector } from 'testcafe';

const usernameInput = Selector('#username');

未完

使用选择器 (no)

暂未更新

选择器查找 (no)

暂未更新

选择器选项 (no)

暂未更新

选择器拓展 (no)

暂未更新

边缘情况 (no)

暂未更新

DOM 节点状态 (no)

暂未更新

特定框架的选择器 (no)

暂未更新

一些例子 (no)

暂未更新

动作

单击 click

单击页面上的元素

1
t.click( selector [, options] )
参数类型描述
selectorFunction | string | Selector | Snapshot | Promiseselector 选择器,有关选择器
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');
});

双击 doubleClick

双击页面上的元素

1
t.doubleClick( selector [, options] )
参数类型描述
selectorFunction | string | Selector | Snapshot | Promiseselector 选择器,有关选择器
options (可选的)Object选项, 有关点击动作选项

右击 rightClick

右击页面上的元素

1
t.rightClick( selector [, options] )
参数类型描述
selectorFunction | string | Selector | Snapshot | Promiseselector 选择器,有关选择器
options (可选的)Object选项, 有关点击动作选项

拖拽 drag

拖拽一定距离 drag

1
t.drag( selector, dragOffsetX, dragOffsetY [, options] )
参数类型描述
selectorFunction | string | Selector | Snapshot | Promiseselector 选择器,有关选择器
dragOffsetXNumber鼠标在 x 轴上需要拖拽的距离
dragOffsetYNumber鼠标在 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);
});

拖拽到另一个元素上 dragToElement

1
t.dragToElement( selector, destinationSelector [, options] )
参数类型描述
selectorFunction | string | Selector | Snapshot | Promiseselector 选择器,有关选择器
destinationSelectorFunction | string | Selector | Snapshot | Promiseselector 选择器, 拖拽目标元素,有关选择器
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);
});

悬停 hover

将鼠标指针悬停在网页元素上

1
t.hover( selector [, options] )
参数类型描述
selectorFunction | string | Selector | Snapshot | Promiseselector 选择器,有关选择器
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');
});

选择文本 selectText

在 input 元素中

1
t.selectText( selector [, startPos] [, endPos] [, options] )
参数类型描述默认值
selectorFunction | string | Selector | Snapshot | Promiseselector 选择器,有关选择器
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 的值,则动作将执行向前选择。

在 textarea 元素中

1
t.selectTextAreaContent( selector [, startLine] [, startPos] [, endLine] [, endPos] [, options] )
参数类型描述默认值
selectorFunction | string | Selector | Snapshot | Promiseselector 选择器,有关选择器
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);
});

如果 startLineendLine 的值大,则执行向前选择。

在 contentEditable 元素中

1
t.selectEditableContent( startSelector, endSelector [, options] )
参数类型描述默认值
startSelectorFunction | string | Selector | Snapshot | Promise标识开始选择的元素 selector 选择器,有关选择器
endSelectorFunction | 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();
});

键入文本 typeText

1
t.typeText( selector, text [, options] )
参数类型描述
selectorFunction | string | Selector | Snapshot | Promiseselector 选择器,有关选择器
textstring要在指定元素中输入的文本
options (可选的)Object选项, 有关输入动作选项

如果要删除文本,请使用 t.selectTextt.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.

输入类型模式例子
Dateyyyy-MM-dd2018-10-25
Weekyyyy-Www2018-W03
Monthyyyy-MM2018-10
DateTimeyyyy-MM-ddThh:mm2018-10-25T13:22
Timehh:mm13:22:28
Color#rrggbb#003500
Rangen45

按键 pressKey

1
t.pressKey( keys [, options] )
参数类型描述
keysstring要在指定元素中输入的文本
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');
});

页面跳转 navigateTo

1
t.navigateTo( url )
参数类型描述
urlstring要导航到的 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 内没有响应,测试将会被恢复(?)。

截图 takeScreenshot

获取整页的屏幕截图 takeScreenshot

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');
});

获取页面元素的屏幕截图 takeElementScreenshot

1
t.takeScreenshot( [path] )
参数类型描述
selectorFunction | string | Selector | Snapshot | Promiseselector 选择器,有关选择目标元素
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, scrollTargetYnumber该点的坐标,是相对于目标元素计算的。元素的中心。
includeMarginsboolean是否包含外边距 (margin)false
includeBordersboolean是否包含边框 (border)true
includePaddingsboolean是否包含内边距 (padding)true
cropObject是否允许裁剪屏幕截图上的目标元素。裁剪到整个元素。如果它不适合窗口大小,则为其可见部分。

crop 对象具有以下字段

字段类型描述
topnumber裁剪矩形的上边缘。如果传递负数,则从元素的下边缘计算坐标。
leftnumber裁剪矩形的左边缘。如果传递负数,则从元素的右边缘计算坐标。
bottomnumber裁剪矩形的下边缘。如果传递负数,则从元素的上边缘计算坐标。
rightnumber裁剪矩形的右边缘。如果传递负数,则从元素的左边缘计算坐标。

take screenshot

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
}
});
});

上传 setFilesToUpload (no)

暂未更新

调整窗口尺寸 resize (no)

暂未更新

动作选项 options

基本动作选项 basic action

1
2
3
{
speed: Number
}
参数类型描述默认值
speednumber动作的速度,在 1(最大速度) 和 0.01(最小速度) 之间1

基本动作选项用于 t.pressKeyt.selectTextt.selectTextAreaContentt.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 });
});

鼠标动作选项 mouse action

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 metaboolean在鼠标操作期间要按下的修饰键false
offsetX offsetYnumber鼠标指针坐标,正整数从左上角计算,负整数从右下角计算目标元素的中心
speednumber动作的速度,在 1(最大速度) 和 0.01(最小速度) 之间1

鼠标动作选项用于 t.dragt.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
}
});
});

拖拽到元素动作选项 dragToElement action

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 metaboolean在鼠标操作期间要按下的修饰键false
offsetX offsetYnumber鼠标指针坐标,正整数从左上角计算,负整数从右下角计算目标元素的中心
destinationOffsetX destinationOffsetYnumber鼠标拖拽完成时的指针坐标,正整数从左上角计算,负整数从右下角计算目标元素的中心
speednumber动作的速度,在 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
}
});
});

点击动作选项 click action

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 metaboolean在鼠标操作期间要按下的修饰键false
offsetX offsetYnumber鼠标指针坐标,正整数从左上角计算,负整数从右下角计算目标元素的中心
caretPosnumber如果在输入元素上执行动作,则为初始插入符号位置,从零开始的整数。文本长度
speednumber动作的速度,在 1(最大速度) 和 0.01(最小速度) 之间1

点击操作选项用于 t.clickt.doubleClickt.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');
});

输入动作选项 typing action

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 metaboolean在鼠标操作期间要按下的修饰键false
offsetX offsetYnumber鼠标指针坐标,正整数从左上角计算,负整数从右下角计算目标元素的中心
caretPosnumber如果在输入元素上执行动作,则为初始插入符号位置,从零开始的整数。文本长度
replaceboolean是否替换原有文本false
pasteboolean是否使用粘贴的方式一次性键入文本false
speednumber动作的速度,在 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 会自动等待节点状态值的变动。

接下来跟一个断言方法,他接受期望值和一些其他的可选参数。

断言方法

等于 eql

1
await t.expect( actual ).eql( expected [, message] [, options ])
参数类型描述
actualAny比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
expectedAny期望值
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);
});

不等于 notEql

1
await t.expect( actual ).notEql( unexpected [, message] [, options ])
参数类型描述
actualAny比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
unexpectedAny不期望的值
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);
});

真值 ok

1
await t.expect( actual ).ok( [ message ] [, options ])
参数类型描述
actualAny比较值,如果是一个 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();
});

假值 notOk

1
await t.expect( actual ).notOk( [ message ] [, options ])
参数类型描述
actualAny比较值,如果是一个 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();
});

包含 contains

1
await t.expect( actual ).contains( expected [, message ] [, options ])
参数类型描述
actualAny比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
expectedAny期望值
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', '用例未通过:网址不包含期望的值');
});

不包含 notContains

1
await t.expect( actual ).notContains( unexpected [, message ] [, options ])
参数类型描述
actualAny比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
unexpectedAny不期望的值
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', '用例未通过:网址包含了不被期望的值');
});

类型等于 tyoeOf

1
await t.expect( actual ).typeOf( typeName [, message ] [, options ])
参数类型描述
actualAny比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
typeNamestring期望的 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');
});

类型不等于 notTypeOf

1
await t.expect( actual ).notTypeOf( typeName [, message ] [, options ])
参数类型描述
actualAny比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
typeNamestring不期望的 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');
});

大于 gt

1
await t.expect( actual ).gt( expected [, message ] [, options ])
参数类型描述
actualAny比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
expectedAny期望的值
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);
});

大于等于 gte

1
await t.expect( actual ).gte( expected [, message ] [, options ])

用法同 gt

小于 lt

1
await t.expect( actual ).lt( expected [, message ] [, options ])

用法同 gt

小于等于 lte

1
await t.expect( actual ).lte( expected [, message ] [, options ])

用法同 gt

在某个范围 within

1
await t.expect( actual ).within( start, finish [, message ] [, options ])
参数类型描述
actualnumber比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
startnumber范围下限(包含)
finishnumber范围上限(包含)
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);
});

不在某个范围 notWithin

1
await t.expect( actual ).notWithin( start, finish [, message ] [, options ])

用法同 within

正则匹配 match

1
await t.expect( actual ).match( regexp [, message ] [, options ])
参数类型描述
actualstring比较值,如果是一个 promise 对象,TestCafe 会自动等待值的变化
regexpRegExp用来匹配 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/);
});

非正则匹配 notMatch

1
await t.expect( actual ).notMatch( regexp [, message ] [, options ])

用法同 match

断言选项

1
2
3
4
{
timeout: Number,
allowUnawaitedPromise: Boolean
}
参数类型描述默认值
timeoutnumber如果在断言中使用了属性选择器或客户端函数,可等待的最大时间 (单位:ms)
allowUnawaitedPromiseboolean如果你要断言一个常规的 promise 方法,请将该选项设置为 true
1
await t.expect(Selector('#elementId').innerText).eql('text', '在 500ms 内检查元素的文本', { timeout: 500 });
1
await t.expect(doSomethingAsync()).ok('检查异步函数是否含回了 promise 对象', { allowUnawaitedPromise: true });

侦测数据变化 (no)

暂未更新

等待

无论出于什么原因,让测试暂停一小会

1
t.wait( timeout )
参数类型描述
timeoutnumber暂停的持续时间,单位 ms
1
2
3
4
await t
.click('#play-1-sec-animation')
.wait(1000)
.expect(header.getStyleProperty('opacity')).eql(0);
0%