评论

基于Minium框架的小程序自动化落地实践

研究搭建Minium框架,并基于Minium框架,完成对公司内部产品的自动化落地实践。

致谢

  • 本文的技术实现细节,要特别感谢微信团队中的严烨,乃华,冯永鹏等小伙伴的热心帮忙和技术指导。

Minium框架简介

框架优点
  • 微信小程序官方推出的小程序自动化框架,是为小程序专门开发的自动化框架, 提供了 Python 和 JavaScript 版本。
  • 支持一套脚本,iOS & Android & 模拟器,三端运行
  • 提供丰富的页面跳转方式,看不到也能去得到
  • 可以获取和设置小程序页面数据,让测试不止点点点
  • 支持往 AppSerive 注入代码片段
  • 可以使用 minium 来进行函数的 mock, 可以直接跳转到小程序某个页面并设置页面数据, 做针对性的全面测试
框架缺点
  • 暂不支持H5页面的调试;
  • 暂不支持插件内wx接口调用;
技术选型
  • minium支持Python 和 JavaScript 版本,而且有专门的团队定期维护,遇到问题可以在微信开发者社区进行提问,因此选择了minium。

Minium框架原理

  • minium提供一个基于unittest封装好的测试框架,利用这个简单的框架对小程序测试可以起到事半功倍的效果。
  • 测试基类Minitest会根据测试配置进行测试,minitest向上继承了unittest.TestCase,并做了以下改动:
  • 加载读取测试配置
  • 在合适的时机初始化minium.Minium、minium.App和minium.Native
  • 根据配置打开IDE,拉起小程序项目和或自动打开真机调试
  • 拦截assert调用,记录检验结果
  • 记录运行时数据和截图,用于测试报告生成
  • 使用MiniTest可以大大降低小程序测试成本。

Properties

名称类型默认值说明
  • appminium.AppNoneApp实例,可直接调用minium.App中的方法
  • miniminium.MiniumNoneMinium实例,可直接调用minium.Minium中的方法
  • nativeminium.NativeNoneNative实例,可直接调用minium.Native中的方法

环境搭建

搭建Minium Doc
  • 微信技术团队伙伴已告知,无需进行本地搭建,本地搭建的都是错的并且还是旧的文档)
  • 无需构建,直接访问官方文档(https://minitest.weixin.qq.com/#/)
安装软件
安装minium

特别说明

如果新手通过本地解压再执行,遇到以下问题

请直接使用以下命令,即可一次性成功安装

pip3 install https://minitest.weixin.qq.com/minium/Python/dist/minium-latest.zip


配置微信开发者工具
  • 安装微信开发者工具(我本机使用的版本是1.06.2205312),并打开安全模式: 设置 -> 安全设置 -> 服务端口: 打开

  • 在工具栏菜单中点击设置,选择项目设置,切换到“本地设置”,将调试基础库选择大于2.7.3的库;

  • 开启微信工具安全设置中的 CLI/HTTP (提供了命令行和HTTP两种调用方式)调用功能。

  • 开启被测试项目的自动化端口号

"path/to/cli" auto --project "path/to/project" --auto-port 9420

路径说明

  • path/to/project: 指代填写存放小程序源码的目录地址,文件夹中需要包含有 project.config.json 文件
  • path/to/cli: 指代开发者工具cli命令路径
  • 与下图一致证明开启成功
"C:/Program Files (x86)/Tencent/微信web开发者工具/cli" auto --project "C:/WeChatProjects/miniprogram-1" --auto-port 9420

配置信息

代码结构目录

模拟器Config.json
{
    "project_path": "C:\\WeChatProjects\\miniprogram-1",
    "dev_tool_path": "C:\\Program Files (x86)\\Tencent\\微信web开发者工具\\cli.bat",
    "debug_mode": "debug",
    "test_port": 9420,
    "platform": "ide",
    "app": "wx",
    "assert_capture": true,
    "request_timeout":60,
    "remote_connect_timeout": 300,
    "auto_relaunch": true
}

真机Config.json
{
    "project_path": "C:\\WeChatProjects\\xxx_chinamobile-pmc_migration2\\unpackage\\dist\\build\\mp-weixin",
    "dev_tool_path": "C:\\Program Files (x86)\\Tencent\\微信web开发者工具\\cli.bat",
    "debug_mode": "debug",
    "test_port": 9420,
    "platform": "Android",
    "app": "wx",
    "enable_app_log": true,
    "device_desire": {
        "serial": "d310bf55"
    },
    "assert_capture": true,
    "request_timeout":60,
    "remote_connect_timeout": 300,
    "auto_relaunch": true
}

Suite.json
{
    "pkg_list": [
      {
        "case_list": [
          "test_*"
        ],
        "pkg": "testcase.*_test"
      }
    ]
  }

测试用例

first_test.py

# !/usr/bin/python
# -*- coding: utf-8 -*-

"""
@File    : first_test.py
@Create Time: 2022-06-01 16:17
@Description:
"""


import minium


class FirstTest(minium.MiniTest):
    def test_get_system_info(self):
        sys_info = self.mini.get_system_info()
        print("FirstTest: ", sys_info)
        self.assertIn("SDKVersion", sys_info)

if __name__ == "__main__":
    import unittest
    loaded_suite = unittest.TestLoader().loadTestsFromTestCase(FirstTest)
    result = unittest.TextTestRunner().run(loaded_suite)
    print(result)


主程序入口

run.py

# !/usr/bin/python
# -*- coding: utf-8 -*-

"""
@File    : run.py
@Create Time: 2022-06-01 17:21
@Description:
"""

import os

# 运行执行class文件中的指定用例
cmd0 = "minitest -m testcase.first_test --case test_get_system_info -c config.json -g"
# 运行执行testcase文件中的指定用例
cmd1 = "minitest -m testcase.first_test -c config.json -g"
# 按照suite配置执行用例
cmd2 = "minitest -s suite.json -c config.json -g"

os.system(cmd0)

执行用例

测试报告

测试结果存储在outputs下,运行命令

python -m http.server 12345 -d outputs

然后在浏览器上访问 http://localhost:12345 即可查看报告

下载项目源代码

申请GitLab项目代码管理权限,下载小程序源代码



可选项(注意!此部分内容只适用于我司内部产品)

下载Node.js

地址:http://nodejs.cn/download/current/

下载并安装编译软件HBuilderX

  • 编译源代码

  • 报错的解决方案,进入源代码目录下,执行NPM INSTALL

编译并发布小程序

因为公司的小程序是通过Vue.js进行编写,发布项目需要先通过HBuilderX工具进行编译。





执行测试脚本

输出测试报告

用例执行成功

用例执行失败


用例执行异常


Nginx简介

  • Nginx (engine x) 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru 站点开发的。
  • 它也是一种轻量级的Web服务器,可以作为独立的服务器部署网站(类似Tomcat)。它高性能和低消耗内存的结构受到很多大公司青睐,如淘宝网站架设。

Nginx配置

Minium框架的Nginx配置
 server {
   listen 80;
   server_name  your.domain.com;
 
   location / {
     alias /path/to/dir/of/report;
     index index.html;
   }
 }

Nginx下载

下载Nginx

下载地址:https://nginx.org/en/download.html

Nginx安装部署

  • 下载完成后,解压缩,运行cmd,使用命令进行操作,不要直接双击nginx.exe,不要直接双击nginx.exe,不要直接双击nginx.exe

  • 使用命令到达nginx的加压缩后的目录

cd C:\nginx-1.22.0

  • 启动nginx服务,启动时会一闪而过是正常的
start nginx

  • 打开任务管理器在进程中看不到nginx.exe的进程(双击nginx.exe时会显示在这里),需要打开详细信息里面能看到隐藏的nginx.exe进程

  • 如果都没有可能是启动报错了查看一下日志,在nginx目录中的logs文件夹下error.log是日志文件

  • 修改配置文件,进入解压缩目录,直接文件夹点击进去即可,不需要从dos操作

  • 在conf目录下找到nginx.conf使用txt文本打开即可,找到server这个节点,修改端口号,如果有需求可以修改主页目录没有就不用修改

  • 修改完成后保存,使用以下命令检查一下配置文件是否正确,后面是nginx.conf文件的路径,successful就说明正确了

nginx -t -c /nginx-1.22.0/conf/nginx.conf

  • 如果程序没启动就直接start nginx启动,如果已经启动了就使用以下命令重新加载配置文件并重启
nginx -s reload

  • 关闭nginx服务使用以下命令,同样也是一闪而过是正常的,看一下是否进程已消失即可。
nginx -s stop  #强制停止Nginx服务:常用

nginx -s quit  #优雅地停止Nginx服务(即处理完所有请求后再停止服务)

Nginx常见命令

nginx -s reopen #重启Nginx

nginx -s reload #重新加载Nginx配置文件,然后以优雅的方式重启Nginx

nginx -s stop #强制停止Nginx服务:常用

nginx -s quit #优雅地停止Nginx服务(即处理完所有请求后再停止服务)

nginx -t #检测配置文件是否有语法错误,然后退出

nginx -?,-h #打开帮助信息

nginx -v #显示版本信息并退出

nginx -V #显示版本和配置选项信息,然后退出

nginx -t #检测配置文件是否有语法错误,然后退出

nginx -T #检测配置文件是否有语法错误,转储并退出

在线查看

  • 打开浏览器,访问 http://ip:port/,在线查看报告是否可以正常展示

常见问题

优化配置

#user  nobody;

#==工作进程数,一般设置为cpu核心数
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {

    #==最大连接数,一般设置为cpu*2048
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    
    #==客户端链接超时时间
    keepalive_timeout  65;

    #gzip  on;

    #当配置多个server节点时,默认server names的缓存区大小就不够了,需要手动设置大一点
    server_names_hash_bucket_size 512;

    #server表示虚拟主机可以理解为一个站点,可以配置多个server节点搭建多个站点
    #每一个请求进来确定使用哪个server由server_name确定
    server {
        #站点监听端口
        listen       8800;
        #站点访问域名
        server_name  localhost;
        
        #编码格式,避免url参数乱码
        charset utf-8;

        #access_log  logs/host.access.log  main;

        #location用来匹配同一域名下多个URI的访问规则
        #比如动态资源如何跳转,静态资源如何跳转等
        #location后面跟着的/代表匹配规则
        location / {
            #站点根目录,可以是相对路径,也可以使绝对路径
            root   html;
            #默认主页
            index  index.html index.htm;
            
            #转发后端站点地址,一般用于做软负载,轮询后端服务器
            #proxy_pass http://10.11.12.237:8080;

            #拒绝请求,返回403,一般用于某些目录禁止访问
            #deny all;
            
            #允许请求
            #allow all;
            
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
            #重新定义或者添加发往后端服务器的请求头
            #给请求头中添加客户请求主机名
            proxy_set_header Host $host;
            #给请求头中添加客户端IP
            proxy_set_header X-Real-IP $remote_addr;
            #将$remote_addr变量值添加在客户端“X-Forwarded-For”请求头的后面,并以逗号分隔。 如果客户端请求未携带“X-Forwarded-For”请求头,$proxy_add_x_forwarded_for变量值将与$remote_addr变量相同  
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            #给请求头中添加客户端的Cookie
            proxy_set_header Cookie $http_cookie;
            #将使用代理服务器的主域名和端口号来替换。如果端口是80,可以不加。
            proxy_redirect off;
            
            #浏览器对 Cookie 有很多限制,如果 Cookie 的 Domain 部分与当前页面的 Domain 不匹配就无法写入。
            #所以如果请求 A 域名,服务器 proxy_pass 到 B 域名,然后 B 服务器输出 Domian=B 的 Cookie,
            #前端的页面依然停留在 A 域名上,于是浏览器就无法将 Cookie 写入。
            
           #不仅是域名,浏览器对 Path 也有限制。我们经常会 proxy_pass 到目标服务器的某个 Path 下,
            #不把这个 Path 暴露给浏览器。这时候如果目标服务器的 Cookie 写死了 Path 也会出现 Cookie 无法写入的问题。
            
            #设置“Set-Cookie”响应头中的domain属性的替换文本,其值可以为一个字符串、正则表达式的模式或一个引用的变量
            #转发后端服务器如果需要Cookie则需要将cookie domain也进行转换,否则前端域名与后端域名不一致cookie就会无法存取
        #配置规则:proxy_cookie_domain serverDomain(后端服务器域) nginxDomain(nginx服务器域)
            proxy_cookie_domain localhost .testcaigou800.com;
            
            #取消当前配置级别的所有proxy_cookie_domain指令
            #proxy_cookie_domain off;
            #与后端服务器建立连接的超时时间。一般不可能大于75秒;
            proxy_connect_timeout 30;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

    }
    
  #当需要对同一端口监听多个域名时,使用如下配置,端口相同域名不同,server_name也可以使用正则进行配置
  #但要注意server过多需要手动扩大server_names_hash_bucket_size缓存区大小
  server {
    listen 80;
    server_name www.abc.com;
    charset utf-8;
    location / {
      proxy_pass http://localhost:10001;
    }
  }
  server {
    listen 80;
    server_name aaa.abc.com;
    charset utf-8;
    location / {
      proxy_pass http://localhost:20002;
    }
  }
}

创建数据库和数据表

数据库:XXXXX

数据表:device

存储数据信息

SQL 语句如下

CREATE TABLE `device` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `execid` int(11) NOT NULL,
  `device_number` int(11) DEFAULT NULL,
  `project_name` varchar(255) DEFAULT NULL,
  `case_field` varchar(255) DEFAULT NULL,
  `case_content` varchar(255) DEFAULT NULL,
  `triggertime` datetime DEFAULT NULL,
  `showtime` datetime DEFAULT NULL,
  `offlinetime` datetime DEFAULT NULL,
  `starttime` datetime DEFAULT NULL,
  `endtime` datetime DEFAULT NULL,
  `status` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=gb18030;

创建 Flask 服务

新建 db_func.py

# !/usr/bin/python
# -*- coding: utf-8 -*-

"""
@Author  : Charles
@File    : db_func.py
@Create Time: 2022-06-21 09:18
@Description:
"""

import time
import pymysql



def connect_db():
    url = "127.0.0.1"
    name = "root"
    pwd = "123456"
    dataBase = "XXXXX"
    return pymysql.connect(host=url, port=3306, user=name, passwd=pwd, db=dataBase)

def query_database(db, sql):
    cursor = db.cursor()
    try:
        if isinstance(sql, str):
            cursor.execute(sql)
            result = list(cursor.fetchall())
        else:
            result = []
            for sq in sql:
                cursor.execute(sq)
                result.append(list(cursor.fetchall()))
    except Exception as err:
        result = ''.join(('An db query exception happened: ', str(err)))
    # db.close()  # 关闭数据库连接
    return result

def update_db(db, sql):
    cursor = db.cursor()
    try:
        if isinstance(sql, str):
            cursor.execute(sql)
            db.commit()
        else:
            print('sql 不是一个合格的字符串:{}'.format(sql))
    except Exception as err:
        result = ''.join(('An db update exception happened: ', str(err)))
        db.rollback()
        print(result)

# 数据库数据插入更新
def db_insert(db, sql):
    cursor = db.cursor()
    i = 0
    try:
        cursor.execute(sql)
        db.commit()
        result = 'db insert success'
    except Exception as err:
        db.rollback()
        result = 'An db insert exception happened: ' + str(err) + '    ' + str(i)
    db.close()  # 关闭数据库连接
    return result

def close_db(db):
    try:
        db.close()
    except Exception as err:
        result = ''.join(('An db closed exception happened: ', str(err)))

def get_current_time():
    return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))

新建 project_index.py

# !/usr/bin/python
# -*- coding: utf-8 -*-

"""
@Author  : Charles
@File    : project_index.py
@Create Time: 2022-06-21 09:53
@Description:

"""


import datetime
import time
from flask import *
from common import db_func


app = Flask(__name__)


@app.route('/')
def display():
    """
    最新一轮回归测试的整体页面展示
    :return:
    """
    db = db_func.connect_db()

    # 如果实时数据正常显示,就把设备掉线时间默认设置为"1970-01-01 08:00:00" ,实时数据加载时长=数据显示时间-设备被触发时间
    # 如果设备显示掉线页面,就把实时数据时间默认设置为"1970-01-01 08:00:00" ,实时数据加载时长=设备掉线时间-设备被触发时间
    sql_all = 'SELECT device_number, project_name, case_field, case_content, ' \
              '(CASE WHEN offlinetime ="1970-01-01 08:00:00" THEN TIMESTAMPDIFF(SECOND,triggertime,showtime) ' \
              'WHEN showtime ="1970-01-01 08:00:00" THEN TIMESTAMPDIFF(SECOND,triggertime,offlinetime) else 0 ' \
              'END) as duration, triggertime, showtime, offlinetime, starttime, endtime, status FROM device ' \
              'where execid in (select max(execid) from device) ;'


    # 如果实时数据正常显示,就把设备掉线时间默认设置为"1970-01-01 08:00:00" ,实时数据加载时长=数据显示时间-设备被触发时间
    # 如果设备显示掉线页面,就把实时数据时间默认设置为"1970-01-01 08:00:00" ,实时数据加载时长=设备掉线时间-设备被触发时间
    sql_fail = 'SELECT device_number, project_name, case_field, case_content, ' \
               '(CASE WHEN offlinetime ="1970-01-01 08:00:00" THEN TIMESTAMPDIFF(SECOND,triggertime,showtime) ' \
               'WHEN showtime ="1970-01-01 08:00:00" THEN TIMESTAMPDIFF(SECOND,triggertime,offlinetime) else 0 ' \
               'END) as duration, starttime, endtime, status FROM device ' \
               'where status="fail" and execid in (select max(execid) from device) ;'

    pass_total_duration = 'SELECT CAST(SUM(TIMESTAMPDIFF(SECOND,triggertime,showtime)) AS CHAR) AS duration FROM device ' \
                          'WHERE case_field="当前楼层" AND STATUS="pass" AND TIMESTAMPDIFF(SECOND,triggertime,showtime) <= "10" ' \
                          'AND execid IN (SELECT MAX(execid) FROM device) '

    fail_total_duration = 'SELECT CAST(SUM(TIMESTAMPDIFF(SECOND,triggertime,offlinetime)) AS CHAR) AS duration FROM device ' \
                          'WHERE case_field="当前楼层" AND STATUS="fail" AND TIMESTAMPDIFF(SECOND,triggertime,offlinetime) > "10" ' \
                          'AND execid IN (SELECT MAX(execid) FROM device) '

    device_total_number = 'SELECT COUNT(*) FROM device WHERE case_field="当前楼层" AND execid IN (SELECT MAX(execid) FROM device) '
    device_pass_number = 'SELECT COUNT(*) FROM device WHERE case_field="当前楼层" AND STATUS="pass" AND execid IN (SELECT MAX(execid) FROM device) '
    device_fail_number = 'SELECT COUNT(*) FROM device WHERE case_field="当前楼层" AND STATUS="fail" AND execid IN (SELECT MAX(execid) FROM device) '


    db_res_all = db_func.query_database(db, sql_all)
    de_res_fail = db_func.query_database(db, sql_fail)

    db_pass_total_duration = db_func.query_database(db, pass_total_duration)

    db_device_total_number = db_func.query_database(db, device_total_number)
    db_device_pass_number = db_func.query_database(db, device_pass_number)
    db_device_fail_number = db_func.query_database(db, device_fail_number)
    
    # Average Loading Time
    if db_device_pass_number[0][0] == 0:
        average_loading_time = "%.2f" % 0
    else:
        average_loading_time = "%.2f" % (int(db_pass_total_duration[0][0]) / int(db_device_pass_number[0][0]))
        print("Average_loading_time: ", average_loading_time)

    # Averavge Loading Time Less Than 10s
    if db_device_pass_number[0][0] == 0:
        average_less_than_10s = "%.2f" % 0
    else:
        average_less_than_10s = "%.2f" % (int(db_pass_lessthan_total_duration[0][0])/int(db_device_pass_number[0][0]))
        print("Averavge Loading Time Less Than 10s: ", average_less_than_10s)

    # Percentage of Loading Time Less Than 10s
    if db_device_pass_number[0][0] == 0:
        percentage_less_than_10s = "{:.2%}".format(0)
    else:
        percentage_less_than_10s = "{:.2%}".format(int(db_device_lessthan_pass_number[0][0])/int(db_device_pass_number[0][0]))
        print("Percentage of Loading Time Less Than 10s: ", percentage_less_than_10s)

    # Percentage of Loading Time Over 10s
    if db_device_pass_number[0][0] == 0:
        percentage_over_10s = "{:.2%}".format(0)
    else:
        percentage_over_10s = "{:.2%}".format(int(db_device_over_pass_number[0][0])/int(db_device_pass_number[0][0]))
        print("Percentage of Loading Time Over 10s: ", percentage_over_10s)

    # 获得SQL语句查询内容
    db_res = [average_loading_time, average_less_than_10s, percentage_less_than_10s, percentage_over_10s, db_res_fail, db_res_pass_over_10s, db_res_all]

    if db_res:
        return render_template("/koneview_page.html", content=db_res)
    else:
        pass

if __name__ == "__main__":
    app.run(host='xx.xx.xx.xx', port=5500, debug=True)

创建 HMTL 网页

新建 project_page.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MyProject Monitor Platform</title>
    <link rel="icon" href="https://www.myproject.cn/zh/Images/myproject-logo-76x52_tcm156-8930.png">
    <script src="static/lib/jquery-1.11.1.min.js" type="text/javascript"></script>
    <div class="navi-brand">
        <a class="logo" onclick="zhugeTrack('顶栏按钮点击',{'按钮名称':'LOGO'});" href="https://www.myproject.com/en/" target="_blank"> <img src="/static/images/myprojectLOGO.png" style="text-align: left; width: 265px; height: 50px; position: absolute; left: 62px; top: 30px; margin-left: -60px;margin-top: -20px;"></a>
    </div>

    <style type="text/css">
        .bg{
            background-image: url("https://pic4.zhimg.com/80/v2-052324a9dae7a6feb2e5ddf6f68ad8c6_720w.jpg");
            background-repeat: repeat;
        }
    em{
        color: red;
        font-style: normal;
    }
   table{
        border-spacing: 0;
        width: 100%;
        border: 1px solid black;
   }

   thead th {
       border: 1px solid black;
       /*text-align: center;*/
       width: auto;
       color: blueviolet;

   }
    th{
        border: 1px solid black;
        text-align: left;
        width: auto;
        background-color: #69ABD6;
    }
    td{
        border: 1px solid black;
        text-align: left;
    }
    </style>


</head>
<body class="bg">
    <table style="margin-bottom: -1px" class="left"  id="table_top_header">
        <h1 align="center" style="color:#0071B9 ; font-size:30px">MyProject Monitor Platform</h1>
        <h3 align="left" style="color:#0071B9 ; font-size:20px">Basic Information</h3>
        <tr>
            <th>Owner</th>
            <th>Project Name</th>
            <th>Running Environment</th>
            <th>Running System</th>
            <th>Running Equipment</th>
            <th>Testing Phase</th>
            <th>Monitor Frequency</th>
            <th>Test Report</th>
        </tr>
        <tr>
            <td>Charles</td>
            <td>MyProject</td>
            <td>Production</td>
            <td>Windows 10 64bit</td>
            <td>Wechat DevTools</td>
            <td>Regression Test</td>
            <td>One day/time</td>
            <td><a style="background: #eee4a6" href="http://ip:port/" target="_blank">Minium</a></td>
        </tr>
    </table>

    <table style="margin-bottom: -1px" class="left"  id="table_analysis">
        <tbody>
        <h3 align="left" style="color:#0071B9 ; font-size:20px">LiveData Analysis</h3>
        <tr>
            <th>Average Loading Time(sec)</th>
            <th>Percentage of Loading Time Less Than 10s</th>
            <th>Percentage of Loading Time Over 10s</th>
        </tr>
        <div >
            <div id="ChangelistTable0">
                <tr>
                    <td  class="text-center" >
                        <div>
                            {% if content[2] %}
                                <span style="background: #e8e272"> <a>{{content[2]}}</a> </span>
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[3] %}
                                <span style="background: #7ad9f4"> <a>{{content[3]}}</a> </span>
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[4] %}
                                <span style="background: #e999d4"> <a>{{content[4]}}</a> </span>
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                </tr>
            </div>
        </div>
        </tbody>
    </table>

    <table style="margin-bottom: -1px" class="left"  id="table_header">
        <tbody>
        <h3 align="left" style="color:#0071B9 ; font-size:20px">TestCase Failed</h3>
        <tr>
            <th>No</th>
            <th>Device Number</th>
            <th>Project Name</th>
            <th>Monitor Field</th>
            <th>Live Data</th>
            <th>Duration(s)</th>
            <th>Latest Case Executed Time</th>
            <th>Latest Case Ended Time</th>
            <th>Latest Case Result</th>
        </tr>
        <div >
            <div id="ChangelistTable1">
                {% for i in range(content[1]| length) %}
                <tr>
                    <td>
                        {{ i+1 }}
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[1][i][0] %}
                            {{ content[1][i][0]| safe }}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td>
                        <div>
                            {% if content[1][i][1] %}
                            {{ content[1][i][1]| safe }}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[1][i][2] %}
                            {{ content[1][i][2]| safe }}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[1][i][3] %}
                            {{ content[1][i][3]| safe }}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[1][i][4] %}
                                {% if content[1][i][4]<=10 %}
                                    <span style="background: lightseagreen"> <a>{{content[1][i][4]}}</a> </span>
                                {% else %}
                                    <span style="background: lightcoral"> <a>{{content[1][i][4]}}</a></span>
                                {% endif  %}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[1][i][5] %}
                            {{ content[1][i][5]| safe }}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[1][i][6] %}
                            {{ content[1][i][6]| safe }}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[1][i][7] %}
                                {% if content[1][i][7]=='pass' %}
                                    <span style="background: mediumseagreen"> <a>PASS</a> </span>
<!--                                    <span style="background: #50d2c8"> <a href={{content[0][i][6]}} target="_blank">Pass</a> </span>-->
                                {% elif content[1][i][7]=='fail' %}
                                    <span style="background: orangered"> <a>FAIL</a> </span>
                            <!--        <span style="background: red"> <a href={{content[0][i][6]}} target="_blank">Fail</a> </span>-->
                                {% else %}
                                    <span style="background: sandybrown"> <a>Scheduled</a> </span>
                                {% endif  %}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                </tr>
                {% endfor %}
            </div>
        </div>
        </tbody>
    </table>

    <table style="margin-bottom: -1px" class="left"  id="table_list">
        <tbody>
        <h3 align="left" style="color:#0071B9 ; font-size:20px">TestCase Description</h3>
        <tr>
            <th>No</th>
            <th>Device Number</th>
            <th>Project Name</th>
            <th>Monitor Field</th>
            <th>Live Data</th>
            <th>Duration(s)</th>
            <th>Latest Triggered Time</th>
            <th>Latest Showed Time</th>
            <th>Latest Dropped Time</th>
            <th>Latest Case Executed Time</th>
            <th>Latest Case Ended Time</th>
            <th>Latest Case Result</th>
        </tr>
        <div >
            <div id="ChangelistTable">
                {% for i in range(content[0]| length) %}
                <tr>
                    <td>
                        {{ i+1 }}
                    </td>
                    <td  class="text-center" >
                        <div>{{ content[0][i][0]| safe }}</div>
                    </td>
                    <td>
                        <div>{{ content[0][i][1]| safe }}</div>
                    </td>
                    <td  class="text-center" >
                        <div>{{ content[0][i][2]| safe }}</div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[0][i][3] %}
                            {{ content[0][i][3]| safe }}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[0][i][4] %}
                                {% if content[0][i][4]<=10 %}
                                    <span style="background: lightseagreen"> <a>{{content[0][i][4]}}</a> </span>
                                {% else %}
                                    <span style="background: lightcoral"> <a>{{content[0][i][4]}}</a></span>
                                {% endif  %}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[0][i][5] %}
                            {{ content[0][i][5]| safe }}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[0][i][6] %}
                            {{ content[0][i][6]| safe }}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[0][i][7] %}
                            {{ content[0][i][7]| safe }}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[0][i][8] %}
                            {{ content[0][i][8]| safe }}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[0][i][9] %}
                            {{ content[0][i][9]| safe }}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                    <td  class="text-center" >
                        <div>
                            {% if content[0][i][10] %}
                                {% if content[0][i][10]=='pass' %}
                                    <span style="background: mediumseagreen"> <a>PASS</a> </span>
<!--                                    <span style="background: #50d2c8"> <a href={{content[0][i][6]}} target="_blank">Pass</a> </span>-->
                                {% elif content[0][i][10]=='fail' %}
                                    <span style="background: orangered"> <a>FAIL</a> </span>
                            <!--        <span style="background: red"> <a href={{content[0][i][6]}} target="_blank">Fail</a> </span>-->
                                {% else %}
                                    <span style="background: sandybrown"> <a>Scheduled</a> </span>
                                {% endif  %}
                             {% else %}
                            {% endif  %}
                        </div>
                    </td>
                </tr>
                {% endfor %}
            </div>
        </div>
        </tbody>
    </table>
</body>
</html>

在线查看报告

打开http://IP:PORT/,在线查看网页报告

项目迁移到Jenkins平台

创建一个新的Node节点

节点名称为Application,标签为Monitoring

标签为Monitoring下的所有节点列表

节点Application上的项目列表

节点Monitoring_01上的项目列表

测试机上部署Agent服务

将Jenkins平台上自动生成的jar包,放入对应的测试机上

输入Jenkins平台上自动生成的命令,进行命令行启动

java -jar agent.jar -jnlpUrl http://XX.XX.XXX.XXX:8080/computer/Applications/jenkins-agent.jnlp -secret XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -workDir "C:\JenkinsWorkSpace\247"

验证Agent服务,是否成功连到Jenkins平台上

Jenkins上进行项目的配置部署





通过Publish HTML reports插件,查看报告


通过http://x.x.x.x:xxxx/ ,查看报告

参考资料

https://git.weixin.qq.com/groups/minitest
https://blog.csdn.net/jiangjunsss/article/details/120228371
https://blog.csdn.net/baguenaudier/article/details/124478687
https://blog.csdn.net/weixin_49546967/article/details/119858529
https://minitest.weixin.qq.com/#/minium/Python/readme
https://developers.weixin.qq.com/community/develop/article/doc/0000cae3a58748ed7f2c8975351413
https://www.cnblogs.com/taiyonghai/p/9402734.html
https://www.cnblogs.com/zpcdbky/p/15339213.html
https://blog.csdn.net/qq_32265203/article/details/110088489
https://blog.csdn.net/qq_43813373/article/details/123116268

最后一次编辑于  2022-08-22  
点赞 2
收藏
评论

5 个评论

  • 匕禾页
    匕禾页
    2022-08-29

    老哥,真机调试的时候,如何跑用例,在真机上能看到


    2022-08-29
    赞同 1
    回复 1
    • Charles
      Charles
      2022-09-04
      IDE启用真机调试1.0功能,config里面改成手机的设备信息,就可以了。
      2022-09-04
      回复
  • 安
    2022-12-07

    哥,请问这个部署Jenkins后,最后还是在本地调起微信开发者工具跑吗

    2022-12-07
    赞同
    回复
  • 虚无
    虚无
    2022-07-13

    有大佬知道这个是怎么回事吗,我百度半天都没找到解决方法

    Download error on https://pypi.org/simple/: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:997) -- Some packages may not be found!

    No local packages or working download links found for pyee

    error: Could not find suitable distribution for Requirement.parse('pyee')


    2022-07-13
    赞同
    回复 1
  • 积木魔方h0hg
    积木魔方h0hg
    2022-07-01

    恭喜您获得2022年第二季度小程序云测优秀文章分享者,请加入官方企业微信群,联系MiniTest小助手领取奖励

    2022-07-01
    赞同
    回复
  • 初心莫负韩宝鹏
    初心莫负韩宝鹏
    发表于小程序端
    2022-06-15

    2022-06-15
    赞同
    回复
登录 后发表内容