Showing preview only (942K chars total). Download the full file or copy to clipboard to get everything.
Repository: small99/AutoLink
Branch: master
Commit: b440d1196413
Files: 84
Total size: 895.0 KB
Directory structure:
gitextract_mfg_aafz/
├── .gitattributes
├── .gitignore
├── AutoLink.py
├── CodeStats.py
├── INSTALL.md
├── LICENSE
├── README.md
├── UPDATEING.md
├── auto/
│ ├── __init__.py
│ ├── configuration.py
│ ├── exceptions.py
│ ├── settings.py
│ ├── version.py
│ └── www/
│ ├── __init__.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── auth.py
│ │ ├── case.py
│ │ ├── editor.py
│ │ ├── keyword.py
│ │ ├── project.py
│ │ ├── settings.py
│ │ ├── suite.py
│ │ ├── task.py
│ │ └── user.py
│ ├── app.py
│ ├── blueprints.py
│ ├── static/
│ │ ├── css/
│ │ │ ├── auto.css
│ │ │ ├── base.css
│ │ │ └── sign.css
│ │ └── js/
│ │ ├── auto.js
│ │ ├── autocomplete.js
│ │ └── highlight.js
│ └── templates/
│ ├── dashboard.html
│ ├── default.html
│ ├── editor.html
│ ├── login.html
│ ├── report.html
│ ├── scheduler.html
│ ├── task_list.html
│ ├── user.html
│ ├── view_img.html
│ └── welcome.html
├── docs/
│ ├── README.md
│ ├── 上传和下载RobotFramework用例.md
│ ├── 关键字概要说明.md
│ ├── 如何使用自动提示快捷输入关键字.md
│ ├── 如何使用调度管理.md
│ ├── 如何创建HTTP接口测试用例.md
│ ├── 如何创建测试项目.md
│ ├── 如何查看关键字详细文档.md
│ ├── 如何管理测试项目中用例顺序.md
│ ├── 如何调用Python自定义库.md
│ ├── 如何运行测试项目.md
│ ├── 安装与启动.md
│ ├── 查看测试报告.md
│ └── 配置SMTP服务及邮件通知.md
├── driver/
│ └── readme.md
├── keyword/
│ ├── AppiumLibrary.xml
│ ├── BuiltIn.xml
│ ├── Collections.xml
│ ├── DatabaseLibrary.xml
│ ├── DateTime.xml
│ ├── Dialogs.xml
│ ├── OperatingSystem.xml
│ ├── Process.xml
│ ├── RequestsLibrary.xml
│ ├── SSHLibrary.xml
│ ├── Screenshot.xml
│ ├── SeleniumLibrary.xml
│ ├── String.xml
│ ├── Telnet.xml
│ └── XML.xml
├── licenses/
│ ├── LICENSE-CodeMirror
│ ├── LICENSE-jquery
│ ├── LICENSE-wide.html
│ └── license_freeware-easyui.txt
├── requirements.txt
├── utils/
│ ├── __init__.py
│ ├── file.py
│ ├── help.py
│ ├── parsing.py
│ ├── resource.py
│ └── run.py
└── version.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
*.js linguist-language=Pyhton
*.css linguist-language=Python
*.html linguist-language=Python
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
# migrations
migrations/
User Guide/
logs/
jobs/
users/
workspace/
.idea/
.beats/
# 忽略列表
.DS_Store/
================================================
FILE: AutoLink.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
import os
import sys
from flask_script import Manager
from auto.www.app import create_app, load_all_task
from auto.settings import HEADER
from utils.help import check_version
if sys.platform.startswith("linux") or sys.platform.startswith("darwin"):
os.environ["PATH"] = os.environ["PATH"] + ":" + os.getcwd() + "/driver"
else:
os.environ["PATH"] = os.environ["PATH"] + ";" + os.getcwd() + "/driver"
print(HEADER)
app = create_app('default')
manager = Manager(app)
if __name__ == '__main__':
check_version()
load_all_task(app)
manager.run()
================================================
FILE: CodeStats.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
# coding=utf-8
import os
import time
basedir = os.getcwd()
filelists = []
# 指定想要统计的文件类型
whitelist = ['py', 'js']
filelists = []
# 遍历文件, 递归遍历文件夹中的所有
def get_file(base_dir):
for parent, dirnames, filenames in os.walk(basedir):
# for dirname in dirnames:
# getFile(os.path.join(parent,dirname)) #递归
for filename in filenames:
if filename in ("AutoStats.py", "commonLibrary.py"):
continue
ext = filename.split('.')[-1]
if ext == "js" and filename != "auto.js":
continue
# 只统计指定的文件类型,略过一些log和cache文件
if ext in whitelist:
filelists.append(os.path.join(parent, filename))
# 统计一个文件的行数
def count_line(fname):
count = 0
for file_line in open(fname).readlines():
# 过滤掉空行
if file_line != '' and file_line != '\n':
count += 1
print('%s ---- %s' % (fname, count))
return count
if __name__ == '__main__' :
startTime = time.clock()
get_file(basedir)
totalline = 0
for filelist in filelists:
totalline = totalline + count_line(filelist)
print('total lines: %s' % totalline)
print('Done! Cost Time: %0.2f second' % (time.clock() - startTime))
================================================
FILE: INSTALL.md
================================================
## 安装与启动
1. 安装Python3版本,确保加入环境变量,pip命令可用
2. 从[AutoLink Github项目](https://github.com/small99/AutoLink)下载源码
3. 执行以下命令安装AutoLink依赖
> pip install -r requirements.txt
4.1 执行以下命令启动AutoLink服务
> python AutoLink.py runserver
4.1.1 访问以下网址,即可
http://127.0.0.1:5000
4.2 执行以下命令可外网访问
> python AutoLink.py runserver -h 0.0.0.0 -p 8000
通过
4.2.1 即可通过你的IP地址来访问
http://ip:8000
注:
- -h选项指定为0.0.0.0即为绑定本机ip启动,网络其他用户通过你的ip和-p指定的端口即可访问AutoLink
- -p指定AutoLink服务启动时的端口
默认账号: AutoLink
默认密码: 123456
5. 下载selenium webdriver对应的浏览器驱动放在driver目录即可
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
## 介绍
AutoLink开源自动化测试集成解决方案.
- AutoLink是RobotFramework的web集成开发环境.
- AutoLink支持RobotFramework语法高亮,自动提示等功能.
- AutoLink可以帮助你轻易的构建web自动化测试脚本、HTTP接口自动化测试脚本以及移动自动化测试脚本.
- AutoLink完美的支持RobotFramework所有的关键字.
- AutoLink可以直接应用到你的企业实践中,节省框架开发成本.
- AutoLink是很简单的,但也很容易使用.
- AutoLink支持项目级、套件级、用例级运行
## 用户指南
- [简介](./docs/README.md)
- [安装与启动](./docs/安装与启动.md)
- [如何创建测试项目](./docs/如何创建测试项目.md)
- [如何运行测试项目](./docs/如何运行测试项目.md)
- [如何管理用例顺序](./docs/如何管理测试项目中用例顺序.md)
- [使用关键字快捷键](./docs/如何使用自动提示快捷输入关键字.md)
- [关键字概要说明](./docs/关键字概要说明.md)
- [如何使用调度管理](./docs/如何使用调度管理.md)
- [上传和下载RobotFramework用例](./docs/上传和下载RobotFramework用例.md)
- [接口测试示例](./docs/如何创建HTTP接口测试用例.md)
- [Python自定义关键字](./docs/如何调用Python自定义库.md)
- [如何查看关键字详细文档](./docs/如何查看关键字详细文档.md)
- [查看测试报告](./docs/查看测试报告.md)
- [配置SMTP及邮件通知](./docs/配置SMTP服务及邮件通知.md)
## 苦叶子发起
- 可以关注我的[公众号],不定期分享开源测试技术
- 加入免费的[读书会]和上千人沟通交流开源测试技术
- 加入付费的[知识星球]与更多的牛人一起交流
- 觉得AutoLink还不错的话,可以打赏一杯咖啡,谢谢
公众号

## 截图
AutoLink Web IDE编辑模式截图欣赏




邮件通知截图

================================================
FILE: UPDATEING.md
================================================
2018-09-10 v1.0.12
1. 新增icon图标资源
2. 新增css定义
3. 新增步骤级前端js处理
4. 新增后端api步骤详细信息
5. 修订主面板截图
6. 修复用户反馈的缺陷
2018-09-06 v1.0.11
1. 修复用户反馈的缺陷
2. 注释flask mail相关代码
3. 新增smtp配置及邮件通知能力
4. 新增smtp配置及邮件通知配置文档
2018-08-27 v1.0.10
1. 优化dashboard静态导航
2. 优化项目读取接口
3. 优化项目树控件刷新机制
4. 优化文档首页截图信息
5. 修复用户反馈缺陷
2018-08-26 v1.0.9
1. 新增导航条
2. 新增优化调度管理
3. 新增调度管理、用户管理、用户指南、和更新清单导航
4. 新增查看报告使用文档
5. 新增调度管理页面查看任务及最有一次任务状态信息功能
6. 新增mac系统判断,以添加合适的环境变量
2018-08-24 v1.0.8
1. 新增关键字帮助文档信息
2. 新增Python自定义库编写示例项目
3. 新增用户指南相关文档教程
4. 标识为v1.0.8版本,强制升级版本
2018-08-23 v1.0.7
1. 修复用户反馈的缺陷
2. 对用户工作区的前端树操作进行了优化
3. 对后端项目管理api进行了优化
5. 新增图片类型资源
6. 本版本为强制更新版本
2018-08-21 v1.0.6
1. 优化报告:显示详细的关键字步骤
2. 修复自动补全功能缺陷
3. 新增debug信息输出
4. 新增github忽略列表,以解决可能出现的冲突问题
5. 修订截图信心
6. 新增接口测试项目demo及对应的文档
2018-08-20 v1.0.5
1. 新增关键字高亮和Ctrl键提示自动生成功能
2018-08-17 v1.0.4
1. 新增用户指南说明
2. 新增ssh和database库关键字支持
3. 在dashboard页面新增注销按钮
4. 登录页面新增回车响应登录
2018-08-16 v1.0.3
1. 根据操作系统类型设置环境变量信息,强制更新版本
2018-08-15 v1.0.2
1. 新增根据文件类型来设置编辑器默认内容
2. 修复用户反馈的缺陷
3. 新增用户指南相关文档
2018-08-13 v1.0.1
1. 续订说明文档及图片细节
2. 修订用户反馈的缺陷
2018-08-13 v1.0.0
1. 发布整个项目源码至github
================================================
FILE: auto/__init__.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
================================================
FILE: auto/configuration.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
import logging
import os
import json
import codecs
from apscheduler.executors.pool import ProcessPoolExecutor
from utils.file import exists_path
class Config:
# conf_path = os.getcwd() + "/.beats/auto.json"
#if exists_path(conf_path):
# config = json.load(codecs.open(conf_path, 'r', 'utf-8'))
# MAIL_SERVER = config["smtp"]["server"]
# MAIL_PORT = config["smtp"]["port"]
# MAIL_USE_TLS = True
# MAIL_USERNAME = config["smtp"]["username"]
# MAIL_PASSWORD = config["smtp"]["password"]
# DEFAULT_MAIL_SENDER = "lymking@foxmail.com"
# FLASKY_ADMIN = config["smtp"]["username"]
# MAIL_USE_SSL = config["smtp"]["ssl"]
# MAIL_DEBUG = True
SSL_REDIRECT = False
SECRET_KEY = 'QWERTYUIOPASDFGHJ'
# logging level
LOGGING_LEVEL = logging.INFO
AUTO_HOME = os.getcwd().replace('\\', '/') + '/.beats'
AUTO_ROBOT = []
executors = {
'default': {'type': 'threadpool', 'max_workers': 20},
'processpool': ProcessPoolExecutor(max_workers=5)
}
job_defaults = {
'coalesce': False,
'max_instances': 10
}
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
DEBUG = True
class ProductionConfig(Config):
DEBUG = False
@classmethod
def init_app(cls, app):
Config.init_app(app)
# 发送服务启动初始化时错误信息给管理员
import logging
from logging.handlers import SMTPHandler
credentials = None
secure = None
if getattr(cls, 'MAIL_USERNAME', None) is not None:
credentials = (cls.MAIL_USERNAME, cls.MAIL_PASSWORD)
if getattr(cls, 'MAIL_USE_TLS', None):
secure = ()
mail_handler = SMTPHandler(
mailhost=(cls.MAIL_SERVER, cls.MAIL_PORT),
fromaddr=cls.FLASKY_MAIL_SENDER,
toaddrs=[cls.FLASKY_ADMIN],
subject=cls.FLASKY_MAIL_SUBJECT_PREFIX + ' AutoLine Startup Error',
credentials=credentials,
secure=secure)
mail_handler.setLevel(logging.ERROR)
app.logger.addHandler(mail_handler)
config = {
"development": DevelopmentConfig,
"production": ProductionConfig,
"default": DevelopmentConfig
}
================================================
FILE: auto/exceptions.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
class AutoBeatException(Exception):
pass
class AutoBeatConfigException(AutoBeatException):
pass
class AutoBeatExecutorTimeout(AutoBeatException):
pass
class AutoBeatTaskTimeout(AutoBeatException):
pass
class AutoBeatWebServerTimeout(AutoBeatException):
pass
class AutoBeatSkipException(AutoBeatException):
pass
================================================
FILE: auto/settings.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
HEADER = """\
___ __ __ _ __
/ _ |__ __/ /____ / / (_)__ / /__
/ __ / // / __/ _ \/ /__/ / _ \/ '_/
/_/ |_\_,_/\__/\___/____/_/_//_/_/\_\
"""
================================================
FILE: auto/version.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
version = '1.0.0.0'
================================================
FILE: auto/www/__init__.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
================================================
FILE: auto/www/api/__init__.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
from flask import Blueprint
from flask_restful import Api
api_bp = Blueprint('api', __name__)
api = Api(api_bp)
from .auth import Auth
api.add_resource(Auth, "/auth/")
from .project import Project, ProjectList
api.add_resource(Project, "/project/")
api.add_resource(ProjectList, "/project_list/")
from .suite import Suite
api.add_resource(Suite, "/suite/")
from .case import Case, ManageFile
api.add_resource(Case, "/case/")
api.add_resource(ManageFile, "/manage_file/")
from .keyword import Keyword
api.add_resource(Keyword, "/keyword/")
from .task import Task, TaskList
api.add_resource(Task, "/task/")
api.add_resource(TaskList, "/task_list/")
from .user import User
api.add_resource(User, "/user/")
from .settings import Settings
api.add_resource(Settings, "/settings/")
================================================
FILE: auto/www/api/auth.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
from flask import current_app, url_for, redirect, session
from flask_restful import Resource, reqparse
from werkzeug.security import generate_password_hash, check_password_hash
import json
import os
import codecs
class Auth(Resource):
def __init__(self):
self.parser = reqparse.RequestParser()
self.parser.add_argument('username', type=str)
self.parser.add_argument('password', type=str)
def get(self):
args = self.parser.parse_args()
username = args["username"]
if username in session:
session.pop(username, None)
return {"status": "success", "msg": "logout success", "url": url_for('routes.index')}, 201
def post(self):
args = self.parser.parse_args()
username = args["username"]
password = args["password"]
app = current_app._get_current_object()
user_path = app.config["AUTO_HOME"] + "/users/" + username
if os.path.exists(user_path):
user = json.load(codecs.open(user_path + '/config.json', 'r', 'utf-8'))
if check_password_hash(user['passwordHash'], password):
session['username'] = username
return {"status": "success", "msg": "login success", "url": url_for('routes.dashboard')}, 201
return {"status": "fail", "msg": "login fail", "url": url_for('routes.index')}, 201
================================================
FILE: auto/www/api/case.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
import os
from urllib.parse import quote
from flask import current_app, session, request, send_file, make_response
from flask_restful import Resource, reqparse
import werkzeug
from utils.file import exists_path, rename_file, make_nod, remove_file, write_file, read_file, get_splitext
class Case(Resource):
def __init__(self):
self.parser = reqparse.RequestParser()
self.parser.add_argument('method', type=str)
self.parser.add_argument('name', type=str)
self.parser.add_argument('new_name', type=str)
self.parser.add_argument('project_name', type=str)
self.parser.add_argument('suite_name', type=str)
self.parser.add_argument('category', type=str)
self.parser.add_argument('new_category', type=str)
self.parser.add_argument('path', type=str)
self.parser.add_argument('data', type=str)
self.app = current_app._get_current_object()
def get(self):
args = self.parser.parse_args()
result = {"status": "success", "msg": "读取文件成功"}
ext = get_splitext(args["path"])
result["ext"] = ext[1]
path = self.app.config["AUTO_HOME"] + "/workspace/%s%s" % (session["username"], args["path"])
data = read_file(path)
if not data["status"]:
result["status"] = "fail"
result["msg"] = "读取文件失败"
result["data"] = data["data"]
return result, 201
def post(self):
args = self.parser.parse_args()
method = args["method"].lower()
if method == "create":
result = self.__create(args)
elif method == "edit":
result = self.__edit(args)
elif method == "delete":
result = self.__delete(args)
elif method == "save":
result = self.__save(args)
else:
print(request.files["files"])
return result, 201
def __create(self, args):
result = {"status": "success", "msg": "创建文件成功"}
user_path = self.app.config["AUTO_HOME"] + "/workspace/%s/%s/%s/%s%s" % (session["username"],
args["project_name"],
args["suite_name"],
args["name"],
args["category"])
if not exists_path(user_path):
make_nod(user_path)
else:
result["status"] = "fail"
result["msg"] = "文件名称重复,创建失败"
return result
def __edit(self, args):
result = {"status": "success", "msg": "文件重命名成功"}
old_name = self.app.config["AUTO_HOME"] + "/workspace/%s/%s/%s/%s%s" % (session["username"],
args["project_name"],
args["suite_name"],
args["name"],
args["category"])
new_name = self.app.config["AUTO_HOME"] + "/workspace/%s/%s/%s/%s%s" % (session["username"],
args["project_name"],
args["suite_name"],
args["new_name"],
args["new_category"])
if not rename_file(old_name, new_name):
result["status"] = "fail"
result["msg"] = "文件重命名失败,名称重复"
return result
def __delete(self, args):
result = {"status": "success", "msg": "目录删除成功"}
user_path = self.app.config["AUTO_HOME"] + "/workspace/%s/%s/%s/%s%s" % (session["username"],
args["project_name"],
args["suite_name"],
args["name"],
args["category"])
if exists_path(user_path):
remove_file(user_path)
else:
result["status"] = "fail"
result["msg"] = "删除失败,不存在的文件"
return result
def __save(self, args):
result = {"status": "success", "msg": "保存成功"}
user_path = self.app.config["AUTO_HOME"] + "/workspace/%s%s" % (session["username"], args["path"])
if not write_file(user_path, args["data"]):
result["status"] = "fail"
result["msg"] = "保存失败"
return result
class ManageFile(Resource):
def __init__(self):
self.parser = reqparse.RequestParser()
self.parser.add_argument('data', type=str)
self.parser.add_argument('file', type=werkzeug.datastructures.FileStorage, location='files', action='append')
self.app = current_app._get_current_object()
def post(self):
args = request.form.to_dict()
print(args)
method = args["method"].lower()
if method == "upload":
file = request.files.to_dict()['files']
return self.__upload(file, args['path']), 201
elif method == "download":
return self.__download(args)
def __upload(self, file, path):
result = {"status": "success", "msg": "上传成功"}
user_path = self.app.config["AUTO_HOME"] + "/workspace/%s" % session['username'] + path + file.filename
if not exists_path(user_path):
file.save(user_path)
else:
result["status"] = "fail"
result["msg"] = "上传失败"
return result
def __download(self, args):
user_path = self.app.config["AUTO_HOME"] + "/workspace/%s" % session['username'] + args["path"]
response = make_response(send_file(user_path))
basename = os.path.basename(user_path)
response.headers["Content-Disposition"] = \
"attachment;" \
"filename*=UTF-8''{utf_filename}".format(
utf_filename=quote(basename.encode('utf-8'))
)
return response
#return send_file(user_path, mimetype='application/octet-stream', as_attachment=True)
================================================
FILE: auto/www/api/editor.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
================================================
FILE: auto/www/api/keyword.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
from flask_restful import Resource, reqparse
from utils.parsing import parser_robot_keyword_list
class Keyword(Resource):
def __init__(self):
self.parser = reqparse.RequestParser()
self.parser.add_argument('category', type=str)
def get(self):
args = self.parser.parse_args()
if args["category"] == "robot":
return parser_robot_keyword_list()
================================================
FILE: auto/www/api/project.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
from flask import current_app, session
from flask_restful import Resource, reqparse
import json
import os
import codecs
from robot.api import TestSuiteBuilder
from robot.api import TestData, ResourceFile, TestCaseFile
from utils.file import list_dir, mk_dirs, exists_path, rename_file, remove_dir, get_splitext
from utils.resource import ICONS
class Project(Resource):
def __init__(self):
self.parser = reqparse.RequestParser()
self.parser.add_argument('method', type=str)
self.parser.add_argument('name', type=str)
self.parser.add_argument('new_name', type=str)
self.parser.add_argument('description', type=str)
self.parser.add_argument('enable', type=str, default="否")
self.parser.add_argument('cron', type=str, default="* * * * * *")
self.parser.add_argument('boolean', type=str, default="启用")
self.app = current_app._get_current_object()
def get(self):
args = self.parser.parse_args()
def post(self):
args = self.parser.parse_args()
method = args["method"].lower()
if method == "create":
result = self.__create(args)
elif method == "edit":
result = self.__edit(args)
elif method == "delete":
result = self.__delete(args)
return result, 201
def __create(self, args):
result = {"status": "success", "msg": "创建项目成功"}
user_path = self.app.config["AUTO_HOME"] + "/workspace/%s/%s" % (session["username"], args["name"])
if not exists_path(user_path):
mk_dirs(user_path)
create_project(
self.app,
session["username"],
{
"name": args["name"],
"description": args["description"],
"boolean": args["boolean"],
"enable": args["enable"],
"cron": args["cron"]
}
)
else:
result["status"] = "fail"
result["msg"] = "项目名称重复,创建失败"
return result
def __edit(self, args):
result = {"status": "success", "msg": "项目重命名成功"}
old_name = self.app.config["AUTO_HOME"] + "/workspace/%s/%s" % (session["username"], args["name"])
new_name = self.app.config["AUTO_HOME"] + "/workspace/%s/%s" % (session["username"], args["new_name"])
if rename_file(old_name, new_name):
edit_project(
self.app,
session["username"],
args["name"],
{
"name": args["new_name"],
"description": args["description"],
"boolean": args["boolean"],
"enable": args["enable"],
"cron": args["cron"]
})
else:
result["status"] = "fail"
result["msg"] = "项目重命名失败,名称重复"
return result
def __delete(self, args):
result = {"status": "success", "msg": "项目删除成功"}
user_path = self.app.config["AUTO_HOME"] + "/workspace/%s/%s" % (session["username"], args["name"])
if exists_path(user_path):
remove_dir(user_path)
remove_project(self.app, session['username'], args['name'])
else:
result["status"] = "fail"
result["msg"] = "删除失败,不存在的项目"
return result
class ProjectList(Resource):
def __init__(self):
self.parser = reqparse.RequestParser()
self.parser.add_argument('name', type=str)
self.parser.add_argument('category', type=str, default="root")
self.parser.add_argument('project', type=str)
self.parser.add_argument('suite', type=str)
self.parser.add_argument('splitext', type=str)
self.app = current_app._get_current_object()
def get(self):
args = self.parser.parse_args()
if args["category"] == "root":
return get_projects(self.app, session["username"])
elif args["category"] == "project":
return get_suite_by_project(self.app, session["username"], args)
elif args["category"] == "suite":
return get_case_by_suite(self.app, session["username"], args)
elif args["category"] == "case":
return get_step_by_case(self.app, session["username"], args)
"""
projects = get_project_list(self.app, session['username'])
children = []
for p in projects:
detail = get_project_detail(self.app, session['username'], p["name"])
children.append({
"text": p["name"],
"iconCls": "icon-project",
"state": "closed",
"attributes": {
"name": p["name"],
"description": p["description"],
"category": "project",
"boolean": p["boolean"]
},
"children": detail
})
return [{
"text": session['username'],
"iconCls": "icon-workspace",
"attributes": {
"category": "root"
},
"children": children}]
"""
def create_project(app, username, project):
user_path = app.config["AUTO_HOME"] + "/users/" + username
if os.path.exists(user_path):
config = json.load(codecs.open(user_path + '/config.json', 'r', 'utf-8'))
config["data"].append(project)
json.dump(config, codecs.open(user_path + '/config.json', 'w', 'utf-8'))
def edit_project(app, username, old_name, new_project):
user_path = app.config["AUTO_HOME"] + "/users/" + username
if os.path.exists(user_path):
config = json.load(codecs.open(user_path + '/config.json', 'r', 'utf-8'))
index = 0
for p in config["data"]:
if p["name"] == old_name:
config["data"][index]["name"] = new_project["name"]
config["data"][index]["description"] = new_project["description"]
config["data"][index]["boolean"] = new_project["boolean"]
break
index += 1
json.dump(config, codecs.open(user_path + '/config.json', 'w', 'utf-8'))
def remove_project(app, username, name):
user_path = app.config["AUTO_HOME"] + "/users/" + username
if os.path.exists(user_path):
config = json.load(codecs.open(user_path + '/config.json', 'r', 'utf-8'))
index = 0
for p in config["data"]:
if p["name"] == name:
del config["data"][index]
break
index += 1
json.dump(config, codecs.open(user_path + '/config.json', 'w', 'utf-8'))
def get_project_list(app, username):
work_path = app.config["AUTO_HOME"] + "/workspace/" + username
if os.path.exists(work_path):
projects = list_dir(work_path)
if len(projects) > 1:
projects.sort()
return projects
return []
def get_project_detail(app, username, p_name):
path = app.config["AUTO_HOME"] + "/workspace/" + username + "/" + p_name
projects = []
# raw_suites = list_dir(path)
# suites = sorted(raw_suites, key=lambda x: os.stat(path + "/" + x).st_ctime)
suites = list_dir(path)
if len(suites) > 1:
suites.sort()
for d in suites:
children = []
# cases = sorted(list_dir(path + "/" + d), key=lambda x: os.stat(path + "/" + d + "/" + x).st_ctime)
cases = list_dir(path + "/" + d)
if len(cases) > 1:
cases.sort()
for t in cases:
text = get_splitext(t)
if text[1] == ".robot":
icons = "icon-robot"
elif text[1] == ".txt":
icons = "icon-resource"
else:
icons = "icon-file-default"
children.append({
"text": t, "iconCls": icons,
"attributes": {
"name": text[0], "category": "case", "splitext": text[1]
}
})
if len(children) == 0:
icons = "icon-suite"
else:
icons = "icon-suite-open"
projects.append({
"text": d, "iconCls": icons,
"attributes": {
"name": d, "category": "suite"
},
"children": children
})
return projects
def get_projects(app, username):
projects = get_project_list(app, username)
children = []
for p in projects:
children.append({
"text": p, "iconCls": "icon-project", "state": "closed",
"attributes": {
"name": p, # "description": p["description"],
"category": "project", # "boolean": p["boolean"]
},
"children": []
})
return [{
"text": session['username'], "iconCls": "icon-workspace",
"attributes": {
"category": "root"
},
"children": children}]
def get_suite_by_project(app, username, args):
path = app.config["AUTO_HOME"] + "/workspace/" + username + "/" + args["name"]
suites = list_dir(path)
children = []
if len(suites) > 1:
suites.sort()
for d in suites:
cases = list_dir(path + "/" + d)
icons = "icon-suite"
if len(cases) > 1:
icons = "icon-suite-open"
children.append({
"text": d, "iconCls": icons, "state": "closed",
"attributes": {
"name": d, "category": "suite"
},
"children": []
})
return children
def get_case_by_suite(app, username, args):
path = app.config["AUTO_HOME"] + "/workspace/" + username + "/%s/%s" % (args["project"], args["name"] )
cases = list_dir(path)
if len(cases) > 1:
cases.sort()
children = []
for t in cases:
text = get_splitext(t)
if text[1] in ICONS:
icons = ICONS[text[1]]
else:
icons = "icon-file-default"
if text[1] in (".robot"):
children.append({
"text": t, "iconCls": icons, "state": "closed",
"attributes": {
"name": text[0], "category": "case", "splitext": text[1]
},
"children": []
})
else:
children.append({
"text": t, "iconCls": icons, "state": "open",
"attributes": {
"name": text[0], "category": "case", "splitext": text[1]
}
})
return children
def get_step_by_case(app, username, args):
print(args)
path = app.config["AUTO_HOME"] + "/workspace/" + username + "/%s/%s/%s%s" % (args["project"], args["suite"], args["name"], args["splitext"])
data = []
if args["splitext"] == ".robot":
data = get_case_data(path)
return data
def get_case_data(path):
suite = TestSuiteBuilder().build(path)
children = []
if suite:
# add library
for i in suite.resource.imports:
children.append({
"text": i.name, "iconCls": "icon-library", "state": "open",
"attributes": {
"name": i.name, "category": "library"
}
})
for v in suite.resource.variables:
children.append({
"text": v.name, "iconCls": "icon-variable", "state": "open",
"attributes": {
"name": v.name, "category": "variable"
}
})
for t in suite.tests:
keys = []
for k in t.keywords:
keys.append({
"text": k.name, "iconCls": "icon-keyword", "state": "open",
"attributes": {
"name": k.name, "category": "keyword"
}
})
children.append({
"text": t.name, "iconCls": "icon-step", "state": "closed",
"attributes": {
"name": t.name, "category": "step"
},
"children": keys
})
for v in suite.resource.keywords:
children.append({
"text": v.name, "iconCls": "icon-user-keyword", "state": "open",
"attributes": {
"name": v.name, "category": "user_keyword"
}
})
return children
================================================
FILE: auto/www/api/settings.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
import json
import codecs
from flask import current_app, session, request, send_file
from flask_restful import Resource, reqparse
from utils.file import exists_path, make_nod
class Settings(Resource):
def __init__(self):
self.parser = reqparse.RequestParser()
self.parser.add_argument('method', type=str)
self.parser.add_argument('ssl', type=bool, default=False)
self.parser.add_argument('server', type=str)
self.parser.add_argument('port', type=str)
self.parser.add_argument('username', type=str)
self.parser.add_argument('password', type=str)
self.parser.add_argument('project', type=str)
self.parser.add_argument('success_list', type=str)
self.parser.add_argument('fail_list', type=str)
self.app = current_app._get_current_object()
def get(self):
args = self.parser.parse_args()
method = args["method"]
conf_path = self.app.config["AUTO_HOME"] + "/auto.json"
if method == "smtp":
config = json.load(codecs.open(conf_path, 'r', 'utf-8'))
result = {
"ssl": config["smtp"]["ssl"],
"server": config["smtp"]["server"],
"port": config["smtp"]["port"],
"username": config["smtp"]["username"],
"password": config["smtp"]["password"]
}
elif method == "email":
conf_path = self.app.config["AUTO_HOME"] + "/users/%s/config.json" % session["username"]
config = json.load(codecs.open(conf_path, 'r', 'utf-8'))
for p in config["data"]:
if p["name"] == args["project"]:
result = {
"success_list": p["success_list"],
"fail_list": p["fail_list"]
}
break
return result
def post(self):
args = self.parser.parse_args()
method = args["method"]
if method == "smtp":
result = self.__smtp(args)
elif method == "email":
result = self.__email(args)
return result, 201
# 配置smtp
def __smtp(self, args):
result = {"status": "success", "msg": "配置smtp服务成功"}
conf_path = self.app.config["AUTO_HOME"] + "/auto.json"
if not exists_path(conf_path):
make_nod(conf_path)
try:
config = json.load(codecs.open(conf_path, 'r', 'utf-8'))
config["smtp"]["ssl"] = args["ssl"]
config["smtp"]["server"] = args["server"]
config["smtp"]["port"] = args["port"]
config["smtp"]["username"] = args["username"]
config["smtp"]["password"] = args["password"]
json.dump(config, codecs.open(conf_path, 'w', 'utf-8'))
self.app.config["MAIL_SERVER"] = args["server"]
self.app.config["MAIL_PORT"] = args["port"]
self.app.config["MAIL_USERNAME"] = args["username"]
self.app.config["MAIL_PASSWORD"] = args["password"]
self.app.config["MAIL_USE_SSL"] = args["ssl"]
except Exception as e:
result["status"] = "fail"
result["msg"] = str(e)
return result
# 设置email通知列表
def __email(self, args):
result = {"status": "success", "msg": "配置smtp服务成功"}
conf_path = self.app.config["AUTO_HOME"] + "/users/%s/config.json" % (session["username"])
try:
config = json.load(codecs.open(conf_path, 'r', 'utf-8'))
index = 0
for p in config["data"]:
if p["name"] == args["project"]:
config["data"][index]["success_list"] = args["success_list"]
config["data"][index]["fail_list"] = args["fail_list"]
break
else:
index = index + 1
continue
json.dump(config, codecs.open(conf_path, 'w', 'utf-8'))
except Exception as e:
result["status"] = "fail"
result["msg"] = str(e)
return result
================================================
FILE: auto/www/api/suite.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
from flask import current_app, session
from flask_restful import Resource, reqparse
from utils.file import mk_dirs, exists_path, rename_file, remove_dir
class Suite(Resource):
def __init__(self):
self.parser = reqparse.RequestParser()
self.parser.add_argument('method', type=str)
self.parser.add_argument('name', type=str)
self.parser.add_argument('new_name', type=str)
self.parser.add_argument('project_name', type=str)
self.app = current_app._get_current_object()
def post(self):
args = self.parser.parse_args()
method = args["method"].lower()
if method == "create":
result = self.__create(args)
elif method == "edit":
result = self.__edit(args)
elif method == "delete":
result = self.__delete(args)
return result, 201
def __create(self, args):
result = {"status": "success", "msg": "创建目录成功"}
user_path = self.app.config["AUTO_HOME"] + "/workspace/%s/%s/%s" % (session["username"], args["project_name"], args["name"])
if not exists_path(user_path):
mk_dirs(user_path)
else:
result["status"] = "fail"
result["msg"] = "目录名称重复,创建失败"
return result
def __edit(self, args):
result = {"status": "success", "msg": "目录重命名成功"}
old_name = self.app.config["AUTO_HOME"] + "/workspace/%s/%s/%s" % (session["username"], args["project_name"], args["name"])
new_name = self.app.config["AUTO_HOME"] + "/workspace/%s/%s/%s" % (session["username"], args["project_name"], args["new_name"])
if not rename_file(old_name, new_name):
result["status"] = "fail"
result["msg"] = "目录重命名失败,名称重复"
return result
def __delete(self, args):
result = {"status": "success", "msg": "目录删除成功"}
user_path = self.app.config["AUTO_HOME"] + "/workspace/%s/%s/%s" % (session["username"], args["project_name"], args["name"])
if exists_path(user_path):
remove_dir(user_path)
else:
result["status"] = "fail"
result["msg"] = "删除失败,不存在的目录"
return result
================================================
FILE: auto/www/api/task.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
from flask import current_app, session, url_for
from flask_restful import Resource, reqparse
import json
import os
import codecs
import threading
import multiprocessing
from dateutil import tz
from robot.api import ExecutionResult
from utils.file import exists_path, read_file, remove_dir
from utils.run import robot_run, is_run, remove_robot, stop_robot, robot_job
from ..app import scheduler
class Task(Resource):
def __init__(self):
self.parser = reqparse.RequestParser()
self.parser.add_argument('method', type=str)
self.parser.add_argument('category', type=str)
self.parser.add_argument('project', type=str)
self.parser.add_argument('suite', type=str)
self.parser.add_argument('case', type=str)
self.parser.add_argument('task_no', type=str)
self.app = current_app._get_current_object()
def post(self):
args = self.parser.parse_args()
category = args["category"].lower()
if args["method"] == "run":
project = self.app.config["AUTO_HOME"] + "/workspace/%s/%s" % (session['username'], args["project"])
output = self.app.config["AUTO_HOME"] + "/jobs/%s/%s" % (session['username'], args["project"])
if category == "project":
if not is_run(self.app, args["project"]):
p = multiprocessing.Process(target=robot_run, args=(session["username"], args["project"], project, output))
p.start()
self.app.config["AUTO_ROBOT"].append({"name": args["project"], "process": p})
else:
return {"status": "fail", "msg": "请等待上一个任务完成"}
elif category == "suite":
case_path = project + "/%s" % args["suite"]
if not is_run(self.app, args["project"]):
p = multiprocessing.Process(target=robot_run, args=(session["username"], args["project"], case_path, output))
p.start()
self.app.config["AUTO_ROBOT"].append({"name": args["project"], "process": p})
else:
return {"status": "fail", "msg": "请等待上一个任务完成"}
elif category == "case":
case_path = project + "/%s/%s" % (args["suite"], args["case"])
if not is_run(self.app, args["project"]):
p = multiprocessing.Process(target=robot_run,
args=(session["username"], args["project"], case_path, output))
p.start()
self.app.config["AUTO_ROBOT"].append(
{"name": "%s" % args["project"], "process": p})
else:
return {"status": "fail", "msg": "请等待上一个任务完成"}
return {"status": "success", "msg": "已启动运行"}
elif args["method"] == "stop":
stop_robot(self.app, args["project"])
return {"status": "success", "msg": "已停止运行"}
elif args["method"] == "delete":
delete_task_record(self.app, args["project"], args["task_no"])
return {"status": "success", "msg": "已经删除记录"}
class TaskList(Resource):
def __init__(self):
self.parser = reqparse.RequestParser()
self.parser.add_argument('method', type=str)
self.parser.add_argument('name', type=str)
self.parser.add_argument('cron', type=str)
self.app = current_app._get_current_object()
def get(self):
args = self.parser.parse_args()
project = args["name"]
return get_task_list(self.app, session['username'], project)
def post(self):
args = self.parser.parse_args()
job_id = "%s_%s" % (session["username"], args["name"])
if args["method"] == "query":
return get_all_task(self.app)
elif args["method"] == "start":
result = {"status": "success", "msg": "调度启动成功"}
lock = threading.Lock()
lock.acquire()
job = scheduler.get_job(job_id)
if job:
scheduler.remove_job(job_id)
cron = args["cron"].replace("\n", "").strip().split(" ")
if args["cron"] != "* * * * * *" and len(cron) == 6:
scheduler.add_job(id=job_id,
name=args["name"],
func=robot_job,
args=(self.app, args["name"], session["username"]),
trigger="cron",
second=cron[0],
minute=cron[1],
hour=cron[2],
day=cron[3],
month=cron[4],
day_of_week=cron[5])
else:
result["msg"] = "cron表达式为默认* * * * * *, <br><br>无法启动调度,请修改cron表达式"
lock.release()
return result
elif args["method"] == "stop":
lock = threading.Lock()
lock.acquire()
job = scheduler.get_job(job_id)
if job:
scheduler.remove_job(id=job_id)
lock.release()
return {"status": "success", "msg": "停止调度成功"}
elif args["method"] == "edit":
result = edit_cron(self.app, args["name"], args["cron"])
if result:
# job_id = "%s_%s" % (session["username"], args["name"])
lock = threading.Lock()
lock.acquire()
job = scheduler.get_job(job_id)
if job:
scheduler.remove_job(job_id)
cron = args["cron"].replace("\n", "").strip().split(" ")
if args["cron"] != "* * * * * *" and len(cron) == 6:
scheduler.add_job(id=job_id,
name=args["name"],
func=robot_job,
args=(self.app, args["name"], session["username"]),
trigger="cron",
second=cron[0],
minute=cron[1],
hour=cron[2],
day=cron[3],
month=cron[4],
day_of_week=cron[5])
lock.release()
return {"status": "success", "msg": "更新调度成功"}
def get_task_list(app, username, project):
job_path = app.config["AUTO_HOME"] + "/jobs/%s/%s" % (username, project)
next_build = 0
task = []
if exists_path(job_path):
next_build = get_next_build_number(job_path)
if next_build != 0:
# 遍历所有任务结果
# 判断最近一个任务状态
icons = {
"running": url_for('static', filename='img/running.gif'),
"success": url_for('static', filename='img/success.png'),
"fail": url_for('static', filename='img/fail.png'),
"exception": url_for('static', filename='img/exception.png')}
#if exists_path(job_path + "/%s" % (next_build - 1)):
running = False
lock = threading.Lock()
lock.acquire()
remove_robot(app)
for p in app.config["AUTO_ROBOT"]:
if p["name"] == project:
running = True
break
lock.release()
if running:
task.append(
{
"status": icons["running"],
"name": "%s_#%s" % (project, next_build-1),
"success": "",
"fail": ""
}
)
last = 1
if running:
last = 2
for i in range(next_build-last, -1, -1):
if exists_path(job_path + "/%s" % i):
try:
suite = ExecutionResult(job_path + "/%s/output.xml" % i).suite
stat = suite.statistics.critical
if stat.failed != 0:
status = icons["fail"]
else:
status = icons['success']
task.append({
"task_no": i,
"status": status,
"name": "<a href='/view_report/%s/%s' target='_blank'>%s_#%s</a>" % (project, i, project, i),
"success": stat.passed,
"fail": stat.failed,
"starttime": suite.starttime,
"endtime": suite.endtime,
"elapsedtime": suite.elapsedtime,
"note": ""
})
except:
status = icons["exception"]
if i == next_build-last:
status = icons["running"]
task.append({
"task_no": i,
"status": status,
"name": "%s_#%s" % (project, i),
"success": "-",
"fail": "-",
"starttime": "-",
"endtime": "-",
"elapsedtime": "-",
"note": "异常"
})
return {"total": next_build-1, "rows": task}
def get_last_task(app, username, project):
icons = {
"running": url_for('static', filename='img/running.gif'),
"success": url_for('static', filename='img/success.png'),
"fail": url_for('static', filename='img/fail.png'),
"exception": url_for('static', filename='img/exception.png')}
job_path = app.config["AUTO_HOME"] + "/jobs/%s/%s" % (username, project)
status = icons["running"]
if exists_path(job_path):
next_build = get_next_build_number(job_path)
last_job = next_build-1
if exists_path(job_path + "/%s" % last_job):
try:
suite = ExecutionResult(job_path + "/%s/output.xml" % last_job).suite
stat = suite.statistics.critical
if stat.failed != 0:
status = icons["fail"]
else:
status = icons['success']
except:
status = icons["running"]
else:
status = icons["exception"]
else:
status = icons['success']
return status
def get_all_task(app):
user_path = app.config["AUTO_HOME"] + "/users/" + session["username"]
if exists_path(user_path):
config = json.load(codecs.open(user_path + '/config.json', 'r', 'utf-8'))
projects = config['data']
task_list = {"total": len(projects), "rows": []}
for p in projects:
# job_path = app.config["AUTO_HOME"] + "/jobs/%s/%s" % (session["username"], p["name"])
# running = False
# lock = threading.Lock()
# lock.acquire()
# remove_robot(app)
# next_build = get_next_build_number(job_path)
# if next_build != 0:
# for pp in app.config["AUTO_ROBOT"]:
# if pp["name"] == p["name"]:
# running = True
# status = icons["running"]
# break
# if running is False:
# if exists_path(job_path + "/%s" % (next_build-1)):
# try:
# suite = ExecutionResult(job_path + "/%s/output.xml" % (next_build-1)).suite
# stat = suite.statistics.critical
# if stat.failed != 0:
# status = icons["fail"]
# else:
# status = icons['success']
# except:
# status = icons["running"]
# else:
# status = icons['success']
# lock.release()
task = {
#"status": status,
"name": p["name"],
#"last_success": get_last_pass(job_path + "/lastPassed"),
#"last_fail": get_last_fail(job_path + "/lastFail"),
"enable": p["enable"],
"next_time": get_next_time(app, p["name"]),
"cron": p["cron"],
"status": get_last_task(app, session["username"], p["name"])
}
task_list["rows"].append(task)
return task_list
def get_last_pass(job_path):
passed = "无"
passed_path = job_path + "lastPassed"
if exists_path(passed_path):
f = codecs.open(passed_path, "r", "utf-8")
passed = f.read()
f.close()
return passed
def get_last_fail(job_path):
fail = "无"
fail_path = job_path + "lastFail"
if exists_path(fail_path):
f = codecs.open(fail_path, "r", "utf-8")
fail = f.read()
f.close()
return fail
def get_next_build_number(job_path):
next_build_number = 1
next_path = job_path + "/nextBuildNumber"
if exists_path(next_path):
f = codecs.open(next_path, "r", "utf-8")
next_build_number = int(f.read())
f.close()
return next_build_number
def get_next_time(app, name):
job = scheduler.get_job("%s_%s" % (session["username"], name))
if job:
to_zone = tz.gettz("CST")
return job.next_run_time.astimezone(to_zone).strftime("%Y-%m-%d %H:%M:%S")
else:
return "-"
def edit_cron(app, name, cron):
user_path = app.config["AUTO_HOME"] + "/users/" + session["username"]
if os.path.exists(user_path):
config = json.load(codecs.open(user_path + '/config.json', 'r', 'utf-8'))
index = 0
for p in config["data"]:
if p["name"] == name:
config["data"][index]["cron"] = cron
break
index += 1
json.dump(config, codecs.open(user_path + '/config.json', 'w', 'utf-8'))
return True
return False
def delete_task_record(app, name, task_no):
task_path = app.config["AUTO_HOME"] + "/jobs/" + session["username"] + "/%s/%s" % (name, task_no)
if os.path.exists(task_path):
remove_dir(task_path)
================================================
FILE: auto/www/api/user.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
import json
import codecs
from flask import current_app, session, request, send_file
from flask_restful import Resource, reqparse
from werkzeug.security import generate_password_hash, check_password_hash
from utils.file import list_dir, exists_path, rename_file, make_nod, remove_dir, write_file, read_file, mk_dirs
class User(Resource):
def __init__(self):
self.parser = reqparse.RequestParser()
self.parser.add_argument('method', type=str)
self.parser.add_argument('username', type=str)
self.parser.add_argument('password', type=str)
self.parser.add_argument('new_password', type=str, default="")
self.parser.add_argument('email', type=str)
self.parser.add_argument('fullname', type=str)
self.app = current_app._get_current_object()
def get(self):
user_list = {"total": 0, "rows": []}
user_path = self.app.config["AUTO_HOME"] + "/users"
if exists_path(user_path):
users = list_dir(user_path)
user_list["total"] = len(users)
for user in users:
if user == "AutoLink":
category = "管理员"
else:
category = "普通用户"
config = json.load(codecs.open(user_path + "/" + user + '/config.json', 'r', 'utf-8'))
user_list["rows"].append({ "name": user, "fullname": config["fullname"], "email": config["email"], "category": category })
return user_list
def post(self):
args = self.parser.parse_args()
method = args["method"].lower()
if method == "create":
result = self.__create(args)
elif method == "edit":
result = self.__edit(args)
elif method == "delete":
result = self.__delete(args)
elif method == "save":
result = self.__save(args)
else:
print(request.files["files"])
return result, 201
def __create(self, args):
result = {"status": "success", "msg": "创建用户成功"}
user_path = self.app.config["AUTO_HOME"] + "/users/%s" % (args["username"])
if not exists_path(user_path):
mk_dirs(user_path)
make_nod(user_path + "/config.json")
user = {"fullname": args["fullname"],
"email": args["email"],
"passwordHash": generate_password_hash(args["password"]),
"data": []}
json.dump(user, codecs.open(user_path + '/config.json', 'w', 'utf-8'))
else:
result["status"] = "fail"
result["msg"] = "用户名称重复,创建失败"
return result
def __edit(self, args):
result = {"status": "success", "msg": "用户信息修改成功"}
user_path = self.app.config["AUTO_HOME"] + "/users/" + args["username"]
if exists_path(user_path):
config = json.load(codecs.open(user_path + '/config.json', 'r', 'utf-8'))
if check_password_hash(config["passwordHash"], args["password"]):
config["passwordHash"] = generate_password_hash(args["new_password"])
config["fullname"] = args["fullname"]
config["email"] = args["email"]
json.dump(config, codecs.open(user_path + '/config.json', 'w', 'utf-8'))
else:
result["status"] = "fail"
result["msg"] = "原始密码错误"
else:
result["status"] = "fail"
result["msg"] = "用户不存在"
return result
def __delete(self, args):
result = {"status": "success", "msg": "用户删除成功"}
user_path = self.app.config["AUTO_HOME"] + "/users/" + args["username"]
if exists_path(user_path):
config = json.load(codecs.open(user_path + '/config.json', 'r', 'utf-8'))
if len(config["data"]) > 0:
result["status"] = "fail"
result["msg"] = "请先删除该用户拥有的项目"
else:
remove_dir(user_path)
else:
result["status"] = "fail"
result["msg"] = "用户不存在,删除失败"
return result
def __save(self, args):
result = {"status": "success", "msg": "保存成功"}
user_path = self.app.config["AUTO_HOME"] + "/workspace/%s%s" % (session["username"], args["path"])
if not write_file(user_path, args["data"]):
result["status"] = "fail"
result["msg"] = "保存失败"
return result
================================================
FILE: auto/www/app.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
import os
import json
import codecs
from flask import Flask
from flask_login import LoginManager
# from flask_mail import Mail
from flask_apscheduler import APScheduler
from auto.configuration import config
from utils.file import list_dir
from utils.run import robot_job
# mail = Mail()
scheduler = APScheduler()
login_manager = LoginManager()
login_manager.login_view = 'auto.login'
def load_all_task(app):
with app.app_context():
user_path = app.config["AUTO_HOME"] + "/users/"
users = list_dir(user_path)
for user in users:
if os.path.exists(user_path + user):
if not os.path.exists(user_path + user + '/config.json'):
continue
conf = json.load(codecs.open(user_path + user + '/config.json', 'r', 'utf-8'))
data = conf['data']
# 遍历项目
for p in data:
if p["cron"] == "* * * * * *":
continue
cron = p["cron"].replace("\n", "").strip().split(" ")
if scheduler.get_job("%s_%s" % (user, p["name"])) is None:
scheduler.add_job(id="%s_%s" % (user, p["name"]),
name=p["name"],
func=robot_job,
args=(app, p["name"], user),
trigger="cron",
replace_existing=True,
second=cron[0],
minute=cron[1],
hour=cron[2],
day=cron[3],
month=cron[4],
day_of_week=cron[5])
else:
scheduler.remove_job("%s_%s" % (user, p["name"]))
scheduler.add_job(id="%s_%s" % (user, p["name"]),
name=p["name"],
func=robot_job,
args=(app, p["name"], user),
trigger="cron",
replace_existing=True,
second=cron[0],
minute=cron[1],
hour=cron[2],
day=cron[3],
month=cron[4],
day_of_week=cron[5])
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
login_manager.init_app(app)
# mail.init_app(app)
# app.config["MAIL"] = mail
scheduler.init_app(app)
scheduler.start()
# for blueprints
from .blueprints import routes as routes_blueprint
app.register_blueprint(routes_blueprint)
from .api import api_bp as api_blueprint
app.register_blueprint(api_blueprint, url_prefix='/api/v1')
if app.config['SSL_REDIRECT']:
from flask_sslify import SSLify
sslify = SSLify(app)
return app
================================================
FILE: auto/www/blueprints.py
================================================
# -*- coding: utf-8 -*-
__author__ = "苦叶子"
"""
公众号: 开源优测
Email: lymking@foxmail.com
"""
from flask import Blueprint, render_template, session, redirect, url_for, current_app, send_file, request
from flask_login import login_required
from utils.file import get_splitext, exists_path
routes = Blueprint('routes', __name__)
@routes.before_request
def before_routes():
if 'username' in session:
pass
else:
pass
# return redirect(url_for('routes.index'))
@routes.route('/')
def index():
return render_template('login.html')
@routes.route('/dashboard', methods=['GET', 'POST'])
def dashboard():
if 'username' in session:
return render_template('dashboard.html', username=session['username'])
else:
return render_template('login.html')
@routes.route('/tree_demo')
def tree_demo():
return render_template('tree_data1.json')
@routes.route("/editor/<project>/<suite>/<case>")
def editor(project, suite, case):
t = get_splitext(case)
default = "default.html"
if t[1] in (".txt", ".robot", ".py", ".js"):
default = "editor.html"
elif t[1] in (".bmp", ".jpg", ".jpeg", ".png", ".gif"):
default = "view_img.html"
return render_template(default, project=project, suite=suite, case=case)
@routes.route("/task_list/<name>")
def task_list(name):
return render_template('task_list.html', project=name)
@routes.route("/scheduler/")
def scheduler():
return render_template('scheduler.html')
@routes.route("/user/")
def user():
return render_template('user.html')
@routes.route("/view_report/<project>/<task>")
def view_report(project, task):
app = current_app._get_current_object()
job_path = app.config["AUTO_HOME"] + "/jobs/%s/%s/%s/log.html" % (session['username'], project, task)
return send_file(job_path)
@routes.route("/q_view_report/<username>/<project>/<task>")
def q_view_report(username, project, task):
app = current_app._get_current_object()
job_path = app.config["AUTO_HOME"] + "/jobs/%s/%s/%s/log.html" % (username, project, task)
return send_file(job_path)
@routes.route("/view_img")
def view_img():
args = request.args.to_dict()
app = current_app._get_current_object()
img_path = app.config["AUTO_HOME"] + "/workspace/%s" % session['username'] + args["path"]
img_path.replace("\\", "/")
if exists_path(img_path):
return send_file(img_path)
return False
@routes.route("/welcome")
def welcome():
return render_template("welcome.html")
================================================
FILE: auto/www/static/css/auto.css
================================================
.icon-editor{
background:url('../img/editor.png') no-repeat center center;
}
.icon-expand{
background:url('../img/expand.png') no-repeat center center;
}
.icon-collapse{
background:url('../img/collapse.png') no-repeat center center;
}
.icon-logout{
background:url('../img/logout.png') no-repeat center center;
}
.icon-workspace{
background:url('../img/workspace.png') no-repeat center center;
}
.icon-suite{
background:url('../img/suite.png') no-repeat center center;
}
.icon-suite-open{
background:url('../img/suite.open.png') no-repeat center center;
}
.icon-project{
background:url('../img/project.png') no-repeat center center;
}
.icon-robot{
background:url('../img/robot.png') no-repeat center center;
}
.icon-resource{
background:url('../img/resource.png') no-repeat center center;
}
.icon-file-default{
background:url('../img/file.default.png') no-repeat center center;
}
.icon-remove{
background:url('../img/delete.png') no-repeat center center;
}
.icon-debug{
background:url('../img/bug.png') no-repeat center center;
}
.icon-run{
background:url('../img/run.png') no-repeat center center;
}
.icon-task-list{
background:url('../img/task.list.png') no-repeat center center;
}
.icon-task{
background:url('../img/task.png') no-repeat center center;
}
.icon-image{
background:url('../img/image.png') no-repeat center center;
}
.icon-keyword-list{
background:url('../img/keyword.list.png') no-repeat center center;
}
.icon-keyword{
background:url('../img/keyword.png') no-repeat center center;
}
.icon-keyword-help{
background:url('../img/keyword.help.png') no-repeat center center;
}
.icon-refresh{
background:url('../img/refresh.png') no-repeat center center;
}
.icon-stop{
background:url('../img/stop.png') no-repeat center center;
}
.icon-edit{
background:url('../img/edit.png') no-repeat center center;
}
.icon-user{
background:url('../img/user.png') no-repeat center center;
}
.icon-python{
background:url('../img/python.text.png') no-repeat center center;
}
.icon-excel{
background:url('../img/excel.png') no-repeat center center;
}
.icon-scheduler{
background:url('../img/scheduler.png') no-repeat center center;
}
.icon-update{
background:url('../img/update.png') no-repeat center center;
}
.icon-settings{
background:url('../img/settings.png') no-repeat center center;
}
.icon-email{
background:url('../img/email.png') no-repeat center center;
}
.icon-notification{
background:url('../img/notification.png') no-repeat center center;
}
.icon-library{
background:url('../img/library.png') no-repeat center center;
}
.icon-variable{
background:url('../img/variable.png') no-repeat center center;
}
.icon-step{
background:url('../img/step.png') no-repeat center center;
}
.icon-user-keyword{
background:url('../img/user-keyword.png') no-repeat center center;
}
.CodeMirror {
border: 1px solid #eee;
height: auto;
}
.CodeMirror-selected {
background-color: green !important;
}
.CodeMirror-selectedtext {
color: white;
}
.styled-background {
background-color: #ff7;
}
.CodeMirror-empty {
outline: 1px solid #c22;
}
.CodeMirror-empty.CodeMirror-focused {
outline: none;
}
.CodeMirror pre.CodeMirror-placeholder {
color: #999;
}
================================================
FILE: auto/www/static/css/base.css
================================================
/*
* Copyright (c) 2014-2018, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* themes for base.
*
* @author <a href="http://vanessa.b3log.org">Liyuan Li</a>
* @version 0.1.0.0, Dec 6, 2015
*/
/* start reset & function */
::-webkit-scrollbar {
background: none;
width: 16px;
height: 16px;
}
::-webkit-scrollbar-corner {
display: none;
background-color: transparent;
}
::-webkit-scrollbar-thumb {
border: solid 0 rgba(0, 0, 0, 0);
border-right-width: 4px;
border-left-width: 4px;
border-radius: 9px;
box-shadow: inset 0 0 0 1px rgba(128, 128, 128, 0.2), inset 0 0 0 4px rgba(128, 128, 128, 0.2);
}
::-webkit-scrollbar-thumb:horizontal {
border-bottom-width: 4px;
border-top-width: 4px;
}
body {
font-size: 13px;
margin: 0;
color: #000;
overflow: hidden;
font-family: Helvetica;
}
ul {
padding: 0;
margin: 0;
list-style: none;
}
* {
box-sizing: border-box;
}
a {
color: #4183c4;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
img {
vertical-align: middle;
}
input,
button {
font-family: Helvetica;
}
.fn-left {
float: left;
}
.fn-right {
float: right;
}
.fn-clear:before,
.fn-clear:after {
display: table;
content: "";
}
.fn-clear:after {
clear: both;
}
.fn-none {
display: none;
}
/* end reset & function */
/* start common */
.ft-small {
color: #999;
font-size: 12px;
}
.ft-red {
color: #9d0000;
}
.list li {
cursor: pointer;
line-height: 20px;
padding: 0 3px;
word-wrap: normal;
word-break: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.list li.selected,
.list li:hover {
background-color: #3875d7;
color: #FFF;
}
.list li.selected .ft-small,
.list li:hover .ft-small {
color: #FFF;
}
/* end common */
/* start icon */
@font-face {
font-family: 'icomoon';
src:url('fonts/icomoon.eot?35cb2z');
src:url('fonts/icomoon.eot?#iefix35cb2z') format('embedded-opentype'),
url('fonts/icomoon.woff?35cb2z') format('woff'),
url('fonts/icomoon.ttf?35cb2z') format('truetype'),
url('fonts/icomoon.svg?35cb2z#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
.font-ico,
[class^="ico-"] {
font-family: 'icomoon';
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
cursor: pointer;
font-size: 13px;
line-height: 20px;
}
.ico-book:before {
content: "\e623";
}
.ico-price:before {
content: "\e616";
}
.ico-start:before {
content: "\e9d7";
}
.ico-share:before {
content: "\e61f";
}
.ico-github:before {
content: "\f00a";
}
.ico-git:before {
content: "\e624";
}
.ico-tencent:before {
content: "\e622";
}
.ico-weibo:before {
content: "\e621";
}
.ico-googleplus:before {
content: "\e61a";
}
.ico-twitter:before {
content: "\e61c";
}
.ico-email:before {
content: "\e619";
}
.ico-facebook:before {
content: "\e61b";
}
.ico-moveup:before {
content: "\f148";
}
.ico-movedown:before {
content: "\f149";
}
.ico-keyboard:before {
content: "\f11c";
}
.ico-findfiles:before {
content: "\e603";
}
.ico-find:before {
content: "\e602";
}
.ico-editor:before {
content: "\e604";
}
.ico-tree:before {
content: "\e600";
}
.ico-build:before {
content: "\e601";
}
.ico-notification:before {
content: "\e607";
}
.ico-report:before {
content: "\e605";
}
.ico-comment:before {
content: "\e620";
}
.ico-goline:before {
content: "\e61e";
}
.ico-info:before {
content: "\e61d";
}
.ico-signup:before {
content: "\e606";
}
.ico-signout:before {
content: "\e618";
}
.ico-redo:before {
content: "\e615";
}
.ico-undo:before {
content: "\e60e";
}
.ico-about:before {
content: "\e60d";
}
.ico-import:before {
content: "\f0ee";
}
.ico-export:before {
content: "\f0ed";
}
.ico-refresh:before {
content: "\f021";
}
.ico-remove:before {
content: "\e60b";
}
.ico-save:before {
content: "\f0c7";
}
.ico-max:before {
content: "\e609";
}
.ico-format:before {
content: "\e612";
}
.ico-buildrun:before {
content: "\e60c";
}
.ico-stop:before {
content: "\e60f";
}
.ico-restore:before {
content: "\e613";
}
.toolbars .ico-restore:before {
content: "\e60a";
}
.ico-min:before {
content: "\e614";
position: absolute;
right: 5px;
}
.ico-close:before {
content: "\e611";
}
/* end ico */
================================================
FILE: auto/www/static/css/sign.css
================================================
/*
* Copyright (c) 2014-2018, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.wrapper {
margin: 0 auto;
width: 980px;
}
.header .logo {
float: left;
height: 32px;
margin-top: 3px;
}
.header {
margin: 8px 0;
}
.header li {
float: left;
margin-left: 25px;
}
.header a {
display: block;
font-weight: bold;
padding: 5px 0;
color: #333;
line-height: 30px;
text-decoration: none;
}
.header a:hover {
color: #4183C4;
}
.content {
border-top: 1px solid #A4A4A4;
border-bottom: 1px solid #919191;
background-color: #202021;
height: 632px;
padding-top: 222px;
}
.content h2 {
color: #FFF;
font-size: 70px;
margin: 0 0 60px;
}
.content h3 {
color: #4183c4;
font-size: 21px;
}
.content .form {
width: 320px;
margin-top: -18px;
position: relative;
}
.content .form input {
border: 1px solid #ccc;
border-radius: 3px;
width: 100%;
background-color: #fafafa;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset;
color: #333;
min-height: 34px;
outline: medium none;
vertical-align: middle;
font-size: 16px;
border: 1px solid #FFF;
padding: 10px;
margin-top: 20px;
}
.content .form input:focus {
background-color: #FFF;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075) inset, 0 0 12px rgba(255, 255, 255, 0.75);
}
.btn {
width: 100%;
color: #fff;
text-shadow: 0 -1px 0 rgba(0,0,0,0.25);
background-color: #60b044;
background-image: linear-gradient(#8add6d, #60b044);
border: 1px solid #5ca941;
padding: 10px;
border-radius: 3px;
cursor: pointer;
}
.btn:hover {
background-color: #569e3d;
background-image: linear-gradient(#79d858, #569e3d);
border-color: #4a993e;
}
.btn.btn-white,
.btn.btn-red {
color: #333;
text-shadow: 0 1px 0 rgba(255,255,255,0.9);
background-color: #eee;
background-image: linear-gradient(#fcfcfc, #eee);
border-color: #d5d5d5;
}
.btn.btn-red {
color: #9d0000;
}
.btn.btn-white:hover {
color: #333;
text-shadow: 0 1px 0 rgba(255,255,255,0.9);
background-color: #ddd;
background-image: linear-gradient(#eee, #ddd);
border-color: #ccc;
}
.btn.btn-red:hover {
background-color: #b33630;
background-image: linear-gradient(#dc5f59, #b33630);
background-repeat: repeat-x;
border-color: #cd504a;
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.3);
}
#msg {
background-color: #fcdede;
border: 1px solid #d2b2b2;
padding: 15px;
font-size: 14px;
color: #911;
position: absolute;
width: 100%;
top: -48px;
}
.footer {
line-height: 30px;
color: #777;
font-size: 12px;
text-align: center;
position: relative;
}
.footer a {
text-decoration: none;
color: #4183c4;
}
.footer a:hover {
text-decoration: underline;
}
.footer .github-btns {
height: 25px;
position: absolute;
top: 5px;
right: 0;
}
/* start sign up */
.dir {
color: #4183c4;
font-size: 18px;
word-wrap: break-word;
margin-top: 20px;
}
#dir {
color: #999;
font-size: 13px;
}
.form.sign-up {
margin-top: -108px;
}
#loginBtn,
#signUpBtn {
margin-top: 20px;
font-size: 16px;
}
/* end sign up */
================================================
FILE: auto/www/static/js/auto.js
================================================
/*
* auto.js
*
* 作者: 苦叶子
*
* 公众号: 开源优测
*
* Email: lymking@foxmail.com
*
*/
/*
* for string format
*/
String.prototype.lym_format = function() {
if (arguments.length == 0) {
return this;
}
for (var StringFormat_s = this, StringFormat_i = 0; StringFormat_i < arguments.length; StringFormat_i++) {
StringFormat_s = StringFormat_s.replace(new RegExp("\\{" + StringFormat_i + "\\}", "g"), arguments[StringFormat_i]);
}
return StringFormat_s;
};
/*
* to json
*/
$.fn.serializeObject = function() {
var json = {};
var arrObj = this.serializeArray();
$.each(arrObj, function() {
if (json[this.name]) {
if (!json[this.name].push) {
json[this.name] = [ json[this.name] ];
}
json[this.name].push(this.value || '');
} else {
json[this.name] = this.value || '';
}
});
return json;
};
/*
* 显示消息
*/
function show_msg(title, msg){
$.messager.show({
title: title,
msg: msg,
timeout: 3000,
showType: 'slide'
});
}
function do_refresh(data){
location.href = data.url;
}
function do_nop(data){
// 空函数
}
function do_msg(data){
show_msg('提示信息', data.msg);
}
function do_init(data){
if(data.data == ""){
//editor.setValue("*** Settings ***\n\n\n*** Variables ***\n\n\n*** Test Cases ***\n\n\n*** Keywords ***\n\n");
if(data.ext==".txt"){
editor.setValue("*** Settings ***\n\n\n*** Variables ***\n\n\n");
}
else if(data.ext ==".robot"){
editor.setValue("*** Settings ***\n\n\n*** Variables ***\n\n\n*** Test Cases ***\n\n\n*** Keywords ***\n\n");
}
}
else{
editor.setValue(data.data);
}
}
function do_ajax(type, url, data, func){
$.ajax({
type : type,
url : url,
data: data,
success : func
});
}
function do_login(fm_id){
var data = $('#{0}'.lym_format(fm_id)).serializeObject();
do_ajax('post', '/api/v1/auth/', data, do_refresh);
}
function do_logout(username){
do_ajax('get', '/api/v1/auth/', '', do_refresh)
}
function do_run(){
var node = $('#project_tree').tree('getSelected');
if(node){
var category = node.attributes["category"];
var data ={"method": "run", "category": category};
if(category =="project"){
data["project"] = node.attributes["name"];
}
else if(category == "suite"){
var project = $('#project_tree').tree('getParent', node.target);
data["project"] = project.attributes["name"];
data["suite"] = node.attributes["name"];
}
else if(category == "case"){
var suite = $('#project_tree').tree('getParent', node.target);
var project = $('#project_tree').tree('getParent', suite.target);
data["project"] = project.attributes["name"];
data["suite"] = suite.attributes["name"];
data["case"] = node.attributes["name"] + node.attributes['splitext'];
}
do_ajax('post',
'/api/v1/task/',
data,
do_msg);
}
}
function do_task_list(){
var node = $('#project_tree').tree('getSelected');
if(node){
var project = node.attributes["name"];
addTab(project, "/task_list/{0}".lym_format(project), 'icon-task')
}
}
function do_in_array(str, array){
for(a in array){
if(array[a] == str){
return true;
}
}
return false;
}
function onDblClick(node) {
var category = node.attributes.category;
var steps = new Array("library", "variable", "step", "user_keyword");
if(category == "case"){
var suite = $('#project_tree').tree('getParent', node.target);
var project = $('#project_tree').tree('getParent', suite.target);
addTab(node.attributes['name'], '/editor/{0}/{1}/{2}{3}'.lym_format(
project.attributes['name'],
suite.attributes['name'],
node.attributes['name'],
node.attributes['splitext']
), "icon-editor");
}
else if(do_in_array(category, steps)){
var testcase = $('#project_tree').tree('getParent', node.target);
var suite = $('#project_tree').tree('getParent', testcase.target);
var project = $('#project_tree').tree('getParent', suite.target);
addTab(testcase.attributes['name'], '/editor/{0}/{1}/{2}{3}'.lym_format(
project.attributes['name'],
suite.attributes['name'],
testcase.attributes['name'],
testcase.attributes['splitext']
), "icon-editor");
}
else if(category == "keyword"){
var step = $('#project_tree').tree('getParent', node.target);
var testcase = $('#project_tree').tree('getParent', step.target);
var suite = $('#project_tree').tree('getParent', testcase.target);
var project = $('#project_tree').tree('getParent', suite.target);
addTab(testcase.attributes['name'], '/editor/{0}/{1}/{2}{3}'.lym_format(
project.attributes['name'],
suite.attributes['name'],
testcase.attributes['name'],
testcase.attributes['splitext']
), "icon-editor");
}
}
function onContextMenu(e, node){
e.preventDefault();
// select the node
$('#project_tree').tree('select', node.target);
// display context menu
$('#{0}_menu'.lym_format(node.attributes['category'])).menu('show', {
left: e.pageX,
top: e.pageY
});
}
function addTab(title, url, icon){
var editor_tabs = $("#editor_tabs");
if (editor_tabs.tabs('exists', title)){
//如果tab已经存在,则选中并刷新该tab
editor_tabs.tabs('select', title);
refreshTab({title: title, url: url});
}
else {
var content='<iframe scrolling="yes" frameborder="0" src="{0}" style="width:100%;height:100%"></iframe>'.lym_format(url);
editor_tabs.tabs('add',{
title: title,
closable: true,
content: content,
iconCls: icon||'icon-default'
});
}
}
function refreshTab(cfg){
var tab = cfg.title?$('#editor_tabs').tabs('getTab',cfg.title):$('#editor_tabs').tabs('getSelected');
if(tab && tab.find('iframe').length > 0){
var frame = tab.find('iframe')[0];
var url = cfg.url?cfg.url:fram.src;
frame.contentWindow.location.href = url;
}
}
function collapse(){
var node = $('#project_tree').tree('getSelected');
$('#project_tree').tree('collapse',node.target);
}
function expand(){
var node = $('#project_tree').tree('getSelected');
$('#project_tree').tree('expand',node.target);
}
function onBeforeExpand(node){
if(node){
var param = $("#project_tree").tree("options").queryParams;
param.category = node.attributes.category;
param.name = node.attributes.name;
if(node.attributes.category == "suite"){
var parent = $("#project_tree").tree('getParent', node.target);
param.project = parent.attributes.name;
}
else if(node.attributes.category == "case")
{
var suite = $("#project_tree").tree('getParent', node.target);
param.suite = suite.attributes.name;
var project = $("#project_tree").tree('getParent', suite.target);
param.project = project.attributes.name;
param.splitext = node.attributes.splitext;
}
}
}
function manage_project(win_id, ff_id, method){
if(method == "create"){
clear_form(ff_id);
}
else if(method == "edit"){
var node = $('#project_tree').tree('getSelected');
if(node){
$("#{0} input#new_name".lym_format(ff_id)).textbox('setValue', node.attributes['name']);
}
}
open_win(win_id);
}
function refresh_workspace(data){
var param = $("#project_tree").tree("options").queryParams
param.category = "root";
$('#project_tree').tree("reload");
show_msg('提示信息', data.msg);
}
function refresh_project_node(data){
var node = $('#project_tree').tree('getSelected');
if(node){
var param = $("#project_tree").tree("options").queryParams;
param.category = "project";
param.name = node.attributes.name;
$('#project_tree').tree('reload', node.target);
}
show_msg('提示信息', data.msg);
}
function refresh_suite_node(data){
var node = $('#project_tree').tree('getSelected');
if(node){
parent = $('#project_tree').tree('getParent', node.target);
var param = $("#project_tree").tree("options").queryParams;
param.category = "project";
param.name = parent.attributes.name;
$('#project_tree').tree("reload", parent.target);
}
show_msg('提示信息', data.msg);
}
function refresh_case_node(data){
var node = $('#project_tree').tree('getSelected');
if(node){
var param = $("#project_tree").tree("options").queryParams;
param.category = "suite";
var suite = $('#project_tree').tree('getParent', node.target);
param.suite = suite.attributes.name
var project = $("#project_tree").tree('getParent', suite.target);
param.project = project.attributes.name;
$('#project_tree').tree("reload", suite.target);
}
show_msg('提示信息', data.msg);
}
function create_project(win_id, ff_id){
var data = $("#{0}".lym_format(ff_id)).serializeObject();
data["method"] = "create";
do_ajax('post', '/api/v1/project/', data, refresh_workspace);
close_win(win_id);
}
function rename_project(win_id, ff_id){
var data = $("#{0}".lym_format(ff_id)).serializeObject();
var node = $('#project_tree').tree('getSelected');
data["name"] = node.attributes['name'];
data["method"] = "edit";
do_ajax('post', '/api/v1/project/', data, refresh_workspace);
close_win(win_id);
}
function delete_project(){
var node = $('#project_tree').tree('getSelected');
if(node){
$.messager.confirm('删除提示', '<br>确定删除项目: {0}?'.lym_format(node.attributes['name']), function(r){
if (r){
var data = {
"name": node.attributes['name'],
"method": "delete"
};
do_ajax('post', "/api/v1/project/", data, refresh_workspace);
}
});
}
}
function manage_suite(win_id, ff_id, method){
if(method == "create"){
clear_form(ff_id);
}
else if(method == "edit"){
var node = $('#project_tree').tree('getSelected');
if(node){
$("#{0} input#new_name".lym_format(ff_id)).textbox('setValue', node.attributes['name']);
}
}
open_win(win_id);
}
function create_suite(win_id, ff_id){
var node = $('#project_tree').tree('getSelected');
if(node){
var data = $("#{0}".lym_format(ff_id)).serializeObject();
data["method"] = "create";
data["project_name"] = node.attributes['name'];
do_ajax('post', '/api/v1/suite/', data, refresh_project_node);
close_win(win_id);
}
}
function rename_suite(win_id, ff_id){
var data = $("#{0}".lym_format(ff_id)).serializeObject();
var node = $('#project_tree').tree('getSelected');
if(node){
var project = $('#project_tree').tree('getParent', node.target);
data["name"] = node.attributes['name'];
data["project_name"] = project.attributes['name'];
data["method"] = "edit";
do_ajax('post', '/api/v1/suite/', data, refresh_suite_node);
close_win(win_id);
}
}
function delete_suite(){
var node = $('#project_tree').tree('getSelected');
if(node){
$.messager.confirm('删除提示', '<br>确定删除目录: {0}?'.lym_format(node.attributes['name']), function(r){
if (r){
var project = $('#project_tree').tree('getParent', node.target);
var data = {
"name": node.attributes['name'],
"project_name": project.attributes['name'],
"method": "delete"
};
do_ajax('post', "/api/v1/suite/", data, refresh_suite_node);
}
});
}
}
function manage_file(win_id, ff_id, method){
if(method == "create"){
clear_form(ff_id);
}
else if(method == "edit"){
var node = $('#project_tree').tree('getSelected');
if(node){
$("#{0} select#new_category".lym_format(ff_id)).combobox("setValue", node.attributes['splitext']);
$("#{0} input#new_name".lym_format(ff_id)).textbox('setValue', node.attributes['name']);
}
}
open_win(win_id);
}
function create_file(win_id, ff_id){
var node = $('#project_tree').tree('getSelected');
if(node){
var project = $('#project_tree').tree('getParent', node.target);
var data = $("#{0}".lym_format(ff_id)).serializeObject();
data["method"] = "create";
data["suite_name"] = node.attributes['name'];
data["project_name"] = project.attributes['name'];
do_ajax('post', '/api/v1/case/', data, refresh_suite_node);
close_win(win_id);
}
}
function rename_file(win_id, ff_id){
var data = $("#{0}".lym_format(ff_id)).serializeObject();
var node = $('#project_tree').tree('getSelected');
if(node){
var suite = $('#project_tree').tree('getParent', node.target);
var project = $('#project_tree').tree('getParent', suite.target);
data["name"] = node.attributes['name'];
data["category"] = node.attributes['splitext'];
data["suite_name"] = suite.attributes['name'];
data["project_name"] = project.attributes['name'];
data["method"] = "edit";
do_ajax('post', '/api/v1/case/', data, refresh_case_node);
close_win(win_id);
}
}
function delete_file(){
var node = $('#project_tree').tree('getSelected');
if(node){
$.messager.confirm('删除提示',
'<br>确定删除文件: {0}{1}?'.lym_format(node.attributes['name'], node.attributes['splitext']),
function(r){
if (r){
var suite = $('#project_tree').tree('getParent', node.target);
var project = $('#project_tree').tree('getParent', suite.target);
var data = {
"name": node.attributes['name'],
"suite_name": suite.attributes['name'],
"project_name": project.attributes['name'],
"category": node.attributes['splitext'],
"method": "delete"
};
do_ajax('post', "/api/v1/case/", data, refresh_case_node);
}
});
}
}
function do_upload(win_id, ff_id){
var node = $('#project_tree').tree('getSelected');
if(node){
var project = $('#project_tree').tree('getParent', node.target);
$("#{0} input#path".lym_format(ff_id)).val("/{0}/{1}/".lym_format(project.attributes['name'], node.attributes['name']));
$("#{0}".lym_format(ff_id)).form('submit', {
success: function (result) {
//var node = $('#project_tree').tree('getSelected');
var d = JSON.parse(result);
//show_msg('提示信息', d.msg);
refresh_suite_node(d);
close_win(win_id);
}
});
}
}
function do_download(ff_id){
var node = $('#project_tree').tree('getSelected');
if(node && node.attributes['category'] == 'case'){
var suite = $('#project_tree').tree('getParent', node.target);
var project = $('#project_tree').tree('getParent', suite.target);
var path = "/{0}/{1}/{2}{3}".lym_format(project.attributes['name'], suite.attributes['name'], node.attributes['name'], node.attributes['splitext']);
$("#{0} input#path".lym_format(ff_id)).val("{0}".lym_format(path));
$("#{0}".lym_format(ff_id)).form('submit', {
success: function (result) {
}
});
}
}
function show_img(value, row, index){
return '<img width="24px" height="24px" border="0" src="{0}"/>'.lym_format(value) ;
}
function do_open_editor(){
var node = $('#project_tree').tree('getSelected');
if(node && node.attributes['category'] == 'case'){
var suite = $('#project_tree').tree('getParent', node.target);
var project = $('#project_tree').tree('getParent', suite.target);
addTab(node.attributes['name'], '/editor/{0}/{1}/{2}{3}'.lym_format(
project.attributes['name'],
suite.attributes['name'],
node.attributes['name'],
node.attributes['splitext']
), "icon-editor");
}
}
function refresh_user_list(data){
$('#user_list').datagrid("reload");
show_msg('提示信息', data.msg);
}
function manage_user(win_id, ff_id, method){
if(method == "create"){
clear_form(ff_id);
}
else if(method == "edit"){
}
open_win(win_id);
}
function create_user(win_id, ff_id){
var data = $("#{0}".lym_format(ff_id)).serializeObject();
data["method"] = "create";
do_ajax('post', '/api/v1/user/', data, refresh_user_list);
close_win(win_id);
}
function edit_user(win_id, ff_id){
var data = $("#{0}".lym_format(ff_id)).serializeObject();
data["method"] = "edit";
do_ajax('post', '/api/v1/user/', data, refresh_user_list);
close_win(win_id);
}
function close_win(id){
$('#{0}'.lym_format(id)).window('close');
}
function open_win(id){
$('#{0}'.lym_format(id)).window('open');
}
function clear_form(id){
$('#{0}'.lym_format(id)).form('clear');
}
function load_smtp(data){
$("#edit_smtp_ff").form("load", data);
$("#edit_smtp_ff input#ssl").prop("checked", data["ssl"]);
}
function init_smtp_ff(){
var data = {"method": "smtp"};
do_ajax('get', '/api/v1/settings/', data, load_smtp);
}
function load_email(data){
$("#notify_ff").form("load", data);
}
function init_email_ff(name){
var data = {"method": "email", "project": name};
do_ajax('get', '/api/v1/settings/', data, load_email);
}
function do_smtp(win_id, ff_id){
var data = $("#{0}".lym_format(ff_id)).serializeObject();
data["method"] = "smtp";
do_ajax('post', '/api/v1/settings/', data, do_nop);
close_win(win_id);
}
================================================
FILE: auto/www/static/js/autocomplete.js
================================================
var auto_complete=['Call Method [object] [method_name] [*args] [**kwargs]','Catenate [*items]','Comment [*messages]','Continue For Loop','Continue For Loop If [condition]','Convert To Binary [item] [base=None] [prefix=None] [length=None]','Convert To Boolean [item]','Convert To Bytes [input] [input_type=text]','Convert To Hex [item] [base=None] [prefix=None] [length=None] [lowercase=False]','Convert To Integer [item] [base=None]','Convert To Number [item] [precision=None]','Convert To Octal [item] [base=None] [prefix=None] [length=None]','Convert To String [item]','Create Dictionary [*items]','Create List [*items]','Evaluate [expression] [modules=None] [namespace=None]','Exit For Loop','Exit For Loop If [condition]','Fail [msg=None] [*tags]','Fatal Error [msg=None]','Get Count [item1] [item2]','Get Length [item]','Get Library Instance [name=None] [all=False]','Get Time [format=timestamp] [time_=NOW]','Get Variable Value [name] [default=None]','Get Variables [no_decoration=False]','Import Library [name] [*args]','Import Resource [path]','Import Variables [path] [*args]','Keyword Should Exist [name] [msg=None]','Length Should Be [item] [length] [msg=None]','Log [message] [level=INFO] [html=False] [console=False] [repr=False]','Log Many [*messages]','Log To Console [message] [stream=STDOUT] [no_newline=False]','Log Variables [level=INFO]','No Operation','Pass Execution [message] [*tags]','Pass Execution If [condition] [message] [*tags]','Regexp Escape [*patterns]','Reload Library [name_or_instance]','Remove Tags [*tags]','Repeat Keyword [repeat] [name] [*args]','Replace Variables [text]','Return From Keyword [*return_values]','Return From Keyword If [condition] [*return_values]','Run Keyword [name] [*args]','Run Keyword And Continue On Failure [name] [*args]','Run Keyword And Expect Error [expected_error] [name] [*args]','Run Keyword And Ignore Error [name] [*args]','Run Keyword And Return [name] [*args]','Run Keyword And Return If [condition] [name] [*args]','Run Keyword And Return Status [name] [*args]','Run Keyword If [condition] [name] [*args]','Run Keyword If All Critical Tests Passed [name] [*args]','Run Keyword If All Tests Passed [name] [*args]','Run Keyword If Any Critical Tests Failed [name] [*args]','Run Keyword If Any Tests Failed [name] [*args]','Run Keyword If Test Failed [name] [*args]','Run Keyword If Test Passed [name] [*args]','Run Keyword If Timeout Occurred [name] [*args]','Run Keyword Unless [condition] [name] [*args]','Run Keywords [*keywords]','Set Global Variable [name] [*values]','Set Library Search Order [*search_order]','Set Log Level [level]','Set Suite Documentation [doc] [append=False] [top=False]','Set Suite Metadata [name] [value] [append=False] [top=False]','Set Suite Variable [name] [*values]','Set Tags [*tags]','Set Test Documentation [doc] [append=False]','Set Test Message [message] [append=False]','Set Test Variable [name] [*values]','Set Variable [*values]','Set Variable If [condition] [*values]','Should Be Empty [item] [msg=None]','Should Be Equal [first] [second] [msg=None] [values=True] [ignore_case=False]','Should Be Equal As Integers [first] [second] [msg=None] [values=True] [base=None]','Should Be Equal As Numbers [first] [second] [msg=None] [values=True] [precision=6]','Should Be Equal As Strings [first] [second] [msg=None] [values=True] [ignore_case=False]','Should Be True [condition] [msg=None]','Should Contain [container] [item] [msg=None] [values=True] [ignore_case=False]','Should Contain Any [container] [*items] [**configuration]','Should Contain X Times [item1] [item2] [count] [msg=None] [ignore_case=False]','Should End With [str1] [str2] [msg=None] [values=True] [ignore_case=False]','Should Match [string] [pattern] [msg=None] [values=True] [ignore_case=False]','Should Match Regexp [string] [pattern] [msg=None] [values=True]','Should Not Be Empty [item] [msg=None]','Should Not Be Equal [first] [second] [msg=None] [values=True] [ignore_case=False]','Should Not Be Equal As Integers [first] [second] [msg=None] [values=True] [base=None]','Should Not Be Equal As Numbers [first] [second] [msg=None] [values=True] [precision=6]','Should Not Be Equal As Strings [first] [second] [msg=None] [values=True] [ignore_case=False]','Should Not Be True [condition] [msg=None]','Should Not Contain [container] [item] [msg=None] [values=True] [ignore_case=False]','Should Not Contain Any [container] [*items] [**configuration]','Should Not End With [str1] [str2] [msg=None] [values=True] [ignore_case=False]','Should Not Match [string] [pattern] [msg=None] [values=True] [ignore_case=False]','Should Not Match Regexp [string] [pattern] [msg=None] [values=True]','Should Not Start With [str1] [str2] [msg=None] [values=True] [ignore_case=False]','Should Start With [str1] [str2] [msg=None] [values=True] [ignore_case=False]','Sleep [time_] [reason=None]','Variable Should Exist [name] [msg=None]','Variable Should Not Exist [name] [msg=None]','Wait Until Keyword Succeeds [retry] [retry_interval] [name] [*args]','Append To List [list_] [*values]','Combine Lists [*lists]','Convert To Dictionary [item]','Convert To List [item]','Copy Dictionary [dictionary]','Copy List [list_]','Count Values In List [list_] [value] [start=0] [end=None]','Dictionaries Should Be Equal [dict1] [dict2] [msg=None] [values=True]','Dictionary Should Contain Item [dictionary] [key] [value] [msg=None]','Dictionary Should Contain Key [dictionary] [key] [msg=None]','Dictionary Should Contain Sub Dictionary [dict1] [dict2] [msg=None] [values=True]','Dictionary Should Contain Value [dictionary] [value] [msg=None]','Dictionary Should Not Contain Key [dictionary] [key] [msg=None]','Dictionary Should Not Contain Value [dictionary] [value] [msg=None]','Get Dictionary Items [dictionary]','Get Dictionary Keys [dictionary]','Get Dictionary Values [dictionary]','Get From Dictionary [dictionary] [key]','Get From List [list_] [index]','Get Index From List [list_] [value] [start=0] [end=None]','Get Match Count [list] [pattern] [case_insensitive=False] [whitespace_insensitive=False]','Get Matches [list] [pattern] [case_insensitive=False] [whitespace_insensitive=False]','Get Slice From List [list_] [start=0] [end=None]','Insert Into List [list_] [index] [value]','Keep In Dictionary [dictionary] [*keys]','List Should Contain Sub List [list1] [list2] [msg=None] [values=True]','List Should Contain Value [list_] [value] [msg=None]','List Should Not Contain Duplicates [list_] [msg=None]','List Should Not Contain Value [list_] [value] [msg=None]','Lists Should Be Equal [list1] [list2] [msg=None] [values=True] [names=None]','Log Dictionary [dictionary] [level=INFO]','Log List [list_] [level=INFO]','Pop From Dictionary [dictionary] [key] [default=]','Remove Duplicates [list_]','Remove From Dictionary [dictionary] [*keys]','Remove From List [list_] [index]','Remove Values From List [list_] [*values]','Reverse List [list_]','Set List Value [list_] [index] [value]','Set To Dictionary [dictionary] [*key_value_pairs] [**items]','Should Contain Match [list] [pattern] [msg=None] [case_insensitive=False] [whitespace_insensitive=False]','Should Not Contain Match [list] [pattern] [msg=None] [case_insensitive=False] [whitespace_insensitive=False]','Sort List [list_]','Add Time To Date [date] [time] [result_format=timestamp] [exclude_millis=False] [date_format=None]','Add Time To Time [time1] [time2] [result_format=number] [exclude_millis=False]','Convert Date [date] [result_format=timestamp] [exclude_millis=False] [date_format=None]','Convert Time [time] [result_format=number] [exclude_millis=False]','Get Current Date [time_zone=local] [increment=0] [result_format=timestamp] [exclude_millis=False]','Subtract Date From Date [date1] [date2] [result_format=number] [exclude_millis=False] [date1_format=None] [date2_format=None]','Subtract Time From Date [date] [time] [result_format=timestamp] [exclude_millis=False] [date_format=None]','Subtract Time From Time [time1] [time2] [result_format=number] [exclude_millis=False]','Append To Environment Variable [name] [*values] [**config]','Append To File [path] [content] [encoding=UTF-8]','Copy Directory [source] [destination]','Copy File [source] [destination]','Copy Files [*sources_and_destination]','Count Directories In Directory [path] [pattern=None]','Count Files In Directory [path] [pattern=None]','Count Items In Directory [path] [pattern=None]','Create Binary File [path] [content]','Create Directory [path]','Create File [path] [content=] [encoding=UTF-8]','Directory Should Be Empty [path] [msg=None]','Directory Should Exist [path] [msg=None]','Directory Should Not Be Empty [path] [msg=None]','Directory Should Not Exist [path] [msg=None]','Empty Directory [path]','Environment Variable Should Be Set [name] [msg=None]','Environment Variable Should Not Be Set [name] [msg=None]','File Should Be Empty [path] [msg=None]','File Should Exist [path] [msg=None]','File Should Not Be Empty [path] [msg=None]','File Should Not Exist [path] [msg=None]','Get Binary File [path]','Get Environment Variable [name] [default=None]','Get Environment Variables','Get File [path] [encoding=UTF-8] [encoding_errors=strict]','Get File Size [path]','Get Modified Time [path] [format=timestamp]','Grep File [path] [pattern] [encoding=UTF-8] [encoding_errors=strict]','Join Path [base] [*parts]','Join Paths [base] [*paths]','List Directories In Directory [path] [pattern=None] [absolute=False]','List Directory [path] [pattern=None] [absolute=False]','List Files In Directory [path] [pattern=None] [absolute=False]','Log Environment Variables [level=INFO]','Log File [path] [encoding=UTF-8] [encoding_errors=strict]','Move Directory [source] [destination]','Move File [source] [destination]','Move Files [*sources_and_destination]','Normalize Path [path]','Remove Directory [path] [recursive=False]','Remove Environment Variable [*names]','Remove File [path]','Remove Files [*paths]','Run [command]','Run And Return Rc [command]','Run And Return Rc And Output [command]','Set Environment Variable [name] [value]','Set Modified Time [path] [mtime]','Should Exist [path] [msg=None]','Should Not Exist [path] [msg=None]','Split Extension [path]','Split Path [path]','Touch [path]','Wait Until Created [path] [timeout=1 minute]','Wait Until Removed [path] [timeout=1 minute]','Get Process Id [handle=None]','Get Process Object [handle=None]','Get Process Result [handle=None] [rc=False] [stdout=False] [stderr=False] [stdout_path=False] [stderr_path=False]','Is Process Running [handle=None]','Join Command Line [*args]','Process Should Be Running [handle=None] [error_message=Process is not running.]','Process Should Be Stopped [handle=None] [error_message=Process is running.]','Run Process [command] [*arguments] [**configuration]','Send Signal To Process [signal] [handle=None] [group=False]','Split Command Line [args] [escaping=False]','Start Process [command] [*arguments] [**configuration]','Switch Process [handle]','Terminate All Processes [kill=False]','Terminate Process [handle=None] [kill=False]','Wait For Process [handle=None] [timeout=None] [on_timeout=continue]','Convert To Lowercase [string]','Convert To Uppercase [string]','Decode Bytes To String [bytes] [encoding] [errors=strict]','Encode String To Bytes [string] [encoding] [errors=strict]','Fetch From Left [string] [marker]','Fetch From Right [string] [marker]','Generate Random String [length=8] [chars=[LETTERS][NUMBERS]]','Get Line [string] [line_number]','Get Line Count [string]','Get Lines Containing String [string] [pattern] [case_insensitive=False]','Get Lines Matching Pattern [string] [pattern] [case_insensitive=False]','Get Lines Matching Regexp [string] [pattern] [partial_match=False]','Get Regexp Matches [string] [pattern] [*groups]','Get Substring [string] [start] [end=None]','Remove String [string] [*removables]','Remove String Using Regexp [string] [*patterns]','Replace String [string] [search_for] [replace_with] [count=-1]','Replace String Using Regexp [string] [pattern] [replace_with] [count=-1]','Should Be Byte String [item] [msg=None]','Should Be Lowercase [string] [msg=None]','Should Be String [item] [msg=None]','Should Be Titlecase [string] [msg=None]','Should Be Unicode String [item] [msg=None]','Should Be Uppercase [string] [msg=None]','Should Not Be String [item] [msg=None]','Split String [string] [separator=None] [max_split=-1]','Split String From Right [string] [separator=None] [max_split=-1]','Split String To Characters [string]','Split To Lines [string] [start=0] [end=None]','Strip String [string] [mode=both] [characters=None]','Set Screenshot Directory [path]','Take Screenshot [name=screenshot] [width=800px]','Take Screenshot Without Embedding [name=screenshot]','Close All Connections','Close Connection [loglevel=None]','Execute Command [command] [loglevel=None] [strip_prompt=False]','Login [username] [password] [login_prompt=login: ] [password_prompt=Password: ] [login_timeout=1 second] [login_incorrect=Login incorrect]','Open Connection [host] [alias=None] [port=23] [timeout=None] [newline=None] [prompt=None] [prompt_is_regexp=False] [encoding=None] [encoding_errors=None] [default_log_level=None] [window_size=None] [environ_user=None] [terminal_emulation=None] [terminal_type=None] [telnetlib_log_level=None] [connection_timeout=None]','Read [loglevel=None]','Read Until [expected] [loglevel=None]','Read Until Prompt [loglevel=None] [strip_prompt=False]','Read Until Regexp [*expected]','Set Default Log Level [level]','Set Encoding [encoding=None] [errors=None]','Set Newline [newline]','Set Prompt [prompt] [prompt_is_regexp=False]','Set Telnetlib Log Level [level]','Set Timeout [timeout]','Switch Connection [index_or_alias]','Write [text] [loglevel=None]','Write Bare [text]','Write Control Character [character]','Write Until Expected Output [text] [expected] [timeout] [retry_interval] [loglevel=None]','Background App [seconds=5]','Capture Page Screenshot [filename=None]','Clear Text [locator]','Click A Point [x=0] [y=0] [duration=100]','Click Button [index_or_name]','Click Element [locator]','Click Element At Coordinates [coordinate_X] [coordinate_Y]','Click Text [text] [exact_match=False]','Close All Applications','Close Application','Element Attribute Should Match [locator] [attr_name] [match_pattern] [regexp=False]','Element Name Should Be [locator] [expected]','Element Should Be Disabled [locator] [loglevel=INFO]','Element Should Be Enabled [locator] [loglevel=INFO]','Element Should Be Visible [locator] [loglevel=INFO]','Element Should Contain Text [locator] [expected] [message=]','Element Should Not Contain Text [locator] [expected] [message=]','Element Text Should Be [locator] [expected] [message=]','Element Value Should Be [locator] [expected]','Get Activity','Get Appium SessionId','Get Appium Timeout','Get Capability [capability_name]','Get Contexts','Get Current Context','Get Element Attribute [locator] [attribute]','Get Element Location [locator]','Get Element Size [locator]','Get Matching Xpath Count [xpath]','Get Network Connection Status','Get Source','Get Text [locator]','Get Webelement [locator]','Get Webelements [locator]','Get Window Height','Get Window Width','Go Back','Go To Url [url]','Hide Keyboard [key_name=None]','Input Password [locator] [text]','Input Text [locator] [text]','Input Value [locator] [text]','Install App [app_path] [app_package]','Landscape','Launch Application','Lock [seconds=5]','Log Source [loglevel=INFO]','Long Press [locator]','Long Press Keycode [keycode] [metastate=None]','Open Application [remote_url] [alias=None] [**kwargs]','Page Should Contain Element [locator] [loglevel=INFO]','Page Should Contain Text [text] [loglevel=INFO]','Page Should Not Contain Element [locator] [loglevel=INFO]','Page Should Not Contain Text [text] [loglevel=INFO]','Pinch [locator] [percent=200%] [steps=1]','Portrait','Press Keycode [keycode] [metastate=None]','Pull File [path] [decode=False]','Pull Folder [path] [decode=False]','Push File [path] [data] [encode=False]','Quit Application','Register Keyword To Run On Failure [keyword]','Remove Application [application_id]','Reset Application','Scroll [start_locator] [end_locator]','Scroll Down [locator]','Scroll Up [locator]','Set Appium Timeout [seconds]','Set Network Connection Status [connectionStatus]','Shake','Start Activity [appPackage] [appActivity] [**opts]','Swipe [start_x] [start_y] [offset_x] [offset_y] [duration=1000]','Swipe By Percent [start_x] [start_y] [end_x] [end_y] [duration=1000]','Switch Application [index_or_alias]','Switch To Context [context_name]','Tap [locator] [x_offset=None] [y_offset=None] [count=1]','Text Should Be Visible [text] [exact_match=False] [loglevel=INFO]','Wait Activity [activity] [timeout] [interval=1]','Wait Until Element Is Visible [locator] [timeout=None] [error=None]','Wait Until Page Contains [text] [timeout=None] [error=None]','Wait Until Page Contains Element [locator] [timeout=None] [error=None]','Wait Until Page Does Not Contain [text] [timeout=None] [error=None]','Wait Until Page Does Not Contain Element [locator] [timeout=None] [error=None]','Xpath Should Match X Times [xpath] [count] [error=None] [loglevel=INFO]','Zoom [locator] [percent=200%] [steps=1]','Create Digest Session [alias] [url] [auth] [headers={}] [cookies=None] [timeout=None] [proxies=None] [verify=False] [debug=0] [max_retries=3] [backoff_factor=0.1] [disable_warnings=0]','Create Ntlm Session [alias] [url] [auth] [headers={}] [cookies=None] [timeout=None] [proxies=None] [verify=False] [debug=0] [max_retries=3] [backoff_factor=0.1] [disable_warnings=0]','Create Session [alias] [url] [headers={}] [cookies=None] [auth=None] [timeout=None] [proxies=None] [verify=False] [debug=0] [max_retries=3] [backoff_factor=0.1] [disable_warnings=0]','Delete [alias] [uri] [data=()] [headers=None] [allow_redirects=None] [timeout=None]','Delete All Sessions','Delete Request [alias] [uri] [data=()] [params=None] [headers=None] [allow_redirects=None] [timeout=None]','Get [alias] [uri] [params=None] [headers=None] [allow_redirects=None] [timeout=None]','Get Request [alias] [uri] [headers=None] [json=None] [params=None] [allow_redirects=None] [timeout=None]','Head [alias] [uri] [headers=None] [allow_redirects=None] [timeout=None]','Head Request [alias] [uri] [headers=None] [allow_redirects=None] [timeout=None]','Options [alias] [uri] [headers=None] [allow_redirects=None] [timeout=None]','Options Request [alias] [uri] [headers=None] [allow_redirects=None] [timeout=None]','Patch [alias] [uri] [data={}] [headers=None] [files={}] [allow_redirects=None] [timeout=None]','Patch Request [alias] [uri] [data=None] [params=None] [headers=None] [files=None] [allow_redirects=None] [timeout=None]','Post [alias] [uri] [data={}] [headers=None] [files=None] [allow_redirects=None] [timeout=None]','Post Request [alias] [uri] [data=None] [params=None] [headers=None] [files=None] [allow_redirects=None] [timeout=None]','Put [alias] [uri] [data=None] [headers=None] [allow_redirects=None] [timeout=None]','Put Request [alias] [uri] [data=None] [params=None] [files=None] [headers=None] [allow_redirects=None] [timeout=None]','To Json [content] [pretty_print=False]','Add Cookie [name] [value] [path=None] [domain=None] [secure=None] [expiry=None]','Add Location Strategy [strategy_name] [strategy_keyword] [persist=False]','Alert Should Be Present [text=] [action=ACCEPT] [timeout=None]','Alert Should Not Be Present [action=ACCEPT] [timeout=0]','Assign Id To Element [locator] [id]','Capture Page Screenshot [filename=selenium-screenshot-{index}.png]','Checkbox Should Be Selected [locator]','Checkbox Should Not Be Selected [locator]','Choose Cancel On Next Confirmation','Choose File [locator] [file_path]','Choose Ok On Next Confirmation','Clear Element Text [locator]','Click Button [locator]','Click Element [locator]','Click Element At Coordinates [locator] [xoffset] [yoffset]','Click Image [locator]','Click Link [locator]','Close All Browsers','Close Browser','Close Window','Confirm Action','Create Webdriver [driver_name] [alias=None] [kwargs={}] [**init_kwargs]','Current Frame Contains [text] [loglevel=INFO]','Current Frame Should Contain [text] [loglevel=INFO]','Current Frame Should Not Contain [text] [loglevel=INFO]','Delete All Cookies','Delete Cookie [name]','Dismiss Alert [accept=True]','Double Click Element [locator]','Drag And Drop [locator] [target]','Drag And Drop By Offset [locator] [xoffset] [yoffset]','Element Should Be Disabled [locator]','Element Should Be Enabled [locator]','Element Should Be Focused [locator]','Element Should Be Visible [locator] [message=None]','Element Should Contain [locator] [expected] [message=None] [ignore_case=False]','Element Should Not Be Visible [locator] [message=None]','Element Should Not Contain [locator] [expected] [message=None] [ignore_case=False]','Element Text Should Be [locator] [expected] [message=None] [ignore_case=False]','Element Text Should Not Be [locator] [not_expected] [message=None] [ignore_case=False]','Execute Async Javascript [*code]','Execute Javascript [*code]','Focus [locator]','Frame Should Contain [locator] [text] [loglevel=INFO]','Get Alert Message [dismiss=True]','Get All Links','Get Cookie [name]','Get Cookie Value [name]','Get Cookies','Get Element Attribute [locator] [attribute=None]','Get Element Count [locator]','Get Element Size [locator]','Get Horizontal Position [locator]','Get List Items [locator] [values=False]','Get Location','Get Locations','Get Matching Xpath Count [xpath] [return_str=True]','Get Selected List Label [locator]','Get Selected List Labels [locator]','Get Selected List Value [locator]','Get Selected List Values [locator]','Get Selenium Implicit Wait','Get Selenium Speed','Get Selenium Timeout','Get Source','Get Table Cell [locator] [row] [column] [loglevel=INFO]','Get Text [locator]','Get Title','Get Value [locator]','Get Vertical Position [locator]','Get WebElement [locator]','Get WebElements [locator]','Get Window Handles','Get Window Identifiers','Get Window Names','Get Window Position','Get Window Size','Get Window Titles','Go Back','Go To [url]','Handle Alert [action=ACCEPT] [timeout=None]','Input Password [locator] [password]','Input Text [locator] [text]','Input Text Into Alert [text] [action=ACCEPT] [timeout=None]','Input Text Into Prompt [text]','List Selection Should Be [locator] [*expected]','List Should Have No Selections [locator]','List Windows','Location Should Be [url]','Location Should Contain [expected]','Locator Should Match X Times [locator] [x] [message=None] [loglevel=INFO]','Log Location','Log Source [loglevel=INFO]','Log Title','Maximize Browser Window','Mouse Down [locator]','Mouse Down On Image [locator]','Mouse Down On Link [locator]','Mouse Out [locator]','Mouse Over [locator]','Mouse Up [locator]','Open Browser [url] [browser=firefox] [alias=None] [remote_url=False] [desired_capabilities=None] [ff_profile_dir=None]','Open Context Menu [locator]','Page Should Contain [text] [loglevel=INFO]','Page Should Contain Button [locator] [message=None] [loglevel=INFO]','Page Should Contain Checkbox [locator] [message=None] [loglevel=INFO]','Page Should Contain Element [locator] [message=None] [loglevel=INFO] [limit=None]','Page Should Contain Image [locator] [message=None] [loglevel=INFO]','Page Should Contain Link [locator] [message=None] [loglevel=INFO]','Page Should Contain List [locator] [message=None] [loglevel=INFO]','Page Should Contain Radio Button [locator] [message=None] [loglevel=INFO]','Page Should Contain Textfield [locator] [message=None] [loglevel=INFO]','Page Should Not Contain [text] [loglevel=INFO]','Page Should Not Contain Button [locator] [message=None] [loglevel=INFO]','Page Should Not Contain Checkbox [locator] [message=None] [loglevel=INFO]','Page Should Not Contain Element [locator] [message=None] [loglevel=INFO]','Page Should Not Contain Image [locator] [message=None] [loglevel=INFO]','Page Should Not Contain Link [locator] [message=None] [loglevel=INFO]','Page Should Not Contain List [locator] [message=None] [loglevel=INFO]','Page Should Not Contain Radio Button [locator] [message=None] [loglevel=INFO]','Page Should Not Contain Textfield [locator] [message=None] [loglevel=INFO]','Press Key [locator] [key]','Radio Button Should Be Set To [group_name] [value]','Radio Button Should Not Be Selected [group_name]','Register Keyword To Run On Failure [keyword]','Reload Page','Remove Location Strategy [strategy_name]','Select All From List [locator]','Select Checkbox [locator]','Select Frame [locator]','Select From List [locator] [*options]','Select From List By Index [locator] [*indexes]','Select From List By Label [locator] [*labels]','Select From List By Value [locator] [*values]','Select Radio Button [group_name] [value]','Select Window [locator=MAIN]','Set Browser Implicit Wait [value]','Set Focus To Element [locator]','Set Screenshot Directory [path] [persist=DEPRECATED]','Set Selenium Implicit Wait [value]','Set Selenium Speed [value]','Set Selenium Timeout [value]','Set Window Position [x] [y]','Set Window Size [width] [height]','Simulate [locator] [event]','Simulate Event [locator] [event]','Submit Form [locator=None]','Switch Browser [index_or_alias]','Table Cell Should Contain [locator] [row] [column] [expected] [loglevel=INFO]','Table Column Should Contain [locator] [column] [expected] [loglevel=INFO]','Table Footer Should Contain [locator] [expected] [loglevel=INFO]','Table Header Should Contain [locator] [expected] [loglevel=INFO]','Table Row Should Contain [locator] [row] [expected] [loglevel=INFO]','Table Should Contain [locator] [expected] [loglevel=INFO]','Textarea Should Contain [locator] [expected] [message=None]','Textarea Value Should Be [locator] [expected] [message=None]','Textfield Should Contain [locator] [expected] [message=None]','Textfield Value Should Be [locator] [expected] [message=None]','Title Should Be [title] [message=None]','Unselect All From List [locator]','Unselect Checkbox [locator]','Unselect Frame','Unselect From List [locator] [*items]','Unselect From List By Index [locator] [*indexes]','Unselect From List By Label [locator] [*labels]','Unselect From List By Value [locator] [*values]','Wait For Condition [condition] [timeout=None] [error=None]','Wait Until Element Contains [locator] [text] [timeout=None] [error=None]','Wait Until Element Does Not Contain [locator] [text] [timeout=None] [error=None]','Wait Until Element Is Enabled [locator] [timeout=None] [error=None]','Wait Until Element Is Not Visible [locator] [timeout=None] [error=None]','Wait Until Element Is Visible [locator] [timeout=None] [error=None]','Wait Until Page Contains [text] [timeout=None] [error=None]','Wait Until Page Contains Element [locator] [timeout=None] [error=None]','Wait Until Page Does Not Contain [text] [timeout=None] [error=None]','Wait Until Page Does Not Contain Element [locator] [timeout=None] [error=None]','Xpath Should Match X Times [xpath] [x] [message=None] [loglevel=INFO]','Close All Connections','Close Connection','Create Local Ssh Tunnel [local_port] [remote_host] [remote_port]','Directory Should Exist [path]','Directory Should Not Exist [path]','Enable Ssh Logging [logfile]','Execute Command [command] [return_stdout=True] [return_stderr=False] [return_rc=False] [sudo=False] [sudo_password=None]','File Should Exist [path]','File Should Not Exist [path]','Get Connection [index_or_alias=None] [index=False] [host=False] [alias=False] [port=False] [timeout=False] [newline=False] [prompt=False] [term_type=False] [width=False] [height=False] [encoding=False]','Get Connections','Get Directory [source] [destination=.] [recursive=False]','Get File [source] [destination=.]','Get Pre Login Banner [host=None] [port=22]','List Directories In Directory [path] [pattern=None] [absolute=False]','List Directory [path] [pattern=None] [absolute=False]','List Files In Directory [path] [pattern=None] [absolute=False]','Login [username] [password] [delay=0.5 seconds]','Login With Public Key [username] [keyfile] [password=] [allow_agent=False] [look_for_keys=False] [delay=0.5 seconds]','Open Connection [host] [alias=None] [port=22] [timeout=None] [newline=None] [prompt=None] [term_type=None] [width=None] [height=None] [path_separator=None] [encoding=None]','Put Directory [source] [destination=.] [mode=0744] [newline=] [recursive=False]','Put File [source] [destination=.] [mode=0744] [newline=]','Read [loglevel=None] [delay=None]','Read Command Output [return_stdout=True] [return_stderr=False] [return_rc=False]','Read Until [expected] [loglevel=None]','Read Until Prompt [loglevel=None]','Read Until Regexp [regexp] [loglevel=None]','Set Client Configuration [timeout=None] [newline=None] [prompt=None] [term_type=None] [width=None] [height=None] [path_separator=None] [encoding=None]','Set Default Configuration [timeout=None] [newline=None] [prompt=None] [loglevel=None] [term_type=None] [width=None] [height=None] [path_separator=None] [encoding=None]','Start Command [command] [sudo=False] [sudo_password=None]','Switch Connection [index_or_alias]','Write [text] [loglevel=None]','Write Bare [text]','Write Until Expected Output [text] [expected] [timeout] [retry_interval] [loglevel=None]','Call Stored Procedure [spName] [spParams=None] [sansTran=False]','Check If Exists In Database [selectStatement] [sansTran=False]','Check If Not Exists In Database [selectStatement] [sansTran=False]','Connect To Database [dbapiModuleName=None] [dbName=None] [dbUsername=None] [dbPassword=None] [dbHost=None] [dbPort=None] [dbCharset=None] [dbConfigFile=./resources/db.cfg]','Connect To Database Using Custom Params [dbapiModuleName=None] [db_connect_string=]','Delete All Rows From Table [tableName] [sansTran=False]','Description [selectStatement] [sansTran=False]','Disconnect From Database','Execute Sql Script [sqlScriptFileName] [sansTran=False]','Execute Sql String [sqlString] [sansTran=False]','Query [selectStatement] [sansTran=False]','Row Count [selectStatement] [sansTran=False]','Row Count Is 0 [selectStatement] [sansTran=False]','Row Count Is Equal To X [selectStatement] [numRows] [sansTran=False]','Row Count Is Greater Than X [selectStatement] [numRows] [sansTran=False]','Row Count Is Less Than X [selectStatement] [numRows] [sansTran=False]','Table Must Exist [tableName] [sansTran=False]'];
================================================
FILE: auto/www/static/js/highlight.js
================================================
var high_light=['Call Method','Catenate','Comment','Continue For Loop','Continue For Loop If','Convert To Binary','Convert To Boolean','Convert To Bytes','Convert To Hex','Convert To Integer','Convert To Number','Convert To Octal','Convert To String','Create Dictionary','Create List','Evaluate','Exit For Loop','Exit For Loop If','Fail','Fatal Error','Get Count','Get Length','Get Library Instance','Get Time','Get Variable Value','Get Variables','Import Library','Import Resource','Import Variables','Keyword Should Exist','Length Should Be','Log','Log Many','Log To Console','Log Variables','No Operation','Pass Execution','Pass Execution If','Regexp Escape','Reload Library','Remove Tags','Repeat Keyword','Replace Variables','Return From Keyword','Return From Keyword If','Run Keyword','Run Keyword And Continue On Failure','Run Keyword And Expect Error','Run Keyword And Ignore Error','Run Keyword And Return','Run Keyword And Return If','Run Keyword And Return Status','Run Keyword If','Run Keyword If All Critical Tests Passed','Run Keyword If All Tests Passed','Run Keyword If Any Critical Tests Failed','Run Keyword If Any Tests Failed','Run Keyword If Test Failed','Run Keyword If Test Passed','Run Keyword If Timeout Occurred','Run Keyword Unless','Run Keywords','Set Global Variable','Set Library Search Order','Set Log Level','Set Suite Documentation','Set Suite Metadata','Set Suite Variable','Set Tags','Set Test Documentation','Set Test Message','Set Test Variable','Set Variable','Set Variable If','Should Be Empty','Should Be Equal','Should Be Equal As Integers','Should Be Equal As Numbers','Should Be Equal As Strings','Should Be True','Should Contain','Should Contain Any','Should Contain X Times','Should End With','Should Match','Should Match Regexp','Should Not Be Empty','Should Not Be Equal','Should Not Be Equal As Integers','Should Not Be Equal As Numbers','Should Not Be Equal As Strings','Should Not Be True','Should Not Contain','Should Not Contain Any','Should Not End With','Should Not Match','Should Not Match Regexp','Should Not Start With','Should Start With','Sleep','Variable Should Exist','Variable Should Not Exist','Wait Until Keyword Succeeds','Append To List','Combine Lists','Convert To Dictionary','Convert To List','Copy Dictionary','Copy List','Count Values In List','Dictionaries Should Be Equal','Dictionary Should Contain Item','Dictionary Should Contain Key','Dictionary Should Contain Sub Dictionary','Dictionary Should Contain Value','Dictionary Should Not Contain Key','Dictionary Should Not Contain Value','Get Dictionary Items','Get Dictionary Keys','Get Dictionary Values','Get From Dictionary','Get From List','Get Index From List','Get Match Count','Get Matches','Get Slice From List','Insert Into List','Keep In Dictionary','List Should Contain Sub List','List Should Contain Value','List Should Not Contain Duplicates','List Should Not Contain Value','Lists Should Be Equal','Log Dictionary','Log List','Pop From Dictionary','Remove Duplicates','Remove From Dictionary','Remove From List','Remove Values From List','Reverse List','Set List Value','Set To Dictionary','Should Contain Match','Should Not Contain Match','Sort List','Add Time To Date','Add Time To Time','Convert Date','Convert Time','Get Current Date','Subtract Date From Date','Subtract Time From Date','Subtract Time From Time','Append To Environment Variable','Append To File','Copy Directory','Copy File','Copy Files','Count Directories In Directory','Count Files In Directory','Count Items In Directory','Create Binary File','Create Directory','Create File','Directory Should Be Empty','Directory Should Exist','Directory Should Not Be Empty','Directory Should Not Exist','Empty Directory','Environment Variable Should Be Set','Environment Variable Should Not Be Set','File Should Be Empty','File Should Exist','File Should Not Be Empty','File Should Not Exist','Get Binary File','Get Environment Variable','Get Environment Variables','Get File','Get File Size','Get Modified Time','Grep File','Join Path','Join Paths','List Directories In Directory','List Directory','List Files In Directory','Log Environment Variables','Log File','Move Directory','Move File','Move Files','Normalize Path','Remove Directory','Remove Environment Variable','Remove File','Remove Files','Run','Run And Return Rc','Run And Return Rc And Output','Set Environment Variable','Set Modified Time','Should Exist','Should Not Exist','Split Extension','Split Path','Touch','Wait Until Created','Wait Until Removed','Get Process Id','Get Process Object','Get Process Result','Is Process Running','Join Command Line','Process Should Be Running','Process Should Be Stopped','Run Process','Send Signal To Process','Split Command Line','Start Process','Switch Process','Terminate All Processes','Terminate Process','Wait For Process','Convert To Lowercase','Convert To Uppercase','Decode Bytes To String','Encode String To Bytes','Fetch From Left','Fetch From Right','Generate Random String','Get Line','Get Line Count','Get Lines Containing String','Get Lines Matching Pattern','Get Lines Matching Regexp','Get Regexp Matches','Get Substring','Remove String','Remove String Using Regexp','Replace String','Replace String Using Regexp','Should Be Byte String','Should Be Lowercase','Should Be String','Should Be Titlecase','Should Be Unicode String','Should Be Uppercase','Should Not Be String','Split String','Split String From Right','Split String To Characters','Split To Lines','Strip String','Set Screenshot Directory','Take Screenshot','Take Screenshot Without Embedding','Close All Connections','Close Connection','Execute Command','Login','Open Connection','Read','Read Until','Read Until Prompt','Read Until Regexp','Set Default Log Level','Set Encoding','Set Newline','Set Prompt','Set Telnetlib Log Level','Set Timeout','Switch Connection','Write','Write Bare','Write Control Character','Write Until Expected Output','Background App','Capture Page Screenshot','Clear Text','Click A Point','Click Button','Click Element','Click Element At Coordinates','Click Text','Close All Applications','Close Application','Element Attribute Should Match','Element Name Should Be','Element Should Be Disabled','Element Should Be Enabled','Element Should Be Visible','Element Should Contain Text','Element Should Not Contain Text','Element Text Should Be','Element Value Should Be','Get Activity','Get Appium SessionId','Get Appium Timeout','Get Capability','Get Contexts','Get Current Context','Get Element Attribute','Get Element Location','Get Element Size','Get Matching Xpath Count','Get Network Connection Status','Get Source','Get Text','Get Webelement','Get Webelements','Get Window Height','Get Window Width','Go Back','Go To Url','Hide Keyboard','Input Password','Input Text','Input Value','Install App','Landscape','Launch Application','Lock','Log Source','Long Press','Long Press Keycode','Open Application','Page Should Contain Element','Page Should Contain Text','Page Should Not Contain Element','Page Should Not Contain Text','Pinch','Portrait','Press Keycode','Pull File','Pull Folder','Push File','Quit Application','Register Keyword To Run On Failure','Remove Application','Reset Application','Scroll','Scroll Down','Scroll Up','Set Appium Timeout','Set Network Connection Status','Shake','Start Activity','Swipe','Swipe By Percent','Switch Application','Switch To Context','Tap','Text Should Be Visible','Wait Activity','Wait Until Element Is Visible','Wait Until Page Contains','Wait Until Page Contains Element','Wait Until Page Does Not Contain','Wait Until Page Does Not Contain Element','Xpath Should Match X Times','Zoom','Create Digest Session','Create Ntlm Session','Create Session','Delete','Delete All Sessions','Delete Request','Get','Get Request','Head','Head Request','Options','Options Request','Patch','Patch Request','Post','Post Request','Put','Put Request','To Json','Add Cookie','Add Location Strategy','Alert Should Be Present','Alert Should Not Be Present','Assign Id To Element','Capture Page Screenshot','Checkbox Should Be Selected','Checkbox Should Not Be Selected','Choose Cancel On Next Confirmation','Choose File','Choose Ok On Next Confirmation','Clear Element Text','Click Button','Click Element','Click Element At Coordinates','Click Image','Click Link','Close All Browsers','Close Browser','Close Window','Confirm Action','Create Webdriver','Current Frame Contains','Current Frame Should Contain','Current Frame Should Not Contain','Delete All Cookies','Delete Cookie','Dismiss Alert','Double Click Element','Drag And Drop','Drag And Drop By Offset','Element Should Be Disabled','Element Should Be Enabled','Element Should Be Focused','Element Should Be Visible','Element Should Contain','Element Should Not Be Visible','Element Should Not Contain','Element Text Should Be','Element Text Should Not Be','Execute Async Javascript','Execute Javascript','Focus','Frame Should Contain','Get Alert Message','Get All Links','Get Cookie','Get Cookie Value','Get Cookies','Get Element Attribute','Get Element Count','Get Element Size','Get Horizontal Position','Get List Items','Get Location','Get Locations','Get Matching Xpath Count','Get Selected List Label','Get Selected List Labels','Get Selected List Value','Get Selected List Values','Get Selenium Implicit Wait','Get Selenium Speed','Get Selenium Timeout','Get Source','Get Table Cell','Get Text','Get Title','Get Value','Get Vertical Position','Get WebElement','Get WebElements','Get Window Handles','Get Window Identifiers','Get Window Names','Get Window Position','Get Window Size','Get Window Titles','Go Back','Go To','Handle Alert','Input Password','Input Text','Input Text Into Alert','Input Text Into Prompt','List Selection Should Be','List Should Have No Selections','List Windows','Location Should Be','Location Should Contain','Locator Should Match X Times','Log Location','Log Source','Log Title','Maximize Browser Window','Mouse Down','Mouse Down On Image','Mouse Down On Link','Mouse Out','Mouse Over','Mouse Up','Open Browser','Open Context Menu','Page Should Contain','Page Should Contain Button','Page Should Contain Checkbox','Page Should Contain Element','Page Should Contain Image','Page Should Contain Link','Page Should Contain List','Page Should Contain Radio Button','Page Should Contain Textfield','Page Should Not Contain','Page Should Not Contain Button','Page Should Not Contain Checkbox','Page Should Not Contain Element','Page Should Not Contain Image','Page Should Not Contain Link','Page Should Not Contain List','Page Should Not Contain Radio Button','Page Should Not Contain Textfield','Press Key','Radio Button Should Be Set To','Radio Button Should Not Be Selected','Register Keyword To Run On Failure','Reload Page','Remove Location Strategy','Select All From List','Select Checkbox','Select Frame','Select From List','Select From List By Index','Select From List By Label','Select From List By Value','Select Radio Button','Select Window','Set Browser Implicit Wait','Set Focus To Element','Set Screenshot Directory','Set Selenium Implicit Wait','Set Selenium Speed','Set Selenium Timeout','Set Window Position','Set Window Size','Simulate','Simulate Event','Submit Form','Switch Browser','Table Cell Should Contain','Table Column Should Contain','Table Footer Should Contain','Table Header Should Contain','Table Row Should Contain','Table Should Contain','Textarea Should Contain','Textarea Value Should Be','Textfield Should Contain','Textfield Value Should Be','Title Should Be','Unselect All From List','Unselect Checkbox','Unselect Frame','Unselect From List','Unselect From List By Index','Unselect From List By Label','Unselect From List By Value','Wait For Condition','Wait Until Element Contains','Wait Until Element Does Not Contain','Wait Until Element Is Enabled','Wait Until Element Is Not Visible','Wait Until Element Is Visible','Wait Until Page Contains','Wait Until Page Contains Element','Wait Until Page Does Not Contain','Wait Until Page Does Not Contain Element','Xpath Should Match X Times','Close All Connections','Close Connection','Create Local Ssh Tunnel','Directory Should Exist','Directory Should Not Exist','Enable Ssh Logging','Execute Command','File Should Exist','File Should Not Exist','Get Connection','Get Connections','Get Directory','Get File','Get Pre Login Banner','List Directories In Directory','List Directory','List Files In Directory','Login','Login With Public Key','Open Connection','Put Directory','Put File','Read','Read Command Output','Read Until','Read Until Prompt','Read Until Regexp','Set Client Configuration','Set Default Configuration','Start Command','Switch Connection','Write','Write Bare','Write Until Expected Output','Call Stored Procedure','Check If Exists In Database','Check If Not Exists In Database','Connect To Database','Connect To Database Using Custom Params','Delete All Rows From Table','Description','Disconnect From Database','Execute Sql Script','Execute Sql String','Query','Row Count','Row Count Is 0','Row Count Is Equal To X','Row Count Is Greater Than X','Row Count Is Less Than X','Table Must Exist'];
================================================
FILE: auto/www/templates/dashboard.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AutoLink - 开源优测自动化测试集成解决方案</title>
<meta name="keywords" content="AutoLink, 开源优测, 苦叶子, web ide"/>
<meta name="description" content="A Web-based IDE for Auto Testing using Auto Open Source Testing Framework, do your development anytime, anywhere."/>
<meta name="author" content="苦叶子">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/easyui/themes/bootstrap/easyui.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/easyui/themes/icon.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/auto.css') }}">
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}" />
<style type="text/css">
.lines-no .datagrid-body td{
border-right:1px dotted transparent;
border-bottom:1px dotted transparent;
}
</style>
</head>
<body class="easyui-layout">
<div data-options="region:'north',border:true" style="padding:0;height:60px;">
<div class="easyui-panel" style="padding:0">
<a href="https://github.com/small99" target="_blank" class="easyui-linkbutton" data-options="plain:true"><img src="{{ url_for('static', filename='img/logo.min.png') }}" alt="开源优测" style="padding-top:8px;"/></a>
<a href="#" onclick="addTab('调度管理', '/scheduler/', 'icon-scheduler')" class="easyui-linkbutton" data-options="iconCls:'icon-scheduler'">调度管理</a>
<a href="#" onclick="addTab('系统管理', '/user', 'icon-settings')" class="easyui-linkbutton" data-options="iconCls:'icon-settings'">系统管理</a>
<a href="https://github.com/small99/AutoLink/tree/master/docs" target="_blank" class="easyui-linkbutton" data-options="iconCls:'icon-keyword-help'">用户指南</a>
<!--<a href="https://github.com/small99/AutoLink/blob/master/UPDATEING.md" target="_blank" class="easyui-linkbutton" data-options="iconCls:'icon-update'">更新清单</a>-->
<!--<a href="#" class="easyui-menubutton" data-options="plain:true">源码</a>
<a href="#" class="easyui-menubutton" data-options="plain:true">运行</a>
<a href="#" class="easyui-menubutton" data-options="plain:true">工具</a>
<a href="#" class="easyui-menubutton" data-options="menu:'#file'">文件</a>
<a href="#" class="easyui-menubutton" data-options="plain:true, menu:'#mm1'">编辑</a>
<a href="#" class="easyui-menubutton" data-options="plain:true, menu:'#mm2'">Help</a>
<a href="#" class="easyui-menubutton" data-options="plain:true, menu:'#mm3'">About</a></a>-->
<a href="#" onclick="do_logout('{{ username }}');" class="easyui-linkbutton" data-options="iconCls:'icon-logout'" style="float:right;margin-top:10px;margin-right:10px;">注销</a>
</div>
<!--
<div id="file" style="width:150px;">
<div data-options="iconCls:'icon-undo'">Undo</div>
<div data-options="iconCls:'icon-redo'">Redo</div>
<div class="menu-sep"></div>
<div onclick="do_logout('{{ username }}');" data-options="iconCls:'icon-back'">退出</div>
</div>
<div id="mm1" style="width:150px;">
<div data-options="iconCls:'icon-undo'">Undo</div>
<div data-options="iconCls:'icon-redo'">Redo</div>
<div class="menu-sep"></div>
<div>Cut</div>
<div>Copy</div>
<div>Paste</div>
<div class="menu-sep"></div>
<div>
<span>Toolbar</span>
<div>
<div>Address</div>
<div>Link</div>
<div>Navigation Toolbar</div>
<div>Bookmark Toolbar</div>
<div class="menu-sep"></div>
<div>New Toolbar...</div>
</div>
</div>
<div data-options="iconCls:'icon-remove'">Delete</div>
<div>Select All</div>
</div>
<div id="mm2" style="width:100px;">
<div>Help</div>
<div>Update</div>
<div>About</div>
</div>
<div id="mm3" class="menu-content" style="background:#f0f0f0;padding:10px;text-align:left">
<img src="http://www.jeasyui.com/images/logo1.png" style="width:150px;height:50px">
<p style="font-size:14px;color:#444;">Try jQuery EasyUI to build your modern, interactive, javascript applications.</p>
</div>
-->
</div>
<div data-options="region:'west',split:true" style="width:240px;padding:5px;">
<ul id="project_tree" class="easyui-tree"
data-options="
url:'/api/v1/project_list/',
method:'get',
queryParams: {},
animate:true,
lines:true,
onBeforeExpand: onBeforeExpand,
onContextMenu: onContextMenu,
onDblClick: onDblClick
">
</ul>
</div>
<!--<div data-options="region:'east',split:true,collapsed:true,title:'工具栏'" style="width:100px;padding:10px;">east region</div>-->
<!--
<div data-options="region:'south',border:false,split:true," style="height:150px;padding:0">
<div id="logger_tabs" class="easyui-tabs" style="width:100%;height:100%">
<div title="输出" style="padding:10px">
<p style="font-size:14px">jQuery EasyUI framework helps you build your web pages easily.</p>
<ul>
<li>easyui is a collection of user-interface plugin based on jQuery.</li>
<li>easyui provides essential functionality for building modem, interactive, javascript applications.</li>
<li>using easyui you don't need to write many javascript code, you usually defines user-interface by writing some HTML markup.</li>
<li>complete framework for HTML5 web page.</li>
<li>easyui save your time and scales while developing your products.</li>
<li>easyui is very easy but powerful.</li>
</ul>
</div>
<div title="通知" style="padding:10px">
<ul class="easyui-tree" data-options="url:'',method:'get',animate:true"></ul>
</div>
</div>
</div>
-->
<div data-options="region:'center'">
<div id="editor_tabs" class="easyui-tabs" style="width:100%;height:100%">
</div>
</div>
<div id="root_menu" class="easyui-menu" style="width:160px;">
<div onclick="addTab('欢迎页', '/welcome', 'icon-workspace');" data-options="iconCls:'icon-workspace'">欢迎页</div>
<div onclick="manage_project('create_project', 'create_project_ff', 'create')" data-options="iconCls:'icon-project'">创建项目</div>
<div class="menu-sep"></div>
<div onclick="expand()" data-options="iconCls:'icon-expand'">展开</div>
<div onclick="collapse()" data-options="iconCls:'icon-collapse'">折叠</div>
<div class="menu-sep"></div>
{% if username == "AutoLink" %}
<div onclick="addTab('用户管理', '/user', 'icon-user')" data-options="iconCls:'icon-user'">用户管理</div>
<div class="menu-sep"></div>
{% endif %}
<div onclick="do_logout('{{ username }}');" data-options="iconCls:'icon-logout'">退出</div>
</div>
<div id="project_menu" class="easyui-menu" style="width:160px;">
<div onclick="manage_suite('create_suite', 'create_suite_ff', 'create')" data-options="iconCls:'icon-suite'">创建目录</div>
<div class="menu-sep"></div>
<div onclick="do_run();" data-options="iconCls:'icon-run'">运行</div>
<div onclick="do_task_list();" data-options="iconCls:'icon-task'">查看任务</div>
<!--<div onclick="manage_project('edit_project', 'edit_project_ff', 'edit')" data-options="iconCls:'icon-debug'">调试</div>-->
<!--<div onclick="addTab('任务管理', '#', 'icon-task')" data-options="iconCls:'icon-task'">任务管理</div>-->
<div class="menu-sep"></div>
<div onclick="manage_project('edit_project', 'edit_project_ff', 'edit')">重命名</div>
<div onclick="delete_project()" data-options="iconCls:'icon-remove'">删除</div>
<div class="menu-sep"></div>
<div onclick="expand()" data-options="iconCls:'icon-expand'">展开</div>
<div onclick="collapse()" data-options="iconCls:'icon-collapse'">折叠</div>
</div>
<div id="suite_menu" class="easyui-menu" style="width:160px;">
<div onclick="manage_file('create_file', 'create_file_ff', 'create')">创建文件</div>
<div onclick="open_win('upload_win');">上传文件</div>
<div class="menu-sep"></div>
<div onclick="do_run();" data-options="iconCls:'icon-run'">运行</div>
<div class="menu-sep"></div>
<div onclick="manage_suite('edit_suite', 'edit_suite_ff', 'edit')">重命名</div>
<div onclick="delete_suite()" data-options="iconCls:'icon-remove'">删除</div>
<div class="menu-sep"></div>
<div onclick="expand()" data-options="iconCls:'icon-expand'">展开</div>
<div onclick="collapse()" data-options="iconCls:'icon-collapse'">折叠</div>
</div>
<div id="case_menu" class="easyui-menu" style="width:160px;">
<div onclick="do_open_editor();">打开</div>
<div onclick="do_download('download_ff');">下载</div>
<div class="menu-sep"></div>
<div onclick="do_run();" data-options="iconCls:'icon-run'">运行</div>
<div class="menu-sep"></div>
<div onclick="manage_file('edit_file', 'edit_file_ff', 'edit')">重命名</div>
<div onclick="delete_file()" data-options="iconCls:'icon-remove'">删除</div>
</div>
<!-- project start -->
<div id="create_project" class="easyui-window" title="创建项目"
data-options="modal:true,closed:true,minimizable:false,maximizable:false,collapsible:false"
style="width:400px;height:170px;padding:10px;">
<form id="create_project_ff" method="post">
<div style="margin-bottom:10px">
<input class="easyui-textbox" id="name" name="name" label="名称" labelPosition="top" style="width:100%" data-options="required:true">
</div>
</form>
<div style="text-align:right;padding:5px 0">
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="close_win('create_project')" style="width:60px">取消</a>
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="create_project('create_project', 'create_project_ff')" style="width:60px">创建</a>
</div>
</div>
<div id="edit_project" class="easyui-window" title="项目重命名"
data-options="modal:true,closed:true,minimizable:false,maximizable:false,collapsible:false"
style="width:400px;height:170px;padding:10px;">
<form id="edit_project_ff" method="post">
<div style="margin-bottom:10px;text-align:center">
<input class="easyui-textbox" id="new_name" name="new_name" style="width:100%;" labelPosition="top" data-options="label:'名称',required:true">
</div>
</form>
<div style="text-align:right;padding:5px 0">
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="close_win('edit_project')" style="width:60px">取消</a>
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="rename_project('edit_project', 'edit_project_ff')" style="width:60px">保存</a>
</div>
</div>
<!-- project end -->
<!-- suite start -->
<div id="create_suite" class="easyui-window" title="创建目录"
data-options="modal:true,closed:true,minimizable:false,maximizable:false,collapsible:false"
style="width:400px;height:170px;padding:10px;">
<form id="create_suite_ff" method="post">
<div style="margin-bottom:10px">
<input class="easyui-textbox" id="name" name="name" label="名称" labelPosition="top" style="width:100%" data-options="required:true">
</div>
</form>
<div style="text-align:right;padding:5px 0">
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="close_win('create_suite')" style="width:60px">取消</a>
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="create_suite('create_suite', 'create_suite_ff')" style="width:60px">创建</a>
</div>
</div>
<div id="edit_suite" class="easyui-window" title="目录重命名"
data-options="modal:true,closed:true,minimizable:false,maximizable:false,collapsible:false"
style="width:400px;height:170px;padding:10px;">
<form id="edit_suite_ff" method="post">
<div style="margin-bottom:10px;text-align:center">
<input class="easyui-textbox" id="new_name" name="new_name" style="width:100%;" labelPosition="top" data-options="label:'名称',required:true">
</div>
</form>
<div style="text-align:right;padding:5px 0">
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="close_win('edit_suite')" style="width:60px">取消</a>
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="rename_suite('edit_suite', 'edit_suite_ff')" style="width:60px">保存</a>
</div>
</div>
<!-- suite end -->
<!-- file start -->
<div id="create_file" class="easyui-window" title="创建文件"
data-options="modal:true,closed:true,minimizable:false,maximizable:false,collapsible:false"
style="width:400px;height:240px;padding:10px;">
<form id="create_file_ff" method="post">
<div style="margin-bottom:10px">
<select class="easyui-combobox" id="category" name="category" label="类型" labelPosition="top" style="width:100%" data-options="required:true">
<option value=".txt">资源文件(.txt)</option>
<option value=".robot">用例文件(.robot)</option>
</select>
</div>
<div style="margin-bottom:10px">
<input class="easyui-textbox" id="name" name="name" label="名称" labelPosition="top" style="width:100%" data-options="required:true">
</div>
</form>
<div style="text-align:right;padding:5px 0">
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="close_win('create_file')" style="width:60px">取消</a>
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="create_file('create_file', 'create_file_ff')" style="width:60px">创建</a>
</div>
</div>
<div id="edit_file" class="easyui-window" title="文件重命名"
data-options="modal:true,closed:true,minimizable:false,maximizable:false,collapsible:false"
style="width:400px;height:240px;padding:10px;">
<form id="edit_file_ff" method="post">
<div style="margin-bottom:10px">
<select class="easyui-combobox" id="new_category" name="new_category" label="类型" labelPosition="top" style="width:100%" data-options="required:true">
<option value=".txt">资源文件(.txt)</option>
<option value=".robot">用例文件(.robot)</option>
</select>
</div>
<div style="margin-bottom:10px">
<input class="easyui-textbox" id="new_name" name="new_name" label="名称" labelPosition="top" style="width:100%" data-options="required:true">
</div>
</form>
<div style="text-align:right;padding:5px 0">
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="close_win('edit_file')" style="width:60px">取消</a>
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="rename_file('edit_file', 'edit_file_ff')" style="width:60px">保存</a>
</div>
</div>
<!-- file end -->
<!-- file upload start -->
<div id="upload_win" class="easyui-window" title="文件上传"
data-options="modal:true,closed:true,minimizable:false,maximizable:false,collapsible:false,iconCls:'icon-case'"
style="width:500px;height:140px;padding:10px;">
<form id="upload_ff" method="POST" action="/api/v1/manage_file/" enctype="multipart/form-data">
<div style="margin-bottom:20px">
<input name="method" id="method" value="upload" hidden>
<input name="path" id="path" value="/" hidden>
<input id="files" name="files" class="easyui-filebox" label="" labelPosition="top"
data-options="prompt:'请选择一个文件...',buttonText:'选择'" style="width:100%">
</div>
<div style="text-align:right;">
<a href="#" class="easyui-linkbutton" style="width:80px;" onclick="do_upload('upload_win', 'upload_ff')">上 传</a>
</div>
</form>
</div>
<!-- file upload end -->
<!-- file download -->
<form id="download_ff" method="post" action="/api/v1/manage_file/">
<input name="method" id="method" value="download" hidden>
<input name="path" id="path" value="/" hidden>
</form>
<!-- file download end-->
<script src="{{ url_for('static', filename='lib/easyui/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='lib/easyui/jquery.easyui.min.js') }}"></script>
<!-- 自定义js库 -->
<script type="text/javascript" src="{{ url_for('static', filename='js/auto.js') }}"></script>
</body>
<SCRIPT type="text/javascript">
$(function () {
addTab("欢迎页", "/welcome", "icon-workspace");
});
</SCRIPT>
</html>
================================================
FILE: auto/www/templates/default.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AutoLink - 开源优测自动化测试集成解决方案</title>
<meta name="keywords" content="AutoLink, 开源优测, 苦叶子, web ide"/>
<meta name="description" content="A Web-based IDE for Auto Testing using Auto Open Source Testing Framework, do your development anytime, anywhere."/>
<meta name="author" content="苦叶子">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/easyui/themes/icon.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/easyui/themes/bootstrap/easyui.css') }}">
<!--<link rel="stylesheet" href="{{ url_for('static', filename='lib/CodeMirror/addon/fold/foldgutter.css') }}">-->
<link rel="stylesheet" href="{{ url_for('static', filename='css/auto.css') }}">
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}" />
</head>
<body class="easyui-layout">
<div data-options="region:'center'" style="padding-left:20px;">
<p>
<br>
<br>
<h1>
不支持预览该类型的文件<br><br>请右击下载到本地进行查看
</h1>
<h3>有建议请直接至https://github.com/small99/AutoLink提交issues<br><br>或是<br><br>发邮件至lymking#foxmail.com提需要</h3>
</p>
</div>
<script src="{{ url_for('static', filename='lib/easyui/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='lib/easyui/jquery.easyui.min.js') }}"></script>
<!-- 自定义js库 -->
<script type="text/javascript" src="{{ url_for('static', filename='js/auto.js') }}"></script>
</body>
</html>
================================================
FILE: auto/www/templates/editor.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AutoLink - 开源优测自动化测试集成解决方案</title>
<meta name="keywords" content="AutoLink, 开源优测, 苦叶子, web ide"/>
<meta name="description" content="A Web-based IDE for Auto Testing using Auto Open Source Testing Framework, do your development anytime, anywhere."/>
<meta name="author" content="苦叶子">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/easyui/themes/icon.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/easyui/themes/bootstrap/easyui.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/codemirror/lib/codemirror.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/codemirror/addon/hint/show-hint.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/codemirror/theme/dracula.css') }}">
<!--<link rel="stylesheet" href="{{ url_for('static', filename='lib/codemirror/addon/fold/foldgutter.css') }}">-->
<link rel="stylesheet" href="{{ url_for('static', filename='css/auto.css') }}">
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}" />
</head>
<body class="easyui-layout">
<div data-options="region:'center'">
<textarea id="code_editor" name="code_editor"></textarea>
<div id="keyword_help" style="padding:5px;background-color: #F5F5DC;"></div>
</div>
<div data-options="region:'east',split:true" title="关键字" style="width:200px;">
<ul id="keyword_list" class="easyui-tree" data-options="
url:'/api/v1/keyword?category=robot',
method:'get',
animate:true,
onDblClick: onKwDblClick,
onClick: onClick
">
</ul>
</div>
<script src="{{ url_for('static', filename='lib/easyui/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='lib/easyui/jquery.easyui.min.js') }}"></script>
<script src="{{ url_for('static', filename='lib/codemirror/lib/codemirror.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/highlight.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/autocomplete.js') }}"></script>
<script src="{{ url_for('static', filename='lib/codemirror/mode/robot/robot_v1.js') }}"></script>
<!--<script src="{{ url_for('static', filename='lib/codemirror/mode/textile/textile.js') }}"></script>>-->
<script src="{{ url_for('static', filename='lib/codemirror/addon/display/placeholder.js') }}"></script>
<!--
<script src="{{ url_for('static', filename='lib/codemirror/addon/fold/foldcode.js') }}"></script>
<script src="{{ url_for('static', filename='lib/codemirror/addon/fold/foldgutter.js') }}"></script>
<script src="{{ url_for('static', filename='lib/codemirror/addon/fold/brace-fold.js') }}"></script>
<script src="{{ url_for('static', filename='lib/codemirror/addon/fold/xml-fold.js') }}"></script>
<script src="{{ url_for('static', filename='lib/codemirror/addon/fold/indent-fold.js') }}"></script>
<script src="{{ url_for('static', filename='lib/codemirror/addon/fold/markdown-fold.js') }}"></script>-->
<script src="{{ url_for('static', filename='lib/codemirror//addon/hint/show-hint.js') }}"></script>
<!--<script src="{{ url_for('static', filename='lib/codemirror//addon/hint/anyword-hint.js') }}"></script>-->
<script src="{{ url_for('static', filename='lib/codemirror//addon/hint/robot-hint.js') }}"></script>
<!-- 自定义js库 -->
<script type="text/javascript" src="{{ url_for('static', filename='js/auto.js') }}"></script>
<SCRIPT type="text/javascript">
$(document).ready(function(){
CodeMirror.commands.autocomplete = function(cm) {
cm.showHint({hint: CodeMirror.hint.anyword});
}
var path = "/{{ project }}/{{ suite }}/{{ case }}";
editor = CodeMirror.fromTextArea(document.getElementById("code_editor"), {
mode: 'robot',
lineNumbers: true,
lineWrapping: true,
styleActiveLine: true,
styleSelectedText: true,
theme: "dracula",
indentUnit:4,
completeSingle: false,
extraKeys: {
"Ctrl": "autocomplete"
}
//keyMap: "sublime",
//foldGutter: true,
//highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: true},
//gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"]
});
editor.on("change", function(cm, event)
{
var data = {
"method": "save",
"path": path,
"data": editor.getValue()
};
do_ajax("post", "/api/v1/case/", data, do_nop);
});
editor.on("keypress", function(cm, event)
{
cm.showHint({hint: CodeMirror.hint.robotScript});
});
editor.markText({line: 6, ch: 26}, {line: 6, ch: 42}, {className: "styled-background"});
do_ajax("get", "/api/v1/case/", {"path": path}, do_init);
//editor.setValue("*** Settings ***\n\n\n*** Variables ***\n\n\n*** Test Cases ***\n\n\n*** Keywords ***\n\n");
});
function onKwDblClick(node) {
var category = node.attributes.category;
if(category == "keyword"){
editor.replaceSelection(node.attributes.keyword + node.attributes.params);
}
}
function onClick(node){
var category = node.attributes.category;
if(category == "keyword"){
$("#keyword_help").html(node.attributes.doc);
}
}
function newTab(cm){
/*if (cm.somethingSelected()) {
cm.indentSelection('add');
} else {
cm.replaceSelection(cm.getOption) ? "\t" : Array(cm.getOption("indentUnit") + 1).join(" "), "end", "+input");
}*/
}
</SCRIPT>
</body>
</html>
================================================
FILE: auto/www/templates/login.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AutoLink - 开源优测自动化测试集成解决方案</title>
<meta name="keywords" content="AutoLink, 开源优测, AutoLine, 苦叶子, web ide"/>
<meta name="description" content="A Web-based IDE for Auto Testing using Auto Open Source Testing Framework, do your development anytime, anywhere."/>
<meta name="author" content="苦叶子">
<link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/sign.css') }}">
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}" />
</head>
<body>
<div class="header">
<div class="wrapper fn-clear">
<a href="/" rel="login">
<img title="AutoLink, A Web-based IDE for Auto Testing using Auto Open Source Testing Framework." src="{{ url_for('static', filename='img/logo.png') }}"
class="logo"/></a>
<ul class="fn-right">
<li><a href="http://weixin.sogou.com/weixin?type=1&s_from=input&query=%E5%BC%80%E6%BA%90%E4%BC%98%E6%B5%8B&ie=utf8&_sug_=n&_sug_type_=" target="_blank" style="color: #cd504a">公众号</a></li>
<li><a rel="help" href="#" target="_blank">读书会</a></li>
<li><a href="https://github.com/small99" target="_blank">GitHub</a></li>
<li><a href="https://gitee.com/lym51" target="_blank">码云</a></li>
</ul>
</div>
</div>
<div class="content">
<div class="wrapper fn-clear">
<div class="fn-left">
<h2>All in AutoLink</h2>
<h3>Auto Testing with Open Source Framework on the AutoLink way.</h3>
</div>
<div class="form fn-right">
<div id="msg" class="fn-none"></div>
<form id="login_fm" action="/api/v1/auth/" method="post">
<input id="username" name="username" placeholder="用户名"/><br/>
<input id="password" name="password" type="password" placeholder="密码"/><br/>
<input class="btn-white btn" type="button" value="登录" onclick="do_login('login_fm')"></input>
</form>
</div>
</div>
</div>
<div class="footer">
<span class="wrapper">
© 苦叶子
</span>
</div>
<script src="{{ url_for('static', filename='lib/easyui/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='lib/easyui/jquery.easyui.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/auto.js') }}"></script>
<script>
$(function () {
$("body").keydown(function(event) {
if (event.keyCode == "13") {//keyCode=13是回车键
if($("#username").val() == ""){
$("#username").focus();
}
else if($("#password").val() == ""){
$("#password").focus();
}
else{
do_login('login_fm');
}
}
});
});
</script>
</body>
</html>
================================================
FILE: auto/www/templates/report.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AutoLink - 开源优测自动化测试集成解决方案</title>
<meta name="keywords" content="AutoLink, 开源优测, 苦叶子, web ide"/>
<meta name="description" content="A Web-based IDE for Auto Testing using Auto Open Source Testing Framework, do your development anytime, anywhere."/>
<meta name="author" content="苦叶子">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/easyui/themes/icon.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/easyui/themes/bootstrap/easyui.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/CodeMirror/lib/codemirror.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/CodeMirror/addon/hint/show-hint.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/CodeMirror/theme/dracula.css') }}">
<!--<link rel="stylesheet" href="{{ url_for('static', filename='lib/CodeMirror/addon/fold/foldgutter.css') }}">-->
<link rel="stylesheet" href="{{ url_for('static', filename='css/auto.css') }}">
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}" />
<style type="text/css">
.lines-no .datagrid-body td{
border-right:1px dotted transparent;
border-bottom:1px dotted transparent;
}
</style>
</head>
<body class="easyui-layout" style="padding:10">
<div data-options="region:'center'" style="padding: 5px 5px 5px 5px">
<table id="task_scheduler" class="easyui-datagrid" style="width:100%;height:auto;"
data-options="singleSelect:true,
fitColumns:true,
url:'/api/v1/task_list/?method=query',
method:'post',
toolbar: toolbar"
>
<thead>
<tr>
<!--<th data-options="field:'status',align:'center' , halign: 'center',formatter:show_img">状态</th>-->
<th data-options="field:'name'">名称</th>
<!--<th data-options="field:'last_success',align:'center' , halign: 'center'">上次成功</th>
<th data-options="field:'last_fail',align:'center' , halign: 'center'">上次失败</th>
<th data-options="field:'duration'">持续时间</th>
<th data-options="field:'enable',align:'center' , halign: 'center'">是否启用调度</th>-->
<th data-options="field:'next_time',align:'center' , halign: 'center'">下一次调度时间</th>
<th data-options="field:'cron',align:'center' , halign: 'center'">cron表达式</th>
<!--<th data-options="field:'boolean'"></th>-->
</tr>
</thead>
</table>
</div>
<!-- edit project cron -->
<div id="cron_win" class="easyui-window" title="编辑Cron表达式"
data-options="iconCls:'icon-edit',collapsible:false,minimizable:false,maximizable:false"
style="width:400px;height:140px;padding:10px;">
<form id="cron_ff" method="post">
<!--
<div style="margin-bottom:10px">
<label for="enable" class="textbox-label">启用:</label>
<input id="enable" type="checkbox" name="enable" value="true" checked>
</div>
-->
<div style="margin-bottom:20px">
<select class="easyui-combobox" name="cron" id="cron" label="Cron表达式" style="width:100%">
<option value="* * * * * *">默认无调度(* * * * * *)</option>
<option value="0 0 1 * * *">每天凌晨1点执行一次(0 0 1 * * *)</option>
<option value="0 0 12 * * *">每天12点调度一次(0 0 12 * * *)</option>
<option value="0 0 23 * * *">每天23点调度一次(0 0 23 * * *)</option>
<option value="0 0/30 * * * *">每隔30分钟调度一次(0 0/5 * * * *)</option>
<option value="0 0 0/1 0 0 6/7">周末每小时调度一次(0 0 0/1 0 0 6/7)</option>
<option value="0 0 0/1 0 0 1/5">工作日每小时调度一次(0 0 0/1 0 0 1/5)</option>
</select>
</div>
<div style="text-align:right;margin-bottom:10px">
<a href="#" class="easyui-linkbutton" style="width:80px;height:32px" onclick="do_edit();">更新</a>
</div>
</form>
</div>
<!-- -->
<script src="{{ url_for('static', filename='lib/easyui/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='lib/easyui/jquery.easyui.min.js') }}"></script>
<script src="{{ url_for('static', filename='lib/CodeMirror/lib/codemirror.js') }}"></script>
<script src="{{ url_for('static', filename='lib/CodeMirror/mode/robot/robot.js') }}"></script>
<!--<script src="{{ url_for('static', filename='lib/CodeMirror/mode/textile/textile.js') }}"></script>>-->
<script src="{{ url_for('static', filename='lib/CodeMirror/addon/display/placeholder.js') }}"></script>
<!--
<script src="{{ url_for('static', filename='lib/CodeMirror/addon/fold/foldcode.js') }}"></script>
<script src="{{ url_for('static', filename='lib/CodeMirror/addon/fold/foldgutter.js') }}"></script>
<script src="{{ url_for('static', filename='lib/CodeMirror/addon/fold/brace-fold.js') }}"></script>
<script src="{{ url_for('static', filename='lib/CodeMirror/addon/fold/xml-fold.js') }}"></script>
<script src="{{ url_for('static', filename='lib/CodeMirror/addon/fold/indent-fold.js') }}"></script>
<script src="{{ url_for('static', filename='lib/CodeMirror/addon/fold/markdown-fold.js') }}"></script>-->
<script src="{{ url_for('static', filename='lib/CodeMirror//addon/hint/show-hint.js') }}"></script>
<!--<script src="{{ url_for('static', filename='lib/CodeMirror//addon/hint/anyword-hint.js') }}"></script>-->
<!-- 自定义js库 -->
<script type="text/javascript" src="{{ url_for('static', filename='js/auto.js') }}"></script>
<SCRIPT type="text/javascript">
$(document).ready(function(){
$('#task_scheduler').datagrid('getPanel').removeClass('lines-both lines-no lines-right lines-bottom').addClass('lines-no');
$("#cron_win").window('close');
});
var toolbar = [
{
text:'刷新',
iconCls:'icon-refresh',
handler:function(){
$("#task_scheduler").datagrid('reload');
}
}, '-',
{
text:'启动',
iconCls:'icon-run',
handler:function(){
var row = $('#task_scheduler').datagrid('getSelected');
if(row){
var data ={"method": "start", "name": "{0}".lym_format(row.name), "cron": "{0}".lym_format(row.cron) };
do_ajax('post',
'/api/v1/task_list/',
data,
do_msg);
$("#task_scheduler").datagrid('reload');
}
else{
show_msg("提示信息", "请选择要启动调度的项目");
}
}
}, '-',
{
text:'停止',
iconCls:'icon-stop',
handler:function(){
$.messager.confirm('提示信息', '现在停止无法生成报告,确定停止?', function(r){
if (r){
var row = $('#task_scheduler').datagrid('getSelected');
if(row){
var data ={"method": "stop", "name": "{0}".lym_format(row.name) };
do_ajax('post',
'/api/v1/task_list/',
data,
do_msg);
$("#task_scheduler").datagrid('reload');
}
else{
show_msg("提示信息", "请选择要停止调度的项目");
}
}
});
}
}, '-',
{
text:'编辑',
iconCls:'icon-edit',
handler:function(){
var row = $('#task_scheduler').datagrid('getSelected');
if(row){
open_win('cron_win');
}
else{
show_msg("提示信息", "请选择要编辑的项目");
}
}
}
];
function do_edit(){
var row = $('#task_scheduler').datagrid('getSelected');
var data = {"name": row["name"], "method": "edit", "cron": $("#cron").combobox("getValue")};
do_ajax('post',
'/api/v1/task_list/',
data,
do_msg);
$("#task_scheduler").datagrid('reload');
close_win("cron_win");
}
</SCRIPT>
</body>
</html>
================================================
FILE: auto/www/templates/scheduler.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AutoLink - 开源优测自动化测试集成解决方案</title>
<meta name="keywords" content="AutoLink, 开源优测, 苦叶子, web ide"/>
<meta name="description" content="A Web-based IDE for Auto Testing using Auto Open Source Testing Framework, do your development anytime, anywhere."/>
<meta name="author" content="苦叶子">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/easyui/themes/icon.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/easyui/themes/bootstrap/easyui.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/CodeMirror/lib/codemirror.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/CodeMirror/addon/hint/show-hint.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='lib/CodeMirror/theme/dracula.css') }}">
<!--<link rel="stylesheet" href="{{ url_for('static', filename='lib/CodeMirror/addon/fold/foldgutter.css') }}">-->
<link rel="stylesheet" href="{{ url_for('static', filename='css/auto.css') }}">
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}" />
<style type="text/css">
.lines-no .datagrid-body td{
border-right:1px dotted transparent;
border-bottom:1px dotted transparent;
}
</style>
</head>
<body class="easyui-layout" style="padding:10">
<div data-options="region:'center'" style="padding: 5px 5px 5px 5px">
<table id="task_scheduler" class="easyui-datagrid" style="width:100%;height:auto;"
data-options="singleSelect:true,
fitColumns:true,
url:'/api/v1/task_list/?method=query',
method:'post',
toolbar: toolbar"
>
<thead>
<tr>
<!--<th data-options="field:'status',align:'center' , halign: 'center',formatter:show_img">状态</th>-->
<th data-options="field:'status',align:'center' , halign: 'center',formatter:do_view_status"></th>
<th data-options="field:'name'">名称</th>
<!--<th data-options="field:'last_success',align:'center' , halign: 'center'">上次成功</th>
<th data-options="field:'last_fail',align:'center' , halign: 'center'">上次失败</th>
<th data-options="field:'duration'">持续时间</th>
<th data-options="field:'enable',align:'center' , halign: 'center'">是否启用调度</th>-->
<th data-options="field:'next_time',align:'center' , halign: 'center'">下一次调度时间</th>
<th data-options="field:'cron',align:'center' , halign: 'center'">cron表达式</th>
<th data-options="field:'task',align:'center' , halign: 'center',formatter:do_view_task">查看任务</th>
<!--<th data-options="field:'boolean'"></th>-->
</tr>
</thead>
</table>
</div>
<!-- edit project cron -->
<div id="cron_win" class="easyui-window" title="编辑Cron表达式"
data-options="iconCls:'icon-edit',collapsible:false,minimizable:false,maximizable:false"
style="width:400px;height:140px;padding:10px;">
<form id="cron_ff" method="post">
<!--
<div style="margin-bottom:10px">
<label for="enable" class="textbox-label">启用:</label>
<input id="enable" type="checkbox" name="enable" value="true" checked>
</div>
-->
<div style="margin-bottom:20px">
<select class="easyui-combobox" name="cron" id="cron" label="Cron表达式" style="width:100%">
<option value="* * * * * *">默认无调度(* * * * * *)</option>
<option value="0 0 1 * * *">每天凌晨1点执行一次(0 0 1 * * *)</option>
<option value="0 0 12 * * *">每天12点调度一次(0 0 12 * * *)</option>
<option value="0 0 23 * * *">每天23点调度一次(0 0 23 * * *)</option>
<option value="0 0/30 * * * *">每隔30分钟调度一次(0 0/30 * * * *)</option>
<option value="0 0 0/1 0 0 6/7">周末每小时调度一次(0 0 0/1 0 0 6/7)</option>
<option value="0 0 0/1 0 0 1/5">工作日每小时调度一次(0 0 0/1 0 0 1/5)</option>
</select>
</div>
<div style="text-align:right;margin-bottom:10px">
<a href="#" class="easyui-linkbutton" style="width:80px;height:32px" onclick="do_edit();">更新</a>
</div>
</form>
</div>
<!-- -->
<!-- edit project notifys -->
<div id="notify_win" class="easyui-window" title="邮件通知"
data-options="iconCls:'icon-notification',collapsible:false,minimizable:false,maximizable:false"
style="width:400px;height:210px;padding:10px;">
<form id="notify_ff" method="post">
<div style="margin-bottom:10px">
<input class="easyui-textbox" id="success_list" name="success_list" label="成功列表" labelPosition="left" style="width:100%;height:50px;" data-options="multiline:true">
</div>
<div style="margin-bottom:10px">
<input class="easyui-textbox" id="fail_list" name="fail_list" label="失败列表" labelPosition="left" style="width:100%;height:50px;" data-options="multiline:true">
</div>
<div style="text-align:right;margin-bottom:10px">
<a href="#" class="easyui-linkbutton" style="width:80px;height:32px" onclick="do_notify_edit();">保存</a>
</div>
</form>
</div>
<!-- -->
<script src="{{ url_for('static', filename='lib/easyui/jquery.min.js') }}"></script>
<script src="{{ url_
gitextract_mfg_aafz/ ├── .gitattributes ├── .gitignore ├── AutoLink.py ├── CodeStats.py ├── INSTALL.md ├── LICENSE ├── README.md ├── UPDATEING.md ├── auto/ │ ├── __init__.py │ ├── configuration.py │ ├── exceptions.py │ ├── settings.py │ ├── version.py │ └── www/ │ ├── __init__.py │ ├── api/ │ │ ├── __init__.py │ │ ├── auth.py │ │ ├── case.py │ │ ├── editor.py │ │ ├── keyword.py │ │ ├── project.py │ │ ├── settings.py │ │ ├── suite.py │ │ ├── task.py │ │ └── user.py │ ├── app.py │ ├── blueprints.py │ ├── static/ │ │ ├── css/ │ │ │ ├── auto.css │ │ │ ├── base.css │ │ │ └── sign.css │ │ └── js/ │ │ ├── auto.js │ │ ├── autocomplete.js │ │ └── highlight.js │ └── templates/ │ ├── dashboard.html │ ├── default.html │ ├── editor.html │ ├── login.html │ ├── report.html │ ├── scheduler.html │ ├── task_list.html │ ├── user.html │ ├── view_img.html │ └── welcome.html ├── docs/ │ ├── README.md │ ├── 上传和下载RobotFramework用例.md │ ├── 关键字概要说明.md │ ├── 如何使用自动提示快捷输入关键字.md │ ├── 如何使用调度管理.md │ ├── 如何创建HTTP接口测试用例.md │ ├── 如何创建测试项目.md │ ├── 如何查看关键字详细文档.md │ ├── 如何管理测试项目中用例顺序.md │ ├── 如何调用Python自定义库.md │ ├── 如何运行测试项目.md │ ├── 安装与启动.md │ ├── 查看测试报告.md │ └── 配置SMTP服务及邮件通知.md ├── driver/ │ └── readme.md ├── keyword/ │ ├── AppiumLibrary.xml │ ├── BuiltIn.xml │ ├── Collections.xml │ ├── DatabaseLibrary.xml │ ├── DateTime.xml │ ├── Dialogs.xml │ ├── OperatingSystem.xml │ ├── Process.xml │ ├── RequestsLibrary.xml │ ├── SSHLibrary.xml │ ├── Screenshot.xml │ ├── SeleniumLibrary.xml │ ├── String.xml │ ├── Telnet.xml │ └── XML.xml ├── licenses/ │ ├── LICENSE-CodeMirror │ ├── LICENSE-jquery │ ├── LICENSE-wide.html │ └── license_freeware-easyui.txt ├── requirements.txt ├── utils/ │ ├── __init__.py │ ├── file.py │ ├── help.py │ ├── parsing.py │ ├── resource.py │ └── run.py └── version.txt
SYMBOL INDEX (184 symbols across 18 files)
FILE: CodeStats.py
function get_file (line 26) | def get_file(base_dir):
function count_line (line 45) | def count_line(fname):
FILE: auto/configuration.py
class Config (line 21) | class Config:
method init_app (line 57) | def init_app(app):
class DevelopmentConfig (line 61) | class DevelopmentConfig(Config):
class ProductionConfig (line 65) | class ProductionConfig(Config):
method init_app (line 69) | def init_app(cls, app):
FILE: auto/exceptions.py
class AutoBeatException (line 14) | class AutoBeatException(Exception):
class AutoBeatConfigException (line 18) | class AutoBeatConfigException(AutoBeatException):
class AutoBeatExecutorTimeout (line 22) | class AutoBeatExecutorTimeout(AutoBeatException):
class AutoBeatTaskTimeout (line 26) | class AutoBeatTaskTimeout(AutoBeatException):
class AutoBeatWebServerTimeout (line 30) | class AutoBeatWebServerTimeout(AutoBeatException):
class AutoBeatSkipException (line 34) | class AutoBeatSkipException(AutoBeatException):
FILE: auto/www/api/auth.py
class Auth (line 20) | class Auth(Resource):
method __init__ (line 21) | def __init__(self):
method get (line 26) | def get(self):
method post (line 35) | def post(self):
FILE: auto/www/api/case.py
class Case (line 22) | class Case(Resource):
method __init__ (line 23) | def __init__(self):
method get (line 36) | def get(self):
method post (line 53) | def post(self):
method __create (line 70) | def __create(self, args):
method __edit (line 85) | def __edit(self, args):
method __delete (line 105) | def __delete(self, args):
method __save (line 121) | def __save(self, args):
class ManageFile (line 133) | class ManageFile(Resource):
method __init__ (line 134) | def __init__(self):
method post (line 140) | def post(self):
method __upload (line 150) | def __upload(self, file, path):
method __download (line 162) | def __download(self, args):
FILE: auto/www/api/keyword.py
class Keyword (line 19) | class Keyword(Resource):
method __init__ (line 20) | def __init__(self):
method get (line 24) | def get(self):
FILE: auto/www/api/project.py
class Project (line 26) | class Project(Resource):
method __init__ (line 27) | def __init__(self):
method get (line 38) | def get(self):
method post (line 41) | def post(self):
method __create (line 54) | def __create(self, args):
method __edit (line 78) | def __edit(self, args):
method __delete (line 101) | def __delete(self, args):
class ProjectList (line 115) | class ProjectList(Resource):
method __init__ (line 116) | def __init__(self):
method get (line 125) | def get(self):
function create_project (line 165) | def create_project(app, username, project):
function edit_project (line 173) | def edit_project(app, username, old_name, new_project):
function remove_project (line 189) | def remove_project(app, username, name):
function get_project_list (line 203) | def get_project_list(app, username):
function get_project_detail (line 215) | def get_project_detail(app, username, p_name):
function get_projects (line 260) | def get_projects(app, username):
function get_suite_by_project (line 281) | def get_suite_by_project(app, username, args):
function get_case_by_suite (line 305) | def get_case_by_suite(app, username, args):
function get_step_by_case (line 338) | def get_step_by_case(app, username, args):
function get_case_data (line 349) | def get_case_data(path):
FILE: auto/www/api/settings.py
class Settings (line 19) | class Settings(Resource):
method __init__ (line 20) | def __init__(self):
method get (line 33) | def get(self):
method post (line 60) | def post(self):
method __smtp (line 72) | def __smtp(self, args):
method __email (line 98) | def __email(self, args):
FILE: auto/www/api/suite.py
class Suite (line 19) | class Suite(Resource):
method __init__ (line 20) | def __init__(self):
method post (line 28) | def post(self):
method __create (line 41) | def __create(self, args):
method __edit (line 52) | def __edit(self, args):
method __delete (line 63) | def __delete(self, args):
FILE: auto/www/api/task.py
class Task (line 29) | class Task(Resource):
method __init__ (line 30) | def __init__(self):
method post (line 40) | def post(self):
class TaskList (line 83) | class TaskList(Resource):
method __init__ (line 84) | def __init__(self):
method get (line 91) | def get(self):
method post (line 97) | def post(self):
function get_task_list (line 164) | def get_task_list(app, username, project):
function get_last_task (line 240) | def get_last_task(app, username, project):
function get_all_task (line 269) | def get_all_task(app):
function get_last_pass (line 319) | def get_last_pass(job_path):
function get_last_fail (line 332) | def get_last_fail(job_path):
function get_next_build_number (line 345) | def get_next_build_number(job_path):
function get_next_time (line 358) | def get_next_time(app, name):
function edit_cron (line 367) | def edit_cron(app, name, cron):
function delete_task_record (line 385) | def delete_task_record(app, name, task_no):
FILE: auto/www/api/user.py
class User (line 22) | class User(Resource):
method __init__ (line 23) | def __init__(self):
method get (line 33) | def get(self):
method post (line 50) | def post(self):
method __create (line 67) | def __create(self, args):
method __edit (line 86) | def __edit(self, args):
method __delete (line 106) | def __delete(self, args):
method __save (line 124) | def __save(self, args):
FILE: auto/www/app.py
function load_all_task (line 30) | def load_all_task(app):
function create_app (line 76) | def create_app(config_name):
FILE: auto/www/blueprints.py
function before_routes (line 20) | def before_routes():
function index (line 29) | def index():
function dashboard (line 34) | def dashboard():
function tree_demo (line 42) | def tree_demo():
function editor (line 47) | def editor(project, suite, case):
function task_list (line 60) | def task_list(name):
function scheduler (line 65) | def scheduler():
function user (line 70) | def user():
function view_report (line 75) | def view_report(project, task):
function q_view_report (line 84) | def q_view_report(username, project, task):
function view_img (line 93) | def view_img():
function welcome (line 105) | def welcome():
FILE: auto/www/static/js/auto.js
function show_msg (line 48) | function show_msg(title, msg){
function do_refresh (line 57) | function do_refresh(data){
function do_nop (line 61) | function do_nop(data){
function do_msg (line 65) | function do_msg(data){
function do_init (line 69) | function do_init(data){
function do_ajax (line 84) | function do_ajax(type, url, data, func){
function do_login (line 93) | function do_login(fm_id){
function do_logout (line 98) | function do_logout(username){
function do_run (line 102) | function do_run(){
function do_task_list (line 129) | function do_task_list(){
function do_in_array (line 137) | function do_in_array(str, array){
function onDblClick (line 147) | function onDblClick(node) {
function onContextMenu (line 185) | function onContextMenu(e, node){
function addTab (line 197) | function addTab(title, url, icon){
function refreshTab (line 215) | function refreshTab(cfg){
function collapse (line 224) | function collapse(){
function expand (line 229) | function expand(){
function onBeforeExpand (line 235) | function onBeforeExpand(node){
function manage_project (line 257) | function manage_project(win_id, ff_id, method){
function refresh_workspace (line 271) | function refresh_workspace(data){
function refresh_project_node (line 280) | function refresh_project_node(data){
function refresh_suite_node (line 291) | function refresh_suite_node(data){
function refresh_case_node (line 306) | function refresh_case_node(data){
function create_project (line 321) | function create_project(win_id, ff_id){
function rename_project (line 330) | function rename_project(win_id, ff_id){
function delete_project (line 340) | function delete_project(){
function manage_suite (line 356) | function manage_suite(win_id, ff_id, method){
function create_suite (line 370) | function create_suite(win_id, ff_id){
function rename_suite (line 383) | function rename_suite(win_id, ff_id){
function delete_suite (line 397) | function delete_suite(){
function manage_file (line 415) | function manage_file(win_id, ff_id, method){
function create_file (line 430) | function create_file(win_id, ff_id){
function rename_file (line 445) | function rename_file(win_id, ff_id){
function delete_file (line 464) | function delete_file(){
function do_upload (line 487) | function do_upload(win_id, ff_id){
function do_download (line 504) | function do_download(ff_id){
function show_img (line 519) | function show_img(value, row, index){
function do_open_editor (line 523) | function do_open_editor(){
function refresh_user_list (line 537) | function refresh_user_list(data){
function manage_user (line 543) | function manage_user(win_id, ff_id, method){
function create_user (line 554) | function create_user(win_id, ff_id){
function edit_user (line 563) | function edit_user(win_id, ff_id){
function close_win (line 572) | function close_win(id){
function open_win (line 576) | function open_win(id){
function clear_form (line 580) | function clear_form(id){
function load_smtp (line 584) | function load_smtp(data){
function init_smtp_ff (line 589) | function init_smtp_ff(){
function load_email (line 594) | function load_email(data){
function init_email_ff (line 598) | function init_email_ff(name){
function do_smtp (line 603) | function do_smtp(win_id, ff_id){
FILE: utils/file.py
function mk_dirs (line 17) | def mk_dirs(path, mode=0o777):
function walk_dir (line 25) | def walk_dir(path):
function list_dir (line 33) | def list_dir(path):
function exists_path (line 41) | def exists_path(path):
function rename_file (line 48) | def rename_file(src, dst):
function remove_readonly (line 57) | def remove_readonly(func, path, _):
function remove_dir (line 63) | def remove_dir(path):
function remove_file (line 67) | def remove_file(path):
function get_splitext (line 71) | def get_splitext(path):
function make_nod (line 75) | def make_nod(path, mode="w", encoding="utf-8"):
function write_file (line 86) | def write_file(path, data, mode="w", encoding="utf-8"):
function read_file (line 99) | def read_file(path, mode="r", encoding="utf-8"):
FILE: utils/help.py
function check_version (line 17) | def check_version():
FILE: utils/parsing.py
function parser_robot_keyword_list (line 29) | def parser_robot_keyword_list():
function parser (line 84) | def parser(doc_dir):
function parser_with_args (line 100) | def parser_with_args(doc_dir):
function generate_high_light (line 124) | def generate_high_light(doc_dir):
function generate_auto_complete (line 141) | def generate_auto_complete(doc_dir):
FILE: utils/run.py
function robot_job (line 31) | def robot_job(app, name, username):
function robot_run (line 44) | def robot_run(username, name, project, output):
function reset_next_build_numb (line 72) | def reset_next_build_numb(output):
function reset_last_status (line 90) | def reset_last_status(result, output, index):
function remove_robot (line 109) | def remove_robot(app):
function stop_robot (line 119) | def stop_robot(app, name):
function is_run (line 135) | def is_run(app, name):
function send_robot_report (line 144) | def send_robot_report(username, name, task_no, result, output):
class RobotRun (line 211) | class RobotRun(threading.Thread):
method __init__ (line 212) | def __init__(self, name, output, lock, executor="auto"):
method run (line 221) | def run(self):
method reset_next_build_numb (line 256) | def reset_next_build_numb(self):
method reset_last_status (line 274) | def reset_last_status(self, index):
Condensed preview — 84 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (970K chars).
[
{
"path": ".gitattributes",
"chars": 93,
"preview": "*.js linguist-language=Pyhton\n*.css linguist-language=Python\n*.html linguist-language=Python\n"
},
{
"path": ".gitignore",
"chars": 1294,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": "AutoLink.py",
"chars": 663,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\nimport os\nimport sys\n\nfrom "
},
{
"path": "CodeStats.py",
"chars": 1342,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\n# coding=utf-8\nimport os\ni"
},
{
"path": "INSTALL.md",
"chars": 534,
"preview": "## 安装与启动\n\n1. 安装Python3版本,确保加入环境变量,pip命令可用\n\n2. 从[AutoLink Github项目](https://github.com/small99/AutoLink)下载源码\n\n3. 执行以下命令安装"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 1217,
"preview": "## 介绍\n\nAutoLink开源自动化测试集成解决方案.\n\n- AutoLink是RobotFramework的web集成开发环境.\n- AutoLink支持RobotFramework语法高亮,自动提示等功能.\n- AutoLink可以"
},
{
"path": "UPDATEING.md",
"chars": 1077,
"preview": "2018-09-10 v1.0.12\n1. 新增icon图标资源\n2. 新增css定义\n3. 新增步骤级前端js处理\n4. 新增后端api步骤详细信息\n5. 修订主面板截图\n6. 修复用户反馈的缺陷\n\n2018-09-06 v1.0.11\n"
},
{
"path": "auto/__init__.py",
"chars": 93,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n"
},
{
"path": "auto/configuration.py",
"chars": 2354,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\nimport logging\nimport os\nim"
},
{
"path": "auto/exceptions.py",
"chars": 440,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\n\nclass AutoBeatException(E"
},
{
"path": "auto/settings.py",
"chars": 273,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\n\nHEADER = \"\"\"\\\n ___ "
},
{
"path": "auto/version.py",
"chars": 114,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\nversion = '1.0.0.0'\n"
},
{
"path": "auto/www/__init__.py",
"chars": 93,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n"
},
{
"path": "auto/www/api/__init__.py",
"chars": 882,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\nfrom flask import Blueprin"
},
{
"path": "auto/www/api/auth.py",
"chars": 1468,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\nfrom flask import current_a"
},
{
"path": "auto/www/api/case.py",
"chars": 6727,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\nimport os\nfrom urllib.pars"
},
{
"path": "auto/www/api/editor.py",
"chars": 93,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n"
},
{
"path": "auto/www/api/keyword.py",
"chars": 493,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\n\nfrom flask_restful import"
},
{
"path": "auto/www/api/project.py",
"chars": 12572,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\nfrom flask import current_"
},
{
"path": "auto/www/api/settings.py",
"chars": 4184,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\nimport json\nimport codecs\nf"
},
{
"path": "auto/www/api/suite.py",
"chars": 2282,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\nfrom flask import current_"
},
{
"path": "auto/www/api/task.py",
"chars": 14663,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\nfrom flask import current_"
},
{
"path": "auto/www/api/user.py",
"chars": 4535,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\nimport json\nimport codecs\n"
},
{
"path": "auto/www/app.py",
"chars": 3429,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\nimport os\nimport json\nimpo"
},
{
"path": "auto/www/blueprints.py",
"chars": 2538,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\nfrom flask import Blueprint"
},
{
"path": "auto/www/static/css/auto.css",
"chars": 3328,
"preview": ".icon-editor{\n\tbackground:url('../img/editor.png') no-repeat center center;\n}\n\n.icon-expand{\n\tbackground:url('../img/exp"
},
{
"path": "auto/www/static/css/base.css",
"chars": 5094,
"preview": "/*\n * Copyright (c) 2014-2018, b3log.org & hacpai.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
},
{
"path": "auto/www/static/css/sign.css",
"chars": 3808,
"preview": "/*\n * Copyright (c) 2014-2018, b3log.org & hacpai.com\n *\n * Licensed under the Apache License, Version 2.0 (the \"License"
},
{
"path": "auto/www/static/js/auto.js",
"chars": 18464,
"preview": "/*\n * auto.js\n *\n * 作者: 苦叶子\n *\n * 公众号: 开源优测\n *\n * Email: lymking@foxmail.com\n *\n*/\n\n/*\n * for string format\n*/\nString.pr"
},
{
"path": "auto/www/static/js/autocomplete.js",
"chars": 30348,
"preview": "var auto_complete=['Call Method\t[object]\t[method_name]\t[*args]\t[**kwargs]','Catenate\t[*items]','Comment\t[*messages]','Co"
},
{
"path": "auto/www/static/js/highlight.js",
"chars": 13123,
"preview": "var high_light=['Call Method','Catenate','Comment','Continue For Loop','Continue For Loop If','Convert To Binary','Conve"
},
{
"path": "auto/www/templates/dashboard.html",
"chars": 18661,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <title>AutoLink - 开源优测自动化测试集成解决方案</title>\n\n "
},
{
"path": "auto/www/templates/default.html",
"chars": 1683,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <title>AutoLink - 开源优测自动化测试集成解决方案</title>\n\n "
},
{
"path": "auto/www/templates/editor.html",
"chars": 6717,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <title>AutoLink - 开源优测自动化测试集成解决方案</title>\n\n "
},
{
"path": "auto/www/templates/login.html",
"chars": 3462,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <title>AutoLink - 开源优测自动化测试集成解决方案</title>\n\n "
},
{
"path": "auto/www/templates/report.html",
"chars": 9529,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <title>AutoLink - 开源优测自动化测试集成解决方案</title>\n\n "
},
{
"path": "auto/www/templates/scheduler.html",
"chars": 12339,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <title>AutoLink - 开源优测自动化测试集成解决方案</title>\n\n "
},
{
"path": "auto/www/templates/task_list.html",
"chars": 7421,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <title>AutoLink - 开源优测自动化测试集成解决方案</title>\n\n "
},
{
"path": "auto/www/templates/user.html",
"chars": 12911,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <title>AutoLink - 开源优测自动化测试集成解决方案</title>\n\n "
},
{
"path": "auto/www/templates/view_img.html",
"chars": 1443,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <title>AutoLink - 开源优测自动化测试集成解决方案</title>\n\n "
},
{
"path": "auto/www/templates/welcome.html",
"chars": 3112,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\">\n <title>AutoLink - 开源优测自动化测试集成解决方案</title>\n\n "
},
{
"path": "docs/README.md",
"chars": 1266,
"preview": "## 阅读地址\n\n- 用户指南是目前AutoLink最新由官方在维护的使用指南\n\n- 如果现在有任何不理解或有歧义的地方,可以在github提交issue指出来,我们会第一时间修订\n\n- 项目源码: [Github源码托管地址](https"
},
{
"path": "docs/上传和下载RobotFramework用例.md",
"chars": 253,
"preview": "## 简介\n\n在AutoLink中,支持文件的上传和下载功能,具体的操作如下\n\n## 上传文件\n\n在用例层级的节点右击,单击\"上传文件\"菜单,选择本地的要上传的文件,即可将文件上传,如图:\n\n\n\n## 快捷键应用二\n\n在输入一个字符的情况下,按下Ctrl键,快速调出"
},
{
"path": "docs/如何使用调度管理.md",
"chars": 406,
"preview": "## 简介\n在AutoLink中集成了APscheduler作为调度管理器。对APscheduler源码或使用有兴趣的可以参见其官网:https://apscheduler.readthedocs.io/en/latest/userguid"
},
{
"path": "docs/如何创建HTTP接口测试用例.md",
"chars": 706,
"preview": "## 如何创建HTTP接口测试用例\n\n### 关于RequestsLibrary\n- RequestsLibrary是RobotFramework的第三方库,是基于python的第三方库requests实现的。\n因此,RequestsLib"
},
{
"path": "docs/如何创建测试项目.md",
"chars": 161,
"preview": "## 如何添加项目\n\n1. 在左边工作区根节点右击,在弹出的菜单选择\"创建项目\", 如图:\n\n\n2. 在弹出是创建项目对话框中,输入项目名称,单击\"创建\"按钮,完成项目创建,"
},
{
"path": "docs/如何查看关键字详细文档.md",
"chars": 40,
"preview": "如图所示\n\n"
},
{
"path": "docs/如何管理测试项目中用例顺序.md",
"chars": 203,
"preview": "## 标准测试项目\n\n下面我们通过一张图看下标准的测试项目组织是怎么样的,如图:\n\n\n\n由图我们知道在AutoLink中项目由三层组织构成:\n\n1. 项目层\n2. 套件层\n"
},
{
"path": "docs/如何调用Python自定义库.md",
"chars": 251,
"preview": "## 简介\n\n在AutoLink中,我们如何用Python写我们自己的测试库呢?\n\n\n## 项目结构\n\n如下图所示,在AutoLink中写Python\n\n\n\n##"
},
{
"path": "docs/如何运行测试项目.md",
"chars": 201,
"preview": "## 如何运行测试项目\n\n### 方式一\n在项目节点上右击,选择\"运行\",即可立即运行该测试项目,如图\n\n\n### 方式二\n\n在项目节点上右击,选择\"查看任务\" -> 在查看任务页"
},
{
"path": "docs/安装与启动.md",
"chars": 710,
"preview": "## 安装\n\n1. 安装Python3及以上版本,确保加入环境变量,pip命令可用\n\n2. 从[AutoLink Github项目](https://github.com/small99/AutoLink)下载源码\n\n3. cd到AutoL"
},
{
"path": "docs/查看测试报告.md",
"chars": 148,
"preview": "## 步骤\n\n1. 在对应的项目节点上右击选择\"查看任务\", 如图\n\n\n\n2. 在该项目任务tab页,单击对应的项目及编号即可\n\n\n\n3. 查看"
},
{
"path": "docs/配置SMTP服务及邮件通知.md",
"chars": 131,
"preview": "## smtp配置\n\n以QQ邮件smtp配置为例\n\n\n\n## 通知列表配置\n\n\n\n## 邮件通知结果\n\n 2017 by Marijn Haverbeke <marijnh@gmail.com> and others\n\nPermission is hereby granted, free o"
},
{
"path": "licenses/LICENSE-jquery",
"chars": 1131,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2011-2018 Twitter, Inc.\nCopyright (c) 2011-2018 The Bootstrap Authors\n\nPermission i"
},
{
"path": "licenses/LICENSE-wide.html",
"chars": 112874,
"preview": "\n\n\n\n\n\n<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <link rel=\"dns-prefetch\" href=\"https://asse"
},
{
"path": "licenses/license_freeware-easyui.txt",
"chars": 1128,
"preview": "This license agreement refers to EasyUI for jQuery software - Freeware License.\r\n\r\nEasyUI Team grants to you a limited, "
},
{
"path": "requirements.txt",
"chars": 448,
"preview": "Flask_Login==0.2.11\nFlask==0.11\nFlask_SSLify==0.1.5\nFlask_APScheduler==1.8.0\nFlask_RESTful==0.3.6\nFlask_Script==2.0.6\nWe"
},
{
"path": "utils/__init__.py",
"chars": 93,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n"
},
{
"path": "utils/file.py",
"chars": 1748,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\nimport os, stat, codecs\nim"
},
{
"path": "utils/help.py",
"chars": 648,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\nimport codecs\nimport reque"
},
{
"path": "utils/parsing.py",
"chars": 4618,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\nimport os\nimport codecs\nim"
},
{
"path": "utils/resource.py",
"chars": 371,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\n\nICONS = {\n \".robot\": \"i"
},
{
"path": "utils/run.py",
"chars": 9130,
"preview": "# -*- coding: utf-8 -*-\n\n__author__ = \"苦叶子\"\n\n\"\"\"\n\n公众号: 开源优测\n\nEmail: lymking@foxmail.com\n\n\"\"\"\nimport sys\nimport os\nimport"
},
{
"path": "version.txt",
"chars": 6,
"preview": "1.0.12"
}
]
About this extraction
This page contains the full source code of the small99/AutoLink GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 84 files (895.0 KB), approximately 241.2k tokens, and a symbol index with 184 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.