demo

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']


MyModelForm.py

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>&lt;!&ndash; /.col &ndash;&gt;-->
          <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 &copy; 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">&times;</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 %}

posted @ 2022-12-28 08:34  saiya6  阅读(22)  评论(0)    收藏  举报