搭建jest+gitlab-ci前端自动化测试环境

5,525 阅读3分钟
原文链接: blog.jajabjbj.top

用 jest + gitlab-ci 搭建前端UI自动化测试环境

前言

下面以 github创建仓库为例 测试领域中比较麻烦的就是前端UI测试
gitlab地址: gitlab.com/yunqiangwu/…

前置要求

  • 拥有gitlab账号(公司内部的也可以)
  • gitlab 已经配置了 CI Runner(具体方法百度)

功能实现效果

  • 修改过测试用例后push触发自动测试
  • 测试完成后生成测试报告
  • 测试报告部署到 git pages 上
  • 发送邮件通知测试结果,以及报告查看链接

先看下我在实际项目中的效果,下面是我收到的自动化测试完成后的邮件:

预览地址
自动化测试报告
测试覆盖率报告

创建项目

mkdir devops-test
cd devops-test
npm init # 初始化 `node` 项目 
# 这里会要你输入信息,节约时间,一路回车
# 下面的操作我用的是cnpm 
cnpm i jest jest-report nightmare --save
cnpm i babel-jest babel-plugin-import babel-plugin-transform-class-properties babel-plugin-transform-decorators-legacy babel-plugin-transform-runtime 
 babel-preset-env --save-dev
#

重点依赖库介绍:

  • jest 测试框架
  • nightmare 浏览器运行库
  • jest-report 测试报告生产库

配置jest环境

  1. package.jsonscripts 中叫如 test 命令

    {
          "name": "devops-test",
          "version": "1.0.0",
          "description": "",
          "main": "index.js",
          "scripts": {
            "test": "jest e2e.js"
          },
          "author": "",
    
          // ...
      
     }
    
  2. 创建jasmine配置文件 项目根目录/tests/jasmine.js 配置测试超时时间

    jasmine.DEFAULT_TIMEOUT_INTERVAL = 12000; // 只有这一行内容
    
  3. package.json 中添加两个字段 babeljest ,这样就能跑 es6 的代码

    {
      "name": "devops-test",
      "version": "1.0.0",
      
      // ...
        
        "babel": {
        "presets": [
          "env"
        ],
        "plugins": [
          "transform-decorators-legacy",
          "transform-class-properties",
          "transform-es2015-destructuring"
        ]
      },
      "jest": {
        "testResultsProcessor": "jest-report",
        "setupTestFrameworkScriptFile": "<rootDir>/tests/jasmine.js",
        "testMatch": [
          "**/?(*.)(spec|test|e2e).js?(x)"
        ]
      },
          
       // ...
          
      }
    

编写测试用例

测试github登录功能<rootDir>/src/e2e/test-github-login.e2e.js

import Nightmare from 'nightmare';
import { helperBuilder } from 'jest-report';

describe('Login', () => {
    let page;
    beforeEach(() => {
        page = Nightmare({ show: true }).viewport(1024, 768);
        page.goto('https://github.com/login');
    });

    // afterEach(() => {
    //     if(page){
    //       page.halt();
    //       page = null;
    //     }
    // });

    it('should login with failure', async() => {
        const reportHelper = helperBuilder('Login', 'should login with failure');

        reportHelper.monitorPage(page);
        await page
            .type('#login_field', 'mockuser')
            .type('#password', 'wrong_password')
            .click('input[type="submit"]')
            .wait('#js-flash-container > div > div'); // should display error
        await page.screenshot(reportHelper.genPicturePath());
        const text = await page.wait('#js-flash-container > div > div')
            .evaluate(() => document.body.innerText)
            .end();
        await page.end();
        expect(text).toContain('Incorrect username or password');
    });

    it('should login successfully', async() => {
        const reportHelper = helperBuilder('Login', 'should login successfully');

        reportHelper.monitorPage(page);
        await page
            .type('#login_field', '正确用户名')
            .type('#password', '正确密码')
            .click('input[type="submit"]')
            .wait('#your_repos > div > div.boxed-group-action > a'); // should display error
        await page.screenshot(reportHelper.genPicturePath());
        const title = await page
            .evaluate(() => document.title)
            .end();
        await page.end();
        
        expect(title).toBe('GitHub');
    });


});

运行测试生成测试报告

1. 执行命令npm test,运行测试脚步

2. 生成测试报告

生成的文件路径默认是 `<rootDir>/dist/test-report`

3. 测试报告效果

添加项目到gitlab

git init
git remote add origin https://gitlab.com/yunqiangwu/devops-test.git
git add .
git commit -m "Initial commit"
git push -u origin master

添加到持续集成

自动部署测试报告 到gitlab pages 网页

cnpm i --save-dev gh-pages

package.json 添加scripts

"scripts": {
  "test": "jest e2e.js",
  "site": "gh-pages -d dist"
},

配置邮件通知工具
<rootDir>/tool/emailnotice.sh

#!/bin/bash
MAIL_FROM='supportman@yeah.net'
# [[ $MAIL_TO == "" ]] && export MAIL_TO=$1
MAIL_TO_ARR_ARG=$1
MAIL_SUBJECT=$2
shift 2
MAIL_CONTENT=$*
MAIL_CONTENT_FILE="/tmp/`/bin/date +%s`.txt"
MAIL_SMTP='smtp://smtp.yeah.net'
MAIL_USER='supportman@yeah.net'
MAIL_PASSWORD='wu950429'

OLD_IFS="$IFS"
#设置分隔符
IFS=","
MAIL_TO_ARR=($MAIL_TO_ARR_ARG)
IFS="$OLD_IFS"
split_1()
{

  MAIL_TO=$1
  echo "发送邮件到:"$1
  #  return 0;
  # create mail content file
  echo "From:${MAIL_FROM}
To:$MAIL_TO
Subject: $MAIL_SUBJECT

$MAIL_CONTENT
"> ${MAIL_CONTENT_FILE}

  # send mail
  curl -s --url "${MAIL_SMTP}" --mail-from "${MAIL_FROM}" --mail-rcpt ${MAIL_TO} --upload-file ${MAIL_CONTENT_FILE} --user "${MAIL_USER}:${MAIL_PASSWORD}"

}

for s in ${MAIL_TO_ARR[@]}
do
split_1 "$s"
done

rm -rf ${MAIL_CONTENT_FILE}

gitlab-ci.yml 配置文件 .gitlab-ci.yml


image: jonneywu/node-xvfb-cnpm

cache:
  key: "$CI_REPOSITORY_URL"
  paths:
    - node_modules
    - dist

stages:
 - test
 - email


test:
   script:
     - if [ ! -d node_modules ]; then cnpm i; fi
     - if [ ! -d node_modules/nightmare ]; then cnpm i nightmare; fi
     - export DISPLAY=':99.0'
     - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
     - npm run test && echo test FAIL
     - kill -9 `ps -ef | grep Xvfb| grep -v grep|awk '{print $2}'`
     - npm run site
   stage: test
   only:
     - master
   artifacts:
     paths:
       - dist

email:
  script:
    - echo email
    - if [[ ! -n $MAIL_TO ]]; then export MAIL_TO=842269153@qq.com,yunqiang.wu@hand-china.com ; fi
    - export root_url=http://`cat CNAME`
    - export test_passed_rate=$(printf "%.2f" `cat dist/test-report/testResultData.json | jq '100*.numPassedTests/.numTotalTests'`)%
    - bash ./tool/emailnotice.sh $MAIL_TO '前端自动化测试' `echo -e "  测试分支: ${CI_COMMIT_REF_NAME} \r\n 提交人:${GITLAB_USER_NAME} \r\n 测试通过率:${test_passed_rate} \r\n 触发来源:${CI_PIPELINE_SOURCE} \r\n 测试环境:mockApi环境 \r\n\r\n预览地址: ${root_url}/index.html \r\n 自动化测试报告:${root_url}/test-report/reporter.html \r\n 测试覆盖率报告: ${root_url}/coverage/lcov-report/index.html \r\n"`
  stage: email
  only:
    - master
  artifacts:
    paths:
      - dist

注意事项

  • react 开发是如果用了css modules的技术开发时,会把 class 名换掉 ,测试框架就无法通过 css selector 找到并操作 Dom元素,在开发时,可以为主要控制的dom节点给iddata-custom 自定义属性,比如用户名输入框、登录按钮,
  • 我这里的介绍的自动化测试,只是测试系统的功能交互是否能正常,如果需要靠自动化测试来判断UI界面的样式对比是符合要求,可能还是要靠人力测试,不过测试的目的保证系统功能成正常使用不出BUG。

参考