├── ApiManager ├── utils │ ├── __init__.py │ ├── testcase.py │ ├── emails.py │ ├── task_opt.py │ ├── runner.py │ └── pagination.py ├── migrations │ └── __init__.py ├── templatetags │ ├── __init__.py │ └── custom_tags.py ├── __init__.py ├── apps.py ├── admin.py ├── tasks.py ├── models.py └── managers.py ├── templates ├── view_report.html ├── debugtalk_list.html ├── echo.html ├── debugtalk.html ├── login.html ├── add_module.html ├── report_list.html ├── add_project.html ├── add_suite.html ├── index.html ├── edit_suite.html ├── add_task.html ├── periodictask_list.html ├── env_list.html ├── register.html ├── config_list.html ├── base.html └── add_config.html ├── logs ├── all.log └── script.log ├── images ├── index_01.jpg ├── login_01.jpg ├── add_case_01.jpg ├── add_case_02.jpg ├── add_case_03.jpg ├── add_case_04.jpg ├── register_01.jpg ├── reports_01.jpg ├── reports_02.jpg ├── add_config_01.jpg ├── add_module_01.jpg ├── add_tasks_01.jpg ├── tasks_list_01.jpg ├── test_list_01.jpg ├── add_project_01.png ├── module_list_01.jpg ├── project_list_01.jpg └── report_list_01.jpg ├── static └── assets │ ├── img │ ├── bg.jpg │ ├── logo.png │ ├── favicon.ico │ ├── loading.GIF │ └── main_bg.png │ ├── iconfont │ ├── fonts │ │ ├── icomoon.eot │ │ ├── icomoon.ttf │ │ └── icomoon.woff │ └── style.css │ ├── css │ └── common.css │ └── js │ ├── wow.min.js │ ├── custom.js │ ├── jquery.sticky.js │ └── commons.js ├── .gitattributes ├── requirements.txt ├── HttpRunnerManager ├── __init__.py ├── wsgi.py ├── activator.py ├── celery.py ├── urls.py └── settings.py ├── .gitignore ├── uwsgi.ini ├── manage.py ├── LICENSE └── README.md /ApiManager/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ApiManager/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ApiManager/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/view_report.html: -------------------------------------------------------------------------------- 1 | {{ reports }} -------------------------------------------------------------------------------- /logs/all.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/logs/all.log -------------------------------------------------------------------------------- /logs/script.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/logs/script.log -------------------------------------------------------------------------------- /images/index_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/index_01.jpg -------------------------------------------------------------------------------- /images/login_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/login_01.jpg -------------------------------------------------------------------------------- /ApiManager/__init__.py: -------------------------------------------------------------------------------- 1 | import platform 2 | 3 | separator = '\\' if platform.system() == 'Windows' else '/' -------------------------------------------------------------------------------- /images/add_case_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/add_case_01.jpg -------------------------------------------------------------------------------- /images/add_case_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/add_case_02.jpg -------------------------------------------------------------------------------- /images/add_case_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/add_case_03.jpg -------------------------------------------------------------------------------- /images/add_case_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/add_case_04.jpg -------------------------------------------------------------------------------- /images/register_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/register_01.jpg -------------------------------------------------------------------------------- /images/reports_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/reports_01.jpg -------------------------------------------------------------------------------- /images/reports_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/reports_02.jpg -------------------------------------------------------------------------------- /images/add_config_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/add_config_01.jpg -------------------------------------------------------------------------------- /images/add_module_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/add_module_01.jpg -------------------------------------------------------------------------------- /images/add_tasks_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/add_tasks_01.jpg -------------------------------------------------------------------------------- /images/tasks_list_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/tasks_list_01.jpg -------------------------------------------------------------------------------- /images/test_list_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/test_list_01.jpg -------------------------------------------------------------------------------- /static/assets/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/static/assets/img/bg.jpg -------------------------------------------------------------------------------- /images/add_project_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/add_project_01.png -------------------------------------------------------------------------------- /images/module_list_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/module_list_01.jpg -------------------------------------------------------------------------------- /images/project_list_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/project_list_01.jpg -------------------------------------------------------------------------------- /images/report_list_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/images/report_list_01.jpg -------------------------------------------------------------------------------- /static/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/static/assets/img/logo.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=python 2 | *.css linguist-language=python 3 | *.html linguist-language=python 4 | -------------------------------------------------------------------------------- /static/assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/static/assets/img/favicon.ico -------------------------------------------------------------------------------- /static/assets/img/loading.GIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/static/assets/img/loading.GIF -------------------------------------------------------------------------------- /static/assets/img/main_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/static/assets/img/main_bg.png -------------------------------------------------------------------------------- /ApiManager/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApimanagerConfig(AppConfig): 5 | name = 'ApiManager' 6 | -------------------------------------------------------------------------------- /static/assets/iconfont/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/static/assets/iconfont/fonts/icomoon.eot -------------------------------------------------------------------------------- /static/assets/iconfont/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/static/assets/iconfont/fonts/icomoon.ttf -------------------------------------------------------------------------------- /static/assets/iconfont/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httprunner/HttpRunnerManager/HEAD/static/assets/iconfont/fonts/icomoon.woff -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django == 2.0.3 2 | PyYAML == 3.12 3 | requests == 2.18.4 4 | eventlet == 0.22.1 5 | mysqlclient == 1.3.12 6 | django-celery == 3.2.2 7 | flower == 0.9.2 8 | dwebsocket == 0.4.2 9 | paramiko == 2.4.1 10 | HttpRunner == 1.5.8 -------------------------------------------------------------------------------- /HttpRunnerManager/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals 2 | 3 | # This will make sure the app is always imported when 4 | # Django starts so that shared_task will use this app. 5 | from .celery import app as celery_app 6 | 7 | __all__ = ['celery_app'] 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .gitignore 3 | .idea/ 4 | ApiManager/__pycache__/ 5 | ApiManager/logic/__pycache__/ 6 | ApiManager/migrations/__pycache__/ 7 | HttpRunnerManager/__pycache__/ 8 | httprunner/__pycache__/ 9 | pyunitreport/__pycache__/ 10 | BeautifulReport/__pycache__/ 11 | Test Report.html 12 | ApiManager/templatetags/*.pyc -------------------------------------------------------------------------------- /uwsgi.ini: -------------------------------------------------------------------------------- 1 | # myweb_uwsgi.ini file 2 | [uwsgi] 3 | 4 | # Django-related settings 5 | 6 | project = HttpRunnerManager 7 | base = /opt 8 | 9 | 10 | chdir = %(base)/%(project) 11 | module = %(project).wsgi:application 12 | 13 | 14 | master = true 15 | processes = 5 16 | 17 | 18 | socket = %(base)/%(project)/%(project).sock 19 | chmod-socket = 666 20 | vacuum = true 21 | -------------------------------------------------------------------------------- /HttpRunnerManager/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for HttpRunnerManager project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "HttpRunnerManager.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /HttpRunnerManager/activator.py: -------------------------------------------------------------------------------- 1 | 2 | def process(request, **kwargs): 3 | app = kwargs.pop('app', None) 4 | fun = kwargs.pop('function', None) 5 | index = kwargs.pop('id', None) 6 | 7 | if app == 'api': 8 | app = 'ApiManager' 9 | try: 10 | app = __import__("%s.views" % app) 11 | view = getattr(app, 'views') 12 | fun = getattr(view, fun) 13 | 14 | # 执行view.py中的函数,并获取其返回值 15 | result = fun(request, index) if index else fun(request) 16 | except (ImportError, AttributeError): 17 | raise 18 | 19 | return result 20 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "HttpRunnerManager.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /HttpRunnerManager/celery.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals 2 | 3 | import os 4 | 5 | from celery import Celery 6 | # set the default Django settings module for the 'celery' program. 7 | from django.conf import settings 8 | 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'HttpRunnerManager.settings') 10 | 11 | app = Celery('HttpRunnerManager') 12 | 13 | # Using a string here means the worker doesn't have to serialize 14 | # the configuration object to child processes. 15 | # - namespace='CELERY' means all celery-related configuration keys 16 | # should have a `CELERY_` prefix. 17 | app.config_from_object('django.conf:settings') 18 | 19 | # Load task modules from all registered Django app configs. 20 | app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) 21 | 22 | 23 | # @app.task(bind=True) 24 | # def debug_task(self): 25 | # print('Request: {0!r}'.format(self.request)) 26 | -------------------------------------------------------------------------------- /ApiManager/templatetags/custom_tags.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django import template 4 | 5 | from ApiManager.utils.common import update_include 6 | 7 | register = template.Library() 8 | 9 | 10 | @register.filter(name='data_type') 11 | def data_type(value): 12 | """ 13 | 返回数据类型 自建filter 14 | :param value: 15 | :return: the type of value 16 | """ 17 | return str(type(value).__name__) 18 | 19 | 20 | @register.filter(name='convert_eval') 21 | def convert_eval(value): 22 | """ 23 | 数据eval转换 自建filter 24 | :param value: 25 | :return: the value which had been eval 26 | """ 27 | return update_include(eval(value)) 28 | 29 | 30 | @register.filter(name='json_dumps') 31 | def json_dumps(value): 32 | return json.dumps(value, indent=4, separators=(',', ': '), ensure_ascii=False) 33 | 34 | 35 | @register.filter(name='is_del') 36 | def id_del(value): 37 | if value.endswith('已删除'): 38 | return True 39 | else: 40 | return False 41 | -------------------------------------------------------------------------------- /ApiManager/utils/testcase.py: -------------------------------------------------------------------------------- 1 | import io 2 | import json 3 | import time 4 | 5 | import yaml 6 | 7 | 8 | def get_time_stamp(): 9 | ct = time.time() 10 | local_time = time.localtime(ct) 11 | data_head = time.strftime("%Y-%m-%d %H-%M-%S", local_time) 12 | data_secs = (ct - int(ct)) * 1000 13 | time_stamp = "%s-%03d" % (data_head, data_secs) 14 | return time_stamp 15 | 16 | 17 | def dump_yaml_file(yaml_file, data): 18 | """ load yaml file and check file content format 19 | """ 20 | with io.open(yaml_file, 'w', encoding='utf-8') as stream: 21 | yaml.dump(data, stream, indent=4, default_flow_style=False, encoding='utf-8') 22 | 23 | 24 | def _dump_json_file(json_file, data): 25 | """ load json file and check file content format 26 | """ 27 | with io.open(json_file, 'w', encoding='utf-8') as stream: 28 | json.dump(data, stream, indent=4, separators=(',', ': '), ensure_ascii=False) 29 | 30 | 31 | def dump_python_file(python_file, data): 32 | with io.open(python_file, 'w', encoding='utf-8') as stream: 33 | stream.write(data) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018/2/07 HttpRunnerManager 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /HttpRunnerManager/urls.py: -------------------------------------------------------------------------------- 1 | """HttpRunnerManager URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url 17 | from django.contrib import admin 18 | from django.views.generic import RedirectView 19 | 20 | from HttpRunnerManager.activator import process 21 | 22 | urlpatterns = [ 23 | url(r'^admin/', admin.site.urls), 24 | url(r'^favicon\.ico$', RedirectView.as_view(url='/static/assets/img/favicon.ico')), 25 | url('^(?P(\w+))/(?P(\w+))/$', process), 26 | url('^(?P(\w+))/(?P(\w+))/(?P(\w+))/$', process), 27 | 28 | ] 29 | -------------------------------------------------------------------------------- /static/assets/css/common.css: -------------------------------------------------------------------------------- 1 | table#variables tr td, table#data tr td, table#header tr td, table#validate tr td, 2 | table#extract tr td, table#hooks tr td, table#params tr td { 3 | padding: 0px; 4 | } 5 | 6 | .tips { 7 | 8 | color: rgba(0, 0, 0, 0.5); 9 | 10 | padding-left: 10px; 11 | 12 | } 13 | 14 | .tips_true, .tips_false { 15 | 16 | padding-left: 10px; 17 | 18 | } 19 | 20 | .tips_true { 21 | 22 | color: green; 23 | 24 | } 25 | 26 | .tips_false { 27 | 28 | color: red; 29 | 30 | } 31 | 32 | #pre_case li{ 33 | position: relative; 34 | } 35 | 36 | #pre_case i { 37 | -webkit-transition: opacity .2s; 38 | transition: opacity .2s; 39 | opacity: 0; 40 | display: block; 41 | cursor: pointer; 42 | color: #c00; 43 | top: 10px; 44 | right: 40px; 45 | position: absolute; 46 | font-style: normal; 47 | } 48 | 49 | #pre_case li:hover i{ 50 | opacity: 1; 51 | } 52 | 53 | 54 | #pre_config li{ 55 | position: relative; 56 | } 57 | 58 | #pre_config i { 59 | -webkit-transition: opacity .2s; 60 | transition: opacity .2s; 61 | opacity: 0; 62 | display: block; 63 | cursor: pointer; 64 | color: #c00; 65 | top: 10px; 66 | right: 40px; 67 | position: absolute; 68 | font-style: normal; 69 | } 70 | 71 | #pre_config li:hover i{ 72 | opacity: 1; 73 | } 74 | -------------------------------------------------------------------------------- /ApiManager/utils/emails.py: -------------------------------------------------------------------------------- 1 | import io 2 | import smtplib 3 | from email.mime.multipart import MIMEMultipart 4 | from email.mime.text import MIMEText 5 | 6 | import os 7 | 8 | from HttpRunnerManager.settings import EMAIL_SEND_USERNAME, EMAIL_SEND_PASSWORD 9 | 10 | 11 | def send_email_reports(receiver, html_report_path): 12 | if '@sina.com' in EMAIL_SEND_USERNAME: 13 | smtp_server = 'smtp.sina.com' 14 | elif '@163.com' in EMAIL_SEND_USERNAME: 15 | smtp_server = 'smtp.163.com' 16 | else: 17 | smtp_server = 'smtp.exmail.qq.com' 18 | 19 | subject = "接口自动化测试报告" 20 | 21 | with io.open(html_report_path, 'r', encoding='utf-8') as stream: 22 | send_file = stream.read() 23 | 24 | att = MIMEText(send_file, "base64", "utf-8") 25 | att["Content-Type"] = "application/octet-stream" 26 | att["Content-Disposition"] = "attachment;filename = TestReports.html" 27 | 28 | body = MIMEText("附件为定时任务生成的接口测试报告,请查收,谢谢!", _subtype='html', _charset='gb2312') 29 | 30 | msg = MIMEMultipart('related') 31 | msg['Subject'] = subject 32 | msg['from'] = EMAIL_SEND_USERNAME 33 | msg['to'] = receiver 34 | msg.attach(att) 35 | msg.attach(body) 36 | 37 | smtp = smtplib.SMTP() 38 | smtp.connect(smtp_server) 39 | smtp.starttls() 40 | smtp.login(EMAIL_SEND_USERNAME, EMAIL_SEND_PASSWORD) 41 | smtp.sendmail(EMAIL_SEND_USERNAME, receiver.split(','), msg.as_string()) 42 | smtp.quit() 43 | 44 | 45 | if __name__ == '__main__': 46 | send_email_reports('##@qq.com, example@163.com', 'D:\\HttpRunnerManager\\reports\\2018-06-05 15-58-00.html') 47 | -------------------------------------------------------------------------------- /ApiManager/utils/task_opt.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from djcelery import models as celery_models 4 | 5 | 6 | def create_task(name, task, task_args, crontab_time, desc): 7 | ''' 8 | 新增定时任务 9 | :param name: 定时任务名称 10 | :param task: 对应tasks里已有的task 11 | :param task_args: list 参数 12 | :param crontab_time: 时间配置 13 | :param desc: 定时任务描述 14 | :return: ok 15 | ''' 16 | # task任务, created是否定时创建 17 | task, created = celery_models.PeriodicTask.objects.get_or_create(name=name, task=task) 18 | # 获取 crontab 19 | crontab = celery_models.CrontabSchedule.objects.filter(**crontab_time).first() 20 | if crontab is None: 21 | # 如果没有就创建,有的话就继续复用之前的crontab 22 | crontab = celery_models.CrontabSchedule.objects.create(**crontab_time) 23 | task.crontab = crontab # 设置crontab 24 | task.enabled = True # 开启task 25 | task.kwargs = json.dumps(task_args, ensure_ascii=False) # 传入task参数 26 | task.description = desc 27 | task.save() 28 | return 'ok' 29 | 30 | 31 | def change_task_status(name, mode): 32 | ''' 33 | 任务状态切换:open or close 34 | :param name: 任务名称 35 | :param mode: 模式 36 | :return: ok or error 37 | ''' 38 | try: 39 | task = celery_models.PeriodicTask.objects.get(name=name) 40 | task.enabled = mode 41 | task.save() 42 | return 'ok' 43 | except celery_models.PeriodicTask.DoesNotExist: 44 | return 'error' 45 | 46 | 47 | def delete_task(name): 48 | ''' 49 | 根据任务名称删除任务 50 | :param name: task name 51 | :return: ok or error 52 | ''' 53 | try: 54 | task = celery_models.PeriodicTask.objects.get(name=name) 55 | task.enabled = False # 设置关闭 56 | task.delete() 57 | return 'ok' 58 | except celery_models.PeriodicTask.DoesNotExist: 59 | return 'error' 60 | -------------------------------------------------------------------------------- /static/assets/iconfont/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src: url('fonts/icomoon.eot?u4m6fy'); 4 | src: url('fonts/icomoon.eot?u4m6fy#iefix') format('embedded-opentype'), 5 | url('fonts/icomoon.ttf?u4m6fy') format('truetype'), 6 | url('fonts/icomoon.woff?u4m6fy') format('woff'), 7 | url('fonts/icomoon.svg?u4m6fy#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="icon-"], [class*=" icon-"] { 13 | /* use !important to prevent issues with browser extensions that change fonts */ 14 | font-family: 'icomoon' !important; 15 | speak: none; 16 | font-style: normal; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | 22 | /* Better Font Rendering =========== */ 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | .icon-logg:before { 28 | content: "\e915"; 29 | } 30 | .icon-manager:before { 31 | content: "\e916"; 32 | } 33 | .icon-user:before { 34 | content: "\e917"; 35 | } 36 | .icon-data:before { 37 | content: "\e918"; 38 | } 39 | .icon-sysmanager:before { 40 | content: "\e919"; 41 | } 42 | .icon-list:before { 43 | content: "\e91a"; 44 | } 45 | .icon-passagewaymanager:before { 46 | content: "\e91b"; 47 | } 48 | .icon-base:before { 49 | content: "\e91c"; 50 | } 51 | .icon-agentlist:before { 52 | content: "\e91d"; 53 | } 54 | .icon-configure:before { 55 | content: "\e91e"; 56 | } 57 | .icon-flow:before { 58 | content: "\e91f"; 59 | } 60 | .icon-orderlist:before { 61 | content: "\e920"; 62 | } 63 | .icon-ydlist:before { 64 | content: "\e921"; 65 | } 66 | .icon-agentpay:before { 67 | content: "\e922"; 68 | } 69 | .icon-agentsum:before { 70 | content: "\e923"; 71 | } 72 | .icon-passagewaysum:before { 73 | content: "\e924"; 74 | } 75 | .icon-custom:before { 76 | content: "\e925"; 77 | } 78 | .icon-address:before { 79 | content: "\e926"; 80 | } 81 | .icon-lock:before { 82 | content: "\e927"; 83 | } 84 | 85 | -------------------------------------------------------------------------------- /templates/debugtalk_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Debugtalk{% endblock %} 3 | {% load staticfiles %} 4 | {% block content %} 5 | 6 |
7 |
8 |
    项目管理
9 |
当前位置: debugtalk.py > debugtalk.py
10 |
11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% for foo in debugtalk %} 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% endfor %} 37 | 38 |
序号所属项目DebugTalk创建时间更新时间
{{ forloop.counter }}{{ foo.belong_project.project_name }}debugtalk.py{{ foo.create_time }}{{ foo.update_time }}
39 |
    40 | {{ page_list }} 41 |
42 | 43 | 44 |
45 | 46 |
47 |
48 | 49 | {% endblock %} -------------------------------------------------------------------------------- /templates/echo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 实验室 5 | 6 | 44 | 45 | 46 | 47 | 服务器IP: 48 | 49 | 50 | 用户名: 51 | 52 | 53 | 密码: 54 | 55 | 56 | shell: 57 | 58 | 59 | 60 | 61 | 62 |

Received Messages:

63 |
64 |
65 | 66 |
67 | 68 | -------------------------------------------------------------------------------- /ApiManager/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from ApiManager.models import UserInfo, ProjectInfo, ModuleInfo, TestCaseInfo, EnvInfo 4 | 5 | 6 | @admin.register(UserInfo) 7 | class UserInfoAdmin(admin.ModelAdmin): 8 | list_display = ('id', 'username', 'password', 'email', 'status', 'create_time', 'update_time') 9 | list_per_page = 20 10 | ordering = ('-create_time',) 11 | list_display_links = ('username',) 12 | # 筛选器 13 | list_filter = ('username', 'email') # 过滤器 14 | search_fields = ('username', 'email') # 搜索字段 15 | date_hierarchy = 'update_time' # 详细时间分层筛选  16 | 17 | 18 | @admin.register(ProjectInfo) 19 | class ProjectInfoAdmin(admin.ModelAdmin): 20 | list_display = ('id', 'project_name', 'responsible_name', 'test_user', 'dev_user', 'publish_app', 'simple_desc', 21 | 'other_desc', 'create_time', 'update_time') 22 | list_per_page = 20 23 | ordering = ('-create_time',) 24 | list_display_links = ('project_name',) 25 | list_filter = ('project_name', 'responsible_name') # 过滤器 26 | search_fields = ('project_name', 'responsible_name') # 搜索字段 27 | date_hierarchy = 'update_time' # 详细时间分层筛选  28 | 29 | 30 | @admin.register(ModuleInfo) 31 | class ModuleInfoAdmin(admin.ModelAdmin): 32 | list_display = ('id', 'module_name', 'belong_project', 'test_user', 'simple_desc' 33 | , 'other_desc', 'create_time', 'update_time') 34 | list_per_page = 20 35 | ordering = ('-create_time',) 36 | list_display_links = ('module_name',) 37 | list_filter = ('module_name', 'test_user') # 过滤器 38 | search_fields = ('module_name', 'test_user') # 搜索字段 39 | date_hierarchy = 'update_time' # 详细时间分层筛选  40 | 41 | 42 | @admin.register(TestCaseInfo) 43 | class TestCaseInfoAdmin(admin.ModelAdmin): 44 | list_display = ( 45 | 'id', 'type', 'name', 'belong_project', 'belong_module', 'include', 'author', 'request', 46 | 'create_time', 47 | 'update_time') 48 | list_per_page = 50 49 | ordering = ('-create_time',) 50 | list_display_links = ('name',) 51 | list_filter = ('belong_project', 'belong_module', 'type', 'name') # 过滤器 52 | search_fields = ('belong_project', 'belong_module', 'type', 'name') # 搜索字段 53 | date_hierarchy = 'update_time' # 详细时间分层筛选 54 | 55 | 56 | @admin.register(EnvInfo) 57 | class EnvInfoAdmin(admin.ModelAdmin): 58 | list_display = ( 59 | 'id', 'env_name', 'base_url', 'simple_desc', 'create_time', 'update_time') 60 | list_per_page = 50 61 | ordering = ('-create_time',) 62 | list_display_links = ('env_name',) 63 | list_filter = ('env_name', 'base_url') # 过滤器 64 | search_fields = ('env_name', 'env_name') # 搜索字段 65 | date_hierarchy = 'update_time' # 详细时间分层筛选 66 | 67 | 68 | admin.site.site_header = 'HttpRunnerManager运维管理系统' 69 | admin.site.site_title = 'HttpRunnerManager' 70 | -------------------------------------------------------------------------------- /templates/debugtalk.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | debugtalk.py 5 | 6 | {% load staticfiles %} 7 | 8 | 10 | 12 | 13 | 14 | 42 | 43 | 44 | 45 | 46 |
47 | 
50 | 
51 | 52 |
53 | 54 | 返回首页 55 |
56 | 57 | 58 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% load staticfiles %} 5 | 6 | 登录 7 | 8 | 9 | 10 | 11 | 96 | 97 | 98 | 99 | 100 | 101 |
102 |

Welcome to HttpRunnerManager

103 |
104 |

用户登录

105 |
106 |
107 | 108 | 109 |
110 |
111 | 112 | 113 |
114 |
115 | 118 | 没有账号?点击注册 119 |
120 | 121 |
122 | 123 |
124 |
125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /ApiManager/tasks.py: -------------------------------------------------------------------------------- 1 | # Create your tasks here 2 | from __future__ import absolute_import, unicode_literals 3 | 4 | import os 5 | import shutil 6 | 7 | from celery import shared_task 8 | from django.core.exceptions import ObjectDoesNotExist 9 | 10 | from ApiManager.models import ProjectInfo 11 | from ApiManager.utils.common import timestamp_to_datetime 12 | from ApiManager.utils.emails import send_email_reports 13 | from ApiManager.utils.operation import add_test_reports 14 | from ApiManager.utils.runner import run_by_project, run_by_module, run_by_suite 15 | from ApiManager.utils.testcase import get_time_stamp 16 | from httprunner import HttpRunner, logger 17 | 18 | 19 | @shared_task 20 | def main_hrun(testset_path, report_name): 21 | """ 22 | 用例运行 23 | :param testset_path: dict or list 24 | :param report_name: str 25 | :return: 26 | """ 27 | logger.setup_logger('INFO') 28 | kwargs = { 29 | "failfast": False, 30 | } 31 | runner = HttpRunner(**kwargs) 32 | runner.run(testset_path) 33 | shutil.rmtree(testset_path) 34 | 35 | runner.summary = timestamp_to_datetime(runner.summary) 36 | report_path = add_test_reports(runner, report_name=report_name) 37 | os.remove(report_path) 38 | 39 | 40 | @shared_task 41 | def project_hrun(name, base_url, project, receiver): 42 | """ 43 | 异步运行整个项目 44 | :param env_name: str: 环境地址 45 | :param project: str 46 | :return: 47 | """ 48 | logger.setup_logger('INFO') 49 | kwargs = { 50 | "failfast": False, 51 | } 52 | runner = HttpRunner(**kwargs) 53 | id = ProjectInfo.objects.get(project_name=project).id 54 | 55 | testcase_dir_path = os.path.join(os.getcwd(), "suite") 56 | testcase_dir_path = os.path.join(testcase_dir_path, get_time_stamp()) 57 | 58 | run_by_project(id, base_url, testcase_dir_path) 59 | 60 | runner.run(testcase_dir_path) 61 | shutil.rmtree(testcase_dir_path) 62 | 63 | runner.summary = timestamp_to_datetime(runner.summary) 64 | report_path = add_test_reports(runner, report_name=name) 65 | 66 | if receiver != '': 67 | send_email_reports(receiver, report_path) 68 | os.remove(report_path) 69 | 70 | 71 | @shared_task 72 | def module_hrun(name, base_url, module, receiver): 73 | """ 74 | 异步运行模块 75 | :param env_name: str: 环境地址 76 | :param project: str:项目所属模块 77 | :param module: str:模块名称 78 | :return: 79 | """ 80 | logger.setup_logger('INFO') 81 | kwargs = { 82 | "failfast": False, 83 | } 84 | runner = HttpRunner(**kwargs) 85 | module = list(module) 86 | 87 | testcase_dir_path = os.path.join(os.getcwd(), "suite") 88 | testcase_dir_path = os.path.join(testcase_dir_path, get_time_stamp()) 89 | 90 | try: 91 | for value in module: 92 | run_by_module(value[0], base_url, testcase_dir_path) 93 | except ObjectDoesNotExist: 94 | return '找不到模块信息' 95 | 96 | runner.run(testcase_dir_path) 97 | 98 | shutil.rmtree(testcase_dir_path) 99 | runner.summary = timestamp_to_datetime(runner.summary) 100 | report_path = add_test_reports(runner, report_name=name) 101 | 102 | if receiver != '': 103 | send_email_reports(receiver, report_path) 104 | os.remove(report_path) 105 | 106 | 107 | @shared_task 108 | def suite_hrun(name, base_url, suite, receiver): 109 | """ 110 | 异步运行模块 111 | :param env_name: str: 环境地址 112 | :param project: str:项目所属模块 113 | :param module: str:模块名称 114 | :return: 115 | """ 116 | logger.setup_logger('INFO') 117 | kwargs = { 118 | "failfast": False, 119 | } 120 | runner = HttpRunner(**kwargs) 121 | suite = list(suite) 122 | 123 | testcase_dir_path = os.path.join(os.getcwd(), "suite") 124 | testcase_dir_path = os.path.join(testcase_dir_path, get_time_stamp()) 125 | 126 | try: 127 | for value in suite: 128 | run_by_suite(value[0], base_url, testcase_dir_path) 129 | except ObjectDoesNotExist: 130 | return '找不到Suite信息' 131 | 132 | runner.run(testcase_dir_path) 133 | 134 | shutil.rmtree(testcase_dir_path) 135 | 136 | runner.summary = timestamp_to_datetime(runner.summary) 137 | report_path = add_test_reports(runner, report_name=name) 138 | 139 | if receiver != '': 140 | send_email_reports(receiver, report_path) 141 | os.remove(report_path) 142 | -------------------------------------------------------------------------------- /templates/add_module.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}新增模块{% endblock %} 3 | {% load staticfiles %} 4 | {% block content %} 5 | 6 |
7 | 8 |
9 |
10 |
    新增模块
11 |
当前位置: 模块管理 > 新增模块
12 |
13 |
14 |
15 |
16 | 17 |
18 | 21 | 22 |
23 |
24 |
25 | 26 |
27 | 33 |
34 |
35 | 36 |
37 | 38 |
39 | 41 | 42 |
43 |
44 | 45 |
46 | 47 |
48 | 50 | 51 |
52 |
53 | 54 |
55 | 56 |
57 | 59 | 60 |
61 |
62 |
63 |
64 |   67 | »   68 | 新 增 用 例 69 | 70 |
71 |
72 |
73 |
74 |
75 |
76 | 77 | {% endblock %} -------------------------------------------------------------------------------- /ApiManager/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from ApiManager.managers import UserTypeManager, UserInfoManager, ProjectInfoManager, ModuleInfoManager, \ 4 | TestCaseInfoManager, EnvInfoManager 5 | 6 | 7 | # Create your models here. 8 | 9 | 10 | class BaseTable(models.Model): 11 | create_time = models.DateTimeField('创建时间', auto_now_add=True) 12 | update_time = models.DateTimeField('更新时间', auto_now=True) 13 | 14 | class Meta: 15 | abstract = True 16 | verbose_name = "公共字段表" 17 | db_table = 'BaseTable' 18 | 19 | 20 | class UserType(BaseTable): 21 | class Meta: 22 | verbose_name = '用户类型' 23 | db_table = 'UserType' 24 | 25 | type_name = models.CharField(max_length=20) 26 | type_desc = models.CharField(max_length=50) 27 | objects = UserTypeManager() 28 | 29 | 30 | class UserInfo(BaseTable): 31 | class Meta: 32 | verbose_name = '用户信息' 33 | db_table = 'UserInfo' 34 | 35 | username = models.CharField('用户名', max_length=20, unique=True, null=False) 36 | password = models.CharField('密码', max_length=20, null=False) 37 | email = models.EmailField('邮箱', null=False, unique=True) 38 | status = models.IntegerField('有效/无效', default=1) 39 | # user_type = models.ForeignKey(UserType, on_delete=models.CASCADE) 40 | objects = UserInfoManager() 41 | 42 | 43 | class ProjectInfo(BaseTable): 44 | class Meta: 45 | verbose_name = '项目信息' 46 | db_table = 'ProjectInfo' 47 | 48 | project_name = models.CharField('项目名称', max_length=50, unique=True, null=False) 49 | responsible_name = models.CharField('负责人', max_length=20, null=False) 50 | test_user = models.CharField('测试人员', max_length=100, null=False) 51 | dev_user = models.CharField('开发人员', max_length=100, null=False) 52 | publish_app = models.CharField('发布应用', max_length=100, null=False) 53 | simple_desc = models.CharField('简要描述', max_length=100, null=True) 54 | other_desc = models.CharField('其他信息', max_length=100, null=True) 55 | objects = ProjectInfoManager() 56 | 57 | 58 | class ModuleInfo(BaseTable): 59 | class Meta: 60 | verbose_name = '模块信息' 61 | db_table = 'ModuleInfo' 62 | 63 | module_name = models.CharField('模块名称', max_length=50, null=False) 64 | belong_project = models.ForeignKey(ProjectInfo, on_delete=models.CASCADE) 65 | test_user = models.CharField('测试负责人', max_length=50, null=False) 66 | simple_desc = models.CharField('简要描述', max_length=100, null=True) 67 | other_desc = models.CharField('其他信息', max_length=100, null=True) 68 | objects = ModuleInfoManager() 69 | 70 | 71 | class TestCaseInfo(BaseTable): 72 | class Meta: 73 | verbose_name = '用例信息' 74 | db_table = 'TestCaseInfo' 75 | 76 | type = models.IntegerField('test/config', default=1) 77 | name = models.CharField('用例/配置名称', max_length=50, null=False) 78 | belong_project = models.CharField('所属项目', max_length=50, null=False) 79 | belong_module = models.ForeignKey(ModuleInfo, on_delete=models.CASCADE) 80 | include = models.CharField('前置config/test', max_length=1024, null=True) 81 | author = models.CharField('编写人员', max_length=20, null=False) 82 | request = models.TextField('请求信息', null=False) 83 | 84 | objects = TestCaseInfoManager() 85 | 86 | 87 | class TestReports(BaseTable): 88 | class Meta: 89 | verbose_name = "测试报告" 90 | db_table = 'TestReports' 91 | 92 | report_name = models.CharField(max_length=40, null=False) 93 | start_at = models.CharField(max_length=40, null=True) 94 | status = models.BooleanField() 95 | testsRun = models.IntegerField() 96 | successes = models.IntegerField() 97 | reports = models.TextField() 98 | 99 | 100 | class EnvInfo(BaseTable): 101 | class Meta: 102 | verbose_name = '环境管理' 103 | db_table = 'EnvInfo' 104 | 105 | env_name = models.CharField(max_length=40, null=False, unique=True) 106 | base_url = models.CharField(max_length=40, null=False) 107 | simple_desc = models.CharField(max_length=50, null=False) 108 | objects = EnvInfoManager() 109 | 110 | 111 | class DebugTalk(BaseTable): 112 | class Meta: 113 | verbose_name = '驱动py文件' 114 | db_table = 'DebugTalk' 115 | 116 | belong_project = models.ForeignKey(ProjectInfo, on_delete=models.CASCADE) 117 | debugtalk = models.TextField(null=True, default='#debugtalk.py') 118 | 119 | 120 | class TestSuite(BaseTable): 121 | class Meta: 122 | verbose_name = '用例集合' 123 | db_table = 'TestSuite' 124 | 125 | belong_project = models.ForeignKey(ProjectInfo, on_delete=models.CASCADE) 126 | suite_name = models.CharField(max_length=100, null=False) 127 | include = models.TextField(null=False) 128 | -------------------------------------------------------------------------------- /templates/report_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}查看报告{% endblock %} 3 | {% load staticfiles %} 4 | {% block content %} 5 |
6 |
7 |
    报告列表
8 |
当前位置: 报告管理 > 报告展示
9 |
10 | 11 |
12 |
13 |
    14 |
  • 17 |
  • 18 | 19 | 22 |
  • 23 |
24 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {% for foo in report %} 43 | 44 | 45 | 46 | 47 | 48 | {% if foo.status == True %} 49 | 50 | {% else %} 51 | 52 | {% endif %} 53 | 54 | 55 | 71 | 72 | 73 | {% endfor %} 74 | 75 |
序号报告名称开始时间测试结果总计用例成功用例操作
{{ forloop.counter }}{{ foo.report_name }}{{ foo.start_at }}PassFail{{ foo.testsRun }}{{ foo.successes }} 56 |
57 |
58 | 63 | 68 |
69 |
70 |
76 | 77 |
    78 | {{ page_list }} 79 |
80 |
81 |
82 |
83 | 97 | 98 | {% endblock %} -------------------------------------------------------------------------------- /ApiManager/utils/runner.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.exceptions import ObjectDoesNotExist 4 | 5 | from ApiManager.models import TestCaseInfo, ModuleInfo, ProjectInfo, DebugTalk, TestSuite 6 | from ApiManager.utils.testcase import dump_python_file, dump_yaml_file 7 | 8 | 9 | def run_by_single(index, base_url, path): 10 | """ 11 | 加载单个case用例信息 12 | :param index: int or str:用例索引 13 | :param base_url: str:环境地址 14 | :return: dict 15 | """ 16 | config = { 17 | 'config': { 18 | 'name': '', 19 | 'request': { 20 | 'base_url': base_url 21 | } 22 | } 23 | } 24 | testcase_list = [] 25 | 26 | testcase_list.append(config) 27 | 28 | try: 29 | obj = TestCaseInfo.objects.get(id=index) 30 | except ObjectDoesNotExist: 31 | return testcase_list 32 | 33 | include = eval(obj.include) 34 | request = eval(obj.request) 35 | name = obj.name 36 | project = obj.belong_project 37 | module = obj.belong_module.module_name 38 | 39 | config['config']['name'] = name 40 | 41 | testcase_dir_path = os.path.join(path, project) 42 | 43 | if not os.path.exists(testcase_dir_path): 44 | os.makedirs(testcase_dir_path) 45 | 46 | try: 47 | debugtalk = DebugTalk.objects.get(belong_project__project_name=project).debugtalk 48 | except ObjectDoesNotExist: 49 | debugtalk = '' 50 | 51 | dump_python_file(os.path.join(testcase_dir_path, 'debugtalk.py'), debugtalk) 52 | 53 | testcase_dir_path = os.path.join(testcase_dir_path, module) 54 | 55 | if not os.path.exists(testcase_dir_path): 56 | os.mkdir(testcase_dir_path) 57 | 58 | for test_info in include: 59 | try: 60 | if isinstance(test_info, dict): 61 | config_id = test_info.pop('config')[0] 62 | config_request = eval(TestCaseInfo.objects.get(id=config_id).request) 63 | config_request.get('config').get('request').setdefault('base_url', base_url) 64 | config_request['config']['name'] = name 65 | testcase_list[0] = config_request 66 | else: 67 | id = test_info[0] 68 | pre_request = eval(TestCaseInfo.objects.get(id=id).request) 69 | testcase_list.append(pre_request) 70 | 71 | except ObjectDoesNotExist: 72 | return testcase_list 73 | 74 | if request['test']['request']['url'] != '': 75 | testcase_list.append(request) 76 | 77 | dump_yaml_file(os.path.join(testcase_dir_path, name + '.yml'), testcase_list) 78 | 79 | 80 | def run_by_suite(index, base_url, path): 81 | obj = TestSuite.objects.get(id=index) 82 | 83 | include = eval(obj.include) 84 | 85 | for val in include: 86 | run_by_single(val[0], base_url, path) 87 | 88 | 89 | 90 | def run_by_batch(test_list, base_url, path, type=None, mode=False): 91 | """ 92 | 批量组装用例数据 93 | :param test_list: 94 | :param base_url: str: 环境地址 95 | :param type: str:用例级别 96 | :param mode: boolean:True 同步 False: 异步 97 | :return: list 98 | """ 99 | 100 | if mode: 101 | for index in range(len(test_list) - 2): 102 | form_test = test_list[index].split('=') 103 | value = form_test[1] 104 | if type == 'project': 105 | run_by_project(value, base_url, path) 106 | elif type == 'module': 107 | run_by_module(value, base_url, path) 108 | elif type == 'suite': 109 | run_by_suite(value, base_url, path) 110 | else: 111 | run_by_single(value, base_url, path) 112 | 113 | else: 114 | if type == 'project': 115 | for value in test_list.values(): 116 | run_by_project(value, base_url, path) 117 | 118 | elif type == 'module': 119 | for value in test_list.values(): 120 | run_by_module(value, base_url, path) 121 | elif type == 'suite': 122 | for value in test_list.values(): 123 | run_by_suite(value, base_url, path) 124 | 125 | else: 126 | for index in range(len(test_list) - 1): 127 | form_test = test_list[index].split('=') 128 | index = form_test[1] 129 | run_by_single(index, base_url, path) 130 | 131 | 132 | def run_by_module(id, base_url, path): 133 | """ 134 | 组装模块用例 135 | :param id: int or str:模块索引 136 | :param base_url: str:环境地址 137 | :return: list 138 | """ 139 | obj = ModuleInfo.objects.get(id=id) 140 | test_index_list = TestCaseInfo.objects.filter(belong_module=obj, type=1).values_list('id') 141 | for index in test_index_list: 142 | run_by_single(index[0], base_url, path) 143 | 144 | 145 | def run_by_project(id, base_url, path): 146 | """ 147 | 组装项目用例 148 | :param id: int or str:项目索引 149 | :param base_url: 环境地址 150 | :return: list 151 | """ 152 | obj = ProjectInfo.objects.get(id=id) 153 | module_index_list = ModuleInfo.objects.filter(belong_project=obj).values_list('id') 154 | for index in module_index_list: 155 | module_id = index[0] 156 | run_by_module(module_id, base_url, path) 157 | 158 | 159 | def run_test_by_type(id, base_url, path, type): 160 | if type == 'project': 161 | run_by_project(id, base_url, path) 162 | elif type == 'module': 163 | run_by_module(id, base_url, path) 164 | elif type == 'suite': 165 | run_by_suite(id, base_url, path) 166 | else: 167 | run_by_single(id, base_url, path) 168 | -------------------------------------------------------------------------------- /templates/add_project.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}新增项目{% endblock %} 3 | {% load staticfiles %} 4 | {% block content %} 5 | 6 |
7 | 8 |
9 |
10 |
    新增项目
11 |
当前位置: 项目管理 > 新增项目
12 |
13 |
14 |
15 |
16 | 17 |
18 | 21 | 22 |
23 |
24 |
25 | 26 |
27 | 29 | 30 |
31 |
32 | 33 |
34 | 35 |
36 | 38 | 39 |
40 |
41 | 42 |
43 | 44 |
45 | 47 | 48 |
49 |
50 | 51 |
52 | 53 |
54 | 56 | 57 |
58 |
59 | 60 |
61 | 62 |
63 | 65 | 66 |
67 |
68 | 69 |
70 | 71 |
72 | 74 | 75 |
76 |
77 |
78 |
79 |   82 | »   83 | 新 增 模 84 | 块 85 | 86 |
87 |
88 |
89 | 90 | 91 |
92 |
93 |
94 | {% endblock %} -------------------------------------------------------------------------------- /ApiManager/managers.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | '''用户类型表操作''' 4 | 5 | 6 | class UserTypeManager(models.Manager): 7 | def insert_user_type(self, user_type): 8 | self.create(user_type=user_type) 9 | 10 | def insert_type_name(self, type_name): 11 | self.create(type_name=type_name) 12 | 13 | def insert_type_desc(self, type_desc): 14 | self.create(type_desc=type_desc) 15 | 16 | def get_objects(self, user_type_id): # 根据user_type得到一条数据 17 | return self.get(user_type_id=user_type_id) 18 | 19 | 20 | '''用户信息表操作''' 21 | 22 | 23 | class UserInfoManager(models.Manager): 24 | def insert_user(self, username, password, email, object): 25 | self.create(username=username, password=password, email=email, user_type=object) 26 | 27 | def query_user(self, username, password): 28 | return self.filter(username__exact=username, password__exact=password).count() 29 | 30 | 31 | '''项目信息表操作''' 32 | 33 | 34 | class ProjectInfoManager(models.Manager): 35 | def insert_project(self, **kwargs): 36 | self.create(**kwargs) 37 | 38 | def update_project(self, id, **kwargs): # 如此update_time才会自动更新!! 39 | obj = self.get(id=id) 40 | obj.project_name = kwargs.get('project_name') 41 | obj.responsible_name = kwargs.get('responsible_name') 42 | obj.test_user = kwargs.get('test_user') 43 | obj.dev_user = kwargs.get('dev_user') 44 | obj.publish_app = kwargs.get('publish_app') 45 | obj.simple_desc = kwargs.get('simple_desc') 46 | obj.other_desc = kwargs.get('other_desc') 47 | obj.save() 48 | 49 | def get_pro_name(self, pro_name, type=True, id=None): 50 | if type: 51 | return self.filter(project_name__exact=pro_name).count() 52 | else: 53 | if id is not None: 54 | return self.get(id=id).project_name 55 | return self.get(project_name__exact=pro_name) 56 | 57 | def get_pro_info(self, type=True): 58 | if type: 59 | return self.all().values('project_name') 60 | else: 61 | return self.all() 62 | 63 | 64 | '''模块信息表操作''' 65 | 66 | 67 | class ModuleInfoManager(models.Manager): 68 | def insert_module(self, **kwargs): 69 | self.create(**kwargs) 70 | 71 | def update_module(self, id, **kwargs): 72 | obj = self.get(id=id) 73 | obj.module_name = kwargs.get('module_name') 74 | obj.test_user = kwargs.get('test_user') 75 | obj.simple_desc = kwargs.get('simple_desc') 76 | obj.other_desc = kwargs.get('other_desc') 77 | 78 | obj.save() 79 | 80 | def get_module_name(self, module_name, type=True, id=None): 81 | if type: 82 | return self.filter(module_name__exact=module_name).count() 83 | else: 84 | if id is not None: 85 | return self.get(id=id).module_name 86 | else: 87 | return self.get(id=module_name) 88 | 89 | 90 | 91 | '''用例信息表操作''' 92 | 93 | 94 | class TestCaseInfoManager(models.Manager): 95 | def insert_case(self, belong_module, **kwargs): 96 | case_info = kwargs.get('test').pop('case_info') 97 | self.create(name=kwargs.get('test').get('name'), belong_project=case_info.pop('project'), 98 | belong_module=belong_module, 99 | author=case_info.pop('author'), include=case_info.pop('include'), request=kwargs) 100 | 101 | def update_case(self, belong_module, **kwargs): 102 | case_info = kwargs.get('test').pop('case_info') 103 | obj = self.get(id=case_info.pop('test_index')) 104 | obj.belong_project = case_info.pop('project') 105 | obj.belong_module = belong_module 106 | obj.name = kwargs.get('test').get('name') 107 | obj.author = case_info.pop('author') 108 | obj.include = case_info.pop('include') 109 | obj.request = kwargs 110 | obj.save() 111 | 112 | def insert_config(self, belong_module, **kwargs): 113 | config_info = kwargs.get('config').pop('config_info') 114 | self.create(name=kwargs.get('config').get('name'), belong_project=config_info.pop('project'), 115 | belong_module=belong_module, 116 | author=config_info.pop('author'), type=2, request=kwargs) 117 | 118 | def update_config(self, belong_module, **kwargs): 119 | config_info = kwargs.get('config').pop('config_info') 120 | obj = self.get(id=config_info.pop('test_index')) 121 | obj.belong_module = belong_module 122 | obj.belong_project = config_info.pop('project') 123 | obj.name = kwargs.get('config').get('name') 124 | obj.author = config_info.pop('author') 125 | obj.request = kwargs 126 | obj.save() 127 | 128 | def get_case_name(self, name, module_name, belong_project): 129 | return self.filter(belong_module__id=module_name).filter(name__exact=name).filter( 130 | belong_project__exact=belong_project).count() 131 | 132 | def get_case_by_id(self, index, type=True): 133 | if type: 134 | return self.filter(id=index).all() 135 | else: 136 | return self.get(id=index).name 137 | 138 | 139 | '''环境变量管理''' 140 | 141 | 142 | class EnvInfoManager(models.Manager): 143 | def insert_env(self, **kwargs): 144 | self.create(**kwargs) 145 | 146 | def update_env(self, index, **kwargs): 147 | obj = self.get(id=index) 148 | obj.env_name = kwargs.pop('env_name') 149 | obj.base_url = kwargs.pop('base_url') 150 | obj.simple_desc = kwargs.pop('simple_desc') 151 | obj.save() 152 | 153 | def get_env_name(self, index): 154 | return self.get(id=index).env_name 155 | 156 | def delete_env(self, index): 157 | self.get(id=index).delete() 158 | -------------------------------------------------------------------------------- /static/assets/js/wow.min.js: -------------------------------------------------------------------------------- 1 | /*! WOW - v1.0.1 - 2014-08-15 2 | * Copyright (c) 2014 Matthieu Aussaguel; Licensed MIT */(function(){var a,b,c,d=function(a,b){return function(){return a.apply(b,arguments)}},e=[].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1};b=function(){function a(){}return a.prototype.extend=function(a,b){var c,d;for(c in b)d=b[c],null==a[c]&&(a[c]=d);return a},a.prototype.isMobile=function(a){return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(a)},a}(),c=this.WeakMap||this.MozWeakMap||(c=function(){function a(){this.keys=[],this.values=[]}return a.prototype.get=function(a){var b,c,d,e,f;for(f=this.keys,b=d=0,e=f.length;e>d;b=++d)if(c=f[b],c===a)return this.values[b]},a.prototype.set=function(a,b){var c,d,e,f,g;for(g=this.keys,c=e=0,f=g.length;f>e;c=++e)if(d=g[c],d===a)return void(this.values[c]=b);return this.keys.push(a),this.values.push(b)},a}()),a=this.MutationObserver||this.WebkitMutationObserver||this.MozMutationObserver||(a=function(){function a(){console.warn("MutationObserver is not supported by your browser."),console.warn("WOW.js cannot detect dom mutations, please call .sync() after loading new content.")}return a.notSupported=!0,a.prototype.observe=function(){},a}()),this.WOW=function(){function f(a){null==a&&(a={}),this.scrollCallback=d(this.scrollCallback,this),this.scrollHandler=d(this.scrollHandler,this),this.start=d(this.start,this),this.scrolled=!0,this.config=this.util().extend(a,this.defaults),this.animationNameCache=new c}return f.prototype.defaults={boxClass:"wow",animateClass:"animated",offset:0,mobile:!0,live:!0},f.prototype.init=function(){var a;return this.element=window.document.documentElement,"interactive"===(a=document.readyState)||"complete"===a?this.start():document.addEventListener("DOMContentLoaded",this.start),this.finished=[]},f.prototype.start=function(){var b,c,d,e;if(this.stopped=!1,this.boxes=function(){var a,c,d,e;for(d=this.element.querySelectorAll("."+this.config.boxClass),e=[],a=0,c=d.length;c>a;a++)b=d[a],e.push(b);return e}.call(this),this.all=function(){var a,c,d,e;for(d=this.boxes,e=[],a=0,c=d.length;c>a;a++)b=d[a],e.push(b);return e}.call(this),this.boxes.length)if(this.disabled())this.resetStyle();else{for(e=this.boxes,c=0,d=e.length;d>c;c++)b=e[c],this.applyStyle(b,!0);window.addEventListener("scroll",this.scrollHandler,!1),window.addEventListener("resize",this.scrollHandler,!1),this.interval=setInterval(this.scrollCallback,50)}return this.config.live?new a(function(a){return function(b){var c,d,e,f,g;for(g=[],e=0,f=b.length;f>e;e++)d=b[e],g.push(function(){var a,b,e,f;for(e=d.addedNodes||[],f=[],a=0,b=e.length;b>a;a++)c=e[a],f.push(this.doSync(c));return f}.call(a));return g}}(this)).observe(document.body,{childList:!0,subtree:!0}):void 0},f.prototype.stop=function(){return this.stopped=!0,window.removeEventListener("scroll",this.scrollHandler,!1),window.removeEventListener("resize",this.scrollHandler,!1),null!=this.interval?clearInterval(this.interval):void 0},f.prototype.sync=function(){return a.notSupported?this.doSync(this.element):void 0},f.prototype.doSync=function(a){var b,c,d,f,g;if(!this.stopped){if(null==a&&(a=this.element),1!==a.nodeType)return;for(a=a.parentNode||a,f=a.querySelectorAll("."+this.config.boxClass),g=[],c=0,d=f.length;d>c;c++)b=f[c],e.call(this.all,b)<0?(this.applyStyle(b,!0),this.boxes.push(b),this.all.push(b),g.push(this.scrolled=!0)):g.push(void 0);return g}},f.prototype.show=function(a){return this.applyStyle(a),a.className=""+a.className+" "+this.config.animateClass},f.prototype.applyStyle=function(a,b){var c,d,e;return d=a.getAttribute("data-wow-duration"),c=a.getAttribute("data-wow-delay"),e=a.getAttribute("data-wow-iteration"),this.animate(function(f){return function(){return f.customStyle(a,b,d,c,e)}}(this))},f.prototype.animate=function(){return"requestAnimationFrame"in window?function(a){return window.requestAnimationFrame(a)}:function(a){return a()}}(),f.prototype.resetStyle=function(){var a,b,c,d,e;for(d=this.boxes,e=[],b=0,c=d.length;c>b;b++)a=d[b],e.push(a.setAttribute("style","visibility: visible;"));return e},f.prototype.customStyle=function(a,b,c,d,e){return b&&this.cacheAnimationName(a),a.style.visibility=b?"hidden":"visible",c&&this.vendorSet(a.style,{animationDuration:c}),d&&this.vendorSet(a.style,{animationDelay:d}),e&&this.vendorSet(a.style,{animationIterationCount:e}),this.vendorSet(a.style,{animationName:b?"none":this.cachedAnimationName(a)}),a},f.prototype.vendors=["moz","webkit"],f.prototype.vendorSet=function(a,b){var c,d,e,f;f=[];for(c in b)d=b[c],a[""+c]=d,f.push(function(){var b,f,g,h;for(g=this.vendors,h=[],b=0,f=g.length;f>b;b++)e=g[b],h.push(a[""+e+c.charAt(0).toUpperCase()+c.substr(1)]=d);return h}.call(this));return f},f.prototype.vendorCSS=function(a,b){var c,d,e,f,g,h;for(d=window.getComputedStyle(a),c=d.getPropertyCSSValue(b),h=this.vendors,f=0,g=h.length;g>f;f++)e=h[f],c=c||d.getPropertyCSSValue("-"+e+"-"+b);return c},f.prototype.animationName=function(a){var b;try{b=this.vendorCSS(a,"animation-name").cssText}catch(c){b=window.getComputedStyle(a).getPropertyValue("animation-name")}return"none"===b?"":b},f.prototype.cacheAnimationName=function(a){return this.animationNameCache.set(a,this.animationName(a))},f.prototype.cachedAnimationName=function(a){return this.animationNameCache.get(a)},f.prototype.scrollHandler=function(){return this.scrolled=!0},f.prototype.scrollCallback=function(){var a;return!this.scrolled||(this.scrolled=!1,this.boxes=function(){var b,c,d,e;for(d=this.boxes,e=[],b=0,c=d.length;c>b;b++)a=d[b],a&&(this.isVisible(a)?this.show(a):e.push(a));return e}.call(this),this.boxes.length||this.config.live)?void 0:this.stop()},f.prototype.offsetTop=function(a){for(var b;void 0===a.offsetTop;)a=a.parentNode;for(b=a.offsetTop;a=a.offsetParent;)b+=a.offsetTop;return b},f.prototype.isVisible=function(a){var b,c,d,e,f;return c=a.getAttribute("data-wow-offset")||this.config.offset,f=window.pageYOffset,e=f+Math.min(this.element.clientHeight,innerHeight)-c,d=this.offsetTop(a),b=d+a.clientHeight,e>=d&&b>=f},f.prototype.util=function(){return null!=this._util?this._util:this._util=new b},f.prototype.disabled=function(){return!this.config.mobile&&this.util().isMobile(navigator.userAgent)},f}()}).call(this); -------------------------------------------------------------------------------- /templates/add_suite.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Add Suite{% endblock %} 3 | {% load staticfiles %} 4 | {% block content %} 5 | 6 |
7 | 8 |
9 | 10 |
11 |
12 |
13 | 14 |
15 |
16 |
项目列表
17 | 24 |
25 |
26 | 27 |
28 |
29 |
模块列表
30 | 33 |
34 |
35 | 36 | 37 |
38 |
39 |
用例列表
40 | 43 |
44 |
45 | 46 |
47 |
48 | 49 | 50 | 52 | 68 |
69 | 70 |
71 |
72 | 73 | 143 | 144 | 145 | {% endblock %} -------------------------------------------------------------------------------- /static/assets/js/custom.js: -------------------------------------------------------------------------------- 1 | /* smooth scroll */ 2 | $(function () { 3 | $('a[href*=#]:not([href=#])').click(function () { 4 | if (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') || location.hostname == this.hostname) { 5 | 6 | var target = $(this.hash); 7 | target = target.length ? target : $('[name=' + this.hash.slice(1) + ']'); 8 | if (target.length) { 9 | $('html,body').animate({ 10 | scrollTop: target.offset().top 11 | }, 1000); 12 | return false; 13 | } 14 | } 15 | }); 16 | }); 17 | 18 | /* scrollspy */ 19 | $('body').scrollspy({target: '#navbar-scroll'}) 20 | 21 | // Closes the Responsive Menu on Menu Item Click 22 | $('.navbar-collapse ul li a').click(function () { 23 | $('.navbar-toggle:visible').click(); 24 | }); 25 | 26 | /* carousel */ 27 | $(document).ready(function () { 28 | $("#screenshots").owlCarousel({ 29 | items: 4, 30 | itemsCustom: [ 31 | [0, 1], 32 | [480, 2], 33 | [768, 3], 34 | [992, 4] 35 | ], 36 | }); 37 | $("#owl-testi").owlCarousel 38 | ({ 39 | navigation: false, // Show next and prev buttons 40 | slideSpeed: 300, 41 | autoHeight: true, 42 | singleItem: true 43 | }); 44 | }); 45 | 46 | 47 | /* sticky navigation */ 48 | $(document).ready(function () { 49 | $("#menu").sticky({topSpacing: 0}); 50 | }); 51 | 52 | jQuery(document).ready(function ($) { 53 | 54 | // site preloader -- also uncomment the div in the header and the css style for #preloader 55 | $(window).load(function () { 56 | $('#preloader').fadeOut('slow', function () { 57 | $(this).remove(); 58 | }); 59 | }); 60 | 61 | }); 62 | 63 | 64 | /* scrollToTop */ 65 | $(document).ready(function () { 66 | 67 | //Check to see if the window is top if not then display button 68 | $(window).scroll(function () { 69 | if ($(this).scrollTop() > 500) { 70 | $('.scrollToTop').fadeIn(); 71 | } else { 72 | $('.scrollToTop').fadeOut(); 73 | } 74 | }); 75 | 76 | //Click event to scroll to top 77 | $('.scrollToTop').click(function () { 78 | $('html, body').animate({scrollTop: 0}, 800); 79 | return false; 80 | }); 81 | 82 | }); 83 | 84 | /* parallax background image http://www.minimit.com/articles/lets-animate/parallax-backgrounds-with-centered-content 85 | /* detect touch */ 86 | if ("ontouchstart" in window) { 87 | document.documentElement.className = document.documentElement.className + " touch"; 88 | } 89 | if (!$("html").hasClass("touch")) { 90 | /* background fix */ 91 | $(".parallax").css("background-attachment", "fixed"); 92 | } 93 | 94 | /* fix vertical when not overflow 95 | call fullscreenFix() if .fullscreen content changes */ 96 | function fullscreenFix() { 97 | var h = $('body').height(); 98 | // set .fullscreen height 99 | $(".content-b").each(function (i) { 100 | if ($(this).innerHeight() <= h) { 101 | $(this).closest(".fullscreen").addClass("not-overflow"); 102 | } 103 | }); 104 | } 105 | 106 | $(window).resize(fullscreenFix); 107 | fullscreenFix(); 108 | 109 | /* resize background images */ 110 | function backgroundResize() { 111 | var windowH = $(window).height(); 112 | $(".landing, .action, .contact, .subscribe").each(function (i) { 113 | var path = $(this); 114 | // variables 115 | var contW = path.width(); 116 | var contH = path.height(); 117 | var imgW = path.attr("data-img-width"); 118 | var imgH = path.attr("data-img-height"); 119 | var ratio = imgW / imgH; 120 | // overflowing difference 121 | var diff = parseFloat(path.attr("data-diff")); 122 | diff = diff ? diff : 0; 123 | // remaining height to have fullscreen image only on parallax 124 | var remainingH = 0; 125 | if (path.hasClass("parallax") && !$("html").hasClass("touch")) { 126 | var maxH = contH > windowH ? contH : windowH; 127 | remainingH = windowH - contH; 128 | } 129 | // set img values depending on cont 130 | imgH = contH + remainingH + diff; 131 | imgW = imgH * ratio; 132 | // fix when too large 133 | if (contW > imgW) { 134 | imgW = contW; 135 | imgH = imgW / ratio; 136 | } 137 | // 138 | path.data("resized-imgW", imgW); 139 | path.data("resized-imgH", imgH); 140 | path.css("background-size", imgW + "px " + imgH + "px"); 141 | }); 142 | } 143 | 144 | $(window).resize(backgroundResize); 145 | $(window).focus(backgroundResize); 146 | backgroundResize(); 147 | 148 | /* set parallax background-position */ 149 | function parallaxPosition(e) { 150 | var heightWindow = $(window).height(); 151 | var topWindow = $(window).scrollTop(); 152 | var bottomWindow = topWindow + heightWindow; 153 | var currentWindow = (topWindow + bottomWindow) / 2; 154 | $(".parallax").each(function (i) { 155 | var path = $(this); 156 | var height = path.height(); 157 | var top = path.offset().top; 158 | var bottom = top + height; 159 | // only when in range 160 | if (bottomWindow > top && topWindow < bottom) { 161 | var imgW = path.data("resized-imgW"); 162 | var imgH = path.data("resized-imgH"); 163 | // min when image touch top of window 164 | var min = 0; 165 | // max when image touch bottom of window 166 | var max = -imgH + heightWindow; 167 | // overflow changes parallax 168 | var overflowH = height < heightWindow ? imgH - height : imgH - heightWindow; // fix height on overflow 169 | top = top - overflowH; 170 | bottom = bottom + overflowH; 171 | // value with linear interpolation 172 | var value = min + (max - min) * (currentWindow - top) / (bottom - top); 173 | // set background-position 174 | var orizontalPosition = path.attr("data-oriz-pos"); 175 | orizontalPosition = orizontalPosition ? orizontalPosition : "50%"; 176 | $(this).css("background-position", orizontalPosition + " " + value + "px"); 177 | } 178 | }); 179 | } 180 | 181 | if (!$("html").hasClass("touch")) { 182 | $(window).resize(parallaxPosition); 183 | //$(window).focus(parallaxPosition); 184 | $(window).scroll(parallaxPosition); 185 | parallaxPosition(); 186 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HttpRunnerManager(已停止维护) 2 | ================= 3 | 4 | Design Philosophy 5 | ----------------- 6 | 7 | 基于HttpRunner的接口自动化测试平台: `HttpRunner`_, `djcelery`_ and `Django`_. HttpRunner手册: http://cn.httprunner.org/ 8 | 9 | Key Features 10 | ------------ 11 | 12 | - 项目管理:新增项目、列表展示及相关操作,支持用例批量上传(标准化的HttpRunner json和yaml用例脚本) 13 | - 模块管理:为项目新增模块,用例和配置都归属于module,module和project支持同步和异步方式 14 | - 用例管理:分为添加config与test子功能,config定义全部变量和request等相关信息 request可以为公共参数和请求头,也可定义全部变量 15 | - 场景管理:可以动态加载可引用的用例,跨项目、跨模快,依赖用例列表支持拖拽排序和删除 16 | - 运行方式:可单个test,单个module,单个project,也可选择多个批量运行,支持自定义测试计划,运行时可以灵活选择配置和环境, 17 | - 分布执行:单个用例和批量执行结果会直接在前端展示,模块和项目执行可选择为同步或者异步方式, 18 | - 环境管理:可添加运行环境,运行用例时可以一键切换环境 19 | - 报告查看:所有异步执行的用例均可在线查看报告,可自主命名,为空默认时间戳保存, 20 | - 定时任务:可设置定时任务,遵循crontab表达式,可在线开启、关闭,完毕后支持邮件通知 21 | - 持续集成:jenkins对接,开发中。。。 22 | 23 | 本地开发环境部署 24 | -------- 25 | 1. 安装mysql数据库服务端(推荐5.7+),并设置为utf-8编码,创建相应HttpRunner数据库,设置好相应用户名、密码,启动mysql 26 | 27 | 2. 修改:HttpRunnerManager/HttpRunnerManager/settings.py里DATABASES字典和邮件发送账号相关配置 28 | ```python 29 | DATABASES = { 30 | 'default': { 31 | 'ENGINE': 'django.db.backends.mysql', 32 | 'NAME': 'HttpRunner', # 新建数据库名 33 | 'USER': 'root', # 数据库登录名 34 | 'PASSWORD': 'lcc123456', # 数据库登录密码 35 | 'HOST': '127.0.0.1', # 数据库所在服务器ip地址 36 | 'PORT': '3306', # 监听端口 默认3306即可 37 | } 38 | } 39 | 40 | EMAIL_SEND_USERNAME = 'username@163.com' # 定时任务报告发送邮箱,支持163,qq,sina,企业qq邮箱等,注意需要开通smtp服务 41 | EMAIL_SEND_PASSWORD = 'password' # 邮箱密码 42 | ``` 43 | 3. 安装rabbitmq消息中间件,启动服务,访问:http://host:15672/#/ host即为你部署rabbitmq的服务器ip地址 44 | username:guest、Password:guest, 成功登陆即可 45 | ```bash 46 | service rabbitmq-server start 47 | ``` 48 | 49 | 4. 修改:HttpRunnerManager/HttpRunnerManager/settings.py里worker相关配置 50 | ```python 51 | djcelery.setup_loader() 52 | CELERY_ENABLE_UTC = True 53 | CELERY_TIMEZONE = 'Asia/Shanghai' 54 | BROKER_URL = 'amqp://guest:guest@127.0.0.1:5672//' # 127.0.0.1即为rabbitmq-server所在服务器ip地址 55 | CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler' 56 | CELERY_RESULT_BACKEND = 'djcelery.backends.database:DatabaseBackend' 57 | CELERY_ACCEPT_CONTENT = ['application/json'] 58 | CELERY_TASK_SERIALIZER = 'json' 59 | CELERY_RESULT_SERIALIZER = 'json' 60 | 61 | CELERY_TASK_RESULT_EXPIRES = 7200 # celery任务执行结果的超时时间, 62 | CELERYD_CONCURRENCY = 10 # celery worker的并发数 也是命令行-c指定的数目 根据服务器配置实际更改 默认10 63 | CELERYD_MAX_TASKS_PER_CHILD = 100 # 每个worker执行了多少任务就会死掉,我建议数量可以大一些,默认100 64 | ``` 65 | 66 | 5. 命令行窗口执行pip install -r requirements.txt 安装工程所依赖的库文件 67 | 68 | 6. 命令行窗口切换到HttpRunnerManager目录 生成数据库迁移脚本,并生成表结构 69 | ```bash 70 | python manage.py makemigrations ApiManager #生成数据迁移脚本 71 | python manage.py migrate #应用到db生成数据表 72 | ``` 73 | 74 | 7. 创建超级用户,用户后台管理数据库,并按提示输入相应用户名,密码,邮箱。 如不需用,可跳过此步骤 75 | ```bash 76 | python manage.py createsuperuser 77 | ``` 78 | 79 | 8. 启动服务, 80 | ```bash 81 | python manage.py runserver 0.0.0.0:8000 82 | ``` 83 | 84 | 9. 启动worker, 如果选择同步执行并确保不会使用到定时任务,那么此步骤可忽略 85 | ```bash 86 | python manage.py celery -A HttpRunnerManager worker --loglevel=info #启动worker 87 | python manage.py celery beat --loglevel=info #启动定时任务监听器 88 | celery flower #启动任务监控后台 89 | ``` 90 | 91 | 10. 访问:http://localhost:5555/dashboard 即可查看任务列表和状态 92 | 93 | 11. 浏览器输入:http://127.0.0.1:8000/api/register/ 注册用户,开始尽情享用平台吧 94 | 95 | 12. 浏览器输入http://127.0.0.1:8000/admin/ 输入步骤6设置的用户名、密码,登录后台运维管理系统,可后台管理数据 96 | 97 | ### 生产环境uwsgi+nginx部署参考:https://www.jianshu.com/p/d6f9138fab7b 98 | 99 | 新手入门手册 100 | ----------- 101 | 1、首先需要注册一个新用户,注册成功后会自动跳转到登录页面,正常登录即可访问页面 102 | ![注册页面](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/register_01.jpg)
103 | ![登录页面](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/login_01.jpg)
104 | 105 | 2、登陆后默认跳转到首页,左侧为菜单栏,上排有快捷操作按钮,当前只简单的做了项目,模块,用例,配置的统计 106 | ![首页](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/index_01.jpg)
107 |
108 | 3、首先应该先添加一个项目,用例都是以项目为维度进行管理, 注意简要描述和其他信息可以为空, 添加成功后会自动重定向到项目列表 109 | ![新增项目](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/add_project_01.png)
110 |
111 | 4、支持对项目进行二次编辑,也可以进行筛选等,项目列表页面可以选择单个项目运行,也可以批量运行,注意:删除操作会强制删除该项目下所有数据,请谨慎操作 112 | ![项目列表](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/project_list_01.jpg)
113 |
114 | 5、当前项目可以新增模块了,之后用例或者配置都会归属模块下,必须指定模块所属的项目,模块列表与项目列表类似,故不赘述 115 | ![新增模块](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/add_module_01.jpg)
116 |
117 | 6、新增用例,遵循HtttpRuunner脚本规范,可以跨项目,跨模块引用用例,支持拖拽排序,动态添加和删减,极大地方便了场景组织, HttpRunner用例编写很灵活,建议规范下编写方式 118 | ![新增用例01](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/add_case_01.jpg)
119 |
120 | ![新增用例02](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/add_case_02.jpg)
121 |
122 | ![新增用例03](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/add_case_03.jpg)
123 |
124 | ![新增用例04](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/add_case_04.jpg)
125 |
126 | 7、新增配置,可定义全局变量,全局hook,公共请求参数和公共headers,一般可用于测试环境,验证环境切换配置,具体用法参考HttpRunner手册 127 | ![新增配置](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/add_config_01.jpg)
128 |
129 | 8、支持添加项目级别定时任务,模块集合的定时任务,遵循crontab表达式, 模块列表为空默认为整个项目,定时任务支持选择环境和配置 130 | ![添加任务](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/add_tasks_01.jpg)
131 | 9、定时任务列表可以对任务进行开启或者关闭、删除,不支持二次更改 132 | ![任务列表](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/tasks_list_01.jpg)
133 |
134 | 10、用例列表运行用例可以选择单个,批量运行,鼠标悬浮到用例名称后会自动展开依赖的用例,方便预览,鼠标悬浮到对应左边序列栏会自动收缩,只能同步运行 135 | ![用例列表](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/test_list_01.jpg)
136 |
137 | 11、项目和模块列表可以选择单个,或者批量运行,可以选择运行环境,配置等,支持同步、异步选择,异步支持自定义报告名称,默认时间戳命名 138 | ![模块列表](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/module_list_01.jpg)
139 |
140 | 12、异步运行的用例还有定时任务生成的报告均会存储在数据库,可以在线点击查看,当前不提供下载功能 141 | ![报告持久化](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/report_list_01.jpg)
142 |
143 | 13、高大上的报告(基于extentreports实现), 可以一键翻转主题哦 144 | ![最终报告01](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/reports_01.jpg)
145 |
146 | ![最终报告02](https://github.com/HttpRunner/HttpRunnerManager/blob/master/images/reports_02.jpg)
147 | 148 | 149 | 150 | ### 其他 151 | MockServer:https://github.com/yinquanwang/MockServer 152 | 153 | 因时间限制,平台可能还有很多潜在的bug,使用中如遇到问题,欢迎issue, 154 | 如果任何疑问好好的建议欢迎github提issue, 或者可以直接加群(628448476),反馈会比较快 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}首页{% endblock %} 3 | {% load staticfiles %} 4 | {% block content %} 5 |
6 |
7 |
8 |
9 |
{{ project_length }}
10 |
Project Total
11 |
12 |
13 |
14 |
{{ module_length }}
15 |
Module Total
16 |
17 |
18 |
19 |
{{ test_length }}
20 |
Case Total
21 |
22 |
23 |
24 |
{{ suite_length }}
25 |
Suite Total
26 |
27 |
28 | 29 |
30 | 31 |
32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 42 | 43 | 166 | 167 | 168 | 169 | {% endblock %} -------------------------------------------------------------------------------- /templates/edit_suite.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Edit Suite{% endblock %} 3 | {% load staticfiles %} 4 | {% load custom_tags %} 5 | {% block content %} 6 | 7 |
8 | 9 |
10 | 11 |
12 |
13 |
14 | 15 |
16 |
17 |
项目列表
18 | 28 |
29 |
30 | 31 |
32 |
33 |
模块列表
34 | 37 |
38 |
39 | 40 |
41 |
42 |
用例列表
43 | 46 |
47 |
48 | 49 |
50 |
51 | 52 | 53 | 55 | 78 |
79 | 80 |
81 |
82 | 83 | 155 | 156 | 157 | {% endblock %} -------------------------------------------------------------------------------- /static/assets/js/jquery.sticky.js: -------------------------------------------------------------------------------- 1 | // Sticky Plugin v1.0.0 for jQuery 2 | // ============= 3 | // Author: Anthony Garand 4 | // Improvements by German M. Bravo (Kronuz) and Ruud Kamphuis (ruudk) 5 | // Improvements by Leonardo C. Daronco (daronco) 6 | // Created: 2/14/2011 7 | // Date: 2/12/2012 8 | // Website: http://labs.anthonygarand.com/sticky 9 | // Description: Makes an element on the page stick on the screen as you scroll 10 | // It will only set the 'top' and 'position' of your element, you 11 | // might need to adjust the width in some cases. 12 | 13 | (function ($) { 14 | var defaults = { 15 | topSpacing: 0, 16 | bottomSpacing: 0, 17 | className: 'is-sticky', 18 | wrapperClassName: 'sticky-wrapper', 19 | center: false, 20 | getWidthFrom: '', 21 | responsiveWidth: false 22 | }, 23 | $window = $(window), 24 | $document = $(document), 25 | sticked = [], 26 | windowHeight = $window.height(), 27 | scroller = function () { 28 | var scrollTop = $window.scrollTop(), 29 | documentHeight = $document.height(), 30 | dwh = documentHeight - windowHeight, 31 | extra = (scrollTop > dwh) ? dwh - scrollTop : 0; 32 | 33 | for (var i = 0; i < sticked.length; i++) { 34 | var s = sticked[i], 35 | elementTop = s.stickyWrapper.offset().top, 36 | etse = elementTop - s.topSpacing - extra; 37 | 38 | if (scrollTop <= etse) { 39 | if (s.currentTop !== null) { 40 | s.stickyElement 41 | .css('width', '') 42 | .css('position', '') 43 | .css('top', ''); 44 | s.stickyElement.trigger('sticky-end', [s]).parent().removeClass(s.className); 45 | s.currentTop = null; 46 | } 47 | } 48 | else { 49 | var newTop = documentHeight - s.stickyElement.outerHeight() 50 | - s.topSpacing - s.bottomSpacing - scrollTop - extra; 51 | if (newTop < 0) { 52 | newTop = newTop + s.topSpacing; 53 | } else { 54 | newTop = s.topSpacing; 55 | } 56 | if (s.currentTop != newTop) { 57 | s.stickyElement 58 | .css('width', s.stickyElement.width()) 59 | .css('position', 'fixed') 60 | .css('top', newTop); 61 | 62 | if (typeof s.getWidthFrom !== 'undefined') { 63 | s.stickyElement.css('width', $(s.getWidthFrom).width()); 64 | } 65 | 66 | s.stickyElement.trigger('sticky-start', [s]).parent().addClass(s.className); 67 | s.currentTop = newTop; 68 | } 69 | } 70 | } 71 | }, 72 | resizer = function () { 73 | windowHeight = $window.height(); 74 | 75 | for (var i = 0; i < sticked.length; i++) { 76 | var s = sticked[i]; 77 | if (typeof s.getWidthFrom !== 'undefined' && s.responsiveWidth === true) { 78 | s.stickyElement.css('width', $(s.getWidthFrom).width()); 79 | } 80 | } 81 | }, 82 | methods = { 83 | init: function (options) { 84 | var o = $.extend({}, defaults, options); 85 | return this.each(function () { 86 | var stickyElement = $(this); 87 | 88 | var stickyId = stickyElement.attr('id'); 89 | var wrapperId = stickyId ? stickyId + '-' + defaults.wrapperClassName : defaults.wrapperClassName 90 | var wrapper = $('
') 91 | .attr('id', stickyId + '-sticky-wrapper') 92 | .addClass(o.wrapperClassName); 93 | stickyElement.wrapAll(wrapper); 94 | 95 | if (o.center) { 96 | stickyElement.parent().css({ 97 | width: stickyElement.outerWidth(), 98 | marginLeft: "auto", 99 | marginRight: "auto" 100 | }); 101 | } 102 | 103 | if (stickyElement.css("float") == "right") { 104 | stickyElement.css({"float": "none"}).parent().css({"float": "right"}); 105 | } 106 | 107 | var stickyWrapper = stickyElement.parent(); 108 | stickyWrapper.css('height', stickyElement.outerHeight()); 109 | sticked.push({ 110 | topSpacing: o.topSpacing, 111 | bottomSpacing: o.bottomSpacing, 112 | stickyElement: stickyElement, 113 | currentTop: null, 114 | stickyWrapper: stickyWrapper, 115 | className: o.className, 116 | getWidthFrom: o.getWidthFrom, 117 | responsiveWidth: o.responsiveWidth 118 | }); 119 | }); 120 | }, 121 | update: scroller, 122 | unstick: function (options) { 123 | return this.each(function () { 124 | var unstickyElement = $(this); 125 | 126 | var removeIdx = -1; 127 | for (var i = 0; i < sticked.length; i++) { 128 | if (sticked[i].stickyElement.get(0) == unstickyElement.get(0)) { 129 | removeIdx = i; 130 | } 131 | } 132 | if (removeIdx != -1) { 133 | sticked.splice(removeIdx, 1); 134 | unstickyElement.unwrap(); 135 | unstickyElement.removeAttr('style'); 136 | } 137 | }); 138 | } 139 | }; 140 | 141 | // should be more efficient than using $window.scroll(scroller) and $window.resize(resizer): 142 | if (window.addEventListener) { 143 | window.addEventListener('scroll', scroller, false); 144 | window.addEventListener('resize', resizer, false); 145 | } else if (window.attachEvent) { 146 | window.attachEvent('onscroll', scroller); 147 | window.attachEvent('onresize', resizer); 148 | } 149 | 150 | $.fn.sticky = function (method) { 151 | if (methods[method]) { 152 | return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 153 | } else if (typeof method === 'object' || !method) { 154 | return methods.init.apply(this, arguments); 155 | } else { 156 | $.error('Method ' + method + ' does not exist on jQuery.sticky'); 157 | } 158 | }; 159 | 160 | $.fn.unstick = function (method) { 161 | if (methods[method]) { 162 | return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 163 | } else if (typeof method === 'object' || !method) { 164 | return methods.unstick.apply(this, arguments); 165 | } else { 166 | $.error('Method ' + method + ' does not exist on jQuery.sticky'); 167 | } 168 | 169 | }; 170 | $(function () { 171 | setTimeout(scroller, 0); 172 | }); 173 | })(jQuery); 174 | -------------------------------------------------------------------------------- /templates/add_task.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}新增任务{% endblock %} 3 | {% load staticfiles %} 4 | {% block content %} 5 | 6 |
7 | 8 |
9 |
10 |
    系统设置
11 |
当前位置: 定时任务 > 新增任务
12 |
13 |
14 | 15 | 16 |
17 | 18 |
19 |
20 |
任务类型
21 | 25 |
26 |
27 | 28 |
29 |
30 |
任务名称
31 | 33 |
34 |
35 | 36 |
37 |
38 |
运行环境
39 | 45 |
46 |
47 | 48 |
49 |
50 |
可选项目
51 | 57 |
58 |
59 | 60 |
61 |
62 |
可选模块
63 | 65 |
66 |
67 | 68 |
69 |
70 |
定时配置
71 | 73 |
74 |
75 | 76 |
77 |
78 |
接收邮件
79 | 81 |
82 |
83 | 84 |
85 |
86 |
任务列表
87 | 88 | 91 | 任 务 执 行 顺 序 92 | 93 | 97 | 98 |
99 |
100 |
101 |
102 | 点 击 提 交 103 | 104 | 105 |
106 |
107 | 108 |
109 |
110 |
111 |
112 | 158 | 159 | {% endblock %} -------------------------------------------------------------------------------- /templates/periodictask_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}定时任务{% endblock %} 3 | {% load staticfiles %} 4 | {% load custom_tags %} 5 | {% block content %} 6 | 7 |
8 |
9 |
HttpRunnerManager
10 |
11 | 亲,确定删除该定时任务么? 12 |
13 | 17 |
18 |
19 | 20 |
21 |
22 |
    任务管理
23 |
当前位置: 系统设置 > 定时任务
24 |
25 | 28 |
29 |
30 | 31 |
32 |
33 |
    34 |
  • 37 |
  • 38 | 41 |
  • 42 |
43 |
44 |
45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | {% for foo in task %} 62 | {% if foo.name != 'celery.backend_cleanup' %} 63 | 64 | 65 | 66 | {% if foo.enabled == True %} 67 | 68 | {% else %} 69 | 70 | {% endif %} 71 | 72 | 73 | 74 | 75 | 96 | 97 | {% endif %} 98 | {% endfor %} 99 | 100 | 101 | 102 |
INDEXENABLEDCRONTAB TIMETASK NAMEKEYWORD ARGUMENTSDATE_CHANGEDOPTIONS
{{ forloop.counter }}{{ foo.description }}(m/h/d/dM/MY){{ foo.name }}{{ foo.kwargs }}{{ foo.date_changed }} 76 |
77 |
78 | 83 | 88 | 93 |
94 |
95 |
103 | 104 |
105 | 108 |
109 | 110 |
    111 | {{ page_list }} 112 |
113 |
114 |
115 |
116 | 159 | 160 | {% endblock %} -------------------------------------------------------------------------------- /ApiManager/utils/pagination.py: -------------------------------------------------------------------------------- 1 | from django.utils.safestring import mark_safe 2 | 3 | from ApiManager.models import ModuleInfo, TestCaseInfo, TestSuite 4 | 5 | 6 | class PageInfo(object): 7 | """ 8 | 分页类 9 | """ 10 | 11 | def __init__(self, current, total_item, per_items=5): 12 | self.__current = current 13 | self.__per_items = per_items 14 | self.__total_item = total_item 15 | 16 | @property 17 | def start(self): 18 | return (self.__current - 1) * self.__per_items 19 | 20 | @property 21 | def end(self): 22 | return self.__current * self.__per_items 23 | 24 | @property 25 | def total_page(self): 26 | result = divmod(self.__total_item, self.__per_items) 27 | if result[1] == 0: 28 | return result[0] 29 | else: 30 | return result[0] + 1 31 | 32 | 33 | def customer_pager(base_url, current_page, total_page): 34 | """ 35 | 返回可分页的html 36 | :param base_url: a标签href值 37 | :param current_page: 当前页 38 | :param total_page: 总共页 39 | :return: html 40 | """ 41 | per_pager = 11 42 | middle_pager = 5 43 | start_pager = 1 44 | if total_page <= per_pager: 45 | begin = 0 46 | end = total_page 47 | else: 48 | if current_page > middle_pager: 49 | begin = current_page - middle_pager 50 | end = current_page + middle_pager 51 | if end > total_page: 52 | end = total_page 53 | else: 54 | begin = 0 55 | end = per_pager 56 | pager_list = [] 57 | 58 | if current_page <= start_pager: 59 | first = "
  • 首页
  • " 60 | else: 61 | first = "
  • 首页
  • " % (base_url, start_pager) 62 | pager_list.append(first) 63 | 64 | if current_page <= start_pager: 65 | prev = "
  • <<
  • " 66 | else: 67 | prev = "
  • <<
  • " % (base_url, current_page - start_pager) 68 | pager_list.append(prev) 69 | 70 | for i in range(begin + start_pager, end + start_pager): 71 | if i == current_page: 72 | temp = "
  • %d
  • " % (base_url, i, i) 73 | else: 74 | temp = "
  • %d
  • " % (base_url, i, i) 75 | pager_list.append(temp) 76 | if current_page >= total_page: 77 | next = "
  • >>
  • " 78 | else: 79 | next = "
  • >>
  • " % (base_url, current_page + start_pager) 80 | pager_list.append(next) 81 | if current_page >= total_page: 82 | last = "
  • 尾页
  • " 83 | else: 84 | last = "
  • 尾页
  • " % (base_url, total_page) 85 | pager_list.append(last) 86 | result = ''.join(pager_list) 87 | return mark_safe(result) # 把字符串转成html语言 88 | 89 | 90 | def get_pager_info(Model, filter_query, url, id, per_items=12): 91 | """ 92 | 筛选列表信息 93 | :param Model: Models实体类 94 | :param filter_query: dict: 筛选条件 95 | :param url: 96 | :param id: 97 | :param per_items: int: m默认展示12行 98 | :return: 99 | """ 100 | id = int(id) 101 | if filter_query: 102 | belong_project = filter_query.get('belong_project') 103 | belong_module = filter_query.get('belong_module') 104 | name = filter_query.get('name') 105 | user = filter_query.get('user') 106 | 107 | obj = Model.objects 108 | 109 | if url == '/api/project_list/': 110 | 111 | obj = obj.filter(project_name__contains=belong_project) if belong_project != 'All' \ 112 | else obj.filter(responsible_name__contains=user) 113 | 114 | elif url == '/api/module_list/': 115 | 116 | if belong_project != 'All': 117 | obj = obj.filter(belong_project__project_name__contains=belong_project) 118 | 119 | elif belong_module != '请选择': 120 | obj = obj.filter(module_name__contains=belong_module) if belong_module != 'All' \ 121 | else obj.filter(test_user__contains=user) 122 | 123 | elif url == '/api/report_list/': 124 | obj = obj.filter(report_name__contains=filter_query.get('report_name')) 125 | 126 | elif url == '/api/periodictask/': 127 | obj = obj.filter(name__contains=name).values('id', 'name', 'kwargs', 'enabled', 'date_changed') \ 128 | if name is not '' else obj.all().values('id', 'name', 'kwargs', 'enabled', 'date_changed', 'description') 129 | 130 | elif url == '/api/suite_list/': 131 | if belong_project != 'All': 132 | obj = obj.filter(belong_project__project_name__contains=belong_project) 133 | elif name is not '': 134 | obj = obj.filter(suite_name__contains=name) 135 | 136 | elif url != '/api/env_list/' and url != '/api/debugtalk_list/': 137 | obj = obj.filter(type__exact=1) if url == '/api/test_list/' else obj.filter(type__exact=2) 138 | 139 | if belong_project != 'All' and belong_module != '请选择': 140 | obj = obj.filter(belong_project__contains=belong_project).filter( 141 | belong_module__module_name__contains=belong_module) 142 | if name is not '': 143 | obj = obj.filter(name__contains=name) 144 | 145 | else: 146 | if belong_project != 'All': 147 | obj = obj.filter(belong_project__contains=belong_project) 148 | elif belong_module != '请选择': 149 | obj = obj.filter(belong_module__module_name__contains=belong_module) 150 | else: 151 | obj = obj.filter(name__contains=name) if name is not '' else obj.filter(author__contains=user) 152 | 153 | if url != '/api/periodictask/': 154 | obj = obj.order_by('-update_time') 155 | 156 | else: 157 | obj = obj.order_by('-date_changed') 158 | 159 | total = obj.count() 160 | 161 | page_info = PageInfo(id, total, per_items=per_items) 162 | info = obj[page_info.start:page_info.end] 163 | 164 | sum = {} 165 | page_list = '' 166 | if total != 0: 167 | if url == '/api/project_list/': 168 | for model in info: 169 | pro_name = model.project_name 170 | module_count = str(ModuleInfo.objects.filter(belong_project__project_name__exact=pro_name).count()) 171 | suite_count = str(TestSuite.objects.filter(belong_project__project_name__exact=pro_name).count()) 172 | test_count = str(TestCaseInfo.objects.filter(belong_project__exact=pro_name, type__exact=1).count()) 173 | config_count = str(TestCaseInfo.objects.filter(belong_project__exact=pro_name, type__exact=2).count()) 174 | sum.setdefault(model.id, module_count + '/ ' + suite_count + '/' + test_count + '/ ' + config_count) 175 | 176 | elif url == '/api/module_list/': 177 | for model in info: 178 | module_name = model.module_name 179 | project_name = model.belong_project.project_name 180 | test_count = str(TestCaseInfo.objects.filter(belong_module__module_name=module_name, 181 | type__exact=1, belong_project=project_name).count()) 182 | config_count = str(TestCaseInfo.objects.filter(belong_module__module_name=module_name, 183 | type__exact=2, belong_project=project_name).count()) 184 | sum.setdefault(model.id, test_count + '/ ' + config_count) 185 | 186 | elif url == '/api/suite_list/': 187 | for model in info: 188 | suite_name = model.suite_name 189 | project_name = model.belong_project.project_name 190 | test_count = str(len(eval(TestSuite.objects.get(suite_name=suite_name, 191 | belong_project__project_name=project_name).include))) 192 | sum.setdefault(model.id, test_count) 193 | 194 | page_list = customer_pager(url, id, page_info.total_page) 195 | 196 | return page_list, info, sum 197 | -------------------------------------------------------------------------------- /HttpRunnerManager/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for HttpRunnerManager project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.7. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | from __future__ import absolute_import, unicode_literals 13 | 14 | import os 15 | 16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 17 | import djcelery 18 | from django.conf.global_settings import SESSION_COOKIE_AGE 19 | 20 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 21 | 22 | # Quick-start development settings - unsuitable for production 23 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 24 | 25 | # SECURITY WARNING: keep the secret key used in production secret! 26 | SECRET_KEY = '=w+1if4no=o&6!la#5j)3wsu%k@$)6bf+@3=i0h!5)h9h)$*s7' 27 | 28 | # SECURITY WARNING: don't run with debug turned on in production! 29 | 30 | DEBUG = True 31 | 32 | ALLOWED_HOSTS = ['*'] 33 | 34 | # Application definition 35 | 36 | INSTALLED_APPS = [ 37 | 'django.contrib.admin', 38 | 'django.contrib.auth', 39 | 'django.contrib.contenttypes', 40 | 'django.contrib.sessions', 41 | 'django.contrib.messages', 42 | 'django.contrib.staticfiles', 43 | 'ApiManager', 44 | 'djcelery', 45 | ] 46 | 47 | MIDDLEWARE = [ 48 | 'django.middleware.security.SecurityMiddleware', 49 | 'django.contrib.sessions.middleware.SessionMiddleware', 50 | 'django.middleware.common.CommonMiddleware', 51 | # 'django.middleware.csrf.CsrfViewMiddleware', 52 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 53 | 'django.contrib.messages.middleware.MessageMiddleware', 54 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 55 | ] 56 | 57 | MIDDLEWARE_CLASSES = [ 58 | 'dwebsocket.middleware.WebSocketMiddleware' 59 | ] 60 | 61 | ROOT_URLCONF = 'HttpRunnerManager.urls' 62 | 63 | TEMPLATES = [ 64 | { 65 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 66 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 67 | 'APP_DIRS': True, 68 | 'OPTIONS': { 69 | 'context_processors': [ 70 | 'django.template.context_processors.debug', 71 | 'django.template.context_processors.request', 72 | 'django.contrib.auth.context_processors.auth', 73 | 'django.contrib.messages.context_processors.messages', 74 | ], 75 | }, 76 | }, 77 | ] 78 | 79 | WSGI_APPLICATION = 'HttpRunnerManager.wsgi.application' 80 | 81 | 82 | # Password validation 83 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 84 | 85 | AUTH_PASSWORD_VALIDATORS = [ 86 | { 87 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 88 | }, 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 97 | }, 98 | ] 99 | 100 | # Internationalization 101 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 102 | 103 | LANGUAGE_CODE = 'zh-Hans' 104 | 105 | TIME_ZONE = 'Asia/Shanghai' 106 | 107 | USE_I18N = True 108 | 109 | USE_L10N = True 110 | 111 | USE_TZ = False 112 | 113 | # Static files (CSS, JavaScript, Images) 114 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 115 | 116 | if DEBUG: 117 | DATABASES = { 118 | 'default': { 119 | 'ENGINE': 'django.db.backends.mysql', 120 | 'NAME': 'HttpRunner', # 新建数据库名 121 | 'USER': 'root', # 数据库登录名 122 | 'PASSWORD': 'Hst888888', # 数据库登录密码 123 | 'HOST': '192.168.91.45', # 数据库所在服务器ip地址 124 | 'PORT': '3306', # 监听端口 默认3306即可 125 | } 126 | } 127 | STATICFILES_DIRS = ( 128 | os.path.join(BASE_DIR, 'static'), # 静态文件额外目录 129 | ) 130 | else: 131 | DATABASES = { 132 | 'default': { 133 | 'ENGINE': 'django.db.backends.mysql', 134 | 'NAME': 'HttpRunner', # 新建数据库名 135 | 'USER': 'root', # 数据库登录名 136 | 'PASSWORD': 'Hst888888', # 数据库登录密码 137 | 'HOST': '192.168.91.45', # 数据库所在服务器ip地址 138 | 'PORT': '3306', # 监听端口 默认3306即可 139 | } 140 | } 141 | STATIC_ROOT = os.path.join(BASE_DIR, 'static') 142 | 143 | STATIC_URL = '/static/' 144 | 145 | STATICFILES_FINDERS = ( 146 | 'django.contrib.staticfiles.finders.FileSystemFinder', 147 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder' 148 | ) 149 | 150 | SESSION_COOKIE_AGE = 300 * 60 151 | 152 | djcelery.setup_loader() 153 | CELERY_ENABLE_UTC = True 154 | CELERY_TIMEZONE = 'Asia/Shanghai' 155 | BROKER_URL = 'amqp://dev:zwc123@192.168.91.45:5672//' if DEBUG else 'amqp://dev:zwc123@192.168.91.45:5672//' 156 | CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler' 157 | CELERY_RESULT_BACKEND = 'djcelery.backends.database:DatabaseBackend' 158 | CELERY_ACCEPT_CONTENT = ['application/json'] 159 | CELERY_TASK_SERIALIZER = 'json' 160 | CELERY_RESULT_SERIALIZER = 'json' 161 | 162 | CELERY_TASK_RESULT_EXPIRES = 7200 # celery任务执行结果的超时时间, 163 | CELERYD_CONCURRENCY = 1 if DEBUG else 10 # celery worker的并发数 也是命令行-c指定的数目 根据服务器配置实际更改 一般25即可 164 | CELERYD_MAX_TASKS_PER_CHILD = 100 # 每个worker执行了多少任务就会死掉,我建议数量可以大一些,比如200 165 | 166 | 167 | EMAIL_SEND_USERNAME = 'quanwang.yin@hstong.com' # 定时任务报告发送邮箱,支持163,qq,sina,企业qq邮箱等,注意需要开通smtp服务 168 | EMAIL_SEND_PASSWORD = 'TANGxinbing135!' # 邮箱密码 169 | 170 | LOGGING = { 171 | 'version': 1, 172 | 'disable_existing_loggers': True, 173 | 'formatters': { 174 | 'standard': { 175 | 'format': '%(asctime)s [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s'} 176 | # 日志格式 177 | }, 178 | 'filters': { 179 | }, 180 | 'handlers': { 181 | 'mail_admins': { 182 | 'level': 'ERROR', 183 | 'class': 'django.utils.log.AdminEmailHandler', 184 | 'include_html': True, 185 | }, 186 | 'default': { 187 | 'level': 'DEBUG', 188 | 'class': 'logging.handlers.RotatingFileHandler', 189 | 'filename': os.path.join(BASE_DIR, 'logs/all.log'), 190 | 'maxBytes': 1024 * 1024 * 100, 191 | 'backupCount': 5, 192 | 'formatter': 'standard', 193 | }, 194 | 'console': { 195 | 'level': 'DEBUG', 196 | 'class': 'logging.StreamHandler', 197 | 'formatter': 'standard' 198 | }, 199 | 'request_handler': { 200 | 'level': 'DEBUG', 201 | 'class': 'logging.handlers.RotatingFileHandler', 202 | 'filename': os.path.join(BASE_DIR, 'logs/script.log'), 203 | 'maxBytes': 1024 * 1024 * 100, 204 | 'backupCount': 5, 205 | 'formatter': 'standard', 206 | }, 207 | 'scprits_handler': { 208 | 'level': 'DEBUG', 209 | 'class': 'logging.handlers.RotatingFileHandler', 210 | 'filename': os.path.join(BASE_DIR, 'logs/script.log'), 211 | 'maxBytes': 1024 * 1024 * 100, 212 | 'backupCount': 5, 213 | 'formatter': 'standard', 214 | }, 215 | }, 216 | 'loggers': { 217 | 'django': { 218 | 'handlers': ['default', 'console'], 219 | 'level': 'INFO', 220 | 'propagate': True 221 | }, 222 | 'HttpRunnerManager.app': { 223 | 'handlers': ['default', 'console'], 224 | 'level': 'DEBUG', 225 | 'propagate': True 226 | }, 227 | 'django.request': { 228 | 'handlers': ['request_handler'], 229 | 'level': 'DEBUG', 230 | 'propagate': True 231 | }, 232 | 'HttpRunnerManager': { 233 | 'handlers': ['scprits_handler', 'console'], 234 | 'level': 'DEBUG', 235 | 'propagate': True 236 | }, 237 | 'scripts': { 238 | 'handlers': ['scprits_handler', 'console'], 239 | 'level': 'DEBUG', 240 | 'propagate': True 241 | }, 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /templates/env_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}环境管理{% endblock %} 3 | {% load staticfiles %} 4 | {% block content %} 5 |
    6 |
    7 |
    HttpRunnerManager
    8 |
    9 |
    10 |
    11 | 13 |
    14 | 15 |
    16 |
    17 |
    18 | 20 |
    21 | 23 |
    24 |
    25 | 26 |
    27 | 29 |
    30 | 32 |
    33 |
    34 | 35 |
    36 | 38 |
    39 | 41 |
    42 |
    43 |
    44 |
    45 | 49 |
    50 |
    51 | 52 |
    53 |
    54 |
    HttpRunnerManager
    55 |
    56 | 亲,确定删除该环境配置么? 57 |
    58 | 62 |
    63 |
    64 | 65 |
    66 |
    67 |
      环境管理
    68 |
    当前位置: 系统设置 > 环境管理
    69 |
    70 | 73 |
    74 |
    75 |
    76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | {% for foo in env %} 91 | 92 | 93 | 94 | 97 | 98 | 99 | 100 | 116 | 117 | {% endfor %} 118 | 119 |
    序号环境名称Host_Port简要描述创建时间操作
    {{ forloop.counter }}{{ foo.env_name }} 96 | {{ foo.base_url }}{{ foo.simple_desc }}{{ foo.create_time }} 101 |
    102 |
    103 | 108 | 113 |
    114 |
    115 |
    120 |
      121 | {{ page_list }} 122 |
    123 |
    124 |
    125 |
    126 | 127 | 172 | 173 | {% endblock %} -------------------------------------------------------------------------------- /templates/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 注 册 7 | 8 | {% load staticfiles %} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 |
    25 |
    26 | 27 |
    28 | 29 |
    30 |
    31 |
    32 |
    33 | 34 | 35 | 37 | 38 | 39 |

    40 | HTTP Runner Manager, Landing Page 41 |

    42 | 43 | 44 |
    45 |

    Take full reuse of Python's existing powerful libraries: HttpRunner, Celery and Django. And 46 | achieve the goal of API automation test, production environment monitoring, and API 47 | performance test, with a concise and elegant manner. 48 | 49 |

    50 |
    51 | 52 | 53 |
    54 | Download 55 |
    56 | 57 | 58 |
    59 | 60 | 61 |
    62 | 63 | 90 | 91 |
    92 |
    93 |
    94 |
    95 |
    96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /templates/config_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}配置信息{% endblock %} 3 | {% load staticfiles %} 4 | {% block content %} 5 |
    6 |
    7 |
    HttpRunnerManager
    8 |
    9 |
    10 |
    11 | 13 |
    14 | 16 |
    17 |
    18 | 19 |
    20 | 22 |
    23 | 25 |
    26 |
    27 | 28 |
    29 |
    30 | 34 |
    35 |
    36 |
    37 |
    38 |
    HttpRunnerManager
    39 |
    40 | 亲,请确认该配置是否被其他用例依赖后再谨慎删除哦 41 |
    42 | 46 |
    47 |
    48 | 49 | 50 |
    51 |
    52 |
      配置列表
    53 |
    当前位置: 用例管理 > 配置展示
    54 |
    55 | 58 | 61 |
    62 |
    63 | 64 |
    65 |
    66 |
      67 |
    • 68 | 81 | 82 |
    • 83 | 84 |
    • 85 | 87 |
    • 88 | 89 |
    • 92 |
    • 93 |
    • 96 |
    • 97 |
    • 98 | 101 |
    • 102 |
    103 |
    104 |
    105 | 106 | 107 |
    108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | {% for foo in test %} 123 | 124 | 125 | 126 | 128 | 129 | 130 | 131 | 132 | 149 | 150 | {% endfor %} 151 | 152 |
    序号名称所属项目所属模块创建者创建时间操作
    {{ forloop.counter }}{{ foo.name }} 127 | {{ foo.belong_project }}{{ foo.belong_module.module_name }}{{ foo.author }}{{ foo.create_time }} 133 |
    134 |
    135 | 140 | 145 | 146 |
    147 |
    148 |
    153 | 154 |
    155 | 158 | 161 |
    162 | 163 |
      164 | {{ page_list }} 165 |
    166 | 167 | 168 |
    169 | 170 |
    171 |
    172 | 206 | {% endblock %} -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% block title %}{% endblock %} 8 | 9 | 10 | 11 | 12 | 13 | {% load staticfiles %} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
    42 | 76 |
    77 | 78 |
    79 |
    80 |
    HttpRunnerManager
    81 |
    82 | Sorry,服务器可能开小差啦, 请重试! 83 |
    84 | 87 |
    88 |
    89 |
    90 | 91 | 140 | 141 |
    142 |
      143 |
    • 144 | 147 |
    • 148 |
    • 149 | 153 |
    • 154 |
    • 155 | 159 |
    • 160 |
    • 161 | 165 |
    • 166 | 167 |
    • 168 | 172 |
    • 173 |
    • 174 | 178 |
    • 179 |
    • 180 | 184 |
    • 185 |
    • 186 | 190 |
    • 191 | 192 |
    193 |
    194 | 195 | {% block content %} 196 | 197 | {% endblock %} 198 | 199 | 253 |
    254 | 255 | 256 | -------------------------------------------------------------------------------- /templates/add_config.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}新增配置{% endblock %} 3 | {% load staticfiles %} 4 | 5 | {% block content %} 6 | 7 | 8 |
    9 | 10 |
    11 |
      配置编辑
    12 |
    当前位置: 用例管理 > 新增配置
    13 |
    14 | 15 |
    16 | 21 | 22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    配置名称
    28 | 30 |
    31 |
    32 | 33 |
    34 |
    35 |
    所属项目
    36 | 43 |
    44 |
    45 |
    46 |
    47 |
    所属模块
    48 | 50 |
    51 |
    52 | 53 |
    54 |
    55 |
    编写人员
    56 | 58 |
    59 |
    60 |
    61 |
    62 |
    63 | 64 | 65 | 66 | 67 | 68 | 69 |
    70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
    Variables:
    OptionKeyTypeValue
    83 |
    84 | 85 |
    86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
    parameters:
    OptionKeyValue
    98 |
    99 | 100 |
    101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |
    hooks:
    Optionsetup_hooksteardown_hooks
    113 |
    114 |
    115 |
    116 |
    117 |
    118 |
    119 |
    Type
    120 | 125 |
    126 |
    127 | 128 | 130 | 132 | 133 | 134 |
    135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 |
    data:
    OptionKeyTypeValue
    148 |
    149 | 150 |
    151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |
    164 |
    165 |
    166 |
    167 | 168 |
    169 | 170 |
    171 |
    172 |   175 | »   176 | 新 增 用 例 177 | 178 |
    179 |
    180 |
    181 | 182 | 233 | {% endblock %} -------------------------------------------------------------------------------- /static/assets/js/commons.js: -------------------------------------------------------------------------------- 1 | /*动态改变模块信息*/ 2 | function show_module(module_info, id) { 3 | module_info = module_info.split('replaceFlag'); 4 | var a = $(id); 5 | a.empty(); 6 | for (var i = 0; i < module_info.length; i++) { 7 | if (module_info[i] !== "") { 8 | var value = module_info[i].split('^='); 9 | a.prepend("") 10 | } 11 | } 12 | a.prepend(""); 13 | 14 | } 15 | 16 | function show_case(case_info, id) { 17 | case_info = case_info.split('replaceFlag'); 18 | var a = $(id); 19 | a.empty(); 20 | for (var i = 0; i < case_info.length; i++) { 21 | if (case_info[i] !== "") { 22 | var value = case_info[i].split('^='); 23 | a.prepend("") 24 | } 25 | } 26 | a.prepend(""); 27 | 28 | } 29 | 30 | /*表单信息异步传输*/ 31 | function info_ajax(id, url) { 32 | var data = $(id).serializeJSON(); 33 | if (id === '#add_task') { 34 | var include = []; 35 | var i = 0; 36 | $("ul#pre_case li a").each(function () { 37 | include[i++] = [$(this).attr('id'), $(this).text()]; 38 | }); 39 | data['module'] = include; 40 | } 41 | 42 | $.ajax({ 43 | type: 'post', 44 | url: url, 45 | data: JSON.stringify(data), 46 | contentType: "application/json", 47 | success: function (data) { 48 | if (data !== 'ok') { 49 | if (data.indexOf('/api/') !== -1) { 50 | window.location.href = data; 51 | } else { 52 | myAlert(data); 53 | } 54 | } 55 | else { 56 | window.location.reload(); 57 | } 58 | } 59 | , 60 | error: function () { 61 | myAlert('Sorry,服务器可能开小差啦, 请重试!'); 62 | } 63 | }); 64 | 65 | } 66 | 67 | function auto_load(id, url, target, type) { 68 | var data = $(id).serializeJSON(); 69 | if (id === '#form_message' || id ==='#belong_message' || id === '#pro_filter') { 70 | data = { 71 | "test": { 72 | "name": data, 73 | "type": type 74 | } 75 | } 76 | } else if (id === '#form_config') { 77 | data = { 78 | "config": { 79 | "name": data, 80 | "type": type 81 | } 82 | } 83 | } else { 84 | data = { 85 | "task": { 86 | "name": data, 87 | "type": type 88 | } 89 | } 90 | } 91 | $.ajax({ 92 | type: 'post', 93 | url: url, 94 | data: JSON.stringify(data), 95 | contentType: "application/json", 96 | success: function (data) { 97 | if (type === 'module') { 98 | show_module(data, target) 99 | } else { 100 | show_case(data, target) 101 | } 102 | } 103 | , 104 | error: function () { 105 | myAlert('Sorry,服务器可能开小差啦, 请重试!'); 106 | } 107 | }); 108 | 109 | } 110 | 111 | function update_data_ajax(id, url) { 112 | var data = $(id).serializeJSON(); 113 | $.ajax({ 114 | type: 'post', 115 | url: url, 116 | data: JSON.stringify(data), 117 | contentType: "application/json", 118 | success: function (data) { 119 | if (data !== 'ok') { 120 | myAlert(data); 121 | } 122 | else { 123 | window.location.reload(); 124 | } 125 | }, 126 | error: function () { 127 | myAlert('Sorry,服务器可能开小差啦, 请重试!'); 128 | } 129 | }); 130 | } 131 | 132 | function del_data_ajax(id, url) { 133 | var data = { 134 | "id": id, 135 | 'mode': 'del' 136 | }; 137 | $.ajax({ 138 | type: 'post', 139 | url: url, 140 | data: JSON.stringify(data), 141 | contentType: "application/json", 142 | success: function (data) { 143 | if (data !== 'ok') { 144 | myAlert(data); 145 | } 146 | else { 147 | window.location.reload(); 148 | } 149 | }, 150 | error: function () { 151 | myAlert('Sorry,服务器可能开小差啦, 请重试!'); 152 | } 153 | }); 154 | } 155 | 156 | function copy_data_ajax(id, url) { 157 | var data = { 158 | "data": $(id).serializeJSON(), 159 | 'mode': 'copy' 160 | }; 161 | $.ajax({ 162 | type: 'post', 163 | url: url, 164 | data: JSON.stringify(data), 165 | contentType: "application/json", 166 | success: function (data) { 167 | if (data !== 'ok') { 168 | myAlert(data); 169 | } 170 | else { 171 | window.location.reload(); 172 | } 173 | }, 174 | error: function () { 175 | myAlert('Sorry,服务器可能开小差啦, 请重试!'); 176 | } 177 | }); 178 | } 179 | 180 | function case_ajax(type, editor) { 181 | var url = $("#url").serializeJSON(); 182 | var method = $("#method").serializeJSON(); 183 | var dataType = $("#DataType").serializeJSON(); 184 | var caseInfo = $("#form_message").serializeJSON(); 185 | var variables = $("#form_variables").serializeJSON(); 186 | var request_data = null; 187 | if (dataType.DataType === 'json') { 188 | try { 189 | request_data = eval('(' + editor.session.getValue() + ')'); 190 | } 191 | catch (err) { 192 | myAlert('Json格式输入有误!'); 193 | return 194 | } 195 | } else { 196 | request_data = $("#form_request_data").serializeJSON(); 197 | } 198 | var headers = $("#form_request_headers").serializeJSON(); 199 | var extract = $("#form_extract").serializeJSON(); 200 | var validate = $("#form_validate").serializeJSON(); 201 | var parameters = $('#form_params').serializeJSON(); 202 | var hooks = $('#form_hooks').serializeJSON(); 203 | var include = []; 204 | var i = 0; 205 | $("ul#pre_case li a").each(function () { 206 | include[i++] = [$(this).attr('id'), $(this).text()]; 207 | }); 208 | caseInfo['include'] = include; 209 | const test = { 210 | "test": { 211 | "name": caseInfo, 212 | "parameters": parameters, 213 | "variables": variables, 214 | "request": { 215 | "url": url.url, 216 | "method": method.method, 217 | "headers": headers, 218 | "type": dataType.DataType, 219 | "request_data": request_data 220 | }, 221 | "extract": extract, 222 | "validate": validate, 223 | "hooks": hooks, 224 | } 225 | }; 226 | if (type === 'edit') { 227 | url = '/api/edit_case/'; 228 | } else { 229 | url = '/api/add_case/'; 230 | } 231 | $.ajax({ 232 | type: 'post', 233 | url: url, 234 | data: JSON.stringify(test), 235 | contentType: "application/json", 236 | success: function (data) { 237 | if (data === 'session invalid') { 238 | window.location.href = "/api/login/"; 239 | } else { 240 | if (data.indexOf('/api/') != -1) { 241 | window.location.href = data; 242 | } else { 243 | myAlert(data); 244 | } 245 | } 246 | }, 247 | error: function () { 248 | myAlert('Sorry,服务器可能开小差啦, 请重试!'); 249 | } 250 | }); 251 | } 252 | 253 | function config_ajax(type) { 254 | var dataType = $("#config_data_type").serializeJSON(); 255 | var caseInfo = $("#form_config").serializeJSON(); 256 | var variables = $("#config_variables").serializeJSON(); 257 | var parameters = $('#config_params').serializeJSON(); 258 | var hooks = $('#config_hooks').serializeJSON(); 259 | var request_data = null; 260 | if (dataType.DataType === 'json') { 261 | try { 262 | request_data = eval('(' + editor.session.getValue() + ')'); 263 | } 264 | catch (err) { 265 | myAlert('Json格式输入有误!'); 266 | return 267 | } 268 | } else { 269 | request_data = $("#config_request_data").serializeJSON(); 270 | } 271 | var headers = $("#config_request_headers").serializeJSON(); 272 | 273 | const config = { 274 | "config": { 275 | "name": caseInfo, 276 | "variables": variables, 277 | "parameters": parameters, 278 | "request": { 279 | "headers": headers, 280 | "type": dataType.DataType, 281 | "request_data": request_data 282 | }, 283 | "hooks": hooks, 284 | 285 | } 286 | }; 287 | if (type === 'edit') { 288 | url = '/api/edit_config/'; 289 | } else { 290 | url = '/api/add_config/'; 291 | } 292 | $.ajax({ 293 | type: 'post', 294 | url: url, 295 | data: JSON.stringify(config), 296 | contentType: "application/json", 297 | success: function (data) { 298 | if (data === 'session invalid') { 299 | window.location.href = "/api/login/"; 300 | } else { 301 | if (data.indexOf('/api/') != -1) { 302 | window.location.href = data; 303 | } else { 304 | myAlert(data); 305 | } 306 | } 307 | }, 308 | error: function () { 309 | myAlert('Sorry,服务器可能开小差啦, 请重试!'); 310 | } 311 | }); 312 | } 313 | 314 | /*提示 弹出*/ 315 | function myAlert(data) { 316 | $('#my-alert_print').text(data); 317 | $('#my-alert').modal({ 318 | relatedTarget: this 319 | }); 320 | } 321 | 322 | function post(url, params) { 323 | var temp = document.createElement("form"); 324 | temp.action = url; 325 | temp.method = "post"; 326 | temp.style.display = "none"; 327 | for (var x in params) { 328 | var opt = document.createElement("input"); 329 | opt.name = x; 330 | opt.value = params[x]; 331 | temp.appendChild(opt); 332 | } 333 | document.body.appendChild(temp); 334 | temp.submit(); 335 | return temp; 336 | } 337 | 338 | function del_row(id) { 339 | var attribute = id; 340 | var chkObj = document.getElementsByName(attribute); 341 | var tabObj = document.getElementById(id); 342 | for (var k = 0; k < chkObj.length; k++) { 343 | if (chkObj[k].checked) { 344 | tabObj.deleteRow(k + 1); 345 | k = -1; 346 | } 347 | } 348 | } 349 | 350 | 351 | function add_row(id) { 352 | var tabObj = document.getElementById(id);//获取添加数据的表格 353 | var rowsNum = tabObj.rows.length;  //获取当前行数 354 | var style = 'width:100%; border: none'; 355 | var cell_check = ""; 356 | var cell_key = ""; 357 | var cell_value = ""; 358 | var cell_type = ""; 361 | var cell_comparator = ""; 364 | 365 | var myNewRow = tabObj.insertRow(rowsNum); 366 | var newTdObj0 = myNewRow.insertCell(0); 367 | var newTdObj1 = myNewRow.insertCell(1); 368 | var newTdObj2 = myNewRow.insertCell(2); 369 | 370 | 371 | newTdObj0.innerHTML = cell_check 372 | newTdObj1.innerHTML = cell_key; 373 | if (id === 'variables' || id === 'data') { 374 | var newTdObj3 = myNewRow.insertCell(3); 375 | newTdObj2.innerHTML = cell_type; 376 | newTdObj3.innerHTML = cell_value; 377 | } else if (id === 'validate') { 378 | var newTdObj3 = myNewRow.insertCell(3); 379 | newTdObj2.innerHTML = cell_comparator; 380 | newTdObj3.innerHTML = cell_type; 381 | var newTdObj4 = myNewRow.insertCell(4); 382 | newTdObj4.innerHTML = cell_value; 383 | } else { 384 | newTdObj2.innerHTML = cell_value; 385 | } 386 | } 387 | 388 | function add_params(id) { 389 | var tabObj = document.getElementById(id);//获取添加数据的表格 390 | var rowsNum = tabObj.rows.length;  //获取当前行数 391 | var style = 'width:100%; border: none'; 392 | var check = ""; 393 | var placeholder = '单个:["value1", "value2], 多个:[["name1", "pwd1"],["name2","pwd2"]]'; 394 | var key = "