什么是 TestCafe
A node.js tool to automate
end-to-end web testing
Write tests in JS or TypeScript, run them and view results
抓几个重点词语:1. E2E Web Testing 2.JSTypeScript 3. Node.js Tool。简单说就是 Node.JS 编写的 Web 端 UI 自动化测试框架。官网:http://devexpress.github.io/t…
TestCafe VS Selenium
这时我想你跟我都有个疑问,跟 Selenium 有啥区别?这里个人简单阅读了下官方文档,写几个粗浅的对比。
Selenium 毕竟已经是 Web 自动化测试的 W3C 标准了,它有非常多的优势,但 TestCafe 作为后起之秀我这还想夸夸 Demo 使用过程的几点优于 Selenium 的感受。
TestCafe 不再像 Selenium 通过各个浏览器提供的 Driver 来驱动浏览器,而是有点类似 Selenium RC 直接往页面注入 JS 来操作页面,所以使用过程中再也不用担心因为浏览器版本和 Driver 版本以及 Selenium 版本不匹配而照成的 Case 执行失败。
TestCafe 是一整套完整的自动化测试框架,不仅提供了 Cases 的管理,运行,失败自动重跑,错误自动截图,并发等,对页面和页面元素的等待也封装完善而且使用简单,不像 Selenium 需要借助其他框架或者二次封装智能等待或者使用隐示 / 显示等待而有点复杂。
TestCafe 可以控制整体的执行速度,甚至可以细到某个操作的执行速度(这个有点类似慢放,用起来大家可以感受下,非常魔性)
TestCafe 因为只要你的浏览器支持 JS,所以支持桌面,移动端平台。
TestCafe debug 模式,通过代码配置或运行时设置,可以控制执行过程中失败时进入调试模式。
PS:当然以上的感受并没有经过项目的积累,纯粹 Demo 过程中的总结,也不晓得真正用到项目中会有哪些坑得踩。
跟大家推荐一个学习资料分享群:903217991,里面大牛已经为我们整理好了许多的学习资料,有自动化,接口,性能等等的学习资料!人生是一个逆水行舟的过程,不进则退,咱们一起加油吧!
TestCafe 快速入门
安装
因为是 Node.js 项目,可以直接通过 npm 安装,全局安装如下
npm install -g testcafe
快速 Demo 一个
baidu.js
fixture `baidu demo`
.page `https://www.baidu.com`;
test(‘baidu search’, async t=>{
await t.typeText(‘#kw’,”hao123″)
.click(‘#su’)
});
通过 Chrome 运行
testcafe chrome baidu.js
上面代码看不懂没关系,感受下 TestCafe 就行。
Cases 管理
自动化测试,终归还是测试,是测试就离不开测试用例,那 TestCafe 如何组织管理测试用例?
fixture 和 test
一个 js 文件可以包含多个 fixture,一个 fixture 可以包含多个 test。我们可以理解为 fixture 是个集合,test 标注的每个函数模块是一个 case。
语法
fixture(“ 测试集描述 ”)fixture 测试集合描述 test(‘ 用例描述 ’,fn(t))
Demo
fixture(“cases manage”).page(“https://www.baidu.com”);
test(‘this case 1’, async I => {
console.log(“this is case 1”);
});
test(‘this case 2’, async I => {
console.log(“this is case 2”);
});
test(‘this case 3’, async I => {
console.log(“this is case 3”);
});
fixture(`cases manage 2`).page(`https://testerhome.com/#gsc.tab=0`);
test(‘this case 1-1’, async I => {
console.log(“this is case 1-1”);
});
test(‘this case 2-1’, async I => {
console.log(“this is case 2-1”);
});
test(‘this case 3-1’, async I => {
console.log(“this is case 3-1”);
});
运行结果:其中你会发现每个 test 执行之前都会执行 fixture 打开页面的操作,但只会启动一次浏览器。那这时又会一个新的问题,除了打开页面的前提条件,是否框架自带了更多的前提 / 后置条件的处理了,也就是各种 beforexxx。当然!
fixture 的前置条件
fixture.beforeEach(fn(t) ):每个 test 执行之前都会被运行 fixture.afterEach(fn(t) ):每个 test 执行之后都会被运行 fixture.before(fn(t)):比 beforeEach 更早运行,且每个 fixture 只运行一次 fixture.after(fn(t)):比 afterEach 更晚运行,且每个 fixture 只运行一次
Demoj
fixture(`beforeeach test1`)
.page(`https://www.baidu.com`)
.beforeEach(async I => {
console.log(‘this is beforeEach’)
})
.before(async I => {
console.log(‘this is before’)
})
.after(async I => {
console.log(‘this is after’)
})
.afterEach(async I=>{
console.log(“this is afterEach”)
});
test(“test beforeAndafter”,I=>{
console.log(“1111”)
});
test(“test beforeAndafter”,I=>{
console.log(“2222”)
});
运行结果:
test 的前置条件
test.before(fun(t)):该 test 运行之前运行 test.after(fun(t)):该 test 运行之后运行
Demo
fixture(`beforeeach test1`)
.page(`https://www.baidu.com`)
.beforeEach(async I => {
console.log(‘this is beforeEach’)
})
.before(async I => {
console.log(‘this is before’)
})
.after(async I => {
console.log(‘this is after’)
})
.afterEach(async I => {
console.log(“this is afterEach”)
});
test
.before(async t => {
console.log(`this is test’s before`)
})
(“test beforeAndafter”, I => {
console.log(“1111”)
})
.after(async t => {
console.log(`this is test’s after`)
});
test(“test beforeAndafter”, I => {
console.log(“2222”)
});
运行结果:注意:从控制台输出看,test 的 before/after 会覆盖 fixture 中的 beforeEach/afterEach。也就是说如果一个 test 里面包含了 before/after 那么 fixture 中的 beforeEach/afterEach 对该 test 无效。
跳过测试
fixture.skip:跳过该 fixture 下的所有 testtest.skip : 跳过该 testfixture.only:只执行该 fixture 下的所有 test,其余的 fixture 下的 test 全部跳过 test.only:只运行该 test,其余全部跳过
元素定位
Demo
1. 创建 Selectors
import {Selector} from ‘testcafe’;
2. 使用 Selectors
// 通过 css 定位
const osCount = Selector(‘.column.col-2 label’).count;
// 通过 id 定位
const submitButtonExists = Selector(‘#submit-button’).exists;
同时因为是 JS 注入方式,所以定位方式非常灵活,几乎 JS 中定位元素的方式都支持。例如
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);
});
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);
});
同时还支持自定义扩展选择器,而且针对当前流行的 React,Vue,Angular,Aurelia 前端框架,还有特点的定位选择器,这里内容很多,有兴趣直接看官方文档:http://devexpress.github.io/t…
操作
元素操作其实上面例子我们已经用过点击,文本输入等方法了,官方也给了很全的 api 文档和 demo:http://devexpress.github.io/t…,这里就讲下一些比较特殊的元素或浏览器的操作。
– resizeWindow():设置窗口大小 - t.maximizeWindow():最大化窗口
fixture`demo`.page(‘https://www.baidu.com’);
test(‘ 设置 win 窗口大小 ’, async I => {
await I.resizeWindow(300, 500)
..maximizeWindow();
});
– getBrowserConsoleMessages():获取页面控制台消息
console.log(await I.getBrowserConsoleMessages())
});
– wait():暂停
test(‘ 暂停 ’, async I => {
await I.wait(3000);
});
– switchToIframe():切换到 iframe- switchToMainWindow():返回到主窗体
fixture`iframe 处理 `
.page`http://www.w3school.com.cn/tiy/t.asp?f=jseg_alert`;
test(‘iframe ‘, async t => {
await t
.click(‘#button-in-main-window’)
// 切换 ifrme
.switchToIframe(‘#iframe-1’)
// 返回主窗体
.switchToMainWindow();
});
– 下拉框选取: 其实就是定位下拉框,再定位到下拉框下的选项,然后点击两次。
fixture` 下拉框选取 `
.page`file:///C:/Note/selenium_html/index.html`;
test.only(‘ 下拉框选取 ‘, async t => {
const phone = Selector(‘#moreSelect’);
const phoneOption = phone.find(‘option’);
await t
.click(phone)
.click(phoneOption.withText(‘oppe’));
});
– 三种警告框的处理 setNativeDialogHandler(fn(type, text, url) [, options]):fu 返回 true 点击确定,返回 false 点击取消,返回文本则在 prompt 输入文本,这个执行过程中就不会看到警告框弹出,直接处理掉。
`fixture` 警告框处理 `
.page`http://www.w3school.com.cn/tiy/t.asp?f=jseg_alert`;
test(‘ 处理 alert ‘, async t => {
await t
.switchToIframe(“iframe[name=’i’]”)
// return true 表示点击确定
.setNativeDialogHandler(() => true)
.click(‘input[value=” 显示警告框 ”]’)
.wait(10000);
});
fixture` 警告框处理 `
.page`http://www.w3school.com.cn/tiy/t.asp?f=jseg_prompt`;
test.only(‘ 处理 提示框 ‘, async t => {
await t
.switchToIframe(“iframe[name=’i’]”)
.setNativeDialogHandler((type, text, url) => {
switch (type) {
case ‘confirm’:
switch (text) {
//false 点击 取消
case ‘Press a button!’:
return false;
// 返回 true 点击确定
case ‘You pressed Cancel!’:
return true;
default:
throw ‘Unexpected confirm dialog!’;
}
case ‘prompt’:
// 警告框填入值 hi vidor
return ‘Hi vidor’;
case ‘alert’:
throw ‘ 我是警告框!!’;
}
})
.click(‘input[value=” 显示提示框 ”]’)
.wait(10000);
});
上传文件 setFilesToUpload(),清空上传:clearUpload():
fixture`My fixture`
.page`http://www.example.com/`;
test(‘ 上传图片 ’, async t => {
await t
.setFilesToUpload(‘#upload-input’, [
‘./uploads/1.jpg’,
‘./uploads/2.jpg’,
‘./uploads/3.jpg’
])
// 清除上传
.clearUpload(‘#upload-input’)
.click(‘#upload-button’);
});
断言
TestCafe 自带了较为齐全的断言方法。断言都是通过 expect() 开始;
import {Selector} from ‘testcafe’;
fixture `My fixture`;
test(‘My test’, async t => {
// 断言 通过 CSS 定位到的有 3 个元素,eql() 表示相等,count 表示定位元素个数
await t.expect(Selector(‘.className’).count).eql(3);
});
test(‘My test’, async t => {
// 断言 ok() 表示为 true,exists 表示元素是否存在
await t.expect(Selector(‘#element’).exists).ok();
});
更多 APIdemo 查看官方文档:http://devexpress.github.io/t…
特性
在介绍几个 TestCafe 比较有意思的几个地方。
执行速度
testcafe 支持测试执行的速度控制。speed(x),x 支持 0.01 到 1 之间,1 则表示正常速度执行。
全局速度控制
可以通过控制台执行命令控制:
testcafe chrome xxxx.js –speed 0.1
控制某个 test 的执行速度
test(“test setTestSpeed”, I => {
I.setTestSpeed(0.1);
……
});
控制某个步骤的执行速度
test(“test setTestSpeed”, I => {
I.click(“#kw”).setTestSpeed(0.5);
});
Chrome 设备模拟
在启动 Chrome 浏览器时,可以设定 Chrome 提供的模拟器。
testcafe “chrome:emulation:device=iphone x” xxx.js
设备模拟器更多参数查看:http://devexpress.github.io/t…
PageObject demo
大家都知道,做 UI 自动化测试,肯定得使用 PO 或者 PF 模式,下面简单 Demo 个例子看看 TestCafe 可以如何组织 PO 模式。baiduPage.js
import {Selector, t as I} from ‘testcafe’
class baiduPage {
baiduInput = Selector(‘#kw’);
baiduButton = Selector(‘#su’).withAttribute(‘value’, ‘ 百度一下 ’);
async searchBaidu(text) {
await I
.typeText(this.baiduInput, text, {
// 清空
replace: true,
})
.click(this.baiduButton)
};
}
export default baiduPage = new baiduPage();
baiduCases.js
import baiduPage from ‘./baidu_page’
fixture`baidu search`.page`https://www.baidu.com/`;
test(‘po demo’, async I => {
await I.typeText(baiduPage.baiduInput, “test”);
baiduPage.searchBaidu(“testCafe”);
await I.typeText(baiduPage.baiduInput,” 居于之前的字符串空两个字符中插入 ”,{
caretPos:2
})
});
参数化 / 数据驱动
其实就是创建一个对象,用 for … of … 循环遍历
fixture`todoPage test cases`.page`http://todomvc.com/examples/react/#/`;
const testCases = [
{
todo: ‘123’,
},
{
todo: ‘!@#$’,
}
// 等等可能性的 cases,这里随便造两个作为 data driver
];
for (const todoText of testCases) {
test(‘create todo list ‘ + todoText.todo, async t => {
await todoPage.createTodoList(todoText.todo);
await t.expect(todoPage.firstTodo.innerText).eql(todoText.todo);
});
}
运行方式 Runner
TestCafe 可以通过命令行的方式来执行测试脚本,但是感觉实际过程中肯定不是很方便,特别如果运行时需要跟一堆参数的情况下,那么 TestCafe 提供了 Runner,更方便配置和运行。如下配置,我需要被运行的 Cases,错误自动截图,并发,生成 report,智能等待,执行速度,执行的浏览器等全部配到 Runner 里面,这样我就不需要通过命令行运行,而且在项目中使用非常方便。
const createTestCase = require(‘testcafe’);
const fs = require(‘fs’);
let testcafe = null;
createTestCase(‘localhost’, 1337, 1338)
.then(tc => {
testcafe = tc;
const runner = testcafe.createRunner();
const stream = fs.createWriteStream(‘report.json’);
return runner
// 需要运行的 cases
.src(
[
‘../demo/podemo/*.js’,
‘../demo/setWindowsSize.js’
]
)
// 设置需要执行的浏览器
.browsers([
‘chrome’,
‘firefox’
])
// 错误自动截图
.screenshots(
// 保存路径
‘../error/’,
true,
// 保存路劲格式
‘${DATE}_${TIME}/test-${TEST_INDEX}/${USERAGENT}/${FILE_INDEX}.png’
)
// 生成 report 格式, 根据需要安装对应 report 模块,
// 详细看:http://devexpress.github.io/testcafe/documentation/using-testcafe/common-concepts/reporters.html
.reporter(‘json’, stream)
// 并发
.concurrency(3)
.run({
skipJsErrors: true, // 页面 js 错误是否忽略, 建议为 true
quarantineMode: true, // 隔离模式,可以理解为失败重跑
selectorTimeout: 15000, // 设置页面元素查找超时时间, 智能等待
assertionTimeout: 7000, // 设置断言超时时间
pageLoadTimeout: 30000, // 设置页面加载超时时间
debugOnFail: true, // 失败开启调试模式 脚本编写建议开启
speed: 1 // 执行速度 0.01 – 1
});
}).then(failedCount => {
console.error(‘Failed Count:’ + failedCount);
testcafe.close();
})
.catch(err => {
console.error(err);
});
结语:
跟大家推荐一个学习资料分享群:903217991,里面大牛已经为我们整理好了许多的学习资料,有自动化,接口,性能等等的学习资料!人生是一个逆水行舟的过程,不进则退,咱们一起加油吧!