浅谈前端自动化测试 - e2e 篇

12,797 阅读4分钟

前言

在上一篇 浅谈前端自动化测试 - 介绍篇 里面我们提到了两类前端测试方法,本篇主要针对 e2e 测试法 来进行展开。 不明白什么是 e2e 测试的 请自行查看上一篇内容

正文

e2e 测试有什么优势

通常情况下,单元测试确实能够帮助我们发现大部分的问题,但是在复杂的前端交互或者可视化项目测试中,单纯的单元测试并不能满足真实测试需求,这时候 e2e 测试的优势就显得尤其显著。

优势主要包括:

  1. 模拟用户行为

  2. 模拟真实运行环境

  3. 截屏比对

  4. 操控运行时环境

怎么实现 e2e 测试

对于怎么去实现 e2e - 肯定得搭建平台 - 至于怎么搭建平台 - 肯定是采用合适的框架(如果你可以手撸 chrome ,可以忽略这段话), 前端测试框架参差不齐种类繁多,这里主要选取 github star 较靠前的几个 做了选型对比,主要包括 puppeteer 、 phantomJs 、 selenium 等。

  1. puppeteer [star 66k]
    活跃度高,社区资料丰富,对于前端易用性很强

  2. phantomJs [star 28k]
    已经停止维护,并且环境安装复杂

  3. selenium [star 19k]
    元老框架 虽然依然维护 但是相比 puppeteer 上手速度较慢 文档易读性较差

最终选择了身为高富帅的 puppeteer。

首先来介绍一下, 它是 Chrome 开发团队在 2017 年发布的一个 Node 包,中文名字叫 [傀儡师], 它提供了高级 API 来通过 DevTools 协议控制 Chrome。

puppeteer 架构看起来和 Chromium 架构很像。

如果想深入对其研究 可以转到 github

了解之后,下面简单介绍下在 e2e 测试中怎么用

puppeteer 怎么用

首先 ,puppeteer 模块提供了启动 Chromium 实例的方法

const puppeteer = require('puppeteer');
puppeteer.launch().then(async browser => {
  const page = await browser.newPage();
  await page.goto('http://localhost:8888/example.html');
  // 其他操作...
  
  await browser.close();
});

并且 page 实例对象拥有大量可以直接用于测试的 API

// 事件监听,监听对应事件来可以做一些性能的测试或者定制化测试方案
page.on('load')  //当页面的 load 事件被触发时触发。
page.on('request') //当页面发送一个请求时触发。参数request 对象是只读的。 并且可以需要拦截并且改变请求
page.on('response') // 当页面的某个请求接收到对应的 response 时触发。

// dom 方法 
page.$(selector) //选择器 此方法在页面内执行 document.querySelector。
page.$$(selector) //选择器 此方法在页面内执行 document.querySelectorAll
page.$eval(selector, pageFunction[, ...args]) // 此方法在页面内执行 document.querySelector,然后把匹配到的元素作为第一个参数传给 pageFunction
// 例如:const searchValue = await page.$eval('#search', el => el.value);
page.addScriptTag(options) // 注入一个指定src(url)或者代码(content)的 script 标签到当前页面。
page.addStyleTag(options) // 添加一个指定link(url)的 <link rel="stylesheet"> 标签。

// 模拟用户行为相关方法
page.click(selector[, options]) // 
page.focus(selector) // 
page.hover(selector) // 
// 模拟键盘
await page.keyboard.down('Shift');
await page.keyboard.press('KeyA');
await page.keyboard.up('Shift');
await page.keyboard.type('Hello World!');
await page.keyboard.press('ArrowLeft');
// 模拟鼠标
await page.mouse.move(0, 0);
await page.mouse.down();
await page.mouse.move(0, 100);
await page.mouse.up();

// 高级方法
page.evaluate(pageFunction[, ...args]) //在页面实例上下文中执行方法
page.pdf([options]) // 生成 pdf
page.screenshot([options]) // 生成截屏

这些简单易用的API 大大提高了 编写定制化自动测试平台的搭建效率。下面举几个例子🌰 。

  • 简易版 模拟用户行为并进行快照比对测试
const jimp = require("jimp");
const puppeteer = require('puppeteer');
puppeteer.launch().then(async browser => {
  const page = await browser.newPage();
  await page.goto('http://localhost:8888/example.html');
  await new Promise((res, rej) => { setTimeout(res, 2000) })
  await page.mouse.move(0, 0);
  await page.mouse.down();
  await page.mouse.move(100, 100);
  await page.mouse.up();
  if(hasScreenshot) {
    const expected = jimp.read(fs.readSync(screenshotFilePath)).bitmap
    const new = jimp.read(await page.screenshot()).bitmap
    // 像素匹配   
    const nums = pixelmatch(expected.data, new.data, diff.data, width, height, {threshold: 0.1});    
    if(nums < thresholdNums) {
        console.log('TEST PASS')
    } else {
        console.log('TEST FAILED')
    }
  } else {
    console.log('Generate screenshot success! please run again')
    jimp.read(await page.screenshot()).write(screenshotFilePath)
  }
  await browser.close();
});
  • 模拟用户登陆
const browser = await puppeteer.launch({
    slowMo: 100,    //放慢速度
    headless: false,
    args: ['--start-fullscreen'] //全屏打开页面
 }).then(async browser => {
    const page = await browser.newPage();
    await page.goto('http://demo.com');
    //输入账号密码
    const uIdElement = await page.$('#uId');
    await uIdElement.type('admin@admin.com', {delay: 20});
    const passwordElement = await page.$('#password', {delay: 20});
    await passwordElement.type('12345');
    //点击确定按钮进行登录
    let okButtonElement = await page.$('#btn-ok');
    //等待页面跳转完成,一般点击某个按钮需要跳转时,都需要等待 page.waitForNavigation() 执行完毕才表示跳转成功
    await Promise.all([
        okButtonElement.click(),
        page.waitForNavigation()  
    ]);
    console.log('admin 登录成功');
    await page.close();
    await browser.close();
 })

  • 模拟不同设备
const
puppeteer =
require
(
'puppeteer'
);
const iPhone = puppeteer.devices['iPhone 6'];
puppeteer.launch().then(async browser => {
  const page = await browser.newPage();
  await page.emulate(iPhone);
  await page.goto('http://demo.com');
  await browser.close();
});

以上就是 puppuetter 在前端测试中的一些简单介绍和简单用法, 如果要编写一个功能全面且强大的测试平台还需要大量的实践和积累。

总结

看过之后是不是觉得写 e2e 测试 so easy,不过主要还是得归功于 puppeteer 这位万能的傀儡师,它不仅仅在前端测试方面可以自由挥舞,还可以用于前端性能监控,异常拦截报警,生产pdf 报告,爬虫信息统计等等各种想到和想不到的应用,不得不说这个是真-高富帅 。

不过,e2e 测试虽然可以模拟各种真实场景以满足真实测试需求,但在实际开发过程中如果只做 e2e 有时还是显得有些臃肿且力不从心,某些情况下没有单元测试那么的直接简洁,直中要害,因此单元测试也很重要。留到下一篇对其详细介绍


一只前端小菜鸟 | 求知若渴 | 梦想与爱皆不可辜负