本篇内容将记录并介绍使用Puppeteer进行自动化网页测试,并依靠约定来避免反复修改测试用例的方案。主要解决页面众多时,修改代码致使的牵连错误没法被发现的运行时问题。文章首发于 我的博客。
对前端感兴趣但愿一块儿讨论的能够加我vx:w554091944
目前咱们在持续开发着一个几十个页面,十万+行代码的项目,随着产品的更迭,总会出现这样的问题。在对某些业务逻辑或者功能进行添加或者修改的时候(尤为是通用逻辑),这些通用的逻辑或者组件每每会牵扯到一些其余地方的问题。因为测试人员受限,咱们很难在完成一个模块单元后,对全部功能从新测试一遍。
同时,因为环境及数据的区别,(以及在开发过程当中对代码完备性的疏忽),代码会在某些特殊数据的解析和和展现上出现问题,在开发和测试中很难去发现。总的来讲,咱们但愿有一个这样的工具,帮咱们解决上述几个问题:前端
其中,最重要的问题,就是将测试代码与功能解耦,避免每次迭代和修改都须要追加新的测试用例。咱们如何作到这一点呢?首先咱们来梳理下测试平台的功能。git
因为咱们的平台主要是进行数据展现,因此咱们在测试过程当中,主要以平常的展现数据为重心便可,针对一些复杂的表单操做先不予处理。针对上述的几个问题,咱们针对自动化测试工具的功能以下:github
根据以上的梳理,咱们能够把整个应用分为几个测试单元api
经过这样的划分,咱们针对各个单元进行具体的测试逻辑书写用例,这样就能够避免再添加新功能和页面时,频繁对测试用例进行修改了。cookie
带着上面咱们的需求,咱们来看下Puppeteer的功能和特性,是否可以知足咱们的要求。网络
文档地址less
Puppeteer是一个Node库,它提供了一个高级 API 来经过 DevTools 协议控制 Chromium 或 Chrome。Puppeteer 默认以 headless 模式运行,可是能够经过修改配置文件运行“有头”模式。dom
咱们可使用Puppeteer完成如下工做:async
咱们来经过一些小案例,来介绍他们的基本功能:工具
puppeteer能够建立page实例,并使用goto方法进行页面访问,page包含一系列方法,能够对页面进行各类操做。
(async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); // ba认证 await page.authenticate({ username, password }); // 访问页面 await page.goto('https://example.com'); // 进行截图 await page.screenshot({path: 'example.png'}); await browser.close(); })();
首先,对于SPA(单页面应用),咱们都知道,当页面进入后,客户端代码才开始进行渲染工做。咱们须要等到页面内容渲染完成后,再进行对应的操做。咱们有如下几种方法来使用
puppeteer针对页面的访问,切换等,提供了waitUntil参数,来肯定知足什么条件才认为页面跳转完成。包括如下事件:
经过waitUnitl,咱们能够当页面请求都完成以后,肯定页面已经访问完成。
waitFor方法能够在指定动做完成后才进行resolve
// wait for selector await page.waitFor('.foo'); // wait for 1 second await page.waitFor(1000); // wait for predicate await page.waitFor(() => !!document.querySelector('.foo'));
咱们能够利用waitForSelector方法,当登陆框渲染成功后,才进行登陆操做
// 等待密码输入框渲染 await page.waitFor('#password'); // 输入用户名 await page.type('input#username', "username"); // 输入密码 await page.type('input#password', "testpass"); // 点击登陆按钮 await Promise.all([ page.waitForNavigation(), // 等跳转完成后resolve page.click('button.login-button'), // 点击该连接将间接致使导航(跳转) ]); await page.waitFor(2000) // 获取cookies const cookies = await page.cookies()
主要利用到page实例的选择器功能
const table = await page.$('.table') const links = await table.$$eval('a.link-detail', links => links.map(link => link.href) ); // 循环访问links ...
puppeteer能够监听在页面访问过程当中的报错,请求等等,这样咱们就能够捕获到页面的访问错误并进行上报啦,这也是咱们进行测试须要的基本功能~
// 当发生页面js代码没有捕获的异常时触发。 page.on('pagerror', () => {}) // 当页面崩溃时触发。 page.on('error', () => {}) // 当页面发送一个请求时触发 page.on('request') // 当页面的某个请求接收到对应的 response 时触发。 page.on('response')
经过以上的几个小案例,咱们发现Puppeteer的功能很是强大,彻底可以知足咱们以上的对页面进行自动访问的需求。接下来,咱们针对咱们的测试单元进行个单元用例的书写
经过咱们上面对测试单元的规划,咱们能够规划一下咱们的测试路径
访问网站 -> 登录 -> 访问页面1 -> 进行基本单元测试 -> 获取详情页跳转连接 -> 依次访问详情页 -> 进行基本单元测试
-> 访问页面2 ...
因此,咱们能够拆分出几个大类,和几个测试单元,来进行各项测试
// 包含基本的测试方法,log输出等 class Base {} // 详情页单元,进行一些基本的单元测试 class PageDetal extends Base {} // 页面单元,进行基本的单元测试,并获取并依次访问详情页 class Page extends PageDetal {} // 进行登陆等操做,并依次访问页面单元进行测试 class Root extends Base {}
同时,咱们如何在功能页面变化时,跟踪到测试的变化呢,咱们能够针对咱们测试的功能,为其添加自定义标签test-role,测试时,根据自定义标签进行测试逻辑的编写。
例如针对时间切换单元,咱们作一下简单的介绍:
// 1. 获取测试单元的元素 const timeSwitch = await page.$('[test-role="time-switch"]'); // 若页面没有timeSwitch, 则不用进行测试 if (!timeSwitch) return // 2. time switch的切换按钮 const buttons = timeSwitch.$$('.time-switch-button') // 3. 对按钮进行循环点击 for (let i = 0; i < buttons.length; i++) { const button = buttons[i] // 点击按钮 await button.click() // 重点! 等待对应的内容出现时,才认定页面访问成功 try { await page.waitFor('[test-role="time-switch-content"]') } catch (error) { reportError (error) } // 截图 await page.screenshot() }
上面只是进行了一个简单的访问内容测试,咱们能够根据咱们的用例单元书写各自的测试逻辑,在咱们平常开发时,只须要对须要测试的内容,加上对应的test-role便可。
根据以上的功能划分,咱们很好的将一整个应用拆分红各个测试单元进行单元测试。须要注意的是,咱们目前仅仅是对页面的可访问性进行测试,仅仅验证当用户进行各类操做,访问各个页面单元时页面是否会出错。并无对页面的具体展现效果进行测试,这样会和页面的功能内容耦合起来,就须要单独的测试用例的编写了。