前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Web前端性能测试平台开发(Flask)

Web前端性能测试平台开发(Flask)

原创
作者头像
晴空SunnySky
修改2023-10-09 14:12:38
3390
修改2023-10-09 14:12:38
举报
文章被收录于专栏:QA在路上QA在路上

开篇先打个小广告,在《牛刀小试-LR性能测试》那篇小文中我有说到性能测试要做到性能的原子化 这样我们把性能可以分为 前端, 网络, 中间件,App(应用),操作系统,数据库等,今天 我们来一起开发一个专门对Web前端性能自动化平台(后续可以在该版本的技术和基础上完善其他功能 比如说:接口的自动化和接口性能以及对其他层的监控数据做可视化)。

一:要啥自行车?奢侈!

大家都懂 敲代码之前有很多事情要做并且是最重要的事情(能敲代码的人多的是,最重要的是能产出发现问题并给出解决策略的idea ),what’s this? 当然是需求分析。

我们的愿景

实现Web前端性能测试(自动遍历所有页面) 监控每个页面加载时间段的耗时,并且统计每个页面中附加的资源(css/js/img/XmlHttpRequest) 最后 利用精美的图表作展现。

技术可行性分析:

自动遍历所有页面?没问题啊 webdriver是这块儿的利器啊。

如何统计页面加载时间呢?performance.timing绝对靠谱。

哪儿有精美的图表? 百度Echarts团队为你分忧解难。

技术选型:

这是个非常值得探讨的问题,有些Leader是综合团队现状和实施成本(新技术是需要花时间学啊);有些知乎党 就不说了 啥最新用啥 只用最新的 不用最合适的;当然 还有一些人完全是靠自己的兴趣拍脑袋决定的。

我们这里选择python+webdriver+flask+sqlite+bootstrap+jquery来完成我们这个小平台的开发,至于为嘛会选择这几种技术, 学习成本低 开发效率高 总而言之一句话 ROI高!

好嘞~ 童鞋们可以先脑补下自己想要什么样的交互页面,我这里给出一个最简单的 嘿嘿~

主页面(展示统计到的页面信息并以堆叠图展示性能数据):

页面详情页面(展示页面中附加资源的组成以及该页面历史版本的数据对比图)

对技术选型这块儿同学们可以根据自己的需要做改变

如果你不想用flask 可以换成Django框架

如果你不想用sqlite数据库 可以换成mysql或者其他的NoSql类数据库

如果你不想用jquery 那你的选择就更多了 什么React,Nodejs,Vuejs,Angularjs还有啥backbone.js眼花缭乱。

我个人喜好小而精致的东西 就拿flask来说吧 它是个微型框架远远没有Django重,但是flask丰富的插件可以供我们快速地完成任务。所以嘛 要啥Django 要啥自行车 别太奢侈

技术没有Low不Low之分! 只有适合不适合。

二:这个轮椅是专门为你设计的

这里是对应软件工程里的概要设计阶段。经过前面的梳理 我们很清楚地知道自己想要的是什么了。嗯~ 接下来咱把硬邦邦的需要转化为软件工程里的东东吧。

数据库:我们建两张表分别存放所有页面的统计数据就叫WebPages,还需要建立一张表来存放每个页面的详细信息 我们叫PageDetail。

建表语句如下:

代码语言:javascript
复制
CREATE TABLE [PageDetail] (
[project] text,
[resourceName] text,
[requestType] text,
[pageRequestCount] INTERGER,
[resourcetSize] INTERGER,
[resourceLoadTime] INTERGER,
[createTime] datetime,
[pageName] text,
[pageLoadTime] INTEGER);
CREATE TABLE WebPages (project text, name text ,url text ,direct INTERGER ,domReady INTERGER, loadEvent INTERGER ,request INTERGER ,ttfb INTERGER,loadPageAll INTERGER ,ifredirect INTERGER, createTime datetime);

接口

我们重新执行脚本来统计前端性能的话 需要调用接口。

我们就命名一个接口吧 python-flask里接口的定义很简单哦~

代码语言:javascript
复制
# 重新执行测试脚本
 @app.route('/redo', methods=['GET', 'POST'])
 def redo():
 if request.method == 'GET':
 return u'该接口不支持GET方法访问'
 else:
 # 向测试环境发生get请求来验证测试环境是否存在
if env_test_code == 200:
# 重新执行测试脚本
Else:
Return 测试环境不存在

如果我们选择不同版本的话 我们也需要调用接口来返回所选版本的测试数据:

代码语言:javascript
复制
@app.route('/index', methods=['GET', 'POST'])
 def index():
 if request.method == 'POST':
 # 从ajax请求中取参数selected_version 取不到则为null
 version_text = request.form.get("selected_version", "null")
 else:
 version_text = '默认版本号'

# 接下来巴拉巴拉从sqlite中取数据并返回

三:走两步~ 走两步!

让我们开始敲代码吧? 好啊! 来吧!

接下来 我们先完成前端部分的开发工作然后再搞后台部分的任务,bootstrap &flask学起!

3.1:flask环境搭建和基础知识

啥是flask?

Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。

Flask也被称为 “microframework” ,因为它使用简单的核心,用 extension 增加其他功能。Flask没有默认使用的数据库、窗体验证工具。然而,Flask保留了扩增的弹性,可以用Flask-extension加入这些功能:ORM、窗体验证工具、文件上传、各种开放式身份验证技术。

环境搭建:

先用pip安装virtualenv库~ (这样做是为了单独拉一个python环境出来以便我们一台服务器上可以跑多套服务)

Dos窗口里敲命令:pip install virtualenv

然后,新建一个目录 我们就叫AutoMan吧,Dos窗口里切换到AutoMan目录后执行:virtualenv venv(创建虚拟环境目录)

我们如果要在virtualenv里安装需要的包,需要先激活虚拟环境(切到venv/scirpts里执行activate)然后再执行pip安装即可。

我们这里需要安装的环境有flask,selenium,requests。我们可以分别执行安装,也可以将flask,selenium,requests放到requirement.txt文件里然后执行pip install –r requirement.txt。

上一张我们项目的目录结构图

目录说明:

1:Web我们的项目目录

1.1:static存放静态文件的目录,它下面又分为css,img,js目录分别存放样式文件图片和js文件(下载jquery.js放到js目录下,bootstrap.css放到css目录下)。

1.2:templates目录存放的是我们的html静态页面。

1.3:views.py里定义了接口以供前端发起请求。

1.4:__init__.py里初始化了Web这个服务。

2:和Web平级的venv是我们flask虚拟环境的目录,发布的时候不需要它。

3:AutoMan.db我们用到的sqlite数据库文件。

4:config.py文件里是我们用到的配置信息,比如说页面信息,统计性能的js方法等。

5:manage.py是我们程序的执行入口。

我们先来初始化我们的应用

在__init__.py文件里输入:

接着,我们可以尝试下在页面输出一个字符串是什么感受,嗯啊,感受!

在views.py文件里输入:

代码语言:javascript
复制

 # 定义路由
 @web_app.route('/')
 def root():
 return 'Hello Flask'

在manage.py文件里启动我们的应用:

代码语言:javascript
复制
from Web import web_app
 web_app.run(host='0.0.0.0', port=5566)

启动服务(我用的是pycharm调试),在浏览器里输入http://localhost: 5566

是不是很神奇~~~~ 我们继续 补充下flask中的路由知识。

Flask中route() 装饰器把一个函数绑定到对应的 URL 上,灰常简单!

比如下面的

代码语言:javascript
复制
@app.route('/ajaxAnalyse')
 def ajax_analyse():
 return render_template('ajaxAnalyse.html')
 我们通过浏览器访问localhost:5566/ajaxAnalyse,flask路由会指定该地址并返回ajaxAnalyse.html模板交给浏览器渲染后呈现给我们。
 
 @app.route('/resource_<name>_<version>')
 def resource_analyse(name, version):
 print name
 return render_template('pages_detail.html')

如果我们想在路由中使用变量,<>包含起来即可,上面这个例子中我们使用两个变量,分别是name(页面名称)和version(项目版本)。

好嘞~ 接下来就简单了,我们开始构造url路由。

首先是首页,首页的话 我们想展示所有页面的统计信息和堆叠图。所以下面的代码即可实现(我们从数据库中读取数据并和html模板一起返回让浏览器渲染)。

代码语言:javascript
复制
# 主页显示所有页面前端信息
 @app.route('/index', methods=['GET', 'POST'])
 def index():
 if request.method == 'POST':
 # 从ajax请求中取参数selected_version 取不到则为null
 version_text = request.form.get("selected_version", "null")
 else:
 version_text = 'Syf1.1.1'
 g.db = sqlite3.connect(config.db_dir)
 all_page_load_data = {}
 page_time_detail = []
 # 从数据库中查询所有页面汇总信息
 for value in ['direct', 'domReady', 'loadEvent', 'request', 'ttfb', 'loadPageAll', 'name']:
 sql = 'select ' + value + " from webLoad where project='" + str(version_text) + "'" + ' order by name'
 current_data = g.db.execute(sql).fetchall()
 current_list = []
 if value == 'name':
 for data in current_data:
 current_list.append(str(data).strip("(u'").strip("',)"))
 all_page_load_data[value] = current_list
 else:
 for data in current_data:
 current_list.append(int(str(data).strip('(').strip(')').split(',')[0]))
 all_page_load_data[value] = current_list
 # 查询每个页面的时间详细
 page_detail_sql = "select name, url, loadEvent, domReady, request, ttfb, 0 as request_count, 0 as request_size " \
 "from webLoad where project='" + str(version_text) + "'" + ' order by name'
 page_detail_data = g.db.execute(page_detail_sql).fetchall()
 for data in page_detail_data:
 page = {}
 page['name'] = data[0]
 page['url'] = data[1]
 page['loadEvent'] = data[2]
 page['domReady'] = data[3]
 page['request'] = data[4]
 page['ttfb'] = data[5]
 page['request_count'] = data[6]
 page['request_size'] = data[7]
 page_time_detail.append(page)
 g.db.close()
 return render_template("index.html", all_page_load_data=all_page_load_data, page_time_detail=page_time_detail)

咦?这里我们貌似引入了一个新词 模板,没错flask中使用Jinja作为模板。

我们一起来看下Jinja中一些常用知识点。

0:表达式

代码语言:javascript
复制
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>My Webpage</title>
</head>
<body>
<ul id="navigation"> {% for item in navigation %}
<li><a href="{{ item.href }}">{{ item.caption }}</a></li>
{% endfor %} </ul>
<h1>My Webpage</h1> {{ a_variable }}
</body>
</html>

两种分隔符{% … %} 和 {{ … }}. 前者用于执行表达式, 后者打印表达式的结果.

1.变量

#这两种表达方式是一样的,如果属性不存在默认的设置是返回空字符串

代码语言:javascript
复制
{{ foo.bar }} {{ foo['bar'] }}

2.Filters(过滤器,我们也可以自定义过滤器)

对变量操作的函数,以|的形式写在后面

#几个例子

  1. attr(obj, name)
代码语言:javascript
复制
 foo|attr("bar") <=>(等价于) foo["bar"]
 

2.batch(value, linecount, fill_with=None)

#从左边取3个值,fill_with为不够的情况下的填充值

代码语言:javascript
复制
{%- for row in items|batch(3, '&nbsp;') %}

4.注释Comments

#写在{# ... #}之间

代码语言:javascript
复制
{# note: disabled template because we no longer use this
{% for user in users %} ... {% endfor %} #}

5.空白 whitespace control

1.没有配置trim_blocks 和 lstrip_blocks

代码语言:javascript
复制
<div> {% if True %} yay {% endif %} </div> -> <div> yay </div>

  1. 配置了 trim_blocks 和 lstrip_blocks
代码语言:javascript
复制
 <div> yay </div>
 

  1. 手动开启lstrip_blocks
代码语言:javascript
复制
 <div> {%+ if something %}yay{% endif %} </div>
 

  1. 手动删除空白
代码语言:javascript
复制
 {% for item in seq -%} {{ item }} {%- endfor %}
 

6.转换符Escaping

# 使用{{}} ,或者raw block {% raw %}

代码语言:javascript
复制
{{ '{{' }} {% raw %} <ul> {% for item in seq %} <li>{{ item }}</li>
{% endfor %} </ul> {% endraw %}

7.Line Statements

配置后可以mark a line as a statement

代码语言:javascript
复制
# for item in seq:
<li>{{ item }}</li> ## this comment is ignored
# endfor

这种方法同 {% for item in seq %} <li>{{ item }}</li> {% endfor %}

8.模板继承 Template Inheritance

#1.父类模板 Template

代码语言:javascript
复制
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> {% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body> <div id="content">{% block content %}{% endblock %}</div>
<div id="footer"> {% block footer %} &copy; Copyright 2008 by <a href="http://domain.invalid/">you</a>. {% endblock %} </div>
</body>

2.子类模板继承Child Template

代码语言:javascript
复制
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %} {{ super() }}
<style type="text/css"> .important { color: #336699; } </style> {% endblock %} {% block content %} <h1>Index</h1>
<p class="important"> Welcome on my awesome homepage. </p> {% endblock %}

3.还可以用self,引用block的变量

代码语言:javascript
复制
<title>{% block title %}{% endblock %}</title>
<h1>{{ self.title() }}</h1> {% block body %}{% endblock %}
Super Blocks 引用parent block的内容
{% block sidebar %} <h3>Table Of Contents</h3> ... {{ super() }} {% endblock %}

4.block 结束符也可以加上名字

代码语言:javascript
复制
{% block sidebar %} {% block inner_sidebar %} ...
{% endblock inner_sidebar %}
{% endblock sidebar %}

5.block中的变量引用

代码语言:javascript
复制
{% for item in seq %} <li>{% block loop_item %}{{ item }}{% endblock %}</li> 
# 打印为空,因为block内部不能引用外面的item变量 {% endfor %}

需要改变为

代码语言:javascript
复制
{% for item in seq %} <li>{% block loop_item scoped %}{{ item }}{% endblock %}</li> {% endfor %}

6.转换符HTML Escaping

可以使用自动或者手动转换

代码语言:javascript
复制
Automatic Escaping自动转换会转换所有字符,除非在应用中设置了safe的或者后面加了|safe
Manual Escaping手动转换:加|e filter: {{ user.username|e }}. 这是escape的缩写

7.控制结构

#1.0For 循环

代码语言:javascript
复制
<h1>Members</h1> <ul> {% for user in users %}
<li>{{ user.username|e }}</li>
{% endfor %} </ul>

2.0 if/else结构

代码语言:javascript
复制
if {% if kenny.sick %} Kenny is sick.
{% elif kenny.dead %} You killed Kenny! You bastard!!!
{% else %} Kenny looks okay --- so far
{% endif %}

8.宏Macros

#1.定义一个宏

代码语言:javascript
复制
{% macro input(name, value='', type='text', size=20) -%} <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}"> {%- endmacro %}

#2.使用

代码语言:javascript
复制
<p>{{ input('username') }}</p> <p>{{ input('password', type='password') }}</p>
#3.类似一个宏里调用另一个宏,使用call block
{% macro render_dialog(title, class='dialog') -%}
<div class="{{ class }}"> <h2>{{ title }}</h2>
<div class="contents"> {{ caller() }}
</div>
</div>
{%- endmacro %}
{% call render_dialog('Hello World') %} This is a simple dialog rendered by using a macro and a call block. {% endcall %}
# 带参数 {% macro dump_users(users) -%} <ul> {%- for user in users %} <li><p>{{ user.username|e }}</p>{{ caller(user) }}</li> {%- endfor %} </ul> {%- endmacro %} {% call(user) dump_users(list_of_user) %} <dl> <dl>Realname</dl> <dd>{{ user.realname|e }}</dd> <dl>Description</dl> <dd>{{ user.description }}</dd> </dl> {% endcall %}

9.Filter sections过滤器块

可以对一个block使用filter

代码语言:javascript
复制
{% filter upper %} This text becomes uppercase {% endfilter %}

10.赋值

代码语言:javascript
复制
{% set navigation = [('index.html', 'Index'), ('about.html', 'About')] %}
{% set key, value = call_something() %}

11.Include(包含)

# 把其他tempalte的内容加进来

代码语言:javascript
复制
{% include 'header.html' %} Body
{% include 'footer.html' %}
{% include ['special_sidebar.html', 'sidebar.html'] ignore missing %}

12. import(导入)

# 如forms.html定义了两个宏

代码语言:javascript
复制
{% macro input(name, value='', type='text') -%}
<input type="{{ type }}" value="{{ value|e }}" name="{{ name }}">
{%- endmacro %}
{%- macro textarea(name, value='', rows=10, cols=40) -%}
<textarea name="{{ name }}" rows="{{ rows }}" cols="{{ cols }}">{{ value|e }}
</textarea>
{%- endmacro %}

#两种import的方法

代码语言:javascript
复制
{% import 'forms.html' as forms %} <dl> <dt>Username</dt> <dd>{{ forms.input('username') }}</dd> <dt>Password</dt> <dd>{{ forms.input('password', type='password') }}</dd> </dl> <p>{{ forms.textarea('comment') }}</p>

代码语言:javascript
复制
{% from 'forms.html' import input as input_field, textarea %} <dl> <dt>Username</dt> <dd>{{ input_field('username') }}</dd> <dt>Password</dt> <dd>{{ input_field('password', type='password') }}</dd> </dl> <p>{{ textarea('comment') }}</p>

Jinja2模板的用法非常多,我们只需要掌握常用的即可,遇到不会的直接去官网搜吧。

下面贴上我这边Index.html页面源码中所用到的Jinja:

基类模板内容:

代码语言:javascript
复制
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %} {% endblock %}</title>
<link rel="stylesheet" type="text/css" href="../static/css/bootstrap.min.css">
<script src="../static/js/jquery.min.js"></script>
<script type="text/javascript" src="../static/js/echarts-all.js"></script>
<script type="text/javascript" src="../static/js/bootstrap.js"></script>
<script type="text/javascript" src="../static/js/events.js"></script>
{% block head %}{% endblock %}
</head>
<body>
<nav class="navbar text-info" role="navigation">
<ul class="nav nav nav-tabs panel-title">
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">前端性能<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/index">Clinical项目</a></li>
<li><a href="#">GeStorage项目</a></li>
<li><a href="#">Training项目</a></li>
<li><a href="#">PerformanceTiming学习</a></li>
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">UI自动化测试<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="ui_auto">Clinical项目</a></li>
<li><a href="ui_auto">GeStorage项目</a></li>
<li><a href="ui_auto">Training项目</a></li>
<li><a href="ui_auto">RobotFrame知识库</a></li>
<li><a href="ui_auto">UI自动化脚本设计</a></li>
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">接口自动化测试<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="service_auto">Clinical项目</a></li>
<li><a href="service_auto">GeStorage项目</a></li>
<li><a href="service_auto">Training项目</a></li>
<li><a href="#">接口测试知识库</a></li>
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">兼容性自动化测试<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="#">Clinical项目</a></li>
<li><a href="#">GeStorage项目</a></li>
<li><a href="#">Training项目</a></li>
<li><a href="#">兼容性实施原理</a></li>
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">性能测试<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="#">Clinical项目</a></li>
<li><a href="#">GeStorage项目</a></li>
<li><a href="#">Training项目</a></li>
<li><a href="#">性能测试实施细则</a></li>
</ul>
</li>
<li><a href="per_auto">全局变量配置</a></li>
</ul>
</nav>
<div id="content" class="container">
{% block content %}
{% endblock %}
</div>
</body>
</html>

我这边index.html内容如下:

代码语言:javascript
复制
{% extends "base.html" %} //继承基类模板
{% block title %}平台主页{% endblock %}
{% block content %}
<div class="row">
<label>项目版本</label>
<select id="project_version" onchange="optionChange()">
{% for option in project_version.Clinical %}
<option value={{option}}>{{option}}</option>
{% endfor %}
</select>
<button id="redo" onClick="ajaxReRun()">重新执行</button>
</div>
<br>
<table id="pages_summary" class="table table-hover">
<th>页面名称</th>
<th>页面URL</th>
<th>DNS耗时</th>
<th>Request耗时</th>
<th>解析DOM耗时</th>
<th>DomReady耗时</th>
<th>LoadEvent耗时</th>
<th>白屏时长</th>
<th>整个页面加载耗时</th>
{% for page in page_load_result %} //Jinja中的循环体
<tr class="active hover">
<td class="active"><a href="javascript:{{page.page_name}}()">{{page.page_name}}</a></td>
<script type="text/javascript">
function {{page.page_name}}()
{
var href = $('#project_version').get(0).selectedOptions[0].text + "_" +"{{page.page_name}}";
location.href = href
}
</script>
<td class="active">{{page.url}}</td>
<td class="active">{{page.dns}}</td>
<td class="active">{{page.request}}</td>
<td class="active">{{page.dom_parser}}</td>
<td class="active">{{page.dom_ready}}</td>
<td class="active">{{page.load_event}}</td>
<td class="active">{{page.whitewait}}</td>
<td class="active">{{page.loadall}}</td>
</tr>
{% endfor %} //Jinja中循环体的结束标识
</table>
<br><br>
<h4>所有页面加载数据统计堆叠图</h4>
<div id="page_summary_chart">
<table id="data_chart" width="100%">
<tr>
<td align="center">
<div id="chart" class="text-align:center" style="width: 1250px;height:650px;">
<script type="text/javascript">
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('chart'));
// 指定图表的配置项和数据
option = {
tooltip : {
trigger: 'axis',
axisPointer : {type : 'shadow'}
},
legend: {
data:['dns(ms)', 'request(ms)', 'dom_parser(ms)', 'dom_ready(ms)', 'load_event(ms)', 'whitewait(ms)', 'loadall(ms)'],
},
toolbox: {
show : true,
feature : {
mark : {show: true},
dataView : {show: true, readOnly: false},
magicType : {show: true, type: ['line', 'bar', 'stack', 'tiled']},
restore : {show: true},
saveAsImage : {show: true}
}
},
calculable : true,
xAxis : [
{
type : 'value'
}
],
yAxis : [
{
type : 'category',
data : {{page_chart_data.page_name|safe}}
}
],
series : [
{
name:'dns(ms)',
type:'bar',
stack: 'Time',
itemStyle : { normal: {label : {show: true, position: 'insideRight'}}},
data: {{page_chart_data.dns|safe}}
},
{
name:'request(ms)',
type:'bar',
stack: 'Time',
itemStyle : { normal: {label : {show: true, position: 'insideRight'}}},
data: {{page_chart_data.request|safe}}
},
{
name:'dom_parser(ms)',
type:'bar',
stack: 'Time',
itemStyle : { normal: {label : {show: true, position: 'insideRight'}}},
data: {{page_chart_data.dom_parser|safe}}
},
{
name:'dom_ready(ms)',
type:'bar',
stack: 'Time',
itemStyle : { normal: {label : {show: true, position: 'insideRight'}}},
data: {{page_chart_data.dom_ready|safe}}
},
{
name:'request(ms)',
type:'bar',
stack: 'Time',
itemStyle : { normal: {label : {show: true, position: 'insideRight'}}},
data: {{page_chart_data.request|safe}}
},
{
name:'load_event(ms)',
type:'bar',
stack: 'Time',
itemStyle : { normal: {label : {show: true, position: 'insideRight'}}},
data: {{page_chart_data.load_event|safe}}
},
{
name:'whitewait(ms)',
type:'bar',
stack: 'Time',
itemStyle : { normal: {label : {show: true, position: 'insideRight'}}},
data: {{page_chart_data.whitewait|safe}}
},
{
name:'loadall(ms)',
type:'bar',
stack: 'Time',
itemStyle : { normal: {label : {show: true, position: 'insideRight'}}},
data: {{page_chart_data.loadall|safe}}
}
]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
</script>
</div>
</td>
</tr>
</table>
</div>
{% endblock %}

(PS:在一个表格中循环查询到的结果并输出到页面)

下面贴出来我这边views.py文件里的全部内容供同学们参考:

代码语言:javascript
复制
# -*- coding:utf-8 -*-
import sqlite3
import sys
import time
import requests
from flask import render_template, g, redirect, url_for, request, send_file
from selenium import webdriver
import config
from Web import web_app
reload(sys)
sys.setdefaultencoding("utf-8")
# 定义根路由
@web_app.route('/')
def root():
return render_template('login.html')
@web_app.route('/login_success')
def login_success():
return render_template('login_success.html')
# 定义路由index
@web_app.route('/index', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
version = request.form.get("selected_version", "null")
else:
version = 'syf1.1.1'
g.db = sqlite3.connect(config.db_dir)
summary_data_sql = 'select page_name, url, dns, request, dom_parser, dom_ready, load_event, whitewait, loadall '\
+ " from WebLoad where version='" + str(version) + "'" + ' order by page_name'
page_load_result = query_db(summary_data_sql)
g.db.close()
project_version = config.project_version_info
# 将页面信息转换为Echarts使用的数据
page_chart_data = {}
page_name_list = []
page_dns_list = []
page_request_list = []
page_dom_parser_list = []
page_dom_ready_list = []
page_load_event_list = []
page_whitewait_list = []
page_loadall_list = []
for result in page_load_result:
page_name_list.append(str(result['page_name']).strip("u'"))
page_dns_list.append(result['dns'])
page_request_list.append(result['request'])
page_dom_parser_list.append(result['dom_parser'])
page_dom_ready_list.append(result['dom_ready'])
page_load_event_list.append(result['load_event'])
page_whitewait_list.append(result['whitewait'])
page_loadall_list.append(result['loadall'])
page_chart_data['page_name'] = page_name_list
page_chart_data['dns'] = page_dns_list
page_chart_data['request'] = page_request_list
page_chart_data['dom_parser'] = page_dom_parser_list
page_chart_data['dom_ready'] = page_dom_ready_list
page_chart_data['load_event'] = page_load_event_list
page_chart_data['whitewait'] = page_whitewait_list
page_chart_data['loadall'] = page_loadall_list
return render_template('index.html', project_version=project_version, page_load_result=page_load_result, page_chart_data=page_chart_data)
# 定义重新执行脚本的路由
@web_app.route('/redo', methods=['GET', 'POST'])
def redo():
if request.method == 'GET':
return 'route does not support get'
else:
# 向测试环境发生get请求来验证测试环境是否存在
env_for_test = request.form.get("selected_version", "null")
current_env = config.hosts['164'] + env_for_test
env_test_code = requests.get(current_env).status_code
if env_test_code == 200:
# 定义页面名称列表
all_page_data = {}
page_urls = config.page_urls
timing_js = config.timing_js
# 统计页面加载时间的6个维度 登录页专用
index_data = {'dns': 0, 'request': 0, 'dom_parser': 0, 'dom_ready': 0, 'load_event': 0, 'whitewait': 0, 'loadall': 0}
# index页面资源的信息 登录页专用
page_index_info = []
# 将统计数据保存到sqlite
connection = sqlite3.connect(config.db_dir)
cursor = connection.cursor()
# 先删除页面资源细分表webDetailLoad所有数据
clear_resource_sql = "delete from PageDetail where project='Clinical' and version='" + env_for_test + "'"
cursor.execute(clear_resource_sql)
# 先清理上次的数据
clear_webload_sql = "delete from WebLoad where project='Clinical' and version='" + env_for_test + "'"
cursor.execute(clear_webload_sql)
connection.commit()
# get到clinical登录页(登录页需要单独收集其页面加载性能指标)
driver = webdriver.Chrome()
login_url = current_env + '/login/index'
driver.get(login_url)
driver.maximize_window()
time.sleep(5)
time_line = driver.execute_script(config.timing_js)
# 将页面加载时间存入all_page_data
for key in index_data.keys():
index_data[key] = time_line[key]
# 将index的数据存入all_page_data
all_page_data['Index'] = index_data
# 获得index页面所有的资源实体
entries = driver.execute_script(config.get_entries_js)
# 遍历登录页面所有资源
for items in entries:
# 以页面名:数组形式保存页面信息
page_index_resource = {}
# 资源名称
page_index_resource['name'] = items['name']
# 资源类型分类
resource_type = str(items['name']).split('.')[-1]
if resource_type in ['jpg', 'png', 'gif', 'jpeg']:
page_index_resource['resourceType'] = 'img'
elif resource_type == 'js':
page_index_resource['resourceType'] = 'js'
elif resource_type == 'css':
page_index_resource['resourceType'] = 'css'
else:
if items['initiatorType'] == 'xmlhttprequest':
page_index_resource['resourceType'] = 'xmlhttprequest'
elif items['initiatorType'] == 'script':
page_index_resource['resourceType'] = 'js'
else:
page_index_resource['resourceType'] = 'other'
# 资源大小
page_index_resource['transferSize'] = items['transferSize']
# 资源耗时
page_index_resource['duration'] = items['duration']
# 将登录页资源信息存入列表
page_index_info.append(page_index_resource)
# 将登录页资源列表信息存入字典对象 对应key为Index
save_resource_sql = 'insert into PageDetail ' + "values('Clinical','" + str(env_for_test) + "'," + "'" + str(
'Index') + "'," + "'" + str(page_index_resource['name']) + "'," + "'" + str(
page_index_resource['resourceType']) + "'," + "'" \
+ str(page_index_resource['transferSize']) + "'," + "'" + str(
page_index_resource['duration']) + "'," + 'CURRENT_TIMESTAMP)'
cursor.execute(save_resource_sql)
connection.commit()
# 登录系统后 对其他所有页面进行遍历以收集其页面加载性能数据
driver.find_element_by_id('txtUserName').send_keys('30048')
driver.find_element_by_id('txtPassword').send_keys('5436')
driver.find_element_by_id('btnConfirm').click()
time.sleep(3)
# 循环登录后的所有页面
for page in page_urls.keys():
# 将页面加载时间统计出来存入all_page_data
current_page_timing = {"dns": 0, "request": 0, "dom_parser": 0, "dom_ready": 0, "load_event": 0, "whitewait": 0, "loadall": 0}
current_page_url = current_env + (str(page_urls[page]))
driver.get(current_page_url)
time.sleep(10)
durations = driver.execute_script(timing_js)
for item in current_page_timing.keys():
current_page_timing[item] = durations[item]
# 将当前页面的name和统计到数据存入到all_page_data
all_page_data[page] = current_page_timing
entries = driver.execute_script("return window.performance.getEntries()")
# 遍历当前页面所有资源
for items in entries:
current_resources = {}
current_resources['name'] = items['name']
# 根据资源类型进行统计
resource_type = str(items['name']).split('.')[-1]
if resource_type in ['jpg', 'png', 'gif', 'jpeg']:
current_resources['resourceType'] = 'img'
elif resource_type == 'js':
current_resources['resourceType'] = 'js'
elif resource_type == 'css':
current_resources['resourceType'] = 'css'
else:
if items['initiatorType'] == 'xmlhttprequest':
current_resources['resourceType'] = 'xmlhttprequest'
elif items['initiatorType'] == 'script':
current_resources['resourceType'] = 'js'
else:
current_resources['resourceType'] = 'other'
# 资源大小
current_resources['transferSize'] = items['transferSize']
# 资源耗时
current_resources['duration'] = items['duration']
# 保存资源
save_resource_sql = 'insert into PageDetail ' + "values('Clinical','" + str(
env_for_test) + "'," + "'" + str(
page) + "'," + "'" + str(
current_resources['name']) + "'," \
+ "'" + str(current_resources['resourceType']) + "'," + "'" + str(
current_resources['transferSize']) + "'," + "'" + str(
current_resources['duration']) + "'," + 'CURRENT_TIMESTAMP)'
cursor.execute(save_resource_sql)
connection.commit()
# 退出chrome driver进程
driver.quit()
# 更新WebLoad表中的数据
for name in all_page_data.keys():
page_time = all_page_data[name]
if name == 'Index':
insert_sql = 'insert into WebLoad ' + "values('Clinical','" + str(env_for_test) + "'," + "'"\
+ str(name) + "'," + "'" + str(current_env) + "/login/index'," + str(page_time['dns']) \
+ ',' + str(page_time['request']) + ',' + str(page_time['dom_parser']) + ',' + str(page_time['dom_ready']) \
+ ',' + str(page_time['load_event']) + ',' + str(page_time['whitewait']) + ',' + str(page_time['loadall']) + ',' + 'CURRENT_TIMESTAMP)'
else:
insert_sql = 'insert into WebLoad ' + "values('Clinical','" + str(env_for_test) + "'," + "'" \
+ str(name) + "'," + "'" + str(current_env) + str(page_urls[name]) + "'," + str(page_time['dns']) \
+ ',' + str(page_time['request']) + ',' + str(page_time['dom_parser']) + ',' + str(page_time['dom_ready']) \
+ ',' + str(page_time['load_event']) + ',' + str(page_time['whitewait']) + ',' + str(page_time['loadall']) + ',' + 'CURRENT_TIMESTAMP)'
cursor.execute(insert_sql)
connection.commit()
connection.close()
return 'success'
else:
return 'environment is not exist'
@web_app.route('/<project_version>_<page_name>')
def page_detail(project_version, page_name):
g.db = sqlite3.connect(config.db_dir)
# 页面资源信息
resource_sql = "select resource_name, resource_type, resource_size, resource_duration from PageDetail where version='" + str(project_version) + "' and page_name='" + str(page_name) + "' order by resource_duration desc"
history_sql = "select version, dns, request, dom_parser, dom_ready, load_event, whitewait, loadall from WebLoad where page_name='" + str(page_name) + "' group by version"
composition_sql = "select resource_type, count(resource_type) as counts, sum(resource_size) as size_count, sum(resource_duration) as duration_count from PageDetail where version='" + str(project_version) + "' and page_name='" + str(page_name) + "' group by resource_type"
top_sql = "select resource_name, resource_duration, resource_size from PageDetail where version='" + str(project_version) + "' and page_name='" + str(page_name) + "' order by resource_duration desc limit 3"
resource_composition = query_db(composition_sql)
resource_summary = query_db(resource_sql)
top_resources = query_db(top_sql)
# 对历史数据比对来说 若只有一个版本需要添加默认的比对数据
history_info = query_db(history_sql)
g.db.close()
if len(history_info) > 1:
pass
else:
default_data = {'version': 'syf-next-version', 'dns': 0, 'request': 0, 'dom_parser': 0, 'dom_ready': 0, 'load_event': 0, 'whitewait': 0, 'loadall': 0}
history_info.append(default_data)
version_list = []
for data in history_info:
version_list.append(str(data['version']).strip("u"))
resource_type_list = []
for item in resource_composition:
resource_type_list.append(item['resource_type'])
return render_template('page_detail.html', resource_summary=resource_summary, history_info=history_info, page_name=page_name,
version_list=version_list, resource_composition=resource_composition, resource_type_list=resource_type_list ,
top_resources=top_resources)
# 全局环境变量配置
@web_app.route('/variable_config', methods=['GET', 'POST'])
def variable_config():
if request.method == 'GET':
return render_template('variable_config.html')
else:
return render_template('variable_config.html')
# 以字典形式返回查询结果
def query_db(query, args=(), one=False):
cur=g.db.execute(query,args)
rv=[dict((str(cur.description[idx][0]), str(value)) for idx, value in enumerate(row)) for row in cur.fetchall()]
return (str(rv[0]) if rv else None) if one else rv
@web_app.route('/ui_auto')
def ui_auto():
return render_template('ui_auto.html')
@web_app.route('/service_auto')
def service_auto():
return render_template('service_auto.html')
@web_app.route('/case_detail')
def case_detail():
return render_template('case_detail.html')
@web_app.route('/cap_auto')
def cap_auto():
return render_template('cap_auto.html')
@web_app.route('/per_auto')
def per_auto():
return render_template('per_auto.html')

3.2:jquery发送ajax

这块儿的是很简单的,jquery发送ajax有固定格式滴~

代码语言:javascript
复制
    function test(){
    $.ajax({
             //提交数据的类型 POST GET
             type:"POST",
             //提交的网址
             url:"接口地址(对应flask中的路由url) ",
             //提交的数据
             data:{Name:"sanmao",Password:"sanmaoword"},
             //返回数据的格式
             datatype: "html",//"xml", "html", "script", "json", "jsonp", "text".
             //在请求之前调用的函数
             beforeSend:function(){$("#msg").html("logining");},
             //成功返回之后调用的函数             
             success:function(data){
            $("#msg").html(decodeURI(data));            
             }   ,
             //调用执行后调用的函数
             complete: function(XMLHttpRequest, textStatus){
                alert(XMLHttpRequest.responseText);
                alert(textStatus);
                 //HideLoading();
             },
             //调用出错执行的函数
             error: function(){
                 //请求出错处理
             }         
          });
  }

我们这里用到两个ajax请求,1:重新执行脚本 2:下拉框选值版本

重新执行脚本时,我们发送ajax的源码是这样的:

代码语言:javascript
复制
// 重新执行测试脚本
 function ajaxReRun(){
 var select = document.getElementById("project_name");
 var options = select.options;
 var index = select.selectedIndex;
 var version_text = options[index].text;
 $.ajax({
 //提交数据的类型 POST GET
 type:"POST",
 //提交的网址
 url:"/redo",
 //提交的数据
 data:({"selected_version": version_text}),
 //返回数据的格式 "xml", "html", "script", "json", "jsonp", "text"
 datatype: "text",
 //ajax请求成功后的事件
 success:function(data){
 if (data=='Env Is Not Exist'){
 alert("亲~ 这个版本的测试环境去哪儿了?");
 }
 else{
 window.location.href = window.location.href;
 alert("所选版本的前端性能测试执行完毕");
 }
 },
 //调用出错执行的函数
 error: function(XMLResponse){
 //请求出错处理
 alert(XMLResponse.responseText)
 }
 });
 }

我们从下拉框中选择版本信息时ajax源码如下:

代码语言:javascript
复制
// 刷新数据
 function optionChange(){
 var select = document.getElementById("project_name");
 var options = select.options;
 var index = select.selectedIndex;
 var version_text = options[index].text;
 $.ajax({
 //提交数据的类型 POST GET
 type:"POST",
 //提交的网址
 url:"/index",
 //提交的数据
 data:({"selected_version": version_text}),
 //返回数据的格式 "xml", "html", "script", "json", "jsonp", "text"
 datatype: "json",
 //ajax请求成功后的事件
 success:function(data){
 $('#weirtable').html($(data).find('#weirtable').html());
 $('#data_container').html($(data).find('#data_container').html());
 },
 //调用出错执行的函数
 error: function(XMLResponse){
 //请求出错处理
 alert(XMLResponse.responseText)
 }
 });
 }

我们可以把上面这两个方法写到Index.html里也可以单独写到一个js文件中然后在Index.html文件中导入js文件即可。

关于Jquery其他的东西 我暂时没用到,同学可以自己扩展这块儿的知识以完善体系。

3.3:bootstrap基础积累

官网上有教程,自己去搜!

3.4:发布走起~

想在windows上发布flask 我去~ 如果你不怕坑太多 那就去鼓捣吧。

我是在CentOS上部署的,下面是部署过程的详细步骤,仅作参考哦~

代码语言:javascript
复制
1. pip  install virtualenv
  virtualenv venv                          # 产生虚拟路径 venv
  source venv/bin/activate        # 启动虚拟环境(deactivate 可退出虚拟环境)
   pip install -r requirements.txt            # 安装依赖包(将依赖到的包全部写到requirements文件中方便管理)
 2. 虚拟路径下安装uwsgi:
      (1) 编辑uwsgi配置文件:[uwsgi]
 #########config.init start##########
 [uwsgi]
 # uwsgi 启动时所使用的地址与端口
 socket = 127.0.0.1:6000
 
 # 指向网站目录
 chdir = /opt/usr/AutoMan
 # python 启动程序文件
 wsgi-file = run.py
 
 # python 程序内用以启动的 application 变量名
 callable = surveillance
 # 处理器数
 processes = 2
 # 线程数
 threads = 2
 # 记录日志(自己添加)
 daemonize = /var/log/auto_man.log
 
 # clear environment on exit
 vacuum          = true
 
 #状态检测地址
 stats = 127.0.0.1:9191
 
 #########config.init end##########
           (2) 安装运行uwsgi
                (venv) #: pip install uwsgi
                (venv) #: uwsgi config.ini
 安装 nginx:
      # yum install nginx
      只需在/etc/nginx/conf.d创建一个AutoMan.conf配置即可:
 server {
     listen       7000 default_server;        # 默认的web访问端口
     server_name  192.168.31.61;     #公网ip
     #charset koi8-r;
 
     include /etc/nginx/default.d/*.conf;
 
     access_log  /var/log/nginx/access_airflask.log;
     error_log  /var/log/nginx/access_airerror.log;         #错误日志
     location / {
         include        uwsgi_params;     #导入的uwsgi配置
         uwsgi_pass     127.0.0.1:6000;   #需和uwsgi的配置文件里socket项的地址
         uwsgi_param UWSGI_PYHOME /opt/usr/AutoMan /venv;   #(虚拟环境下)

四:车门关好了没? 大家一起发车

相信各位同学对flask有了初步的认识,我个人对这个平台的后续扩展构想如下:

1:添加接口的自动化测试模块,可以在web页面上设计接口用例 所见即所得。

需要用到的技术:requests库,flask-celery-基于后台的作业执行。

2:UI自动化测试模块,目前设想UI这块儿不作为重点,使用RF的ride来设计编写用例,然后将test-suite文件上传到该平台,继而在该平台上选择suites并在后台执行pabot即可。

需要用到的技术:flask中实现多文件上传。

3:安全测试模块,安全测试的水 不是一般的深,敬请期待个人关于安全测试学习的文章。

4:性能测试过程中的监控,这块儿的东西实现起来也是蛮方便的,目前只想到tomcat, os, db的监控。

5:使用python的多线程技术做接口的性能测试。

期待高手和前辈们的指正和沟通~~~~~

我正在参与2023腾讯技术创作特训营第二期有奖征文,瓜分万元奖池和键盘手表

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一:要啥自行车?奢侈!
  • 二:这个轮椅是专门为你设计的
  • 三:走两步~ 走两步!
    • 3.1:flask环境搭建和基础知识
      • 3.2:jquery发送ajax
        • 3.3:bootstrap基础积累
          • 3.4:发布走起~
          • 四:车门关好了没? 大家一起发车
          相关产品与服务
          手游安全测试
          手游安全测试(Security Radar,SR)为企业提供私密的安全测试服务,通过主动挖掘游戏业务安全漏洞(如钻石盗刷、服务器宕机、无敌秒杀等40多种漏洞),提前暴露游戏潜在安全风险,提供解决方案及时修复,最大程度降低事后外挂危害与外挂打击成本。该服务为腾讯游戏开放的手游安全漏洞挖掘技术,杜绝游戏外挂损失。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档