致谢
- 本文的技术实现细节,要特别感谢微信团队中的严烨,乃华,冯永鹏等小伙伴的热心帮忙和技术指导。
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/#/)
安装软件
- 下载并安装python 3.8+,地址:https://www.python.org/downloads/release/python-390/
- 下载并安装微信开发者工具,地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
安装minium
- 下载 https://minitest.weixin.qq.com/minium/Python/dist/minium-latest.zip,然后执行
- 第一步:解压,解压路径自己记住(我这里解压到 C:\minium-1.2.6)
- 第二步:切换到文件目录下
- 第三步:执行即可 (如下图)
- 第四步:测试
特别说明
如果新手通过本地解压再执行,遇到以下问题
请直接使用以下命令,即可一次性成功安装
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
-
下载HBuilderX
地址:https://dcloud.io/hbuilderx.html -
安装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/,在线查看报告是否可以正常展示
常见问题
-
(1)端口号被占用
解决方法:https://blog.csdn.net/qq_32265203/article/details/110088489 -
(2)nginx文件夹路径含中文
解决方法:中文路径改为英文路径,或者换一个不包含中文的路径 -
其他错误就详细看log中的描述
解决方法:百度
优化配置
#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
老哥,真机调试的时候,如何跑用例,在真机上能看到
为什么我的测试报告中没有小程序日志和网络请求日志
哥,请问这个部署Jenkins后,最后还是在本地调起微信开发者工具跑吗
有大佬知道这个是怎么回事吗,我百度半天都没找到解决方法
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')
啊