pytest-html报告修改与汉化
前言
Pytest框架可以使用两种测试报告,其中一种就是使用pytest-html插件生成的测试报告,但是报告中有一些信息没有什么用途或者显示的不太好看,还有一些我们想要在报告中展示的信息却没有,最近又有人问我pytest-html生成的报告,能不能汉化?答案是肯定的,那么今天就教大家如何优化和汉化pytest-html测试报告解决上述问题
生成报告
我们先编写一个简单的测试代码,生成一份原始的测试报告,来看看哪些需要修改
测试代码
test_pytest_html.py
""" ------------------------------------ @Time : 2019/8/28 19:45 @Auth : linux超 @File : test_pytest_html.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : 28174043@qq.com @GROUP: 878565760 ------------------------------------ """ import pytestdef login(username, password):"""模拟登录"""user = "linux超"pwd = "linux超哥"if user == username and pwd == password:return {"code": 1001, "msg": "登录成功", "data": None}else:return {"code": 1000, "msg": "用户名或密码错误", "data": None}test_data = [# 测试数据{"case": "用户名正确, 密码正确","user": "linux超","pwd": "linux超哥","expected": {"code": 1001, "msg": "登录成功", "data": None}},{"case": "用户名正确, 密码为空","user": "linux超","pwd": "","expected": {"code": 1000, "msg": "用户名或密码错误", "data": None}},{"case": "用户名为空, 密码正确","user": "","pwd": "linux超哥","expected": {"code": 1000, "msg": "用户名或密码错误", "data": None}},{"case": "用户名错误, 密码错误","user": "linux","pwd": "linux","expected": {"code": 1000, "msg": "用户名或密码错误", "data": None}} ]class TestLogin(object):@pytest.mark.parametrize("data", test_data)def test_login(self, data):result = login(data["user"], data["pwd"])assert result == data["expected"]if __name__ == '__main__':pytest.main(['-sv', "D:\PythonTest\MyPytestHtml\pytest_html_optimization", "--html", "report.html"])
原始报告
修改Environment
可以看到原始的测试报告里面的Environment中所有的信息和我们的被测系统的环境关系不是很大,主要的信息其实是一些开发环境,那么如果我想添加或者删除一些信息,比如删除Java_Home,添加测试接口地址该如何添加讷?
我们需要在项目的根目录新键contest.py文件,文件中添加如下代码
def pytest_configure(config):# 添加接口地址与项目名称config._metadata["项目名称"] = "Linux超博客园自动化测试项目v1.0"config._metadata['接口地址'] = 'https://www.cnblogs.com/linuxchao/'# 删除Java_Homeconfig._metadata.pop("JAVA_HOME")
修改后的效果
修改Summary
原始的Summary部分只显示了测试用例数及用例执行时间,那么如果我们想在这个位置添加测试人员等一些信息该如何实现?同样在conftest.py文件中添加如下代码
@pytest.mark.optionalhook def pytest_html_results_summary(prefix):prefix.extend([html.p("所属部门: xx测试中心")])prefix.extend([html.p("测试人员: Linux超")])
修改后的效果
修改Results
Results主要展示的是测试结果信息,而且这里也有一些不符合我们实际需求的信息,比如Test列显示的内容很长,它主要是测试用例所在的路径及测试类,测试用例及测试数据拼接而成的,可读性很差,从这个描述根本看不出来我们的测试用例测试的是什么,所以我们希望它能这样显示:测试+用例名称[user:用户名-pwd:密码] ,如:测试用户名正确,密码正确[user:Linux超-pwd:Linux超哥],下面我们就来实现这样的效果
优化Test
先手我们分析一波源码,看一下原始的测试报告的Test列表的信息是怎么生成的。找到我们pytest-html插件的安装位置,打开plugin.py文件,你会发现这样一块代码
1 class TestResult: 2 def __init__(self, outcome, report, logfile, config): 3 self.test_id = report.nodeid 4 if getattr(report, "when", "call") != "call": 5 self.test_id = "::".join([report.nodeid, report.when]) 6 self.time = getattr(report, "duration", 0.0) 7 self.outcome = outcome
第3行,5行,就是报告中Test信息的来源了,test_pytest_html.py::TestLogin::test_login[data0] 这样一条信息,实际是用例的nodeid,而【data0】是测试用例参数化时的每个参数,我之前写过一篇关于参数化方法@pytest.mark.parametrize("data", test_data)的参数ids作用的文章,ids的作用主要就是用来标记测试用例(不知道的可以去看一下那篇文章),增加测试用例执行后输出信息的可读性,因此我们可以使用这个参数来改变【data0】,让它显示我们的测试数据,ok,在我们的测试代码中添加并修改如下代码
# 改变输出结果 ids = ["测试:{}->[用户名:{}-密码:{}-预期:{}]".format(data["case"], data["user"], data["pwd"], data["expected"]) for data in test_data ]class TestLogin(object):# 添加ids参数@pytest.mark.parametrize("data", test_data, ids=ids)def test_login(self, data):result = login(data["user"], data["pwd"])assert result == data["expected"]
修改完之后再次执行我们的测试代码,生成测试报告
很遗憾,中文部分显示的都是乱码,还得继续修改
解决中文乱码
在conftest.py文件中继续添加如下代码
import pytest from py._xmlgen import html@pytest.mark.hookwrapper def pytest_runtest_makereport(item):outcome = yieldreport = outcome.get_result()getattr(report, 'extra', [])report.nodeid = report.nodeid.encode("utf-8").decode("unicode_escape") # 解决乱码
再次执行测试代码,查看报告输出
中文已经可以正常显示了,很开心,但是前面还是有一堆的目录及测试类,我仍然不想要,那么我们继续修改
删除多余部分
要删除前面多余的部分,接下来只能通过修改报告的html了,因为源码中我实在没找到在哪里能够修改这个显示,我们查看一下html报告的页面元素属性
看到这里,你想到用什么方法来修改了嘛?我想到的是使用js代码改变这个元素的innerText, 我们先实验一下,切换到【控制台】依次执行如下代码
// 找到Test列中所有的表格元素 var test_name = document.getElementBysClassName("col-name"); // 修改第1个表格的innerText属性 test_name[0].innerText = test_name[0].innerText.split("\[")[1].split(["\]")[0]; // 以[符号分割一次,再以]分割一次,得到目标字符串
以上带代码只能修改一个表格,所以要修改多个表格只能使用循环了,再次重写js代码
var case_name_td = document.getElementsByClassName("col-name")for(var i = 0; i < case_name_td.length; i++)try{case_name_td[i].innerText = case_name_td[i].innerText.split("\[")[1].split("\]")[0];}catch(err){// 如果表格中没有[]会抛异常,如果抛异常我就显示null,如果你想显示别的东西自己改吧,因为通常只要我们使用参数化就有[]显示case_name_td[i].innerText = "null";}
js代码我们已经写好了, 但是我们要放在哪里讷,又是个问题?通过查看报告的html源码我发现在html代码的开头引入了这样一些代码
在pytest-html插件的源码中,发现插件的resource目录下有一个main.js文件,里面的代码就是上述html代码中的js脚本,一模一样的
而且在main.js中确实存在一个init()方法,通过以上分析,应该不难猜测生成测试报告时,会自动把main.js文件加载到html中,而且是从init()方法开始的,因此我们尝试一下把我们编写的js代码放到init()方法内部实验一下
function init () {reset_sort_headers();add_collapse();show_filters();toggle_sort_states(find('.initial-sort'));find_all('.sortable').forEach(function(elem) {elem.addEventListener("click",function(event) {sort_column(elem);}, false)});// 修改用例报告显示的用例名称 add by linux超var case_name_td = document.getElementsByClassName("col-name")for(var i = 0; i < case_name_td.length; i++)try{case_name_td[i].innerText = case_name_td[i].innerText.split("\[")[1].split("\]")[0];}catch(err){// 如果表格中没有[]会抛异常,如果抛异常我就显示null,如果你想显示别的东西自己改吧,因为通常只要我们使用参数化就有[]显示case_name_td[i].innerText = "null";}};
添加完之后记得保存,再次执行测试代码,查看测试报告
修改后的效果
你没有看错,确实成功了,小小成就感还是有的,哈哈!
删除Links
报告中的links从来没看见有内容过,所以我打算把这一列删除,在contest.py文件中添加如下代码
@pytest.mark.optionalhook def pytest_html_results_table_header(cells):cells.pop(-1) # 删除link列@pytest.mark.optionalhook def pytest_html_results_table_row(report, cells):cells.pop(-1) # 删除link列
修改后的效果
增加失败截图与用例描述
用例添加失败截图和添加一列用例的描述信息,在之前文章中已经说过了,所以你可以通过以下链接去看我之前的文章,错误截图只有web自动化测试才会有哦
https://www.cnblogs.com/linuxchao/p/linuxchao-pytest-report.html
https://www.cnblogs.com/linuxchao/p/linuxchao-pytest-Actual.html
完整的conftest.py代码
仅供参考
conftest.py
汉化报告
最后一步,汉化我们的测试报告,因为都是英文的,可能有的同学不太喜欢或者说领导不太喜欢,接下来就开始我们的汉化之路,汉化还是比较简单的, 只要小心谨慎即可
声明:以下修改的所有文件均存在pytest-html插件的源码目录下
修改plugin.py
plugin.py文件主要用来生成测试报告的html代码的,因此汉化肯定离不开修改这个文件了,接下来一步一步按照我的步骤修改吧(注释部分是源码,紧接着的是我修改后的代码)
# if len(log) == 0: # log = html.div(class_='empty log') # log.append('No log output captured.') if len(log) == 0: # modify by linux超log = html.div(class_='empty log')log.append('未捕获到日志') additional_html.append(log)
# head = html.head( # html.meta(charset='utf-8'), # html.title('Test Report'), # html_css) head = html.head( # modify by linux超html.meta(charset='utf-8'),html.title('测试报告'),html_css)
# outcomes = [Outcome('passed', self.passed), # Outcome('skipped', self.skipped), # Outcome('failed', self.failed), # Outcome('error', self.errors, label='errors'), # Outcome('xfailed', self.xfailed, # label='expected failures'), # Outcome('xpassed', self.xpassed, # label='unexpected passes')] outcomes = [Outcome('passed', self.passed, label="通过"),Outcome('skipped', self.skipped, label="跳过"),Outcome('failed', self.failed, label="失败"),Outcome('error', self.errors, label='错误'),Outcome('xfailed', self.xfailed,label='预期失败'),Outcome('xpassed', self.xpassed,label='预期通过')]
# if self.rerun is not None: # outcomes.append(Outcome('rerun', self.rerun)) if self.rerun is not None:outcomes.append(Outcome('重跑', self.rerun))# summary = [html.p( # '{0} tests ran in {1:.2f} seconds. '.format( # numtests, suite_time_delta)), # html.p('sfsf', # class_='filter', # hidden='true')] summary = [html.p( # modify by linux超'执行了{0}个测试用例, 历时:{1:.2f}秒 . '.format(numtests, suite_time_delta)),html.p('(取消)勾选复选框, 以便筛选测试结果',class_='filter',hidden='true')]
# cells = [ # html.th('Result', # class_='sortable result initial-sort', # col='result'), # html.th('Test', class_='sortable', col='name'), # html.th('Duration', class_='sortable numeric', col='duration'), # html.th('Links')]# modify by linux超 cells = [html.th('通过/失败',class_='sortable result initial-sort',col='result'),html.th('用例', class_='sortable', col='name'),html.th('耗时', class_='sortable numeric', col='duration'),html.th('链接')]
# results = [html.h2('Results'), html.table([html.thead( # html.tr(cells), # html.tr([ # html.th('No results found. Try to check the filters', # colspan=len(cells))], # id='not-found-message', hidden='true'), # id='results-table-head'), # self.test_logs], id='results-table')] results = [html.h2('测试结果'), html.table([html.thead( # modify by linux超html.tr(cells),html.tr([html.th('无测试结果, 试着选择其他测试结果条件',colspan=len(cells))],id='not-found-message', hidden='true'),id='results-table-head'),self.test_logs], id='results-table')]
#html.p('Report generated on {0} at {1} by '.format( html.p('生成报告时间{0} {1} Pytest-Html版本:'.format( # modify by linux超
# body.extend([html.h2('Summary')] + summary_prefix # + summary + summary_postfix) body.extend([html.h2('用例统计')] + summary_prefix # modify by linux超+ summary + summary_postfix)
# environment = [html.h2('Environment')] environment = [html.h2('测试环境')] # modify by linux超
plugin.py文件到这就修改完了,你可以生成报告查看以下效果,接下来修改main.js
修改main.js
/* function add_collapse() {// Add links for show/hide allvar resulttable = find('table#results-table');var showhideall = document.createElement("p");showhideall.innerHTML = '<a href="javascript:show_all_extras()">Show all details</a> / ' +'<a href="javascript:hide_all_extras()">Hide all details</a>';resulttable.parentElement.insertBefore(showhideall, resulttable); */ function add_collapse() { // modify by linux超// Add links for show/hide allvar resulttable = find('table#results-table');var showhideall = document.createElement("p");showhideall.innerHTML = '<a href="javascript:show_all_extras()">显示详情</a> / ' +'<a href="javascript:hide_all_extras()">隐藏详情</a>';resulttable.parentElement.insertBefore(showhideall, resulttable);
修改style.css
最后修改style.css,这个文件主要用来美化测试报告的,如果你对css比较熟悉,可以自定义报告样式
.expander::after {content: " (展开详情)";color: #BBB;font-style: italic;cursor: pointer; } .collapser::after {content: " (隐藏详情)";color: #BBB;font-style: italic;cursor: pointer; }
到目前为止,所有的汉化都已经完成了,接下来我们执行一下测试代码,查看一下报告的效果
最后报告效果
安装汉化版插件
为了方便大家使用,跳过修改源码过程,我已经把汉化版的pytest-html插件源码上传到了我的GitHub https://github.com/13691579846/pytest-html,下面说一下使用方法
方法1
1.如果你已经安装过了pytest-html,请先卸载 pip uninstall pytest-html
2.下载插件源码到本地,git clone https://github.com/13691579846/pytest-html 或者按照下图方法下载
3.打开CMD切换到插件中setup.py文件所在目录,执行如下命令
python setup.py install
方法2
1.如果你未安装过pytest-html插件,请先执行如下命令安装
pip install pytest-html
2.按照方法1的方式下载汉化后的插件,把下载后的插件中的部分分件覆盖到python第三方库目录下pytest-html下的部分文件
方法3
直接下载汉化后的源码,把源码中的pytest_html直接丢到python第三方库存放目录也可
方法4
最后一个方法就是按照我的文章步骤,一步一步修改自己的源码
方法5
下载pytest源码后使用pip install ./pytest-html 安装
这是我整理的《2024最新Python自动化测试全套教程》,以及配套的接口文档/项目实战【网盘资源】,需要的朋友可以下方视频的置顶评论获取。肯定会给你带来帮助和方向。
【已更新】B站讲的最详细的Python接口自动化测试实战教程全集(实战最新版)
相关文章:

pytest-html报告修改与汉化
前言 Pytest框架可以使用两种测试报告,其中一种就是使用pytest-html插件生成的测试报告,但是报告中有一些信息没有什么用途或者显示的不太好看,还有一些我们想要在报告中展示的信息却没有,最近又有人问我pytest-html生成的报告&a…...

react-native从入门到实战系列教程一Swiper组件的使用及bug修复
轮播图,在app中随处可见,这么重要的功能我们怎么可能不学习下在react-native中的实现方式。 依然是第三方组件react-native-swiper 官网地址 https://www.npmjs.com/package/react-native-swiper 组件使用的组件及事件参考官方即可。 实现效果 官网…...
springboot开发的常用注解总结-配置组件类注解
Spring Boot 提供了许多注解,这些注解大大简化了 Spring 应用的配置和开发过程。以下是一些常见的 Spring Boot注解及其作用。 目录 配置组件类 (Configure Component )Configuration解释:Demo Code:更深度使用&#x…...
DataX 最新版本安装部署
1、下载 git clone gitgithub.com:alibaba/DataX.git 2、打包 mvn -U clean package assembly:assembly -Dmaven.test.skiptrue...

【架构】应用保护
这篇文章总结一下应用保护的手段。如今说到应用保护,更多的会想到阿里的sentinel,手段丰富,应用简单。sentinel的限流、降级、熔断,可以自己去试一下,sentinel主要通过配置实现功能,不难。sentinel的简介放…...

从核心到边界:六边形、洋葱与COLA架构的深度解析
文章目录 1 引言2 软件架构3 架构分类4 典型的应用架构4.1 分层架构4.2 CQRS4.3 六边形架构4.4 洋葱架构4.5 DDD 5 COLA架构设计5.1 分层设计5.2 扩展设计5.3 规范设计5.3.1 组件规范5.3.2 包规范5.3.3 命名规范 6 COLA架构总览7 小结 1 引言 软件的首要技术使命:管…...

04-Fastjson反序列化漏洞
免责声明 本文仅限于学习讨论与技术知识的分享,不得违反当地国家的法律法规。对于传播、利用文章中提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本文作者不为此承担任何责任,一旦造成后果请自行承担&…...
ABC365(A-D)未补
A - Leap Year(模拟) 题意:给定一个数字n,如果n不是4的倍数,输出365;如果n是4的倍数但不是100的倍数,输出366;如果n是100的倍数但不是400的倍数,输出365;如果…...
Python用png生成不同尺寸的图标
Kimi生成 from PIL import Imagedef generate_icon(source_image_path, output_image_path, size):with Image.open(source_image_path) as img:# 转换图片为RGBA模式,确保有透明通道if img.mode ! RGBA:img img.convert(RGBA)# 调整图片大小到指定尺寸img img.r…...

1688中国站获得工厂档案信息 API
公共参数 名称类型必须描述keyString是免费申请调用key(必须以GET方式拼接在URL中)secretString是调用密钥api_nameString是API接口名称(包括在请求地址中)[item_search,item_get,item_search_shop等]cacheString否[yes,no]默认y…...

定时任务框架 xxl-job
🍓 简介:java系列技术分享(👉持续更新中…🔥) 🍓 初衷:一起学习、一起进步、坚持不懈 🍓 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正🙏 🍓 希望这篇文章对你有所帮助,欢…...

C/C++关键字大全
目录 一、const 二、static 三、#define 和 typedef 四、#define 和 inline 五、#define 和 const 六、new 和 malloc 七、const 和 constexpr 八、volatile 九、extern 十、前置 和后置 十一、atomic 十二、struct 和 class 一、const 1、const 关键字可用于定义…...

ROS2 Linux Mint 22 安装教程
前言: 本教程在Linux系统上使用。 一、linux安装 移动硬盘安装linux:[LinuxToGo教程]把ubuntu装进移动固态,随时随用以下是我建议安装linux mint版本的清单: 图吧工具箱:https://www.tbtool.cn/linux mint: https://…...

快速将网站从HTTP升级为HTTPS
在当今数字化的世界中,网络安全变的越来越重要,HTTPS(超文本传输安全协议)不仅能够提供加密的数据传输,还能增强用户信任度,提升搜索引擎排名,为网站带来多重益处。所以将网站从HTTP升级到HTTPS…...

Qt程序移植至Arm开发板
目录 1.工具准备: 系统调试工具SecureCRT 虚拟机安装linux(Ubuntu) 交叉编译工具链 ARM 端Qt 环境(Qt-5.7.1) 1) linux processor SD安装 2)交叉编译工具链配置 2.编译Qt工程: 2.0 交叉编译 依赖库源码,生成动…...

删除分区 全局索引 drop partition global index Statistics变化
1.不一定unusable,可以先删除data (index 再删除过程中会更新结构)再drop/truncate. ---------------------- CREATE TABLE interval_sale ( prod_id NUMBER(6) , cust_id NUMBER , time_id DATE ) PARTITION BY RANGE (time_i…...

git回退未commit、回退已commit、回退已push、合并某一次commit到另一个分支
文章目录 1、git回退未commit2、git回退已commit3、git回退已push的代码3.1 直接丢弃某一次的push3.2 撤销push后,不丢弃改动,重新修改后要再次push 4、合并某一次commit到另一个分支 整理几个工作上遇到的git问题。 1、git回退未commit git回退未comm…...

yolov8pose 部署rknn(rk3588)、部署地平线Horizon、部署TensorRT,部署工程难度小、模型推理速度快,DFL放后处理中
特别说明:参考官方开源的yolov8代码、瑞芯微官方文档、地平线的官方文档,如有侵权告知删,谢谢。 模型和完整仿真测试代码,放在github上参考链接 模型和代码。 之前写了yolov8、yolov8seg、yolov8obb 的 DFL 放在模型中和放在后处理…...
程序员找工作之操作系统面试题总结分析
程序员在找工作面试时,操作系统方面可能会被问到的问题涵盖了多个核心知识点和概念。以下是对这些面试问题的总结和分析: 1. 核心硬件与体系结构 微机的核心部件:询问微机硬件系统中最核心的部件是什么(CPU)。处理机…...
TypeScript 迭代器和生成器详解
目录 迭代器(Iterators) 生成器(Generators) 使用场景 for..of vs. for..in 语句 for..of 循环 for..in 循环 区别总结 注意事项 总结 在 TypeScript 中,迭代器(Iterators)和生成器&am…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...

Visual Studio Code 扩展
Visual Studio Code 扩展 change-case 大小写转换EmmyLua for VSCode 调试插件Bookmarks 书签 change-case 大小写转换 https://marketplace.visualstudio.com/items?itemNamewmaurer.change-case 选中单词后,命令 changeCase.commands 可预览转换效果 EmmyLua…...

WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...