精华内容
下载资源
问答
  • 前端自动化测试探索

    2021-02-25 00:12:53
    本文来自于cnblogs,文章主要探讨了前端自动化测试领域的可测试方向和一些开源的工具,并阐述了一些实践经验等。测试是完善的研发体系中不可或缺的一环。前端同样需要测试,你的css改动可能导致页面错位、js改动可能...
  • 一、前端自动化测试   前端自动化测试一般是指是在预设条件下运行前端页面或逻辑模块,评估运行结果。预设条件应包括正常条件和异常条件,以达到自动运行测试过程、减少或避免人工干预测试的目的。在前端自动化...
  • 前端自动化测试实践

    万次阅读 多人点赞 2018-10-31 23:35:49
    通过前端自动化测试,来解放自我

    前言
            我本身就职于某安防企业,正儿八经的传统企业,这两年还有个别称叫“大白马”,懂的人自然懂。做的产品说大一点和物联网沾边吧。服务的客户主要还是以大中型国企和政府机关,所以很多产品的使用群体没有互联网公司来的那么庞大,因此也造成了我们之前许多产品在设计和开发时重功能,抢交付,轻体验的坏毛病,不止是我们公司,我了解到的大多数同行业公司都有相同的毛病。但是互联网飞速发展的今天,这些以前留下来的毛病越来越被客户诟病,用户体验已经成为了产品核心竞争力的一部分。这些年来公司在这一点上也在积极的改变,伴随而来的就是测试越是越来严苛,现在我们有时候看见测试的妹子都是绕着走。这可能也是某种程度上造成我们现在团队单身比例比较高的原因吧<<^^>>。对于我们前端来说工作量就更加重了,每天上午被测试妹子各种打扰,下午参见各种需求分析和UI/UE评审,然后晚上开始写代码,好不容易代码写完了,本以为告一段罗了,可以有时间处理一下个人的问题了。哎!想多了,发测阶段才是噩梦的开始,有时候我们自己也想不通,为什么一到发测的时候各种问题就开始出现了,然后就是各种加班,大家感觉很累,但是又没什么成就感,就是重复的的修改,重复的自测,测得你怀疑人生。有时候真的好羡慕那些搞后端的,他们分分钟就知道程序有没有什么问题,而我们要一遍一遍的去操作页面,填写数据,提交数据,查看页面显示是否异常,哎…都是泪。可能说到这里有很多搞前端的兄弟已经生有同感了。于是,我也开始了走上了自动化测试的道路。

    目录:

    1. 作为一个前端开发人员自动化测试我心中的疑问
    2. 什么让我又坚定不移的走上了自动化测试的道路
    3. 如何进行前端自动化测试

    一 关于前端自动化测试最开始存在的疑问

         其实关于自动化测试,我好早之前就了解过,由于自己写了一段时间的java,后来开始做前端开发,关于前端自动化测试我心中有一直有各种疑问,另一方面工作压力的激增,所以就没有真正的去了解和实践。

         关于疑问主要只有下面七点:

    1. 前端页面是整个应用的入口,他直接面向客户的,他更多时候体现的是与客户的互动和良好的有户体验,这和后端开发是有本质上的区别,后端更加关注的是数据,如何快速,安全的提供数据,存储数据。特别是RESTful风格盛行的当下,前后端的分工更加明确,后端的自动化测试相对以前没有分离时变得更加容易,更多的时候他们只需要保证他们提供给前端的接口安全可靠就行了。
    2. 前端自动化测试需要测试的方面太多, 除了常见的单元测试,前端还需要对UI组件和页面进行测试,来判断输出页面是否达到了交互稿的效果,这一点也是目前前端测试中比较复杂的,一方面现在前端页面展复杂多变,现另外一方面很多时候对输出的页面已经精确到了像素级别了,这你让我们怎么测,好像貌似没有比肉眼更快速,靠谱了的方式了,那这部分工作能不能用程来解决呢?
    3. 需要模拟大量的交互事件,前面我们提到前端做的更多是与用户进行互动,那么自动化测试的时候就需要模拟大量的用户行为。PC端我们需要用代码模拟click,dbclick,mousedown,mouseup,mousemove,drag,input…,移动端我们还需要模拟touch,scroll,gesture…,关键是怎么模拟?
    4. 运行前端的终端设备种类太多,PC,Mobile Phone,Pad…这些意味着我们需要去适配不同的终端,那么在测试的时候我们是不是也需模拟在不同的终端进行测试呢,终端模拟完了是不是还要模拟在不同浏览器下加载我们的应用呢?哎…老瓜疼,老瓜疼。
    5. 性能测试,纳尼?前端还有性能测试,前端不就是将将设计稿和交互稿通过代码实现页面输出就好了吗?怎么还有性能测试?别紧张,所谓性能不过就是页面的加载快慢,白屏时间,页面交互的流畅性。哦!这个自己打开浏览浏览网页,自己体验一下不就好了嘛,实在不行打开F12调试窗口,资源加载快慢一目了然,还要什么自动化测试?怎么自动化测试?自动化测试又能带来什么好处?

          一口气说了5点,完了吗?没有,我前面提到了自己就职于传统行业,那么“传统”二字怎么体现出来呢?接着说

    1. 除了常规的前端技术外,像我们的公司的前端里面业务逻辑重还夹杂着类似OCX控件,NP插件…等等非常规浏览器对象的操作,这部分需要和C++进行交互。那这又怎么测试?
    2. 传统IT企业团队选择技术相对保守,一方面是行业限制,因为有的客户就是要求你的产品必须在IE6上运行,你能怎么着,vue,react…这些现在被吹上天的前端技术你用的着吗?没用。另外一个问题就是开发人员的业务能力,很多这类企业由于自身所处业务的限制,所以在招人的时候,可能更多的也是看学历,当然长相也可以看看,至于业务能力,这些都是可以慢慢培养的嘛!但是,就公司这种传统的技术氛围又能培养出怎么的开发人员呢?大多数人也就是围绕着这些传统的前端技术转圈,一圈又一圈。但是有一点可以肯定,那就是加班的习惯肯定给你培养出来。好多工作一两年的前端开发人员人都只是听过前端自动化测试,但是基本都没有什么实际经验,因此在自动化测试方面,就只有自己去摸索了,完了之后团队还不一定会用这套测试方案,因为交付时间不允许。
    3. 好了,说了这么多,累了,直接跳到最大的问题,那就是时间,不管是传统企业还是互联网大厂,小厂,时间对于开发人员来说永远是不够的,这么短的开发周期,要做这么多的事,开发工作都干不完,哪还有什么时间和精力写自动化测试脚本,能把功能交付了就哦弥陀佛了,至于测试那还是较给测试部门吧。

          综上所有因素,结果就是一个,罢了,务实一点,还是加班手动测吧。

    二 什么让我又坚定不移的走上了自动化测试的道路?

          前面讲了这么多前端实现自动化测试的难点,但是我还是依然觉决定去深入学习前端自动化测试,并积极的在团队中推广。究其原因,总结一下:
            首先,我个人认为无论前端后端,只要是用代码写的,都需要进行自动化测试,测试的全不全我们暂且不说,测试需要伴随整个的开发过程,不能全部将发现问题的时间堆到测试部门介入后,这样一来产品发测的风险会很大,有可能会被打回来,严重影响产品发布。二来,就像我像前面提到的那样,可能发测前各种问题会蜂拥而至,造成自己天天加班,熬夜多了,你懂的。自动化测试可以帮我们提前暴露问题,节约我们手动跑测试用例的时间。
          其次,目前市面上前端自动化测试的方案已经比较成熟了,我们前面提到的那些问题,大部分是可以得到较好解决的,社区里面前端达人们分享了许多关于前端自动化测试的经验。所以在前端自动化测试,在技术上是没有问题的,所以我们需要大胆的去尝试。
            再者,这是前端发展的趋势,我们可以打开目前开源的热门框架,Vue,React,UI组件库iviewelement-ui,以及常用的npm包的源代码看看,他们的目录结构里面绝对都含有自动化测试脚本,可见前端自动化测试不仅仅是花钱秀腿。历史的车轮是滚滚向前的,谁都停不下来,身为前端的我们也不能再是一个切图仔,页面小王子了。互联网技术的发展,用户对于产品的更高要求,产品的快速迭代,这些都要求我们前端开发人员需要具备更高的开发效率,而自动化测试貌似是我们加班之外最好的一个选择了。
          最重要的一点,每次的发测太折磨了。

    三 如何进行前端自动化测试?

    首先,先了解一下前端自动化测试的分类

    1. 单元测试
    2. e2e测试
    3. 视觉回归测试
    4. 性能测试

    单元测试

            对程序中某一块独立的业务模块进行测试,可以是一个小功能,也一个函数,这属于白盒测试。下面是单元测试常用的组合方案:
    1. karma+Jasmine+PhantomJS
    2. karma+mocha+PhantomJS
         &nbsp首先两者没有什么本质的区别,mocha相对于Jasmine出来的要晚一点,可以使用的多种断言库,包括nodejs的assert断言。
            我自己更习惯使用mocha,两种写法很相像的,切换起来也是比较容易的。

    安装:

    npm install karma-cli -g
    

    利用工具自动生成测试配置项

    karma init
    

    然后通过键盘的方向建,现在测试框架的组合

    PS E:\workpace\units> karma init
    
    Which testing framework do you want to use ?
    Press tab to list possible options. Enter to move to the next question.
    > mocha
    
    Do you want to use Require.js ?
    This will add Require.js plugin.
    Press tab to list possible options. Enter to move to the next question.
    > yes
    
    Do you want to capture any browsers automatically ?
    Press tab to list possible options. Enter empty string to move to the next question.
    > PhantomJS
    >
    
    What is the location of your source and test files ?
    You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
    Enter empty string to move to the next question.
    >
    
    Should any of the files included by the previous patterns be excluded ?
    You can use glob patterns, eg. "**/*.swp".
    Enter empty string to move to the next question.
    >
    
    Do you wanna generate a bootstrap file for RequireJS?
    This will generate test-main.js/coffee that configures RequireJS and starts the tests.
    > no
    
    Which files do you want to include with <script> tag ?
    This should be a script that bootstraps your test by configuring Require.js and kicking __karma__.start(), probably your test-main.js file.
    Enter empty string to move to the next question.
    >
    
    Do you want Karma to watch all the files and run the tests on change ?
    Press tab to list possible options.
    > yes
    
    
    Config file generated at "E:\workpace\units\karma.conf.js".
    
    npm install karma chai requirejs karma-mocha karma-requirejs --save-dev
    

    经过上面的步骤就可以开始写测试案例了。

    我在单元测试中测试的有:

    1. 复杂的业务模块处理函数
    //person.js
    class Person{
      constructor(name,age){
         this.name = name;
         this.age = age;
      }
      /*
       * 个人介绍
      */
      introduce(){
        return `大家好,我是${this.name},今年${this.age}岁,我是一名前端开发工程师.` 
      }
      /*
       * 请求个人发表的文章列表
      */
      getArticleList(){
            return new Promise((resolve,reject)=>{
                 setTimeout(()=>{
                     const list = {
                         code:200,
                         data:[{
                            id:'001',
                            name:'前端自动化测试的实践之路',
                         }],
                        desc:'请求成功'
                     }
                     resolve(list)
                 },1000)
            })
      }
    }
    module.exports = Person;
    

          下面开始写测试脚本,一般一个模块写一个测试脚本文件,比如要测前面的Person类,就在test文件中创建person.spec.js测试脚本文件

    //person.spec.js
    const person = require("../src/person.js");
    const expect = require("chai").expect;
    describe('person.js', () => {
      it('1.测试个人介绍函数introduce', () => {
        const name = "王洋洋",age = 26;
        const Person = new person(name,age);
        expect(Person.introduce()).to.be.equal(`大家好,我是${name},今年${age}岁,我是一名前端开发工程师.` )
      })
    })
    
    1. 测试异步请求

          这里我一般用来测试与后台数据交互的接口,现在的web基本上都是前后端分离的开发模式,前后端的数据基都是通过异步请求的方式获取到的,我们在自动化测试的时候有必要模拟所有参数然后对所有的CGI进行测试。这里接着在上面的person.spec.js中的person.js测试套件中写用例,顺便提一下describe定义的就是一个测试套件,it定义的是一个测试用例,一个套件中可以包含多个其他的测试套件或测试用例。下面模拟一个异步请求的测试用例 :

     it('1.测试个人介绍函数introduce', (done) => {
        const name = "王洋洋",age = 26;
        const Person = new person(name,age);
        Person.getArticleList().then(result=>{
            expect(result).to.be.an('object');
            done()
        })
     })
    

    执行启动测试的命令

    PS E:\workpace\units> mocha ./test/person.spec.js
    
    
      person.js
        √ 1.测试个人介绍函数introduce
        √ 1.测试请求个人文章列表的异步请求 (1004ms)
    
    
      2 passing (1s)
    
    PS E:\workpace\units>
    

          可以看到异步请求的测试结果中还顺带有请求响应的实践,这个对后面的性能测试,也是有用 的。当然,真是的业务功能逻辑不可能这样简单,这里只是提到了这两中情况怎么测,具体到真实的业务场景中需要根据具体的业务功能写更加复杂的测试案例。

    1. 测试封装的UI组件

          上面两种测试用例是对js代码块的测试,目前前端提倡组件化,模块化,像什么Vue,React框架在各大公司盛行,我本身就是一个Vue框架的深度用户。这些框架衍生出来了各种UI组件库,比如Vue的iviewelement-ui,React的uxcore…我们打开这些组件的npm包,我们发现他们的目录下面都有一个test文件夹,这里面有关于各个组件的各种测试案例。这里就是我们提到的对于封装的UI组件的单元测试,这个是很有必要的,因为在平时的工作中,我们也会写一些行业内的公用组件来进行重复利用的,这个组件也会在后续不断的迭代,如果没有自动化测试,每次迭代都需要手动把所有功能重新测试一下,想像一下,这需要多少时间,如果有自动化测试,5s中不到可能就OK了。好了,下面我们写一个Vue组件的测试案例,至于为什么是Vue,因为我React不熟…。万事万物都事相同的。其实vue官方脚手架中已经将自动化测试的选项加进入了,我们只需要在初始化项目目录的时候选择就行了

    vue init webpack unit-demo
    
    ? Project name unit-demo
    ? Project description A Vue.js project
    ? Author wangyy <wang839305939@outlook.com>
    ? Vue build standalone
    ? Install vue-router? Yes
    ? Use ESLint to lint your code? No
    ? Set up unit tests Yes
    ? Pick a test runner karma
    ? Setup e2e tests with Nightwatch? Yes
    ? Should we run `npm install` for you after the project has been created? (recommended) npm
    
       vue-cli · Generated "unit-demo".
    
    
    # Installing project dependencies ...
    # ========================
    

    这里与有可能安装依赖失败,如果失败可以重新用cnpm装一次

    cnpm install
    //安装结束后执行单元测试命令
    npm run unit
    
    unit-demo@1.0.0 unit E:\workpace\units\vuetest\unit-demo
    > cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run
    
    30 10 2018 18:26:26.142:INFO [karma]: Karma v1.7.1 server started at http://0.0.0.0:9876/
    30 10 2018 18:26:26.159:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency
    30 10 2018 18:26:26.173:INFO [launcher]: Starting browser PhantomJS
    30 10 2018 18:26:38.168:INFO [PhantomJS 2.1.1 (Windows 8.0.0)]: Connected on socket M5J-odoqXKpQY1WYAAAA with id 51250636
    
      HelloWorld.vue
        √ should render correct contents
    
    PhantomJS 2.1.1 (Windows 8.0.0): Executed 1 of 1 SUCCESS (0.063 secs / 0.03 secs)
    TOTAL: 1 SUCCESS
    
    
    =============================== Coverage summary ===============================
    Statements   : 100% ( 2/2 )
    Branches     : 100% ( 0/0 )
    Functions    : 100% ( 0/0 )
    Lines        : 100% ( 2/2 )
    ================================================================================
    PS E:\workpace\units\vuetest\unit-demo>
    

    hello.vue组件测试用例

    import Vue from 'vue'
    import Hello from '@/components/Hello'
    
    describe('Hello.vue', () => {
      it('should render correct contents', () => {
        const Constructor = Vue.extend(Hello)
        const vm = new Constructor().$mount()
        expect(vm.$el.querySelector('.hello h1').textContent)
          .to.equal('Welcome to Your Vue.js App')
      })
    })
    

    通过用例可以发现,Vue UI组件的测试关键点就是通过创建出dom元素,然后去检测dom元素中的特征值,然后判断该测试用例是否通过,这只是一个思路,具体要测试那些特性,还是要根据组件的复杂度来写案例。

    e2e测试

          也叫端到端测试,从测试目的上说它是用来测试一个应用从头到尾的流程是否和设计时候所想的一样。简而言之,它是从用户的角度出发,认为整个系统都是一个黑盒,只有UI会暴露给用户。从测试实现上来说就是以功能来作为最小测试单元,通过测试脚本模拟用户的行为然后驱动浏览器来自动测试这些功能,比如自登陆,退出,页面跳转等一系列用户和界面的交互的功能。这一点最开始我是处于懵逼状态,因为大多数情况下都是人为的去测试功能点,至于通过测试脚本去驱动浏览器。即使偶尔异想天开想过,也无从下手。当然,自己不知道,不代表别人不知道,当我们打开自动化测试的大门后,你会发现里面好多大牛在这方面已经有很深的耕耘了。我们需要做的就是沿着他们的研究轨迹,不断的去丰富我们在这方面的知识,如果有一天自己有什么想法也可以积极的探索,积极的和大家分享。不多说,还是从vue-cli创建的工程来了解e2e测试
          在初始化工程好后,直接运行

    npm run e2e
    

          开始执行测试,然后我们会开打浏览器打开,了浏览器会显示"正在接受自动换测试软件的控制",测试结束后,浏览器关闭

    PS E:\workpace\units\vuetest\unit-demo> npm run e2e
    
    > unit-demo@1.0.0 e2e E:\workpace\units\vuetest\unit-demo
    > node test/e2e/runner.js
    
    Starting selenium server... started - PID:  19864
    
    [Test] Test Suite
    =====================
    
    Running:  default e2e tests
     √ Element <#app> was visible after 62 milliseconds.
     √ Testing if element <.hello> is present.
     √ Testing if element <h1> contains text: "Welcome to Your Vue.js App".
     √ Testing if element <img> has count: 1
    
    OK. 4 assertions passed. (26.305s)
    
    PS E:\workpace\units\vuetest\unit-demo>
    

    在这里插入图片描述
    开起来有点科幻。

    e2e测试方案目前我接触到的有两种:

    1. Nightwatch+Selenium
    2. PhantomJs + Casperjs

          Nightwatch是一个基于Selenium WebDriver API的e2e自动化测试框架,可以使用js方法与css选择器来编写运行在Selenium服务器上的端到端测试,这种方式会拉起本地的浏览器,就像上面这副图一样,浏览器会显示"正在接受自动换测试软件的控制",当然至于具体打开什么浏览器就要看安装的webdriven是什么了。这种方式另外一个好处就是可以拉起不同浏览器,对不同浏览器进行兼容性测试。

    Nightwatch有两种方式去调起浏览器跑测试

    1. 通过Selenium,调各个浏览器的webDriver,唤起浏览器。这个需要安装java、Selenium、webDriver
    2. 直接通过各个浏览器厂家提供的webDriver驱动浏览器。

          很明显vue-cli初始化工程的时候选择的是第一种方案,具体怎么操作可以去Nightwatch官网看看。

          第二种方案PhantomJs+Casperjs是使用无头浏览器进行测试,PhantomJs我们可以把他看成一个chrome的无头浏览器,Casperjj对phantomjs和webpage模块的封装,让我们可以以链式风格的方式写代码。调用Casperjs相关API来操作应用和浏览器。这种方案特别的地方是可以不用在桌面打开浏览器就可以跑测试案例。具体怎么操作可以去PhantomJs官网看看。
          具体选择哪种方案这个看个人喜好,有的就是要看见实物才放心,那就选择第一种;如果习惯悄悄地干活,那也可以选择第二种,我个人从体验上来说还是更加喜欢第一种,毕竟这种看着别人干活,自己只需要一个命令就搞定的事,还是比较哇咔咔的。

          有了方案后,再来看看这些方案能不能满足e2e的测试需求,大致有那些需求呢?

    1. 和页面的交互,就是模拟各种和页面的交互事件(click,input…),交互完之的页面内容显示是否正确,简单点来说,就是我们平时在哪里手动测试的那些操作,能不能用脚本进行模拟,这也是我们最关心的事。
      答案是完全没问题的,这两种方案都可以模拟各种事件,然后去获取页面内容,然后将内容和自己预想的值进行比较,最后得出测试用例是否通过的结果;
    2. 页面元素是否显示正确,这里需要对元素的属性值和设计稿中的属性值进行对比。
      可以通过获取页面元素以及元素的CSS属性值。去判断这些属性值是否正常,来达到测试目的。

    视觉回归测试

          简单来说就是测试应用整体界面是否达到了UI设计图的要求。
          这个就比较困难了,也是前端自动化测试中最难的部分吧,毕竟这是感官层面上的事。当然也不是没有方案。这里介绍两种方案:
    1. Gemini
    Gemini(https://github.com/gemini-testing/gemini)项目是 Yandex 团队开发的视觉回归测试工具
    2. PhantomCSS
          PhantomCSS(https://github.com/Huddle/PhantomCSS)由 Huddle公司的James Cryer 带领开发团队编写。它依赖于 CasperJS 和 Resemble.JS。
          其实上面两种方式基本原理都是通过截取应用图像然后和基准图像通过像素之间的差异值进行对比,然后得出测试结论,这是在实现的方式和以来的环境他又一定的差异。
          视觉回归测试具体的实现,我们目前还没有针对这两种方案去实践,一方面是最近确实时间上有点紧张,另一方面个人人为,虽然这两种方式能进行自动化测试,但是在实用性上还是存在疑惑的,因为现在的web页面展现的内容相对比较复杂,页面内容也比较丰富。既然有方案就去尝试吧,后面有时间我去实践一下,然后再分享出来和大家共勉。

    性能测试

          前端的性能测试,打开Chrome DevTools就一目了然了:
    在这里插入图片描述
          通过调试窗口,能够清楚的看见页面各种资源的加载情况,看起来比较直观的,但是这还是需要人为的去打开调试窗口,刷新页面,然后通过我们的火眼金睛去发现其中的异常,这和我们所说的自动化测试好像有点不太相符,自动化测试的原则就是,能用代码解决的事,就不要扯其他的,写就完事了。

           先看看前端性能测试技有那些技术指标:

    1. 白屏时间
      用户首次看到网页有内容的时间,即第一次渲染流程完成时间。
    2. 首屏时间:
      用户看到第一屏,即整个网页顶部大小为当前窗口的区域,示完整的时间。
    3. 首资源下载时间
      从开始下载到第一个资源均下载完成的时间,不包括页面绘制时间。
    4. 总资源下载时间
      从开始下载到所有资源均下载完成的时间,不包括页面绘制时间。
    5. 用户可操作时间
      从页面开始加载到用户操作可响应的时间。

          然后根据这些指标来确定测试方案,目前我自己是使用的是 PhantomJs来进行性能测试的
          PhantomJs前端介绍了他是一个Chrome内核的无头浏览器,所谓无头,是指我们在眼再桌面看不见,当然除了获取测试数据外还需要生成一份测试报告,先来看看如何使用phantomjs来获取相关页面加载性能的数据?
    首先安装phantomjs:

    npm install phantomjs
    

    安装好后,我们就可以开始写测试脚本了

    var page = require('webpage').create();
    var loadStartTime =Date.now();
    var pageUrl = 'https://www.taobao.com/';
    var resources = {};
    function analysisLoadTime(page){
        page.evaluate(function() {
            function calculateTime(time){
                return (parseFloat(time)/1000).toFixed(3);
            }
            console.log("页面名称:",document.title);
            console.log("详细耗时:")
            var performance = window.performance.timing;
            var connectEnd = performance.connectEnd
               ,connectStart = performance.connectStart
               ,domComplete=performance.domComplete
               ,domContentLoadedEventEnd=performance.domContentLoadedEventEnd
               ,domContentLoadedEventStart =performance.domContentLoadedEventStart
               ,domInteractive=performance.domInteractive
               ,domLoading = performance.domLoading
               ,domainLookupStart = performance.domainLookupStart
               ,fetchStart = performance.fetchStart
               ,loadEventEnd = performance.loadEventEnd
               ,domainLookupEnd = performance.domainLookupEnd
               ,loadEventStart = performance.loadEventStart
               ,navigationStart = performance.navigationStart
               ,redirectEnd = performance.redirectEnd
               ,redirectStart = performance.redirectStart
               ,requestStart = performance.requestStart
               ,responseEnd = performance.responseEnd
               ,responseStart = performance.responseStart
               ,unloadEventStart = performance.unloadEventStart
               ,unloadEventEnd = performance.unloadEventEnd
               ,secureConnectionStart = performance.secureConnectionStart
            var PromotFotUnloadTime = navigationStart;
            var redirectTime = redirectEnd-redirectStart;
            var AppCacheTime = fetchStart-domInteractive;
            var DNSTime =domainLookupEnd-domainLookupStart
            var TCP_time = connectEnd-connectStart;
            var request_time = responseEnd-requestStart;
            var DomLoad_time =domComplete-domLoading;
            var DomCOntentLoaded_time =domInteractive-domLoading;
            var EventLoad_time = loadEventEnd-loadEventStart
            var operate_time = loadEventEnd-navigationStart;
            var white_time = domLoading-navigationStart
            var first_view_time = domInteractive-navigationStart
            console.log("页面白屏时间:",calculateTime(white_time));
            console.log("首屏时间:",calculateTime(first_view_time));
            console.log("资源请求耗时:",calculateTime(request_time));
            console.log("用户可操作时间:",calculateTime(operate_time));
    
        });    
    }
    page.open(pageUrl, function(status) {
        if(status=="success"){
            var loadFinishTime =(Date.now()-loadStartTime)/1000;
            console.log("页面加载成功,总共耗时["+loadFinishTime+"s]");
            page.evaluate(function() {
                console.log(document.title);
            });
            analysisLoadTime(page)
        }else{
            console.log("页面加载失败")
        }
        phantom.exit();
    });
    page.onConsoleMessage = function(msg){
        console.log(msg);
    }
    page.onResourceRequested = function (req) {
        resources[req.id] = {
            url:req.url,
            startTime:Date.now(),
            endTime:null,
            total:0
        }
    };
    
    page.onResourceReceived = function (res) {
        if(resources[res.id]){
            resources[res.id].endTime = Date.now();
            resources[res.id].total =(resources[res.id].endTime-resources[res.id].startTime)/1000+"s";
            console.log("请求:",res.id,"-->耗时:[",resources[res.id].total,"]s")
        }
       
    };
    
    

          这里面关系相关的API我就不去介绍了,度娘上面很多我就不赘述了。这里主要提一下关键点,就是浏览器自带的window.performance对象,里面包含了当前页面相关性能参数。
          通过phantomjs中加载页面,去获取这个对象,然后对相关数据进行分析就可以得到页面相关的性能参数。当然,每次执行结果会不同,一方面和网络情况有关,另外一方面和缓存有很大关系,这个在写脚本的时候一定要注意到。
          通过上面的的一顿学习,前面我提到的大部分问题都是可以找到比较合适的解决方案,但是还是有一点,就是再浏览器中如果加载有类似OCX,NP插件的话,就测不了。如果谁在这方面有比较好的方案,可以@一下我,相互交流一下,我也会在空余时间继续浏览这方面的测试方案。

    总结

          其实我对前端自动化测试最开始真像我前面提到的那些问题一样,一脸懵逼,但是通过不断的去学习和实践,现在对自动测试已经有了一个比较深刻的认识,面对不同的测试场景,也能想到相关的测试方案。希望这篇文章对前端自动化测试感兴趣而目前又有点懵逼的朋友有一点帮助。关键的一点还是要自己去实践,光看是弄不明白的。请忽略“通假字”。

    展开全文
  • 主要介绍了详解Puppeteer前端自动化测试实践,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 前端自动化测试代码,详细讲解,前端晋级必备
  • 一起来了解下前端自动化测试吧,让测试更有效率,代码质量更有保障。 课程大纲 一.自动化测试基础知识 为什么写测试 测试分层 - 测试金字塔 两种测试风格 Mock 分类 测试要素、关注点 二.实战 测试框架选型 简单...
  • 什么是前端自动化测试? 前端编写一段js 去运行生产中的js代码,我们预期会出现的结果与实际出现的结果是否相等,在上线前检测成问题,这样通过代码自动检测,而非人肉点击就是前端自动化测试 前端自动化测试的优势 ...

    下一篇文章
    前端自动化测试(二)TDD与BDD 实todoList
    什么是前端自动化测试?
    前端编写一段js 去运行生产中的js代码,我们预期会出现的结果与实际出现的结果是否相等,在上线前检测成问题,这样通过代码自动检测,而非人肉点击就是前端自动化测试

    前端自动化测试的优势

    ​ 1.更好的代码组织,项目的可维护性增强。
    ​ 2.更小的Bug出现概率,尤其是回归测试中的Bug。
    ​ 3.修改工程质量差的项目,更加安全。
    4.项目具备潜在的文档特性。
    ​ 5.扩展前端知识面。

    学习自动化测试更有利于读懂各种源码,测试用例其实就是源码的文档,他详细告诉了实现的功能

    tips:下列指定版本只是为了,以后学习不冲突

    一、安装

      npm i jest@24.8.0 -D
    

    二、配置jest

    // 初始化配置
    npx jest --init
    

    package.json

    // 配置启动,加上--watchAll会自动监听所有test文件改动并运行 默认是a模式
    // --watch 自动是o模式
    // 配置任意一个,就会默认为该模式,同时在watch Usage中消失
      "scripts": {
        "test": "jest --watchAll" 
      },
    

    jest 命令行使用

    // 按 f 以仅运行失败的测试。
    › Press f to run only failed tests.
    // 按 o 仅运行与已更改文件相关的测试。
     › Press o to only run tests related to changed files.
    //  按 p 以按文件名正则表达式模式进行筛选
    //只想执行那些测试文件就可以用p模式 
     › Press p to filter by a filename regex pattern.
    //  按 t 以按测试名称 regex 模式进行筛选
    // 只想执行那些测试用例就可以用t模式 也叫feilter模式
     › Press t to filter by a test name regex pattern.
    //  按q退出监视
     › Press q to quit watch mode.
    //  按u确定更新快照(只在快照时显示)
     › Press u to update failing snapshots.
    //  按i以交互方式更新失败的快照。
      Press i to update failing snapshots interactively.
    // 按 s 跳过当前用例
       Press s to skip the current test.
    //  按 Enter 可触发测试运行。
     › Press Enter to trigger a test run.
    //  按w展示所有使用方式
     Watch Usage: Press w to show more.
    

    当没有改动或者错误的文件时,再次按对应的就能退出该模式
    按 o的时候报错
    –watch is not supported without git/hg, please use --watchAll
    需要在git中使用,通过git来记录修改的文件所以要安装git并初始化git文件同时需要提交到本地

    1. 查看测试覆盖率
      npx jest --coverage

    在jest.config.js中 添加

     coverageDirectory: "coverage",//生成代码覆盖率报告会放在coverage目录下
    

    会在根目录生成coverage文件夹,点击打开lcov-report 下的 index.html 就能看见图形化界面

    1. jest 默认是commoJs的规范是在node环境下运行,如果想用es6的语法,需要安装babel转换
      在你运行jest时,jest内部封装了,会运行 jest(babel-jest)检测你是否安装了babel-core,就会去取.babelrc的配置,
      运行测试前结合babel先把代码进行一次转化
    cnpm install @babel/core@7.4.5 @babel/preset-env@7.4.5 -D
    

    配置.babelrc

    在根目录新建一个文件.babelrc

    {
      "presets": [
        [
          "@babel/preset-env",{
          "targets":{
            "node":"current"
          }
        }
      ]
      ]
    }
    

    三、初识jest

    我们简单写点测试用例来认识一下jest

    新建一个math.js 用来创建 加减方法

    export function add(x,y){
      return x+y
    }
    
    export function minus(x,y){
      return x-y
    }
    export function multi(x,y){
      return x*y
    }
    

    下面我们就来测试一下这个math.js

    //引入需要测试的方法
    import {add,minus,multi} from './math'
      
    test('测试加法 3+3',()=>{
        //我期望3+3得到6
      expect(add(3,3)).toBe(6)
    })
      
    test('测试减法 3-3',()=>{
      expect(minus(3,3)).toBe(0)
    })
      
    test('测试乘法 3*3',()=>{
      expect(multi(3,3)).toBe(9)
    })
    

    上面的测试是否很简单,没错前端测试就是这样简单明了,

    通过代码去执行方法,或模拟用户行为

    test原理 简单用原生js来实现一下上述代码

    import {add,minus,multi} from './math'
    
    var result = add(3,7);
    var expected = 10;
    if(result!== expected){
      throw Error(`3+7应该等于${expected},但是结果却是${result}`)
    }
    
    var result = minus(3,3);
    var expected = 0;
    if(result!== expected){
      throw Error(`3-3应该等于${expected},但是结果却是${result}`)
    }
    
    
    
    // 实现一个 
    //  expect(add(3,3)).toBe(6)
    //  expect(minus(6,3)).toBe(3)
    function expect (result){
      return {
        toBe: function(actual){
            if(result!==actual){
              throw new Error(`预期值和实际值不相等 预期${actual} 结果却是${result}`)
            }
        }
      }
    }
    //  expect(add(3,3)).toBe(6)
    
    // 进一步完善
    function test (desc,fn){
      try{
        fn()
        console.log(`${desc}通过测试`);
      } catch(e){
        console.log(`${desc}没有通过测试 ${e}`);
      }
    }
    
    test('测试加法 3+3',()=>{
      expect(add(3,3)).toBe(6)
    })
    

    接下来让我们简单的认识一部分jest的匹配器,

    jest匹配器

    
      
    test('测试10与10匹配',()=>{
      //toBe 匹配器
      //类似 object.is  ===
      // 只能匹配值,不能匹配对象等引用
      expect(10).toBe(10)
    })
    
    
    test ('测试对象内容相等',()=>{
      //toEqual 匹配器
      // 能匹配值,对象等引用
      const a = {one:1};
      expect(a).toEqual({one:1})
    })
    
    test ('测试内容为null',()=>{
      //toBeNull 匹配器
      const a = null;
      expect(a).toBeNull();
    })
    
    test ('测试内容为undefined',()=>{
      //toBeUndefined 匹配器
      const a = undefined;
      expect(a).toBeUndefined();
    })
    
    test ('测试内容为defined',()=>{
      //toBeDefined 匹配器
      const a = null;
      expect(a).toBeDefined();
    })
    
    test("测试内容为真",()=>{
      // toBeTruthy 匹配器
      const a = 1;
      expect(a).toBeTruthy()
    })
    test("测试内容为假",()=>{
      // toBeFalsy 匹配器
      const a = 0;
      expect(a).toBeFalsy()
    })
    
    test("不为真",()=>{
      // not 匹配器取反操作
      const a = 1
      expect(a).not.toBeFalsy()
    })
    // 数字相关匹配器
    
    test('count大于9',()=>{
      const count = 10
      expect(count).toBeGreaterThan(9);
    })
    
    test('count小于9',()=>{
      // toBeLessThanOrEqual
      const count = 8
      expect(count).toBeLessThan(9);
    })
    test('count大于等于9',()=>{
      const count = 9
      expect(count).toBeGreaterThanOrEqual(9);
    })
    
    
    // js运算错误示例
    test("0.1+0.2",()=>{
      const a = 0.1
      const b = 0.2
      // expect(a+b).toEqual(0.3)
      /* 
       Expected: 0.3
        Received: 0.30000000000000004
    */
    // 对于浮点型计算匹配需要使用
    // toBeCloseTo
    expect(a+b).toBeCloseTo(0.3)
    })
    
    // String 相关匹配器
    test("str中包含字符",()=>{
        //toMatch 可以是正则表达式
        const str = "www.baidu.com"
        // expect(str).toMatch('baidu')
         expect(str).toMatch(/baid/)
    })
    
    // 数组相关匹配器
    test("数组中包含某一项",()=>{
      const arr = ['a','b','c']
      // 可以set后在匹配
      expect(arr).toContain('a')
    })
    
    // 异常
    const throwNewErrorFunc = ()=>{
      throw new Error('this is a new error')
    }
    test('toThorow',()=>{
      expect(throwNewErrorFunc).toThrow()
      // 如果要填写内容意思就是匹配异常内容相当,也可以是正则表达式
    })
    

    jest中的钩子函数

    大致分为 :

    beforeAll 所有测试用例之前

    beforeEach 每个测试用例执行前都调用

    afterEach 每个测试用例执行之后

    afterAll 所有测试用执行之后

    下面来测试一下执行顺序

    ​ 对于归类分组,你可以手动自己分文件,来归类,也可以用describe来分类,

    每一个describe都是一个单独的作用域,可以作用于,下面的所有的describe,
    同级的互不影响,每个describe都可以拥有独自的钩子函数,执行顺序,先执行外部,再执行内部

    新建一个文件counter.js

    //模拟用于测试的方法
    export default class Counter{
      constructor(){
        this.number = 0
      }
      addOne(){
        this.number+=1
      }
      addTwo(){
        this.number+=2
      }
      minusOne(){
        this.number-=1
      }
      minusTwo(){
        this.number-=2
      }
    }
    

    测试该文件 新建 counter.test.js

    import Counter from "./counter"
    
    // 相同的归类分组
      // 2种方式,一种分文件,一种是用describe分组
    describe('测试counter的相关代码',()=>{ 
      console.log('测试counter的相关代码');
    
      let counter  = null
    beforeAll(()=>{
      // 所有测试用例之前
      console.log('beforeAll');
       
    })
    beforeEach(()=>{
      // 每个测试用例执行前都调用
      console.log('beforeEach');
      counter = new Counter()
    })
    afterEach(()=>{
      // 每个测试用例执行之后
      console.log('afterEach');
    })
    afterAll(()=>{
        // 所有测试用例之后
      console.log('AfterAll');
    })
    
    
        describe('测试增加的代码',()=>{
          beforeEach(()=>{
            console.log('beforeEach to add');
          })
          afterEach(()=>{
            console.log('afterEach to add');
          })
          afterAll(()=>{
            console.log('afterAll to add');
          })
          beforeAll(()=>{
            console.log('beforeAll to add');
          })
          console.log('测试增加的代码');
          test('测试Counter中的addOne方法',()=>{
            console.log('测试Counter中的addOne方法');
            counter.addOne();
            expect(counter.number).toBe(1)
          })
          test('测试Counter中的addTwo方法',()=>{
            console.log('测试Counter中的addTwo方法');
            counter.addTwo();
            expect(counter.number).toBe(2)
          })
        })
    
        describe('测试减少的代码',()=>{
          console.log('测试减少的代码');
          test('测试Counter中的minusOne方法',()=>{
            console.log('测试Counter中的minusOne方法');
            counter.minusOne();
            expect(counter.number).toBe(-1)
          })
          
            test('测试Counter中的minusTwo方法',()=>{
              console.log('测试Counter中的minusTwo方法');
              counter.minusTwo();
              expect(counter.number).toBe(-2)
            })
        })
      })
      // 如果只想执行某一个用例,可以用test.only来修饰 only可以同时存在多个
    /* 
    // 每一个describe都是一个单独的作用域,可以作用于,下面的所有的describe,
      同级的互不影响,每个describe都可以拥有独自的钩子函数,执行顺序,先执行外部,再执行内部
    
    
    */
    /* 
    执行顺序如下:
      console.log counter.test.js:7
        测试counter的相关代码
    
      console.log counter.test.js:44
        测试增加的代码
    
      console.log counter.test.js:58
        测试减少的代码
    
      console.log counter.test.js:12
        beforeAll
    
      console.log counter.test.js:42
        beforeAll to add
    
      console.log counter.test.js:18
        beforeEach
    
      console.log counter.test.js:33
        beforeEach to add
    
      console.log counter.test.js:46
        测试Counter中的addOne方法
    
      console.log counter.test.js:36
        afterEach to add
    
      console.log counter.test.js:23
        afterEach
    
      console.log counter.test.js:39
        afterAll to add
    
      console.log counter.test.js:18
        beforeEach
    
      console.log counter.test.js:60
        测试Counter中的minusOne方法
    
      console.log counter.test.js:23
        afterEach
    
      console.log counter.test.js:27
        AfterAll
    */
    

    jest测试异步代码

    安装 axios

    npm i axios -D
    

    新建文件 fetchData用于模拟异步代码

        import axios from 'axios'
    //该接口返回值
    //{
     // "success": true
    //}
    //回调类型的异步函数
    export const fetchDataCbk = function(fn){
      
      axios.get('http://www.dell-lee.com/react/api/demo.json')
    .then(function(response) {
      fn(response.data)
    })
    }
    //无回调类型的异步函数
    export const fetchData = function(){
      return axios.get('http://www.dell-lee.com/react/api/demo.json')
    
    }
    

    测试文件fetchData.test.js

    import {fetchData,fetchDataCbk} from "./fetchData"
    // 回调类型的异步函数测试
    // 只有执行到done执行才结束
    test('用done来测试返回结果为{success:true}',(done)=>{
      fetchDataCbk((data)=>{
        expect(data).toEqual({
          success: true
        })
        done()
      })
      
    })
    
    // 无回调类型的异步函数测试
    //多种实现方法
    test('测试返回结果为{success:true}',()=>{
      return fetchData().then((res)=>{
        expect(res.data).toEqual({
          success:true
        })
      })
      
    })
    
    test('测试返回结果为{success:true}',async()=>{
     const res = await fetchData()
        expect(res.data).toEqual({
          success:true
        })
      
    })
    test('测试返回结果为{success:true}',()=>{
      return expect(fetchData()).resolves.toMatchObject({
      data:{
        success: true
      }
      })
      
    })
    test('测试返回结果为{success:true}',async()=>{
      await expect(fetchData()).resolves.toMatchObject({
      data:{
        success: true
      }
      })
      
    })
     //测试返回404
    test('测试返回结果为 404',()=>{
      expect.assertions(1);//测试用例必须执行一次
      return fetchData().catch((e)=>{
        expect(e.toString().indexOf('404')!==-1).toBe(true)
      })
      
    })
    test('测试返回结果为 404',()=>{
      return  expect(fetchData()).rejects.toThrow()
      
    })
    

    jest 中的mock

    当我们测试请求时,我们并不需要测试接口返回的数据,接口测试是属于后端的测试了,我们只关心,代码是否正常执行

    而且如果都去请求,那么测试效率会很慢,这个时候我们就需要用mock来模拟ajax请求,不去请求真实的ajax

    新建文件 demo.js

    import Axios from "axios"
    
    export const runCallback  = function (callBack){
      callBack()
    }
    export const createObject  = function (callBack){
      new callBack()
    }
    
    export const getData = function(){
      return Axios.get('/api')
    }
    

    测试 demo.test.js

    import {runCallback,createObject,getData} from './demo'
    import Axios from "axios"
    
      jest.mock('axios') //模拟axios
    //回调的异步函数
    test('测试runCallback', ()=>{
     
      // 可以自由定义返回值
      const func = jest.fn(()=>{
        return '456'
      })
        // 上面等同提出来下面
      // func.mockImplementation(()=>{
      //   return '456'
      // })
    
      // 如果只想返回this
      // func.mockReturnThis()
    
      // func.mockReturnValueOnce('一次返回')
      // func.mockReturnValue('456') 定义返回值
      	runCallback(func)
     // 通过jest.fn 创建的mock函数,可以用toBeCallEd捕获这个函数是否被调用了
      expect(func).toBeCalled()
      // expect(func).toBeCalledWith('value') 每一次调用传入的都是value
      console.log(func.mock);
      /*打印的mock:
       {
          calls: [ [] ], //包含调用时,传递的参数,可以通过判断calls的length来判断调用了几次
          instances: [ undefined ],指func 运行时,this的指向
          invocationCallOrder: [ 1 ],调用的顺序
          results: [ { type: 'return', value: '456' } ] 执行调用的返回值
        }
      */
    })
    test('测试createObject',()=>{
      const fn = jest.fn()
      createObject(fn)
      console.log(fn.mock);
    })
    
    test('测试getData',async()=>{
      // 模拟返回,不会去请求真实的数据
      // mockResolvedValueOnce
      Axios.get.mockResolvedValue({data:'hello'})
      await getData().then((data)=>{
        expect(data).toEqual({data:'hello'})
      })
    })
    

    我们除开上面的模拟axios的方式,我们还可以通过模拟异步函数,通过使用模拟的异步函数来达到不请求axios的效果

    被测试文件 demo.js

    import Axios from "axios"
    
    export const fetchData = function(){
      return Axios.get('/api')
    }
    
    export const getNumber = function(){
      return 123
    }
    

    在同级创建一个_mocks_文件夹

    同样创建一个demo.js来模拟原demo.js 的异步请求

    export const fetchData = ()=>{
      return new Promise((resolved,reject)=>{
        resolved("(function(){return '123'})()")
      })
    }
    

    测试文件 demo.test.js

    jest.mock('./demo.js') //模拟后会去查找__mocks__下的demo.js,而非真实的的demo.js
    // 或者直接将config.js中的automock 改成true 自动开启模拟
    // unmok  不模拟
    import {fetchData} from './demo'
    // 当我们开启模拟时,如果想让模拟文件中异步需要模拟,而同步不需要模拟就需要下面这样引入同步方法
    const {getNumber} = jest.requireActual('./demo.js') //引入真正的demo
    
    
    
    test('测试异步fetchData',async()=>{
      return fetchData().then(data=>{
        console.log(data);
        expect(eval(data)).toEqual('123')
      })
    })
    test('测试同步getNumber',async()=>{
      expect(getNumber()).toBe(123)
    })
    

    快照 snapshot

    故名思意,就是类似拍照一样,给你的代码生成一个副本,当代码有所变动,就去与副本中的代码对比,判断是否需要本次的修改,什么时候使用快照呢,当你的代码基本完善,无需修改时,就可以生成一个快照,以后出现代码修改,就可以通过快照的检测,知道那个文件发生了改动

    测试文件 demo.js

    export const generateConfig= ()=>{
      return {
        sever:"localhost",
        port:8080,
        proxy:8081
      }
    }
    export const generateAnotherConfig= ()=>{
      return {
        sever:"localhost",
        port:8080,
        proxy:8082
      }
    }
    
    export const generateTimeConfig= ()=>{
      return {
        sever:"localhost",
        port:8080,
        proxy:8084,
        time:new Date()
      }
    }
    

    测试文件 demo.test.js

    import {
      generateConfig,
      generateAnotherConfig,
      generateTimeConfig
    } from "./demo";
    
    test("测试generateConfig 函数", () => {
      expect(generateConfig()).toMatchSnapshot();
    });
    //假设,测试一个配置文件,如果你修改了配置文件,如果使用的是toEqual(),
    /*那么每次修改配置,都需要同步修改test,这样很麻烦,使用toMatchSnapshot()
    (快照), 会在根目录生成一个snapshots文件保存运行时,的测试的配置项代码,就
    好像,拍了一个照片,之后就会和对比新快照,和旧快照是否一致,判断测试用例是否
    通过, 
    假设这时修改了配置
     1 snapshot failed from 1 test suite. Inspect your code changes or press `u` to update them.
     你只需打开w操作指令,按u表示,更新快照,就可以再次通过,
     类似于提示你,是否要确实这次修改
     
     当出现多个快照时,如果不想所有快照都更新,想一个一个确认更新,这个时候
     w 中会多出一个 i 模式,让你进入到一个一个确认下,这时再按u就表示确认更新快照
     如果感觉是错的,或者不确定,可以按s跳过该快照用例
     */
    test("测试generateAnotherConfig 函数", () => {
      expect(generateAnotherConfig()).toMatchSnapshot();
    });
    
    // 当配置项中存在new Date() 这种动态变化的参数,就需要配置去忽略它,不然无法通过
    test("测试generateTimeConfig 函数", () => {
      expect(generateTimeConfig()).toMatchSnapshot({
        time: expect.any(Date) //任意date类型都行
      });
    });
    
    //生成行内快照,下面的object就是运行后生成的快照
    // 前置条件需要安装包cnpm i prettier@1.18.2 -D
    // 行内快照 用toMatchInlineSnapshot不会单独生成一个文件,而是把快照直接
    // 生成到函数内,
    test("测试generateAnotherConfig 函数", () => {
      expect(generateAnotherConfig()).toMatchInlineSnapshot(`
        Object {
          "port": 8080,
          "proxy": 8082,
          "sever": "localhost",
        }
      `);
    });
    
    

    jest中的timer

    当我们测试延时器等等时,不可能去等待时间再执行,这样测试效率会极低,所以jest提供了如下方式来快捷的测试timer

    被测试文件 timer.js

    export const timer = (callback)=>{
      
     setTimeout(()=>{
        callback()
        setTimeout(()=>{
          callback()
        },3000)
      },3000);
    }
    

    测试文件 timer.test.js

    import {timer} from './timer'
    
    beforeEach(()=>{
      
        jest.useFakeTimers(); //每个测试用例执行前,初始一下防止影响
      
    })
    test('测试定时器',()=>{
      const  fn = jest.fn()
      timer(fn);
      // jest.runAllTimers(); //让定时器立即执行,与上面的use配对使用
      // jest.runOnlyPendingTimers(); //只执行队列中存在的timer
      jest.advanceTimersByTime(3000)//快进定时器
      expect(fn).toHaveBeenCalledTimes(1) //fn只调用一次
      jest.advanceTimersByTime(3000) //快进是在上一个的基础上,存在多个测试用例时,可能会印象下面的,所以我们需要在运行之前重置一下
      expect(fn).toHaveBeenCalledTimes(2) 
    })
    

    jest中类的mock

    新建模拟类的测试文件

    util.js

     export default class Util{
     	 init(){
    
     	 	}
      	a(){
    
     	 }
      	b(){
    
      	}
    }
    

    新建文件 demo.js

    import Util from './util'
    const demoFunction = (a,b)=>{
      const util = new Util();
      util.a(a)
      util.b(b)
    }
    export default demoFunction
    

    测试文件 demo.test.js

    import demoFunction from './demo'
    import Util from './util'
    
    jest.mock('./util')
    //jest.mock 发现uitl是一个类,会自动把类的构造函数和方法变成jest.fn()
    /* 
      const Util= jest.fn()
      until.prototype.a = jest.fn()
      until.prototype.b = jest.fn()
      如果不满意默认处理,可以自定义在文件__mock__下util.js自行模拟
      如果不是很复杂可以直接传递第二个参数,就会执行第二个参数的代码
      jest.mock('./util',()=>{
         const Util= jest.fn()
      Util.prototype..a = jest.fn()
      Util.prototype..b = jest.fn() 
      return Util
      })
    */
    
    // 这里测试的关注点是是否有执行,如果类里a b 方法很复杂就会很耗性能,而他们执行的结果并非所关心的,所以用mock模拟
    test('测试 demoFunction',()=>{
      demoFunction()
      expect(Util).toHaveBeenCalled() //是否执行过
      expect(Util.mock.instances[0].a).toHaveBeenCalled()
      expect(Util.mock.instances[0].b).toHaveBeenCalled()
    })
    
    
    

    这里引入2种概念 单元测试,和集成测试

    单元测试
    只关注,该单元的代码,对于外部的引入不关心,如果对性能有影响就会用mock
    就想上面的测试demoFunction,我只关心有没有执行过a,b方法并不关心执行的结果

    简单说就是对单一功能的测试

    集成测试
    对单元中所有都测试
    我不仅要执行,同时也关心执行后对该单元的影响

    对多种功能的集合测试

    测试驱动开发

    Test Driven Development (TDD) 测试驱动开发
    开发流程

    1. 编写测试用例
    2. 运行测试,测试用例无法通过测试
    3. 编写代码,使测试用例通过测试
    4. 优化代码,完成开发
    5. 新添加,继续重复上述步骤

    TDD的优势

    1. 长期减少回归bug
    2. 代码质量更好
    3. 测试覆盖率高

    行为驱动开发

    BDD(Behavior Driven Developmen)

    先编写代码,基于用户的行为去编写测试代码

    TDD 与BDD 区别

    TDD:

    1. 先写测试再写代码
    2. 一般结合单元测试使用,是白盒测试
    3. 测试重点在代码
    4. 安全低(重点在代码对与用户交互给人的安全低)
    5. 测试速度快

    BDD:

    1. 先写代码再写测试
    2. 一般结合集成测试使用,是黑盒测试
    3. 测试重点在UI(DOM)
    4. 安全感高(基于用户使用的测试,给人的安全感高)
    5. 测试速度慢

    完成上述基础的学习,那么看懂下面的代码就是轻而易举了

    接下来我们将在vue中使用jest

    vue自动化测试

    创建一个vue项目,勾选上jest,生成一个包含jest的vue文件

    更多请查看 Vue Test Utils 文档

    常用api

    mount
    创建一个包含被挂载和渲染的 Vue 组件的 Wrapper。
    shallowMount  
    和 mount 一样,创建一个包含被挂载和渲染的 Vue 组件的 Wrapper,不同的是被存根的子组件
    wrapper options
    	wrapper.vm
                    这是该 Vue 实例。你可以通过 wrapper.vm 访问一个实例所有的方法和属性。这只存在于 Vue 组件包裹器                  或绑定了 Vue 组件包裹器的 HTMLElement 中
           .contains
    				判断 Wrapper 是否包含了一个匹配选择器的元素或组件。
    		.emitted
    				执行一个自定义事件。返回一个包含由 Wrapper vm 触发的自定义事件的对象。
             .trigger
    				为 WrapperArray 的每个 Wrapper DOM 节点都触发一个事件。
    		.find
    				返回匹配选择器的第一个 DOM 节点或 Vue 组件的 Wrapper。
             .findAll
    				返回所有,类似jquery
             .props   
    				返回 Wrapper vm 的 props 对象。如果提供了 key,则返回这个 key 对应的值
    

    配置 jest.config.js

    module.exports = {
      preset: '@vue/cli-plugin-unit-jest',
        moduleFileExtensions: [ 'js', 'jsx', 'json', 'vue' ], //查找文件的后缀
        transform: { //匹配到对应的后缀文件使用对应的转化
          '^.+\\.vue$': 'vue-jest', //解析vue语法,
          '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',//将静态资源转换为字符
          '^.+\\.jsx?$': 'babel-jest' //es6语法转换es5
        },
        transformIgnorePatterns: [ //不需要转换的文件
          '/node_modules/'
        ],
        moduleNameMapper: { //路径映射,
          '^@/(.*)$': '<rootDir>/src/$1'
        },
        snapshotSerializers: [ //对快照vue语法编译
          'jest-serializer-vue'
        ],
        testMatch: [ //测试文件位置,满足下列规则就当成测试文件
          '**/tests/unit/**/*.(spec|test).(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
        ],
        testURL: 'http://localhost/', //测试下的浏览器地址
        watchPlugins: [ //添加交互选择
          'jest-watch-typeahead/filename',
          'jest-watch-typeahead/testname'
        ]
      }
      
    
    

    如果你是手动按照最开始那种安装jest, 那么 jest-watch-typeahead/filename、 jest-watch-typeahead/testname、jest-serializer-vue、babel-jest、jest-transform-stub、@vue/cli-plugin-unit-jest、vue-jest 这些包就需要你手动安装一下,

    packge.json

    如果你的项目没有git初始化,请使用–watchAll

    接下来我们找到vue的tests 示例 发现和jest不一样,不慌,我们先用另一个写法实现一下,vue的test-utils, 为我们提供了一些用于测试的api可以查看官网,

    import { shallowMount } from '@vue/test-utils' 
    // shallowMount浅渲染,只渲染当前组件,不渲染包含的子组件,适合单元测试,
    // mount 全渲染,适合集成测试
    import HelloWorld from '@/components/HelloWorld.vue'
    
    describe('HelloWorld.vue', () => {
      it('renders props.msg when passed', () => {
        const msg = 'new message'
        //一个 Wrapper 是一个包括了一个挂载组件或 vnode,以及测试该组件或 vnode 的方法。
        //wrapper.vm,可以访问到vue的实例
        const wrapper = shallowMount(HelloWorld, {
          propsData: { msg }
        })
        //返回 Wrapper 的文本内容。渲染的文本中包含msg
        expect(wrapper.text()).toMatch(msg)
      })
    })
    
    

    //模拟一个上述实现

    import Vue from 'vue'
    import HelloWorld from ' @/components/HelloWorld '
    describe( 'HelloWorld.vue', () => {
    	it(' renders props.msg when passed',() =>{
            const root = document. createElement( 'div');
            root. className ='root' ;
            document.body.appendChild( root) ;
            new Vue({
             render: h => h(HelloWorld,{
                 props:{
                     msg: 'dell lee'
                 }
             })
                }).$mount( '.root' )
            consloe.log(document.body.innerHTML);
            expect ( document.getElementsByClassName( 'hello' ). length). toBe(1);
          })
    })
    
    
    

    测试 vuex

    对store进行的单元测试

    store.js

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    const store = new Vuex.Store({
      state:{
        value:1
      },
      mutations:{
        ADD:(state,data)=>{
          state.value = data
        }
      },
      actions:{
        commitAdd:({commit},data)=>{
          commit("ADD")
        }
      }
    })
    
    export default store
    
    import store from '@/store'
    it(`当 store 执行add发生变化`, ()=>{
    const value = 123 ;
    store.commit( ' ADD', value);
    expect( store.state.value).toBe( value);
    })
    
    

    当你的测试文件使用了store,需要在挂载的时候,将store传入,否则store会找不到

    import store from '../../store'
    it(`
    	使用store
    	`,()=>{
        const wrapper = mount(todoList,{store})
    	........
    })
    

    vue中的异步测试

    我们不需要请求真实的的地址,我们只需要在tests文件同级建立一个新文件夹_mocks_下面新建一个axios.js

    axios.js 示例

    export defult get(url){ //模拟get方法
        if (url === '/axios.json'){
            return new promise((resolve)=>{
                const data = [{value:1},{value:2}]
                resolve(data)
            })
        }
    }
    

    在test文件调用mount时会执行,mounted,在执行请求获取数据时,test会优先查看_mocks_ 下面的axios.js 去替换真实的请求

    import store from '../../store'
    it(`
    	1. 用户进入页面时,请求远程数据
    	2. 列表应该展示远程返回的数据
    	`,(done)=>{
        const wrapper = mount(todoList,{store})
        //异步测试,需要使用nextTick和done来等待挂载成功后再执行,jest不会自己等待异步
        wrapper.vm.$nextTick(()=>{
        const listItems = wrapper.findAll('[data-test=item]')
        expect(listItems.length).toBe(2)
             done()
        })
    })
    

    如果存在定时器

    模拟的测试组件

    <template>
    		<div>
                <ul>
                    <li v-for="item in data" data-test="item">{{item}}</li>
       		 </ul>
        </div>
    </template>
    <script>
    export defult {
     data(){
    	return {
    	data:[]
    	}
    }
    method:{
    	getList(){
    	setTimeout(()=>{
    		axios.get('/axios.json').then((res)=>{
    			this.data = res.data
    		})
    	},4000)
    	}
    }
    }
    </script>
        
    

    错误示例

    在测试代码存在异步的代码,jest并不会去等待定时器执行完,会直接忽略,如果需要,需要使用done()

    这样确实可以,但是会等待4秒并不是我们想要的结果

    it(`
    	1. 用户进入页面时,等待4秒
    	2. 列表应该展示远程返回的数据
    	`,(done)=>{
        const wrapper = mount(todoList)
        	setTimeout(()=>{
    		  const listItems = wrapper.findAll('[data-test=item]')
        		expect(listItems.length).toBe(2)
                done()
    	},4000)
       
    })
    

    正确示例

    beforEach(()=>{
    	jest.useFakeTimets() //用于模拟定时器
    })
    it(`
    	1. 用户进入页面时,等待4秒
    	2. 列表应该展示远程返回的数据
    	`,(done)=>{
        const wrapper = mount(todoList)
     	jest.tunAllTimers() //如果遇到timers让他立即执行
         wrapper.vm.$nextTick(()=>{
        const listItems = wrapper.findAll('[data-test=item]')
        expect(listItems.length).toBe(2)
             done()
        })
    })
    

    下一篇文章
    前端自动化测试(二)TDD与BDD 实战todoList

    展开全文
  • 前端自动化前端测试框架,CYPRSS. 后selenium时代测试框架,三驾马车(CYPRESS,testcafe,puppeteer)中一个图书的源代码
  • 1.前端自动化测试 之 视觉测试

    千次阅读 2018-05-04 13:46:56
    前端自动化测试 之 视觉测试 前端测试分类 前端测试主要分五大方向测试,而这五大方向也分很多小方向测试,首先简单的介绍每个方向的概念 界面样式测试 固定界面样式测试:主要针对文字内容不变的区域,...

    前端自动化测试 之 视觉测试

    image

    前端测试分类

    Dmaic Model.png

    前端测试主要分五大方向测试,而这五大方向也分很多小方向测试,首先简单的介绍每个方向的概念

    1. 界面样式测试

      • 固定界面样式测试:主要针对文字内容不变的区域,例如页面的页头,页脚这类结构、内容不变的区域,而测试一般通过截图对比解决。

      • 结构不变界面样式测试:主要针对结构不变的区域,例如新闻区域这类结构不变,内容变化的区域,这类测试一般通过DOM元素对比解决。

      • 计算样式测试:主要针对计算样式不变的区域,这类测试一般通过比较计算样式解决,但是这种测试不推荐,因为测试成本比较大。

    2. 功能测试

      • 服务器数据预期测试:主要针对用户在前端界面进行某种操作后,提交数据给后台后,测试后台能否返回预期的数据

      • 界面功能测试:主要针对用户在前端界面进行某种交互性操作后,测试能否获取预期的功能、界面交互

    3. 多浏览器测试

      • 多浏览器测试:基于界面样式测试、功能测试的基础上来进行不同浏览器的的测试,俗称兼容性测试。
    4. 性能测试

      • 白屏时间:用户浏览器输入网址后至浏览器出现至少1px画面为止。
      • 首屏时间:用户浏览器首屏内所有的元素呈现所花费时间。
      • 页面回归时间:用户浏览器非第一次加载所有的元素呈现所花费时间。
      • 用户可操作时间(dom ready) :网站某些功能可以使用的时间。
      • 页面总下载时间(onload):网站中所有资源加载完成并且可用时间。
    5. 质量测试

    什么样的项目适合自动化测试呢?

    什么样的项目适合自动化测试呢?

    如上图所示,真正工作中无法全部满足以上条件,所以需要作出权衡,一般来说,只需要满足以下几点,就可以对项目开展自动化测试:

    1. 需求稳定,不会频繁变更

      自动化测试最大的挑战就是需求的变化,而自动化脚本本身就需要修改、扩展、debug,去适应新的功能,如果投入产出比太低,那么自动化测试也失去了其价值和意义;

      折中的做法是选择相对稳定的模块和功能进行自动化测试,变动较大、需求变更较频繁的部分用手工测试;

    2. 多平台运行,组合遍历型、大量的重复任务

      测试数据、测试用例、自动化脚本的重用性和移植性较强,降低成本,提高效率和价值;

    3. 软件维护周期长,有生命力

      自动化测试的需求稳定性要求、自动化框架的设计、脚本开发与调试均需要时间,这其实也是一个软件开发过程,如果项目周期较短,没有足够的时间去支持这一过程,那自动化测试也就不需要了;

    4. 被测系统开发较为规范,可测试性强

      主要出于这几点考虑:被测试系统的架构差异、测试技术和工具的适应性、测试人员的能力能否设计开发出适应差异的自动化测试框架;

    什么是UI测试

    对于界面布局,传统的测试都是由人工对比设计图和产品界面。当界面有修改之后,再由人通过肉眼去检查修改(包括正确的和错误的修改),这样即费时而且测试结果又不稳定,因为人工对比测试存在两个巨坑:1.效率低;2.人的不确定性。对于拥有大量复杂界面的Web应用,界面布局的测试的数量巨大,再加上这两个问题,导致这类应用的界面布局测试/回归测试时间很长,成本很高,所以很多基于Agile(敏捷开发)项目基本不可能在迭代周期内高质量的完成其视觉测试。对于每天做一次,那更是不可能完成的任务。

    视觉感知测试/视觉回归测试

    为了解决上面提到的各种问题,视觉感知测试孕育而生。它使用传统的对图片进行二进制比较的办法,结合敏捷迭代开发的理念,产生的一种针对界面布局的自动化测试方法。

    视觉感知测试

    视觉感知测试就是对第一个版本的所有界面进行第一次测试。

    视觉感知测试包含以下几个主要的测试步骤:

    视觉感知测试流程.jpg

    需要注意的是!

    1. 配对URL(忽略hostname)

      通过配对URL,对所有的截图按照相同的URL进行分组。当然有时候会出现新的界面,有时候老的界面会被删除。对于新的界面就需要人工进行首次验证测试 。

    2. 像素级别的图形比较

      对于分组之后的截图进行像素级别的比较并生产差别图。有时候为了降噪,可以只对局部关心的组件进行比较。

    3. 人工查看所有不同

      最后通过人工审查差别图报告完成测试。

    视觉感知测试结果:

    预期(expected)实际(actual)比较结果(diff)
    1diff

    视觉回归测试

    我们认为如果一个界面通过第一次的人工验证并发布之后,它就是一个正确的标准界面,并且是包含了人工测试价值的资产。当下一次测试的时候,这部分价值就应该被保留并重用起来,用于减少新的一次测试的时间,从而实现界面的快速回归测试。

    视觉回归测试包含以下几个主要的测试步骤:
    视觉回归测试流程 -.jpg

    回归和感知测试流程差不多只是差异值要更小一点,并且只有效果图需要替换内容。

    视觉自动测试怎么做?

    要进行视觉自动测试,有三种方式。
    * 第一种是截屏比对(局部、整页)。
    * 第二种通过JavaScript调用window.getComputedStyle()获取计算后的样式并进行断言。
    * 第三种dom结构对比加css样式对比。

    这三种各有明显的优势和不足。 第二种方式强绑定了实现,从而变得可能比较脆弱。 第一种方式离设计太近了,当页面中有可变内容时就会有问题。
    第三种方式,无法进行视觉感知测试结果只能进行视觉回归测试和上一版的dom继续比较差异。

    我更倾向与第一种截图对比;它的测试基于用户所见而不是用户所见的抽象。当然第三种也是非常好的 page-monitor 有兴趣的朋友可以自行了解。为什么第三种那么好为什么不使用呢?因为上面这个库是基于phantomjs并且它的实现方式过于复杂不适合新手玩玩。

    像素对比工具,有哪些?

    名称地址
    PhantomCSShttps://github.com/HuddleEng/PhantomCSS
    GhostStoryhttps://github.com/thingsinjars/Hardy
    Cactushttps://github.com/winston/cactus
    Needlehttps://github.com/python-needle/needle
    CSSCritichttps://github.com/cburgmer/csscritic
    sikulihttp://www.sikuli.org/
    Mogohttp://mogotest.com/
    pixelmatchhttps://github.com/mapbox/pixelmatch
    pixel-diffhttps://github.com/koola/pixel-diff

    好了介绍了那么多,怎么选一个合适的Headless Browser呢?

    Headless Browser???我是视觉测试要无头浏览器干嘛?

    因为有了像素对比工具我们还需要一个浏览器进行截图和设计图进行像素比较。

    比较常见出名的几个Headless Browser,有哪些?

    名称内核地址
    PuppeteerWebkithttps://github.com/GoogleChrome/puppeteer
    PhantomJSWebkithttp://phantomjs.org/
    SlimerJSGeckohttps://github.com/laurentj/slimerjs
    TrifleJSIEhttps://github.com/sdesalas/trifleJS

    PhantomJS 基于 Webkit 内核,不支持 Flash 的播放;SlimerJS 基于火狐的 Gecko 内核,支持 Flash播放,并且执行过程会有页面展示。

    我们这里呢就只讲Webkit内核的,其他的我就不讲了。

    PhantomJS简介:

    PhantomJS 是一个基于webkit的JavaScript API。它使用QtWebKit作为它核心浏览器的功能,使用webkit来编译解释执行JavaScript代码。任何你可以在基于webkit浏览器做的事情,它都能做到。它不仅是个隐形的浏览器,提供了诸如CSS选择器、支持Web标准、DOM操作、JSON、HTML5、Canvas、SVG等,同时也提供了处理文件I/O的操作,从而使你可以向操作系统读写文件等。PhantomJS的用处可谓非常广泛,诸如网络监测、网页截屏、无需浏览器的 Web 测试、页面访问自动化等。

    但是 PhantomJS 因为毕竟不是真实的用户浏览器环境,使用起来还是有不少的诟病。之前一直在使用 PhantomJS ,功能虽然够用,不过和在真实的浏览器里面访问的界面来对比差别还是比较大的。

    Puppeteer简介:

    Puppeteer是Chrome团队开发的一个Node库。它提供了一个高级API来控制无头或完整的Chrome。它通过使用Chrome无界面模式 (Headless Chrome )和DevTools协议的组合来实现这一点。它使用一个更上层的API来封装其功能,让用户界面测试自动化变得轻而易举。
    人们基于Chrome DevTools协议开发了一系列Google Chrome工具。你在浏览器中点击更多工具 ->开发工具,打开的就是DevTools。DevTools协议是DevTools的动力基础,我们现在可以使用Chrome中的DevTools来做更多的事情。

    好了简介讲完了,我们来对比一下这两个Headless Browser的区别。

    截图比较
    代码:

    PhantomJS:

    var page = require('webpage').create();
    
    page.viewportSize = { width: 400, height: 400 };
    page.open("http://localhost:8899/VS", function(status) {
        if (status === "success") {
            page.render("a.jpg");
    
        } else {
            console.log("Page failed to load.");
        }
        phantom.exit(0);
    });
    

    Puppeteer:

    const puppeteer = require('puppeteer');
    
    (async() => {
        const browser = await puppeteer.launch();
        const page = await browser.newPage();
        await page.goto('http://localhost:8899/VS');
    
        await page.setViewport({ width: 400, height: 400 })
    
        //保存图片
        const images = await page.screenshot({ path: 'VS.jpg', fullPage: true, omitBackground: true });
    
        //关闭浏览器
        await browser.close();
    
    })();
    PuppeteerPhantomJSChrome浏览器
    a.jspVS.jspQQ截图20180322154826.png

    浏览器效果(大图):

    image

    好了看到这里已经可以分别出来胜负了。

    使用puppeteer进行视觉感知测试

    我们来做一个简单的dome

    我们这里拿掘金来做一个视觉感知测试的例子。

    1.首先创建项目名字就叫“PerceptionTest”把。

    2.安装依赖

    安装Puppeteer

    npm install puppeteer --save

    像素对比工具我就选我最常用的blink-dif了

    npm install blink-diff --save

    3.依赖安完了我们来创建一个js文件“app.js”

    4.加载依赖

    const puppeteer = require('puppeteer'),//无头浏览器
        BlinkDiff = require('blink-diff'),//像素对比
        imgUrl = __dirname + "/blink-diff_img/";//图片目录

    5.使用puppeteer进行截图

    (async () => {
        //创建puppeteer
        const browser = await puppeteer.launch({ headless: true });
        //new 一个新的tab页面
        const page = await browser.newPage();
        //设置浏览器的尺寸
        await page.setViewport({ width: 1920, height: 945 });
        //打开url
        await page.goto('https://juejin.im/');
        //保存截图
       await page.screenshot({ path: imgUrl + 'Screenshots.png', fullPage: true });
    
        //关闭浏览器
        await browser.close();
    })();

    6.分析页面找到要替换的内容

    为什么要替换内容呢,因为我们UI测试指的是测试界面样式而不是去匹配里面的内容,如果不替换里面的内容那像素对比工具比较出来的相似度肯定很低。所以我们要替换掉内容,要让内容完整统一,我们才好更加精确的去比较差异。

    好了我们来分析一下要替换的内容。
    替换的内容.png

    要替换的内容如下:
    1. 标题
    2. 标签
    3. 作者
    4. 发布时间
    5. 阅读数
    6. 列表图片

    7.替换内容

    puppeteer提供了非常丰富的api,其中有个api叫page.evaluate可以向页面插入一段js。

     await page.evaluate(async () => {
    
            //列表
            var Lists = document.querySelectorAll("div.feed.welcome__feed > ul > li > div > a > div");
            Lists.forEach(function (element, index, array) {
    
                element.querySelector("a.title").innerHTML = "测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试";
    
                //替换标签
                element.querySelector("ul > li.item.category > span").innerHTML = "测试";
    
                //替换作者
                element.querySelector("ul > li.item.username.clickable > div > a").innerHTML = "测试";
    
                //替换发布时间
                element.querySelector("div.info-row.meta-row > ul > li:nth-child(3)").innerHTML = "9999天前";
    
                //替换发布时间
                element.querySelector("div.info-row.meta-row > ul > li:nth-child(4)").innerHTML = "99999999999 次阅读";
    
                //列表图片
                if (element.querySelectorAll("div.lazy.thumb.thumb.loaded").length==1) {
                    element.querySelector("div.lazy.thumb.thumb.loaded").style.background = "#fdedc9";
                } else {
                    var loaded=document.createElement("div");
                    loaded.className=" lazy thumb thumb loaded";
                    loaded.style.background = "#fdedc9";
                    loaded.setAttribute("data-v-b2db8566","");
                    loaded.setAttribute("data-v-009ea7bb","");
                    loaded.setAttribute("data-v-f2ca14b0","");
                    element.appendChild(loaded);
                }
            });
    
        });

    8.使用Blink-Diff进行像素对比较

    const diff = new BlinkDiff({
            imageAPath: imgUrl + 'example.png', // 设计图
            imageBPath: imgUrl + 'Screenshots.png',//页面截图
            //低于其中差异的像素数/ p(默认值:500) - 百分比阈值:1 = 100%,0.2 = 20%
            threshold: 0.02, // 1% threshold
            imageOutputPath: imgUrl + 'Diff.png'//Diff路径
        });
    
        diff.run(function (error, result) {
            if (error) {
                throw error;
            } else {
                console.log(diff.hasPassed(result.code) ? '通过' : '失败');
                console.log('总像素:' + result.dimension);
                console.log('发现:' + result.differences + ' 差异.');
            }
        });

    完整代码:

    const puppeteer = require('puppeteer'),
        BlinkDiff = require('blink-diff'),
        imgUrl = __dirname + "/blink-diff_img/";
    
    (async () => {
        const browser = await puppeteer.launch({ headless: true });
        const page = await browser.newPage();
        await page.setViewport({ width: 1920, height: 945 });
        await page.goto('https://juejin.im/');
    
    
    
        await page.evaluate(async () => {
    
            //列表
            var Lists = document.querySelectorAll("div.feed.welcome__feed > ul > li > div > a > div");
            Lists.forEach(function (element, index, array) {
    
                element.querySelector("a.title").innerHTML = "测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试";
    
                //替换标签
                element.querySelector("ul > li.item.category > span").innerHTML = "测试";
    
                //替换作者
                element.querySelector("ul > li.item.username.clickable > div > a").innerHTML = "测试";
    
                //替换发布时间
                element.querySelector("div.info-row.meta-row > ul > li:nth-child(3)").innerHTML = "9999天前";
    
                //替换发布时间
                element.querySelector("div.info-row.meta-row > ul > li:nth-child(4)").innerHTML = "99999999999 次阅读";
    
                //列表图片
                if (element.querySelectorAll("div.lazy.thumb.thumb.loaded").length==1) {
                    element.querySelector("div.lazy.thumb.thumb.loaded").style.background = "#fdedc9";
                } else {
                    var loaded=document.createElement("div");
                    loaded.className=" lazy thumb thumb loaded";
                    loaded.style.background = "#fdedc9";
                    loaded.setAttribute("data-v-b2db8566","");
                    loaded.setAttribute("data-v-009ea7bb","");
                    loaded.setAttribute("data-v-f2ca14b0","");
                    element.appendChild(loaded);
                }
            });
    
        });
    
        await page.screenshot({ path: imgUrl + 'Screenshots.png', fullPage: true });
    
        const diff = new BlinkDiff({
            imageAPath: imgUrl + 'example.png', // 设计图
            imageBPath: imgUrl + 'Screenshots.png',//页面截图
            threshold: 0.02, // 1% threshold
            imageOutputPath: imgUrl + 'Diff.png'//Diff路径
        });
    
        diff.run(function (error, result) {
            if (error) {
                throw error;
            } else {
                console.log(diff.hasPassed(result.code) ? '通过' : '失败');
                console.log('总像素:' + result.dimension);
                console.log('发现:' + result.differences + ' 差异.');
            }
        });
    
    
        //关闭puppeteer
        await browser.close();
    })();

    差异图

    有差异无差异
    Diff.pngDiff.png

    git完整代码

    git完整代码:https://github.com/my07ke/Perception_test

    好了,欲知后事如何,请听下回分解。

    cs.gif

    点击链接加入群聊【前端|WEB|CSS|Javascript|HTML】:https://jq.qq.com/?_wv=1027&k=5hGQc2J

    参考:

    浅谈UI自动化测试

    视觉感知测试

    展开全文
  • 前端自动化测试的尝试。 利用脚本操作控制浏览器的行为来测试具体业务逻辑。 preview 你可能需要使用全局代理来查看该图: test 测试吧主针对与他相关的吧模块的设置功能。 吧主:平台化测试 吧名:贴吧地区test ...
  • webim 前端自动化测试框架

    千次阅读 2017-11-14 10:04:04
    webim 前端自动化测试框架文档 https://github.com/wytheme/wytheme.github.io/blob/master/raw/react-autotest.md 对应的模板 ...
    展开全文
  • 前端自动化测试(一)jest学习与使用 TDD开发 todoList 在src下创建文件containers 如下 TDD开发 app.vue <template> <div id="app"> <TodoList/> </div> </template> <...
  • React 之 jest 前端自动化测试

    千次阅读 2020-02-06 13:57:46
    为什么要前端自动化测试: 自动化测试可以间接的提供代码的测试,多人协作时相互之间未知逻辑的改动等产生的未知或新问题的预警。有效避免一些未考虑到及低级的错误。 自动化测试需要工作: 自动化测试需要我们...
  • 前端自动化测试(一)

    千次阅读 2019-09-09 17:11:16
    因为可能项目开发周期短根本没有机会写,所以你没有办法体会到前端自动化测试的重要性。 来说说为什么前端自动化测试如此重要! 先看看前端常见的问题: 修改某个模块功能时,其它模块也受影响,很难快速定位bug ...
  • 前端自动化测试的合适怪兽。 Wendigo ( /wɛndɪɡo/ )使用简化了前端和端到端的自动化测试。 使用npm install --save-dev wendigo 。 警告:本文档参考的是Wendigo 2,如果您使用的是Wendigo的早期版本,请转...
  • 前端自动化测试工具——SlimerJS

    千次阅读 2016-07-06 22:46:44
    前端自动化测试工具——SlimerJS 1.基本了解 对于富客户端的Web应用页面,自动登录、页面修改、抓取页面内容、屏幕截图、页面功能测试…面对这些需求,使用后端语言需要花费不少的精力才能实现。此时...
  • 目录web前端自动化测试(基于QT4W框架)一、背景1.1前端测试种类二、技术方案三、核心代码3.1控件封装3.2操作控件方法实现3.3测试用例case3.4断言方法 web前端自动化测试(基于QT4W框架) | 导语 现在基于前端的自动...
  • 新一代前端自动化测试工具:cypress

    千次阅读 2019-05-21 14:59:49
    你还在为前端自动化测试苦恼吗,这里为你推荐一款优秀的主流测试工具!
  • 前面我写了一篇博客是《前端自动化测试工具PhantomJS+CasperJS结合使用教程》其中使用CasperJS不仅可以进行单元测试,还可以进行浏览器测试,是个很不错的工具,今天介绍的工具是Karma+Jasmine+PhantomJS组合的前端...
  • 转载:前端自动化测试探索

    千次阅读 2015-07-11 22:39:52
    前端自动化测试探索 转载地址:http://fex.baidu.com/blog/2015/07/front-end-test/ 背景 测试是完善的研发体系中不可或缺的一环。前端同样需要测试,你的css改动可能导致页面错位、js改动可能导致...
  • 前端自动化测试框架Cypress(二)–第一个脚本 使用vs code打开项目,file–open folder选择项目 在cypress-integration目录下新建你的工程目录,如demo1,在demo1下新建一个baidu.js文件 //baidu.js //孩儿她们爹 ...
  • 前端自动化测试框架Cypress–环境搭建npm安装 参考文档: 官方地址:https://www.cypress.io/,详细的文档介绍:https://docs.cypress.io/guides/overview/why-cypress.html Windows环境安装 推荐使用npm安装,要求...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 141,908
精华内容 56,763
关键字:

前端自动化测试