因某个机会轻轻碰了一下 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.JS\TypeScript 3. Node.js Tool。
简单说就是 Node.JS 编写的 Web 端 UI 自动化测试框架。
官网:http://devexpress.github.io/testcafe/
这时我想你跟我都有个疑问,跟 Selenium 有啥区别?这里个人简单阅读了下官方文档,写几个粗浅的对比。
对比项 | Selenium 3.x | TestCafe | 谁更优 |
---|---|---|---|
社区 | Web 自动化测试一哥 ,学习资料非常多 | 较新的工具,官方提供了详细的学习资料,Github 上 Start 人数也超过 5K | Selenium |
支持语言 | java,python,ruby,Node.js...... | JavaScript,TypeScript | Selenium |
支持的浏览器 | Chrome,IE,Firefox,Safari,Edge 等有 Driver 的浏览器都支持 | 支持所有能支持 JS 的浏览器,也就意味着支持多端 | TestCafe |
完善性 | 需要跟其它框架结合使用,如 TestNG 等 | 自身就是一个完整的自动化测试框架 | TestCafe |
易学性 | 简单易学 | 简单易学 | 不分上下 |
Selenium 毕竟已经是 Web 自动化测试的 W3C 标准了,它有非常多的优势,但 TestCafe 作为后起之秀我这还想夸夸 Demo 使用过程的几点优于 Selenium 的感受。
PS:当然以上的感受并没有经过项目的积累,纯粹 Demo 过程中的总结,也不晓得真正用到项目中会有哪些坑得踩。
因为是 Node.js 项目,可以直接通过 npm 安装,全局安装如下
npm install -g testcafe
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 就行。
自动化测试,终归还是测试,是测试就离不开测试用例,那 TestCafe 如何组织管理测试用例?
一个 js 文件可以包含多个 fixture,一个 fixture 可以包含多个 test。 我们可以理解为 fixture 是个集合,test 标注的每个函数模块是一个 case。
fixture("测试集描述")
fixture `测试集合描述`
test('用例描述',fn(t))
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.beforeEach( fn(t) ):每个 test 执行之前都会被运行
fixture.afterEach( fn(t) ):每个 test 执行之后都会被运行
fixture.before(fn(t)):比 beforeEach 更早运行,且每个 fixture 只运行一次
fixture.after(fn(t)):比 afterEach 更晚运行,且每个 fixture 只运行一次
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.before(fun(t)):该 test 运行之前运行
test.after(fun(t)):该 test 运行之后运行
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 下的所有 test
test.skip : 跳过该 test
fixture.only :只执行该 fixture 下的所有 test,其余的 fixture 下的 test 全部跳过
test.only : 只运行该 test,其余全部跳过
import { Selector } from 'testcafe';
// 通过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/testcafe/documentation/test-api/selecting-page-elements/
元素操作其实上面例子我们已经用过点击,文本输入等方法了,官方也给了很全的 api 文档和 demo:http://devexpress.github.io/testcafe/documentation/test-api/actions/ ,这里就讲下一些比较特殊的元素或浏览器的操作。
fixture`demo`.page('https://www.baidu.com');
test('设置win窗口大小', async I => {
await I.resizeWindow(300, 500)
..maximizeWindow( );
});
test('获取控制台输出', async I => {
console.log(await I.getBrowserConsoleMessages())
});
test('暂停', async I => {
await I.wait(3000);
});
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'));
});
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);
});
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/testcafe/documentation/test-api/assertions/
在介绍几个 TestCafe 比较有意思的几个地方。
testcafe 支持测试执行的速度控制。 speed(x),x 支持 0.01 到 1 之间,1 则表示正常速度执行。
可以通过控制台执行命令控制:
testcafe chrome xxxx.js --speed 0.1
test("test setTestSpeed", I => {
I.setTestSpeed(0.1);
......
});
test("test setTestSpeed", I => {
I.click("#kw").setTestSpeed(0.5);
});
在启动 Chrome 浏览器时,可以设定 Chrome 提供的模拟器。
testcafe "chrome:emulation:device=iphone x" xxx.js
大家都知道,做 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);
});
}
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);
});
TestCafe 还有非常多有意思的东西可以去发掘,例如跟 Jenkins 等集成一类的。 个人 demo 了一些例子觉得是个非常值得推荐的 UI 自动化测试框架,特别是用 JS 编写的在 codecept,WebdriverIO 我推荐 TestCafe。 也许国内现在用的人不多,但相信是金子总会发光的。