models.py
from django.db import models
class It(models.Model):
""" 接口项目表 """
it_name = models.CharField(max_length=32, default='', verbose_name='项目名称')
it_desc = models.TextField(max_length=255, default='', verbose_name='项目描述')
it_start_time = models.DateField(verbose_name='项目开始时间')
it_end_time = models.DateField(verbose_name='项目结束时间')
def __str__(self):
return self.it_name
def xxoo(self):
result = ""
if self.api_set.count():
result = "%.2f%%" % (self.api_set.filter(api_pass_status=1).count() / self.api_set.count() * 100)
else:
result = r"0.00%"
return result
class Api(models.Model):
""" 接口用例表 """
api_sub_it = models.ForeignKey(to='It',verbose_name='所属接口项目', on_delete=models.CASCADE)
api_name = models.CharField(max_length=32, default='', verbose_name='用例名称')
api_desc = models.CharField(max_length=255, default='', verbose_name='用例描述')
api_url = models.CharField(max_length=255, default='', verbose_name='请求URL')
api_method = models.CharField(max_length=32, default='', verbose_name='请求类型')
api_params = models.CharField(max_length=255, default={}, verbose_name='请求参数')
api_data = models.CharField(max_length=255, default={}, verbose_name='请求data')
api_assert_type_choices = (
(0,'=='),
(1,'>='),
(2,'<='),
(3,'include'),
(4,'regex')
)
api_assert_type = models.SmallIntegerField(default="0", verbose_name="校验方法", choices=api_assert_type_choices)
api_expect = models.CharField(max_length=8392, default={}, verbose_name='预期结果')
api_report = models.TextField(default="", verbose_name='报告')
api_run_time = models.DateTimeField(null=True, verbose_name='执行时间')
api_pass_status_choices = (
(0,'未通过'),
(1,'已通过')
)
api_pass_status = models.IntegerField(choices=api_pass_status_choices, verbose_name='执行是否通过', default=0)
api_run_status_choices = (
(0,'未执行'),
(1,'已执行'),
)
api_run_status = models.IntegerField(choices=api_run_status_choices,verbose_name='是否执行', default=0)
def __str__(self):
return self.api_name
class Logs(models.Model):
log_report = models.TextField(default="", verbose_name='报告')
log_sub_it = models.ForeignKey(to='It', verbose_name='所属接口项目', on_delete=models.CASCADE)
log_run_time = models.DateTimeField(null=True, auto_now_add=True, verbose_name='日志产生时间')
log_pass_count = models.IntegerField(verbose_name='通过数量')
log_failed_count = models.IntegerField(default=0, verbose_name='失败数量')
log_errors_count = models.IntegerField(verbose_name='错误数量')
log_run_count = models.IntegerField(verbose_name='执行用例数量')
def pass_rate(self):
""" 通过率 """
if self.log_run_count:
result = "%.2f%%" % (self.log_pass_count / self.log_run_count * 100)
if result == r'0.00%':
result = 0
elif result == r"100.00%":
result = r"100%"
return result
else:
return 0
class Meta:
ordering = ['-log_run_time']
from typing import List
from django.forms import ModelForm
from django.forms import widgets as wid
from app.models import It, Api
class ItModelForm(ModelForm):
class Meta:
model = It
fields = "__all__"
labels = {
"it_name": "项目名称",
"it_desc": "项目描述",
"it_start_time": "项目开始时间",
"it_end_time": "项目结束时间",
}
error_messages = {
"it_name": {"required": "不能为空"},
"it_desc": {"required": "不能为空"},
"it_start_time": {"required": "不能为空"},
"it_end_time": {"required": "不能为空"},
}
widgets = {
"it_name": wid.Input(attrs={"class": "form-control", "placeholder": "请输入项目名称"}),
"it_desc": wid.Textarea(attrs={"class": "form-control", "placeholder": "请输入项目描述"}),
"it_start_time": wid.DateInput(attrs={"class": "form-control", "type": "date"}),
"it_end_time": wid.DateInput(attrs={"class": "form-control", "type": "date"}),
}
# def __inti__(self,*args,**kwargs):
# super().__init__(*args,**kwargs)
# for name,field in self.fields.items():
# field.widget.attrs['class'] = 'form-control'
# field.widget.attrs['placeholder'] = '请输入%s' %(field.label,)
class ApiModelForm(ModelForm):
""" 用例表 """
class Meta:
model = Api
fields = "__all__"
exclude = ['api_run_status', 'api_run_time', 'api_pass_status', 'api_report', 'api_sub_it']
labels = {
"api_name": "用例名称",
"api_desc": "用例描述",
"api_url": "请求URL",
"api_method": "请求类型",
"api_params": "请求参数",
"api_data": "请求data",
"api_expect": "预期结果",
}
error_messages = {
"api_name": {"required": "不能为空"},
"api_desc": {"required": "不能为空"},
"api_url": {"required": "不能为空"},
"api_method": {"required": "不能为空"},
"api_params": {"required": "不能为空"},
"api_data": {"required": "不能为空"},
"api_expect": {"required": "不能为空"},
}
widgets = {
"api_name": wid.Input(attrs={"class": "form-control", "placeholder": "请输入用例名称"}),
"api_desc": wid.Textarea(attrs={"class": "form-control", "placeholder": "请输入用例描述"}),
"api_url": wid.Input(attrs={"class": "form-control", "placeholder": "请输入请求url地址"}),
"api_method": wid.Input(attrs={"class": "form-control", "placeholder": "请输入请求方法"}),
"api_params": wid.Input(attrs={"class": "form-control", "placeholder": "请输入请求参数"}),
"api_data": wid.Textarea(attrs={"class": "form-control", "placeholder": "请输入请求data"}),
"api_assert_type": wid.Input(attrs={"class": "form-control"}),
"api_expect": wid.Textarea(attrs={"class": "form-control", "placeholder": "请输入预期结果"}),
}
# exclude = ['api_run_status', 'api_run_time', 'api_pass_status', 'api_report', 'api_sub_it']
#
# bootstrapClass_filter: List[str] = ['api_run_status', 'api_run_time', 'api_pass_status', 'api_report', 'api_sub_it']
#
# def __int__(self, *args, **kwargs):
# super().save(*args,**kwargs)
# for name, field in self.fields.items():
# if name in List:
# continue
# old_class = field.widget.attrs.get['class', ""]
# field.widget.attrs['class'] = '{} form-control'.format(old_class)
# field.widget.attrs['placeholder'] = '请输入%s' % (field.label,)
RequestHandler.py
import json
import os.path
import unittest
import requests
from io import BytesIO
from deepdiff import DeepDiff
from app.models import Logs, Api
from HTMLTestRunner import HTMLTestRunner
"""
处理请求相关和生成测试报告
"""
class MyCase(unittest.TestCase):
def test_case(self):
# self._testMethodName = self.title
self._testMethodDoc = self.desc
if self.expect_type == 0:
self.assertEqual(self.expect,self.res)
else:
self.assertIn(self.expect,self.res)
# self.assertEqual(DeepDiff(self.res, self.expect).get('type_changes'), None, msg=self.msg)
class RequestOperate(object):
def __init__(self, case_obj, suite_list):
self.case_obj = case_obj
self.suite_list = suite_list
def handler(self):
"""
关于请求的一系列流程
1、提取case_obj中的字段,使用request发送请求
2、使用unittest进行断言
3、更改数据库字段
4、将执行结果添加到日志表中
5、前端返回
"""
self.send_msg()
def send_msg(self):
""" 发请求 """
res = requests.request(
method=self.case_obj.api_method,
url=self.case_obj.api_url,
data=self._check_data(),
params=self._check_params()
)
self.assert_msg(res)
def assert_msg(self, res):
""" 护理断言 """
case = MyCase(methodName='test_case')
try:
case.res = res.json()
except Exception as e:
case.res = res.text
case.expect_type = self.case_obj.api_assert_type
case.expect = self._check_expect()
case.msg = "自定义错误信息:{}".format(DeepDiff(res.text, self._check_expect()))
case.title = self.case_obj.api_name
case.desc = self.case_obj.api_desc
self.suite_list.addTest(case)
suite = unittest.TestSuite()
suite.addTest(case)
# unittest.TextTestRunner(verbosity=2).run(suite)
self.create_single_report(suite)
def create_single_report(self, suite):
""" 生成单个用例报告 """
f = open('a.html', 'wb')
# f = BytesIO()
result = HTMLTestRunner(
stream=f,
verbosity=2,
title=self.case_obj.api_name,
description=self.case_obj.api_desc,
).run(suite)
f.close()
self.update_api_status(result,f)
def create_batch_report(self, suite):
""" 生成批量用例报告 """
f = open('b.html', 'wb')
# f = BytesIO()
result = HTMLTestRunner(
stream=f,
verbosity=2,
title=self.case_obj.api_name,
description=self.case_obj.api_desc,
).run(suite)
f.close()
self.update_log_status(result, f)
def update_log_status(self, result, f):
""" 更新log表 """
log_data = {'pass': 0, 'failed': 0, 'total': 0, 'errors': 0}
for i in result.__dict__['result']:
if i[0]:
log_data['failed'] += 1
else:
log_data['pass'] += 1
log_data['total'] += 1
log_data['errors'] = result.__dict__['errors'].__len__()
Logs.objects.create(
log_report=self.read_file_b(),
log_sub_it_id=self.case_obj.api_sub_it_id,
log_pass_count=log_data['pass'],
log_failed_count=log_data['failed'],
log_errors_count=log_data['errors'],
log_run_count=log_data['total']
)
def update_api_status(self, result, f):
"""
更新数据库相关字段的状态:
1、api_reprot
2、api_run_time
3、api_pass_status
4、api_run_status
"""
obj = Api.objects.filter(pk=self.case_obj.pk).first()
obj.api_report = self.read_file()
import datetime
obj.api_run_time = datetime.datetime.now()
obj.api_run_status = 1
for i in result.__dict__['result']:
if i[0]:
obj.api_pass_status = 0
else:
obj.api_pass_status = 1
obj.save()
def read_file(self):
with open('a.html', 'r', encoding='utf-8') as f:
return f.read()
def read_file_b(self):
with open('b.html', 'r', encoding='utf-8') as f:
return f.read()
def _check_expect(self):
""" 处理预期值 """
if self.case_obj.api_expect:
try:
return json.loads(self.case_obj.api_expect)
except Exception as e:
return self.case_obj.api_expect
else:
return {}
def _check_data(self):
""" 校验请求的data参数 """
if self.case_obj.api_data:
return json.loads(self.case_obj.api_data)
else:
return {}
def _check_params(self):
""" 校验请求的params时标准的json串 """
if self.case_obj.api_params:
return json.loads(self.case_obj.api_params)
else:
return {}
def run_case(api_list):
# 在批量执行每一个用例之前先创建一个suite_list,当批量执行每一个用例时,将封装好的用例对象添加到suite_list中
# 当批量执行完毕后,suite_list中包含了所有批量执行的用例,此时,再用HTMLTestRunner去生成一个批量执行的测试报告
suite_list = unittest.TestSuite()
for i in api_list:
RequestOperate(case_obj=i, suite_list=suite_list).handler()
RequestOperate(case_obj=i, suite_list=suite_list).create_batch_report(suite_list)
urls.py
from django.urls import path, re_path
from .views import *
urlpatterns = [
# 项目表相关
path('index', index, name='index'),
path('add_it', add_it, name='add_it'),
re_path(r'edit_it/(?P<pk>\d+)$', edit_it, name='edit_it'),
re_path(r'delete_it/(?P<pk>\d+)$', delete_it, name='delete_it'),
re_path(r'upload/(?P<pk>\d+)$', upload, name='upload'),
# 接口用例表相关
re_path(r'add_api/(?P<pk>\d+)$', add_api, name='add_api'),
re_path(r'list_api/(?P<pk>\d+)$', list_api, name='list_api'),
re_path(r'edit_api/(?P<pk>\d+)$', edit_api, name='edit_api'),
re_path(r'delete_api/(?P<pk>\d+)$', delete_api, name='delete_api'),
re_path(r"run_case/(?P<pk>\d+)$", run_case, name='run_case'),
# 下载用例报告
re_path(r"download_case_report/(?P<pk>\d+)$", download_case_report, name='download_case_report'),
# log日志
path(r'logs_list', logs_list, name='logs_list'),
path(r'show_tab', show_tab, name='show_tab'),
re_path(r"get_log_report/(?P<pk>\d+)$", get_log_report, name='get_log_report'),
]
views.py
import json
import xlrd
from django.db import transaction
from django.http import JsonResponse, FileResponse, HttpResponse
from django.shortcuts import render, redirect
from django.utils.encoding import escape_uri_path
from . import MyModelForm, RequestHandler
from .models import It, Api, Logs
# Create your views here.
def index(request):
""" 项目主页 """
it_obj = It.objects.all()
return render(request, 'index.html', {"it_obj": it_obj})
def add_it(request):
""" 项目主页 """
if request.method == "POST":
form_data = MyModelForm.ItModelForm(request.POST)
if form_data.is_valid():
form_data.save()
return redirect('/autotest/index')
else:
return render(request, 'add_it.html', {"it_form_obj": form_data})
else:
it_form_obj = MyModelForm.ItModelForm()
return render(request, 'add_it.html', {"it_form_obj": it_form_obj})
def edit_it(request, pk):
""" 项目主页 """
it_obj = It.objects.filter(pk=pk).first()
if request.method == "POST":
form_data = MyModelForm.ItModelForm(request.POST, instance=it_obj)
if form_data.is_valid():
form_data.save()
return redirect('/autotest/index')
else:
return render(request, 'add_it.html', {"it_form_obj": form_data})
else:
it_form_obj = MyModelForm.ItModelForm(instance=it_obj)
return render(request, 'add_it.html', {"it_form_obj": it_form_obj})
def delete_it(request, pk):
It.objects.filter(pk=pk).delete()
return redirect('/autotest/index')
def upload(request,pk):
""" 导入用例 """
it_obj = It.objects.filter(pk=pk).first()
if request.method == "POST":
try:
with transaction.atomic():
file_obj = request.FILES.get("f1")
book_obj = xlrd.open_workbook(filename=None,file_contents=file_obj.read())
sheet = book_obj.sheet_by_index(0)
data_list = []
title = sheet.row_values(0)
for item in range(1,sheet.nrows):
data_list.append(dict(zip(title,sheet.row_values(item))))
for item in data_list:
Api.objects.create(
api_sub_it_id=pk,
api_name=item['title'],
api_desc=item['desc'],
api_url=item['url'],
api_method=item['method'],
api_data=item['body'],
api_expect=item['expect'],
)
return JsonResponse({"status": 200, "path": '/autotest/list_api/{}'.format(pk)})
except Exception as e:
return JsonResponse({
"status": 500,
"path": '/autotest/list_api/{}'.format(pk),
"errors": "请上传 [xls] 类型的表格,并且表格的字段要符合要求!错误详情:{}".format(e)
})
else:
return render(request,'upload.html', {"it_obj": it_obj})
def add_api(request, pk):
""" 添加用例 pk:所属项目的pk """
it_obj = It.objects.filter(pk=pk).first()
if request.method == "POST":
form_data = MyModelForm.ApiModelForm(request.POST)
if form_data.is_valid():
form_data.instance.api_sub_it = it_obj
form_data.save()
return redirect('/autotest/index')
else:
return render(request, 'add_api.html', {"api_form_obj": form_data})
else:
api_form_obj = MyModelForm.ApiModelForm()
return render(request, 'add_api.html', {"api_form_obj": api_form_obj, 'it_obj': it_obj})
def list_api(request, pk):
""" 用例列表 """
it_obj = It.objects.filter(pk=pk).first()
api_obj = Api.objects.filter(api_sub_it_id=pk)
return render(request, 'list_api.html', {"api_obj": api_obj, "it_obj": it_obj})
def edit_api(request, pk):
""" pk:用例表的pk """
api_obj = Api.objects.filter(pk=pk).first()
if request.method == "POST":
form_data = MyModelForm.ApiModelForm(request.POST, instance=api_obj)
if form_data.is_valid():
form_data.instance.__dict__['api_pass_status'] = 0
form_data.instance.__dict__['api_run_status'] = 0
form_data.instance.__dict__['api_report'] = ""
form_data.save()
return redirect('/autotest/list_api/{}'.format(api_obj.api_sub_it_id))
else:
return render(request, 'edit_api.html', {"api_form_obj": form_data})
else:
api_form_obj = MyModelForm.ApiModelForm(instance=api_obj)
return render(request, 'edit_api.html', {"api_form_obj": api_form_obj, "it_obj": api_obj.api_sub_it})
def delete_api(request, pk):
""" 删除用例 pk:用例表的pk"""
# 由于返回时,需要项目的pk值,这里不能直接删除
api_obj = Api.objects.filter(pk=pk).first()
it_obj_pk = api_obj.api_sub_it_id
api_obj.delete()
return redirect('/autotest/list_api/{}'.format(it_obj_pk))
# from django.views.decorators.csrf import csrf_exempt
# @csrf_exempt
def run_case(request, pk=0):
""" 执行用例 """
# 批量执行
if request.method == "POST":
chk_value = request.POST.get('chk_value')
chk_value = json.loads(chk_value)
api_list = Api.objects.filter(pk__in=chk_value)
RequestHandler.run_case(api_list)
return JsonResponse({"path": '/autotest/logs_list'})
else:
case_obj = Api.objects.filter(pk=pk).first()
RequestHandler.run_case([case_obj])
# return redirect('/autotest/list_api/{}'.format(case_obj.api_sub_it_id))
return redirect('/autotest/logs_list')
def download_case_report(request, pk):
api_obj = Api.objects.filter(pk=pk).first()
response = FileResponse(api_obj.api_report)
response['Content-Type'] = "application/octet-stream"
response['Content-Disposition'] = 'attachment;filename="{}.{}"'.format(escape_uri_path(api_obj.api_name), 'html')
# return response
return HttpResponse(api_obj.api_report)
def logs_list(request):
""" log日志主页 """
if request.method == "POST":
return HttpResponse("ok")
else:
log_obj = Logs.objects.all()
return render(request, 'logs_list.html', {"log_obj": log_obj})
def show_tab(request):
""" 可视化 """
if request.method == "POST":
pass_count = 0
failed_count = 0
log_objs = Logs.objects.all()
for foo in log_objs:
pass_count += foo.log_pass_count
failed_count += foo.log_failed_count
data_dict = {
"pie": [
{"value": 0, "name": '失败'},
{"value": 0, "name": '通过'},
]
}
data_dict['pie'][0]['value'] = failed_count
data_dict['pie'][1]['value'] = pass_count
return JsonResponse(data_dict)
else:
return render(request,'show_tab.html')
def get_log_report(request,pk):
log_obj = Logs.objects.filter(pk=pk).first()
return HttpResponse(log_obj.log_report)
add_api.html
{% extends 'base.html' %}
{% block breadcrumb %}
<ol class="breadcrumb float-sm-left">
<li class="breadcrumb-item"><a href="{% url 'index' %}">index</a></li>
<li class="breadcrumb-item active">{{ it_obj.it_name }}</li>
<li class="breadcrumb-item active">添加用例</li>
</ol>
{% endblock %}
{% block content-head %}
<h5 class="m-0">添加项目</h5>
{% endblock %}
{% block content %}
<form action="" method="post" novalidate>
{% csrf_token %}
{% for foo in api_form_obj %}
<div>
<label for="{{ foo.id_for_label }}" id="{{ foo.id_for_label }}" class="form-control">{{ foo.label }}</label>
{{ foo }}
</div>
<span style="color: red;">{{ foo.errors.0 }}</span>
{% endfor %}
<div>
<input type="submit" class="btn btn-success" value="提交">
</div>
</form>
{% endblock %}
add_it.html
{% extends 'base.html' %}
{% block breadcrumb %}
<ol class="breadcrumb float-sm-left">
<li class="breadcrumb-item"><a href="{% url 'index' %}">index</a></li>
<li class="breadcrumb-item active">项目列表</li>
<li class="breadcrumb-item active">添加项目</li>
</ol>
{% endblock %}
{% block content-head %}
<h5 class="m-0">添加项目</h5>
{% endblock %}
{% block content %}
<form action="" method="post" novalidate>
{% csrf_token %}
{% for foo in it_form_obj %}
<div>
<label for="{{ foo.id_for_label }}" id="{{ foo.id_for_label }}" class="form-control">{{ foo.label }}</label>
{{ foo }}
</div>
<span style="color: red;">{{ foo.errors.0 }}</span>
{% endfor %}
<div>
<input type="submit" class="btn btn-success" value="提交">
</div>
</form>
{% endblock %}
base.html
<!DOCTYPE html>
<!--
This is a starter template page. Use this page to start your new project from
scratch. This page gets rid of all links and provides the needed markup only.
-->
<html lang="en">
<head>
{% load static %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AutoTest</title>
<!-- Google Font: Source Sans Pro -->
<!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">-->
<!-- Font Awesome Icons -->
<link rel="stylesheet" href="/static/plugins/fontawesome-free/css/all.min.css">
<!-- Theme style -->
<link rel="stylesheet" href="/static/dist/css/adminlte.min.css">
</head>
<body class="hold-transition sidebar-mini">
<div class="wrapper">
<!-- Navbar -->
<nav class="main-header navbar navbar-expand navbar-white navbar-light">
<!-- Left navbar links -->
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a>
</li>
<li class="nav-item d-none d-sm-inline-block">
<a href="index3.html" class="nav-link">Home</a>
</li>
<li class="nav-item d-none d-sm-inline-block">
<a href="#" class="nav-link">Contact</a>
</li>
</ul>
<!-- Right navbar links -->
<ul class="navbar-nav ml-auto">
<!-- Navbar Search -->
<li class="nav-item">
<a class="nav-link" data-widget="navbar-search" href="#" role="button">
<i class="fas fa-search"></i>
</a>
<div class="navbar-search-block">
<form class="form-inline">
<div class="input-group input-group-sm">
<input class="form-control form-control-navbar" type="search" placeholder="Search" aria-label="Search">
<div class="input-group-append">
<button class="btn btn-navbar" type="submit">
<i class="fas fa-search"></i>
</button>
<button class="btn btn-navbar" type="button" data-widget="navbar-search">
<i class="fas fa-times"></i>
</button>
</div>
</div>
</form>
</div>
</li>
<!-- Messages Dropdown Menu -->
<li class="nav-item dropdown">
<a class="nav-link" data-toggle="dropdown" href="#">
<i class="far fa-comments"></i>
<span class="badge badge-danger navbar-badge">3</span>
</a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
<a href="#" class="dropdown-item">
<!-- Message Start -->
<div class="media">
<img src="/static/dist/img/user1-128x128.jpg" alt="User Avatar" class="img-size-50 mr-3 img-circle">
<div class="media-body">
<h3 class="dropdown-item-title">
Brad Diesel
<span class="float-right text-sm text-danger"><i class="fas fa-star"></i></span>
</h3>
<p class="text-sm">Call me whenever you can...</p>
<p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p>
</div>
</div>
<!-- Message End -->
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">
<!-- Message Start -->
<div class="media">
<img src="/static/dist/img/user8-128x128.jpg" alt="User Avatar" class="img-size-50 img-circle mr-3">
<div class="media-body">
<h3 class="dropdown-item-title">
John Pierce
<span class="float-right text-sm text-muted"><i class="fas fa-star"></i></span>
</h3>
<p class="text-sm">I got your message bro</p>
<p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p>
</div>
</div>
<!-- Message End -->
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">
<!-- Message Start -->
<div class="media">
<img src="/static/dist/img/user3-128x128.jpg" alt="User Avatar" class="img-size-50 img-circle mr-3">
<div class="media-body">
<h3 class="dropdown-item-title">
Nora Silvester
<span class="float-right text-sm text-warning"><i class="fas fa-star"></i></span>
</h3>
<p class="text-sm">The subject goes here</p>
<p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p>
</div>
</div>
<!-- Message End -->
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item dropdown-footer">See All Messages</a>
</div>
</li>
<!-- Notifications Dropdown Menu -->
<li class="nav-item dropdown">
<a class="nav-link" data-toggle="dropdown" href="#">
<i class="far fa-bell"></i>
<span class="badge badge-warning navbar-badge">15</span>
</a>
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
<span class="dropdown-header">15 Notifications</span>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">
<i class="fas fa-envelope mr-2"></i> 4 new messages
<span class="float-right text-muted text-sm">3 mins</span>
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">
<i class="fas fa-users mr-2"></i> 8 friend requests
<span class="float-right text-muted text-sm">12 hours</span>
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item">
<i class="fas fa-file mr-2"></i> 3 new reports
<span class="float-right text-muted text-sm">2 days</span>
</a>
<div class="dropdown-divider"></div>
<a href="#" class="dropdown-item dropdown-footer">See All Notifications</a>
</div>
</li>
<li class="nav-item">
<a class="nav-link" data-widget="fullscreen" href="#" role="button">
<i class="fas fa-expand-arrows-alt"></i>
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-widget="control-sidebar" data-slide="true" href="#" role="button">
<i class="fas fa-th-large"></i>
</a>
</li>
</ul>
</nav>
<!-- /.navbar -->
<!-- Main Sidebar Container -->
<aside class="main-sidebar sidebar-dark-primary elevation-4">
<!-- Brand Logo -->
<a href="index3.html" class="brand-link">
<img src="/static/dist/img/AdminLTELogo.png" alt="AdminLTE Logo" class="brand-image img-circle elevation-3" style="opacity: .8">
<span class="brand-text font-weight-light">自动化测试平台</span>
</a>
<!-- Sidebar -->
<div class="sidebar">
<!-- Sidebar user panel (optional) -->
<!-- <div class="user-panel mt-3 pb-3 mb-3 d-flex">-->
<!-- <div class="image">-->
<!-- <img src="../static/dist/img/user2-160x160.jpg" class="img-circle elevation-2" alt="User Image">-->
<!-- </div>-->
<!-- <div class="info">-->
<!-- <a href="#" class="d-block">Alexander Pierce</a>-->
<!-- </div>-->
<!-- </div>-->
<!-- SidebarSearch Form -->
<div class="form-inline">
<div class="input-group" data-widget="sidebar-search">
<input class="form-control form-control-sidebar" type="search" placeholder="Search" aria-label="Search">
<div class="input-group-append">
<button class="btn btn-sidebar">
<i class="fas fa-search fa-fw"></i>
</button>
</div>
</div>
</div>
<!-- Sidebar Menu -->
<nav class="mt-2">
<ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
<li class="nav-item">
<a href="{% url 'index' %}" class="nav-link">
<i class="nav-icon fas fa-th"></i>
<p>
项目列表
<span class="right badge badge-danger">New</span>
</p>
</a>
</li><li class="nav-item">
<a href="{% url 'logs_list' %}" class="nav-link">
<i class="nav-icon fas fa-th"></i>
<p>
Log日志
<span class="right badge badge-danger">New</span>
</p>
</a>
</li><li class="nav-item">
<a href="{% url 'show_tab' %}" class="nav-link">
<i class="nav-icon fas fa-th"></i>
<p>
可视化图表
<span class="right badge badge-danger">New</span>
</p>
</a>
</li>
</ul>
</nav>
<!-- /.sidebar-menu -->
</div>
<!-- /.sidebar -->
</aside>
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<!-- <div class="col-sm-6">-->
<!-- <h1 class="m-0">Starter Page</h1>-->
<!-- </div><!– /.col –>-->
<div class="col-sm-6">
{% block breadcrumb %}
<ol class="breadcrumb float-sm-left">
<li class="breadcrumb-item"><a href="#">Home</a></li>
<li class="breadcrumb-item active">Starter Page</li>
</ol>
{% endblock %}
</div><!-- /.col -->
</div><!-- /.row -->
</div><!-- /.container-fluid -->
</div>
<!-- /.content-header -->
<!-- Main content -->
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-lg-12">
{% block content %}
<div class="card card-primary card-outline">
<div class="card-header">
<h5 class="m-0">title</h5>
</div>
<div class="card-body">
</div>
</div>
{% endblock %}
</div>
<!-- /.col-md-6 -->
</div>
<!-- /.row -->
</div><!-- /.container-fluid -->
</div>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->
<!-- Control Sidebar -->
<aside class="control-sidebar control-sidebar-dark">
<!-- Control sidebar content goes here -->
<div class="p-3">
<h5>Title</h5>
<p>Sidebar content</p>
</div>
</aside>
<!-- /.control-sidebar -->
<!-- Main Footer -->
<footer class="main-footer">
<!-- To the right -->
<div class="float-right d-none d-sm-inline">
Anything you want
</div>
<!-- Default to the left -->
<strong>Copyright © 2014-2021 <a href="https://adminlte.io">AdminLTE.io</a>.</strong> All rights reserved.
</footer>
</div>
<!-- ./wrapper -->
<!-- REQUIRED SCRIPTS -->
<!-- jQuery -->
<script src="/static/plugins/jquery/jquery.min.js"></script>
<!-- Bootstrap 4 -->
<script src="/static/plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- AdminLTE App -->
<script src="/static/dist/js/adminlte.min.js"></script>
{% block js %}
{% endblock %}
</body>
</html>
edit_api.html
{% extends 'base.html' %}
{% block breadcrumb %}
<ol class="breadcrumb float-sm-left">
<li class="breadcrumb-item"><a href="{% url 'index' %}">index</a></li>
<li class="breadcrumb-item active">{{ it_obj.it_name }}</li>
<li class="breadcrumb-item active">编辑用例</li>
</ol>
{% endblock %}
{% block content-head %}
<h5 class="m-0">添加项目</h5>
{% endblock %}
{% block content %}
<form action="" method="post" novalidate>
{% csrf_token %}
{% for foo in api_form_obj %}
<div>
<label for="{{ foo.id_for_label }}" id="{{ foo.id_for_label }}" class="form-control">{{ foo.label }}</label>
{{ foo }}
</div>
<span style="color: red;">{{ foo.errors.0 }}</span>
{% endfor %}
<div>
<input type="submit" class="btn btn-success" value="提交">
</div>
</form>
{% endblock %}
edit_it.html
{% extends 'base.html' %}
{% block breadcrumb %}
<ol class="breadcrumb float-sm-left">
<li class="breadcrumb-item"><a href="{% url 'index' %}">index</a></li>
<li class="breadcrumb-item active">项目列表</li>
<li class="breadcrumb-item active">添加项目</li>
</ol>
{% endblock %}
{% block content-head %}
<h5 class="m-0">添加项目</h5>
{% endblock %}
{% block content %}
<form action="" method="post" novalidate>
{% csrf_token %}
{% for foo in it_form_obj %}
<div>
<label for="{{ foo.id_for_label }}" id="{{ foo.id_for_label }}" class="form-control">{{ foo.label }}</label>
{{ foo }}
</div>
<span style="color: red;">{{ foo.errors.0 }}</span>
{% endfor %}
<div>
<input type="submit" class="btn btn-success" value="提交">
</div>
</form>
{% endblock %}
index.html
{% extends 'base.html' %}
{% block breadcrumb %}
<ol class="breadcrumb float-sm-left">
<li class="breadcrumb-item"><a href="{% url 'index' %}">index</a></li>
<li class="breadcrumb-item active">项目列表</li>
</ol>
{% endblock %}
{% block content %}
{% if it_obj %}
<div class="card card-primary">
<div class="card-header">
<a href="{% url 'add_it' %}">添加项目</a>
</div>
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th>序号</th>
<th>名称</th>
<th>描述</th>
<th>开始时间</th>
<th>结束时间</th>
<th>用例数量</th>
<th>覆盖率</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for foo in it_obj %}
<tr>
<!-- <td>{{ foo.pk }}</td>-->
<td>{{ forloop.counter }}</td>
<td>{{ foo.it_name }}</td>
<td>{{ foo.it_desc }}</td>
<td>{{ foo.it_start_time }}</td>
<td>{{ foo.it_end_time }}</td>
<td>{{ foo.api_set.count }}</td>
<td>{{ foo.xxoo }}</td>
<td>
<a href="{% url 'delete_it' foo.pk %}" class="btn btn-danger btn-sm">删除</a>
<a href="{% url 'edit_it' foo.pk %}" class="btn btn-info btn-sm">编辑</a>
<a href="{% url 'add_api' foo.pk %}" class="btn btn-warning btn-sm">添加用例</a>
<a href="{% url 'list_api' foo.pk %}" class="btn btn-success btn-sm">查看用例</a>
<a href="{% url 'upload' foo.pk %}" class="btn btn-default btn-sm">导入用例</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
没有数据,去<a href="{% url 'add_it' %}">创建</a>
{% endif %}
{% endblock %}
list_api.html
{% extends 'base.html' %}
{% block breadcrumb %}
<ol class="breadcrumb float-sm-left">
<li class="breadcrumb-item"><a href="{% url 'index' %}">index</a></li>
<li class="breadcrumb-item active">{{ it_obj.it_name }}</li>
<li class="breadcrumb-item active">用例列表</li>
</ol>
{% endblock %}
{% block content %}
{% if api_obj %}
<div class="card card-primary">
<!-- <div class="card-header">-->
<!-- <a href="{% url 'add_it' %}">添加项目</a>-->
<!-- </div>-->
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th>选择</th>
<th>序号</th>
<th>名称</th>
<th>描述</th>
<th>请求URL</th>
<th>请求类型</th>
<th>请求参数</th>
<th>预期值</th>
<th>用例报告</th>
<th>通过状态</th>
<th>是否执行</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for foo in api_obj %}
<tr>
<!-- <td>{{ foo.pk }}</td>-->
<td>
<input type="checkbox" value="{{ foo.pk }}" name="checkbox-list">
</td>
<td>{{ forloop.counter }}</td>
<td>{{ foo.api_name }}</td>
<td>{{ foo.api_desc | slice:"20" }}</td>
<td title="{{ foo.api_url }}">{{ foo.api_url | truncatechars:10 }}</td>
<td>{{ foo.api_method }}</td>
<td>{{ foo.api_params }}</td>
<td>{{ foo.api_expect }}</td>
{% if foo.api_report %}
<td><a href="{% url 'download_case_report' foo.pk %}">查看</a></td>
{% else %}
<td>无</td>
{% endif %}
<!-- <td>{{ foo.api_report }}</td>-->
<td>{{ foo.get_api_pass_status_display }}</td>
<td>{{ foo.get_api_run_status_display }}</td>
<td>
<a href="{% url 'delete_api' foo.pk %}" class="btn btn-danger btn-sm">删除</a>
<a href="{% url 'edit_api' foo.pk %}" class="btn btn-info btn-sm">编辑</a>
<a href="{% url 'run_case' foo.pk %}" class="btn btn-info btn-sm">执行</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div>
{% csrf_token %}
<input type="button" value="批量执行" class="btn btn-success" id="execute">
</div>
{% else %}
没有数据,去<a href="{% url 'add_api' it_obj.pk %}">创建</a>
{% endif %}
{% endblock %}
{% block js %}
<!-- <script src="./static/sweetalert.min.js"></script>-->
<script>
$("#execute").click(function(){
var chk_value = new Array();
$.each($('input[name="checkbox-list"]:checked'), function(index,item){
chk_value.push($(item).val());
});
if (chk_value.length == 0) {
swal({
"title": "请至少勾选一个用例后,再点击执行"
})
} else {
<!-- console.log(11111111111,chk_value)-->
$.ajax({
"url": "{% url 'run_case' 0 %}",
"type": "POST",
"data": {
"chk_value": JSON.stringify(chk_value),
"csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val()
},
success: function(data){
<!-- console.log(data)-->
window.location = data['path']
}
})
}
})
</script>
{% endblock %}
log_list.html
{% extends 'base.html' %}
{% block content %}
<div class="card card-primary">
<!-- <div class="card-header">-->
<!-- <a href="{% url 'add_it' %}">添加项目</a>-->
<!-- </div>-->
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th>序号</th>
<th>所属项目</th>
<th>创建时间</th>
<th>执行用例数</th>
<th>用例失败数</th>
<th>用例报错数</th>
<th>用例通过数</th>
<th>通过率</th>
<th>用例报告</th>
</tr>
</thead>
<tbody>
{% for foo in log_obj %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ foo.log_sub_it.it_name }}</td>
<td>{{ foo.log_run_time }}</td>
<td>{{ foo.log_run_count }}</td>
<td>{{ foo.log_failed_count }}</td>
<td>{{ foo.log_errors_count }}</td>
<td>{{ foo.log_pass_count }}</td>
<td>{{ foo.pass_rate }}</td>
<td><a href="{% url 'get_log_report' foo.pk %}">查看</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
show_tab.html
{% extends 'base.html' %}
{% block content %}
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">用例通过相关</h3>
<div class="card-tools">
<button type="button" class="btn btn-tool" data-card-widget="collapse">
<i class="fas fa-minus"></i>
</button>
<button type="button" class="btn btn-tool" data-card-widget="remove">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="card-body">
<div class="chart">
<div id="areaChart" style="min-height: 250px; height: 400px; max-height: 450px; max-width: 100%;"></div>
</div>
</div>
<!-- /.card-body -->
</div>
{% csrf_token %}
{% endblock %}
{% block js %}
<script src="./static/echarts.min.js"></script>
<script>
function pie(data_dict){
PieObj = echarts.init(document.getElementById("areaChart"));
PieObjOption = {
title: {
text: '接口自动化用例',
subtext: '执行统计',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: 'Access From',
type: 'pie',
radius: '50%',
<!-- data: [-->
<!-- { value: 1048, name: '失败' },-->
<!-- { value: 735, name: '通过' },-->
<!-- ],-->
data: data_dict,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
PieObj.setOption(PieObjOption)
}
function init(){
$.ajax({
"url": "{% url 'show_tab' %}",
"type": "POST",
"data": {"csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val()},
success: function (data){
pie(data['pie'])
}
})
}
init()
</script>
{% endblock %}
upload.html
{% extends 'base.html' %}
{% block breadcrumb %}
<ol class="breadcrumb float-sm-left">
<li class="breadcrumb-item"><a href="{% url 'index' %}">index</a></li>
<li class="breadcrumb-item active">{{ it_obj.it_name }}</li>
</ol>
{% endblock %}
{% block content %}
<div class="alert alert-info alert-dismissible" id="alert">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h5><i class="icon fas fa-info"></i> 重要提示 !</h5>
只支持上传Excel文件
</div>
<div class="form-group">
<label for="ajaxFile">File input</label>
<div class="input-group">
<div class="custom-file">
<input type="file" class="custom-file-input" id="ajaxFile">
<label class="custom-file-label" for="ajaxFile">Choose file</label>
</div>
<div class="input-group-append">
<span class="input-group-text" id="ajaxBtn">Upload</span>
</div>
</div>
</div>
<span style="color: red" id="errors"></span>
{% csrf_token %}
{% endblock %}
{% block js %}
<script>
$("#ajaxBtn").click(function(){
// 首先实例化一个formData对象
var formData = new FormData()
// 然后使用formData来添加数据,即获取文件对象
var file_obj = document.getElementById('ajaxFile').files[0]
formData.append('f1', file_obj)
// 处理csrftoken
formData.append("csrfmiddlewaretoken",$("[name='csrfmiddlewaretoken']").val())
formData.append('user','huang')
$.ajax({
url: "{% url 'upload' it_obj.pk %}",
type: "POST",
data: formData,
processData: false,
contentType: false,
success: function(data){
if (data['status'] == 500){
$("#alert").attr("class","alert alert-danger alert-dismissible")
$("#errors").text(data['errors'])
}else {
window.location.href = data['path']
}
}
})
})
</script>
{% endblock %}