因某个机会轻轻碰了一下TestCafe,感受到其强大,这里写个文档,做个小结。

什么是TestCafe

官方:

  1. A node.js tool to automate
  2. end-to-end web testing
  3. 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/

TestCafe VS Selenium

这时我想你跟我都有个疑问,跟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的感受。

  • TestCafe 不再像Selenium 通过各个浏览器提供的Driver来驱动浏览器,而是有点类似Selenium RC直接往页面注入JS来操作页面,所以使用过程中再也不用担心因为浏览器版本和Driver版本以及Selenium版本不匹配而照成的Case执行失败。
  • TestCafe 是一整套完整的自动化测试框架,不仅提供了Cases的管理,运行,失败自动重跑,错误自动截图,并发等,对页面和页面元素的等待也封装完善而且使用简单,不像Selenium需要借助其他框架或者二次封装智能等待或者使用隐示/显示等待而有点复杂。
  • TestCafe 可以控制整体的执行速度,甚至可以细到某个操作的执行速度(这个有点类似慢放,用起来大家可以感受下,非常魔性)
  • TestCafe 因为只要你的浏览器支持JS,所以支持桌面,移动端平台。
  • TestCafe debug模式,通过代码配置或运行时设置,可以控制执行过程中失败时进入调试模式。

PS:当然以上的感受并没有经过项目的积累,纯粹Demo过程中的总结,也不晓得真正用到项目中会有哪些坑得踩。

TestCafe 快速入门

安装

因为是Node.js 项目,可以直接通过npm安装,全局安装如下

  1. npm install -g testcafe

快速Demo一个

baidu.js

  1. fixture `baidu demo`
  2. .page `https://www.baidu.com`;
  3. test('baidu search', async t=>{
  4. await t.typeText('#kw',"hao123")
  5. .click('#su')
  6. });

通过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");
});

运行结果:
UI自动化测试框架----TestCafe - 图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")
});

运行结果:
UI自动化测试框架----TestCafe - 图2

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

运行结果:
UI自动化测试框架----TestCafe - 图3

注意: 从控制台输出看,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,其余全部跳过

元素定位

Demo

  1. 创建Selectors
import { Selector } from 'testcafe';
  1. 使用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/testcafe/documentation/test-api/selecting-page-elements/

操作

元素操作其实上面例子我们已经用过点击,文本输入等方法了,官方也给了很全的api文档和demo:http://devexpress.github.io/testcafe/documentation/test-api/actions/ ,这里就讲下一些比较特殊的元素或浏览器的操作。

  • resizeWindow():设置窗口大小
  • t.maximizeWindow( ):最大化窗口
fixture`demo`.page('https://www.baidu.com');

test('设置win窗口大小', async I => {
    await I.resizeWindow(300, 500)
              ..maximizeWindow( );
});
  • getBrowserConsoleMessages():获取页面控制台消息
test('获取控制台输出', async I => {
    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/testcafe/documentation/test-api/assertions/

特性

在介绍几个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提供的模拟器。
UI自动化测试框架----TestCafe - 图4

testcafe "chrome:emulation:device=iphone x" xxx.js

设备模拟器更多参数查看:http://devexpress.github.io/testcafe/documentation/using-testcafe/common-concepts/browsers/using-chrome-device-emulation.html

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

写在最后

TestCafe 还有非常多有意思的东西可以去发掘,例如跟Jenkins等集成一类的。 个人demo了一些例子觉得是个非常值得推荐的 UI 自动化测试框架,特别是用JS编写的在codecept,WebdriverIO我推荐TestCafe。 也许国内现在用的人不多,但相信是金子总会发光的。