diff --git a/README.md b/README.md
index b730e1c5..6fc4eba0 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ Thank you to all our sponsors! ✨🍰✨ ([become a sponsor](docs/sponsors.md))
### 金牌赞助商(Gold Sponsor)
-[
](https://testing-studio.com)
+[
](https://testing-studio.com)
> [霍格沃兹测试学院](https://testing-studio.com) 是由测吧(北京)科技有限公司与知名软件测试社区 [TesterHome](https://testerhome.com/) 合作的高端教育品牌。由 BAT 一线**测试大咖执教**,提供**实战驱动**的接口自动化测试、移动自动化测试、性能测试、持续集成与 DevOps 等技术培训,以及测试开发优秀人才内推服务。[点击学习!](https://ke.qq.com/course/348893?flowToken=1014523)
@@ -59,7 +59,7 @@ Thank you to all our sponsors! ✨🍰✨ ([become a sponsor](docs/sponsors.md))
关注 HttpRunner 的微信公众号,第一时间获得最新资讯。
-
+
[Requests]: http://docs.python-requests.org/en/master/
[unittest]: https://docs.python.org/3/library/unittest.html
diff --git a/docs/CNAME b/docs/CNAME
new file mode 100644
index 00000000..3c1a8252
--- /dev/null
+++ b/docs/CNAME
@@ -0,0 +1 @@
+docs.httprunner.org
diff --git a/docs/FAQ.md b/docs/FAQ.md
new file mode 100644
index 00000000..b43101d1
--- /dev/null
+++ b/docs/FAQ.md
@@ -0,0 +1 @@
+# 常见问题
diff --git a/docs/Installation.md b/docs/Installation.md
new file mode 100644
index 00000000..16f1d750
--- /dev/null
+++ b/docs/Installation.md
@@ -0,0 +1,127 @@
+## 运行环境
+
+HttpRunner 是一个基于 Python 开发的测试框架,可以运行在 macOS、Linux、Windows 系统平台上。
+
+**Python 版本**:HttpRunner 支持 Python 3.4 及以上的所有版本,并使用 Travis-CI 进行了[持续集成测试][travis-ci],测试覆盖的版本包括 2.7/3.4/3.5/3.6/3.7。虽然 HttpRunner 暂时保留了对 Python 2.7 的兼容支持,但强烈建议使用 Python 3.4 及以上版本。
+
+**操作系统**:推荐使用 macOS/Linux。
+
+## 安装方式
+
+HttpRunner 的稳定版本托管在 PyPI 上,可以使用 `pip` 进行安装。
+
+```bash
+$ pip install httprunner
+```
+
+如果你需要使用最新的开发版本,那么可以采用项目的 GitHub 仓库地址进行安装:
+
+```bash
+$ pip install git+https://github.com/HttpRunner/HttpRunner.git@master
+```
+
+## 版本升级
+
+假如你之前已经安装过了 HttpRunner,现在需要升级到最新版本,那么你可以使用`-U`参数。该参数对以上三种安装方式均生效。
+
+```bash
+$ pip install -U HttpRunner
+$ pip install -U git+https://github.com/HttpRunner/HttpRunner.git@master
+```
+
+## 安装校验
+
+在 HttpRunner 安装成功后,系统中会新增如下 5 个命令:
+
+- `httprunner`: 核心命令
+- `ate`: 曾经用过的命令(当时框架名称为 ApiTestEngine),功能与 httprunner 完全相同
+- `hrun`: httprunner 的缩写,功能与 httprunner 完全相同
+- `locusts`: 基于 [Locust][Locust] 实现[性能测试](run-tests/load-test.md)
+- [`har2case`][har2case]: 辅助工具,可将标准通用的 HAR 格式(HTTP Archive)转换为`YAML/JSON`格式的测试用例
+
+httprunner、hrun、ate 三个命令完全等价,功能特性完全相同,个人推荐使用`hrun`命令。
+
+运行如下命令,若正常显示版本号,则说明 HttpRunner 安装成功。
+
+```text
+$ hrun -V
+2.0.2
+
+$ har2case -V
+0.2.0
+```
+
+## 开发者模式
+
+默认情况下,安装 HttpRunner 的时候只会安装运行 HttpRunner 的必要依赖库。
+
+如果你不仅仅是使用 HttpRunner,还需要对 HttpRunner 进行开发调试(debug),那么就需要进行如下操作。
+
+HttpRunner 使用 [pipenv][pipenv] 对依赖包进行管理,若你还没有安装 pipenv,需要先执行如下命令进行按照:
+
+```bash
+$ pip install pipenv
+```
+
+获取 HttpRunner 源码:
+
+```bash
+$ git clone https://github.com/HttpRunner/HttpRunner.git
+```
+
+进入仓库目录,安装所有依赖:
+
+```bash
+$ pipenv install --dev
+```
+
+运行单元测试,若测试全部通过,则说明环境正常。
+
+```bash
+$ pipenv run python -m unittest discover
+```
+
+查看 HttpRunner 的依赖情况:
+
+```text
+$ pipenv graph
+
+HttpRunner==2.0.0
+ - colorama [required: Any, installed: 0.4.0]
+ - colorlog [required: Any, installed: 3.1.4]
+ - har2case [required: Any, installed: 0.2.0]
+ - PyYAML [required: Any, installed: 3.13]
+ - Jinja2 [required: Any, installed: 2.10]
+ - MarkupSafe [required: >=0.23, installed: 1.0]
+ - PyYAML [required: Any, installed: 3.13]
+ - requests [required: Any, installed: 2.20.0]
+ - certifi [required: >=2017.4.17, installed: 2018.10.15]
+ - chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4]
+ - idna [required: >=2.5,<2.8, installed: 2.7]
+ - urllib3 [required: >=1.21.1,<1.25, installed: 1.24]
+ - requests-toolbelt [required: Any, installed: 0.8.0]
+ - requests [required: >=2.0.1,<3.0.0, installed: 2.20.0]
+ - certifi [required: >=2017.4.17, installed: 2018.10.15]
+ - chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4]
+ - idna [required: >=2.5,<2.8, installed: 2.7]
+ - urllib3 [required: >=1.21.1,<1.25, installed: 1.24]
+```
+
+调试运行方式:
+
+```bash
+# 调试运行 hrun
+$ pipenv run python main-debug.py hrun -h
+
+# 调试运行 locusts
+$ pipenv run python main-debug.py locusts -h
+```
+
+## Docker
+
+TODO
+
+[travis-ci]: https://travis-ci.org/HttpRunner/HttpRunner
+[Locust]: http://locust.io/
+[har2case]: https://github.com/HttpRunner/har2case
+[pipenv]: https://docs.pipenv.org/
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 00000000..9a4d0708
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,45 @@
+# HttpRunner V2.x 中文使用文档
+
+## 在线阅读
+
+本文档托管在`GitHub Pages`上,访问地址:
+
+https://cn.httprunner.org (托管在 GitHub Pages)
+
+或者
+
+https://httprunner.debugtalk.com (托管在七牛云 CDN)
+
+## 本地预览
+
+### 安装依赖
+
+本项目文档采用[`mkdocs`][mkdocs]生成,如需在本地预览查看,则需安装该工具。
+
+```bash
+$ pip install mkdocs
+```
+
+`mkdocs`支持主题配置,本项目选择了第三方的[`mkdocs-material`][mkdocs-material]。
+
+```bash
+$ pip install mkdocs-material
+```
+
+### 启动本地server
+
+在项目根目录中运行如下命令:
+
+```bash
+$ mkdocs serve
+INFO - Building documentation...
+INFO - Cleaning site directory
+[I 180211 22:48:35 server:283] Serving on http://127.0.0.1:8000
+[I 180211 22:48:35 handlers:60] Start watching changes
+[I 180211 22:48:35 handlers:62] Start detecting changes
+```
+
+然后在浏览器中访问`http://127.0.0.1:8000`即可。
+
+[mkdocs]: http://www.mkdocs.org/
+[mkdocs-material]: https://squidfunk.github.io/mkdocs-material/
\ No newline at end of file
diff --git a/docs/hogwarts.png b/docs/assets/hogwarts.png
similarity index 100%
rename from docs/hogwarts.png
rename to docs/assets/hogwarts.png
diff --git a/docs/qrcode.jpg b/docs/assets/qrcode.jpg
similarity index 100%
rename from docs/qrcode.jpg
rename to docs/assets/qrcode.jpg
diff --git a/docs/attachments/DJI-HttpRunner.pdf b/docs/attachments/DJI-HttpRunner.pdf
new file mode 100644
index 00000000..d73dcf5c
Binary files /dev/null and b/docs/attachments/DJI-HttpRunner.pdf differ
diff --git a/docs/attachments/MTSC2019-HttpRunner-2.0.pdf b/docs/attachments/MTSC2019-HttpRunner-2.0.pdf
new file mode 100644
index 00000000..a25e8caf
Binary files /dev/null and b/docs/attachments/MTSC2019-HttpRunner-2.0.pdf differ
diff --git a/docs/attachments/PyCon-HttpRunner.pdf b/docs/attachments/PyCon-HttpRunner.pdf
new file mode 100644
index 00000000..a5073f79
Binary files /dev/null and b/docs/attachments/PyCon-HttpRunner.pdf differ
diff --git a/docs/concept/nominal.md b/docs/concept/nominal.md
new file mode 100644
index 00000000..3d6af631
--- /dev/null
+++ b/docs/concept/nominal.md
@@ -0,0 +1,67 @@
+## 测试用例(testcase)
+
+从 2.0 版本开始,HttpRunner 开始对测试用例的定义进行进一步的明确,参考 [wiki][wiki_testcase] 上的描述。
+
+> A test case is a specification of the inputs, execution conditions, testing procedure, and expected results that define a single test to be executed to achieve a particular software testing objective, such as to exercise a particular program path or to verify compliance with a specific requirement.
+
+概括下来,一条测试用例(testcase)应该是为了测试某个特定的功能逻辑而精心设计的,并且至少包含如下几点:
+
+- 明确的测试目的(achieve a particular software testing objective)
+- 明确的输入(inputs)
+- 明确的运行环境(execution conditions)
+- 明确的测试步骤描述(testing procedure)
+- 明确的预期结果(expected results)
+
+对应地,HttpRunner 的测试用例描述方式进行如下设计:
+
+- 测试用例应该是完整且独立的,每条测试用例应该是都可以独立运行的;在 HttpRunner 中,每个 `YAML/JSON` 文件对应一条测试用例。
+- 测试用例包含 `测试脚本` 和 `测试数据` 两部分:
+ - `测试用例 = 测试脚本 + 测试数据`
+ - `测试脚本` 重点是描述测试的 `业务功能逻辑`,包括预置条件、测试步骤、预期结果等,并且可以结合辅助函数(debugtalk.py)实现复杂的运算逻辑;可以将 `测试脚本` 理解为编程语言中的 `类(class)`;
+ - `测试数据` 重点是对应测试的 `业务数据逻辑`,可以理解为类的实例化数据;
+ - `测试数据` 和 `测试脚本` 分离后,就可以比较方便地实现数据驱动测试,通过对测试脚本传入一组数据,实现同一业务功能在不同数据逻辑下的测试验证。
+
+
+## 测试步骤(teststep)
+
+测试用例是测试步骤的 `有序` 集合,而对于接口测试来说,每一个测试步骤应该就对应一个 API 的请求描述。
+
+## 测试用例集(testsuite)
+
+`测试用例集` 是 `测试用例` 的 `无序` 集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的。
+
+如果确实存在先后依赖关系怎么办,例如登录功能和下单功能。正确的做法应该是,在下单测试用例的前置步骤中执行登录操作。
+
+```yaml
+- config:
+ name: order product
+
+- test:
+ name: login
+ testcase: testcases/login.yml
+
+- test:
+ name: add to cart
+ api: api/add_cart.yml
+
+- test:
+ name: make order
+ api: api/make_order.yml
+```
+
+## 测试场景
+
+`测试场景` 和 `测试用例集` 是同一概念,都是 `测试用例` 的 `无序` 集合。
+
+
+- 接口
+- 测试用例集
+- 参数
+- 变量
+- 测试脚本(YAML/JSON)
+- debugtalk.py
+- 环境变量
+
+## 项目根目录
+
+[wiki_testcase]: https://en.wikipedia.org/wiki/Test_case
\ No newline at end of file
diff --git a/docs/data/account.csv b/docs/data/account.csv
new file mode 100644
index 00000000..2502b5b8
--- /dev/null
+++ b/docs/data/account.csv
@@ -0,0 +1,4 @@
+username,password,phone
+test1,111111,18600000001
+test2,222222,18600000002
+test3,333333,18600000003
\ No newline at end of file
diff --git a/docs/data/api_server.py b/docs/data/api_server.py
new file mode 100644
index 00000000..bff04ddf
--- /dev/null
+++ b/docs/data/api_server.py
@@ -0,0 +1,223 @@
+import hashlib
+import hmac
+import json
+import random
+import string
+from functools import wraps
+
+from flask import Flask, make_response, request
+
+SECRET_KEY = "DebugTalk"
+
+app = Flask(__name__)
+
+""" storage all users' data
+data structure:
+ users_dict = {
+ 'uid1': {
+ 'name': 'name1',
+ 'password': 'pwd1'
+ },
+ 'uid2': {
+ 'name': 'name2',
+ 'password': 'pwd2'
+ }
+ }
+"""
+users_dict = {}
+
+""" storage all token data
+data structure:
+ token_dict = {
+ 'device_sn1': 'token1',
+ 'device_sn2': 'token1'
+ }
+"""
+token_dict = {}
+
+
+def gen_random_string(str_len):
+ """ generate random string with specified length
+ """
+ return ''.join(
+ random.choice(string.ascii_letters + string.digits) for _ in range(str_len))
+
+def get_sign(*args):
+ content = ''.join(args).encode('ascii')
+ sign_key = SECRET_KEY.encode('ascii')
+ sign = hmac.new(sign_key, content, hashlib.sha1).hexdigest()
+ return sign
+
+def gen_md5(*args):
+ return hashlib.md5("".join(args).encode('utf-8')).hexdigest()
+
+
+def validate_request(func):
+
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ device_sn = request.headers.get('device_sn', "")
+ token = request.headers.get('token', "")
+
+ if not device_sn or not token:
+ result = {
+ 'success': False,
+ 'msg': "device_sn or token is null."
+ }
+ response = make_response(json.dumps(result), 401)
+ response.headers["Content-Type"] = "application/json"
+ return response
+
+ if token_dict.get(device_sn) != token:
+ result = {
+ 'success': False,
+ 'msg': "Authorization failed!"
+ }
+ response = make_response(json.dumps(result), 403)
+ response.headers["Content-Type"] = "application/json"
+ return response
+
+ return func(*args, **kwargs)
+
+ return wrapper
+
+
+@app.route('/')
+def index():
+ return "Hello World!"
+
+@app.route('/api/get-token', methods=['POST'])
+def get_token():
+ device_sn = request.headers.get('device_sn', "")
+ os_platform = request.headers.get('os_platform', "")
+ app_version = request.headers.get('app_version', "")
+ data = request.get_json()
+ sign = data.get('sign', "")
+
+ expected_sign = get_sign(device_sn, os_platform, app_version)
+
+ if expected_sign != sign:
+ result = {
+ 'success': False,
+ 'msg': "Authorization failed!"
+ }
+ response = make_response(json.dumps(result), 403)
+ else:
+ token = gen_random_string(16)
+ token_dict[device_sn] = token
+
+ result = {
+ 'success': True,
+ 'token': token
+ }
+ response = make_response(json.dumps(result))
+
+ response.headers["Content-Type"] = "application/json"
+ return response
+
+@app.route('/api/users')
+@validate_request
+def get_users():
+ users_list = [user for uid, user in users_dict.items()]
+ users = {
+ 'success': True,
+ 'count': len(users_list),
+ 'items': users_list
+ }
+ response = make_response(json.dumps(users))
+ response.headers["Content-Type"] = "application/json"
+ return response
+
+@app.route('/api/reset-all')
+@validate_request
+def clear_users():
+ users_dict.clear()
+ result = {
+ 'success': True
+ }
+ response = make_response(json.dumps(result))
+ response.headers["Content-Type"] = "application/json"
+ return response
+
+@app.route('/api/users/', methods=['POST'])
+@validate_request
+def create_user(uid):
+ user = request.get_json()
+ if uid not in users_dict:
+ result = {
+ 'success': True,
+ 'msg': "user created successfully."
+ }
+ status_code = 201
+ users_dict[uid] = user
+ else:
+ result = {
+ 'success': False,
+ 'msg': "user already existed."
+ }
+ status_code = 500
+
+ response = make_response(json.dumps(result), status_code)
+ response.headers["Content-Type"] = "application/json"
+ return response
+
+@app.route('/api/users/')
+@validate_request
+def get_user(uid):
+ user = users_dict.get(uid, {})
+ if user:
+ result = {
+ 'success': True,
+ 'data': user
+ }
+ status_code = 200
+ else:
+ result = {
+ 'success': False,
+ 'data': user
+ }
+ status_code = 404
+
+ response = make_response(json.dumps(result), status_code)
+ response.headers["Content-Type"] = "application/json"
+ return response
+
+@app.route('/api/users/', methods=['PUT'])
+@validate_request
+def update_user(uid):
+ user = users_dict.get(uid, {})
+ if user:
+ user = request.get_json()
+ success = True
+ status_code = 200
+ users_dict[uid] = user
+ else:
+ success = False
+ status_code = 404
+
+ result = {
+ 'success': success,
+ 'data': user
+ }
+ response = make_response(json.dumps(result), status_code)
+ response.headers["Content-Type"] = "application/json"
+ return response
+
+@app.route('/api/users/', methods=['DELETE'])
+@validate_request
+def delete_user(uid):
+ user = users_dict.pop(uid, {})
+ if user:
+ success = True
+ status_code = 200
+ else:
+ success = False
+ status_code = 404
+
+ result = {
+ 'success': success,
+ 'data': user
+ }
+ response = make_response(json.dumps(result), status_code)
+ response.headers["Content-Type"] = "application/json"
+ return response
diff --git a/docs/data/app_version.csv b/docs/data/app_version.csv
new file mode 100644
index 00000000..a0c236a4
--- /dev/null
+++ b/docs/data/app_version.csv
@@ -0,0 +1,3 @@
+app_version
+2.8.5
+2.8.6
diff --git a/docs/data/debugtalk.py b/docs/data/debugtalk.py
new file mode 100644
index 00000000..53b56d02
--- /dev/null
+++ b/docs/data/debugtalk.py
@@ -0,0 +1,48 @@
+import hashlib
+import hmac
+import random
+import string
+import time
+
+SECRET_KEY = "DebugTalk"
+
+def gen_random_string(str_len):
+ random_char_list = []
+ for _ in range(str_len):
+ random_char = random.choice(string.ascii_letters + string.digits)
+ random_char_list.append(random_char)
+
+ random_string = ''.join(random_char_list)
+ return random_string
+
+def get_sign(*args):
+ content = ''.join(args).encode('ascii')
+ sign_key = SECRET_KEY.encode('ascii')
+ sign = hmac.new(sign_key, content, hashlib.sha1).hexdigest()
+ return sign
+
+def gen_user_id():
+ return int(time.time() * 1000)
+
+def get_user_id():
+ return [
+ {"user_id": 1001},
+ {"user_id": 1002},
+ {"user_id": 1003},
+ {"user_id": 1004}
+ ]
+
+def get_account(num):
+ accounts = []
+ for index in range(1, num+1):
+ accounts.append(
+ {"username": "user%s" % index, "password": str(index) * 6},
+ )
+
+ return accounts
+
+def get_os_platform():
+ return [
+ {"os_platform": "ios"},
+ {"os_platform": "android"}
+ ]
diff --git a/docs/data/demo-parameters-get-token.yml b/docs/data/demo-parameters-get-token.yml
new file mode 100644
index 00000000..d1bfb633
--- /dev/null
+++ b/docs/data/demo-parameters-get-token.yml
@@ -0,0 +1,10 @@
+config:
+ name: get token with parameters
+
+testcases:
+ get token with $user_agent, $app_version, $os_platform:
+ testcase: demo-testcase-get-token.yml
+ parameters:
+ user_agent: ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
+ app_version: ${P(app_version.csv)}
+ os_platform: ${get_os_platform()}
diff --git a/docs/data/demo-quickstart-0.json b/docs/data/demo-quickstart-0.json
new file mode 100644
index 00000000..57dd4ce9
--- /dev/null
+++ b/docs/data/demo-quickstart-0.json
@@ -0,0 +1,58 @@
+[
+ {
+ "config": {
+ "name": "testcase description",
+ "variables": {}
+ }
+ },
+ {
+ "test": {
+ "name": "/api/get-token",
+ "request": {
+ "url": "http://127.0.0.1:5000/api/get-token",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "FwgRiO7CNA50DSU",
+ "os_platform": "ios",
+ "app_version": "2.8.6",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"
+ }
+ },
+ "validate": [
+ {"eq": ["status_code", 200]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]},
+ {"eq": ["content.token", "baNLX1zhFYP11Seb"]}
+ ]
+ }
+ },
+ {
+ "test": {
+ "name": "/api/users/1000",
+ "request": {
+ "url": "http://127.0.0.1:5000/api/users/1000",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "FwgRiO7CNA50DSU",
+ "token": "baNLX1zhFYP11Seb",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "name": "user1",
+ "password": "123456"
+ }
+ },
+ "validate": [
+ {"eq": ["status_code", 201]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]},
+ {"eq": ["content.msg", "user created successfully."]}
+ ]
+ }
+ }
+]
\ No newline at end of file
diff --git a/docs/data/demo-quickstart-0.yml b/docs/data/demo-quickstart-0.yml
new file mode 100644
index 00000000..9e50c8b6
--- /dev/null
+++ b/docs/data/demo-quickstart-0.yml
@@ -0,0 +1,41 @@
+- config:
+ name: testcase description
+ variables: {}
+
+- test:
+ name: /api/get-token
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ app_version: 2.8.6
+ device_sn: FwgRiO7CNA50DSU
+ os_platform: ios
+ json:
+ sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98
+ method: POST
+ url: http://127.0.0.1:5000/api/get-token
+ validate:
+ - eq: [status_code, 200]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+ - eq: [content.token, baNLX1zhFYP11Seb]
+
+- test:
+ name: /api/users/1000
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ device_sn: FwgRiO7CNA50DSU
+ token: baNLX1zhFYP11Seb
+ json:
+ name: user1
+ password: '123456'
+ method: POST
+ url: http://127.0.0.1:5000/api/users/1000
+ validate:
+ - eq: [status_code, 201]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+ - eq: [content.msg, user created successfully.]
\ No newline at end of file
diff --git a/docs/data/demo-quickstart-1.json b/docs/data/demo-quickstart-1.json
new file mode 100644
index 00000000..433666c8
--- /dev/null
+++ b/docs/data/demo-quickstart-1.json
@@ -0,0 +1,57 @@
+[
+ {
+ "config": {
+ "name": "testcase description",
+ "variables": {}
+ }
+ },
+ {
+ "test": {
+ "name": "/api/get-token",
+ "request": {
+ "url": "http://127.0.0.1:5000/api/get-token",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "FwgRiO7CNA50DSU",
+ "os_platform": "ios",
+ "app_version": "2.8.6",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"
+ }
+ },
+ "validate": [
+ {"eq": ["status_code", 200]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]}
+ ]
+ }
+ },
+ {
+ "test": {
+ "name": "/api/users/1000",
+ "request": {
+ "url": "http://127.0.0.1:5000/api/users/1000",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "FwgRiO7CNA50DSU",
+ "token": "baNLX1zhFYP11Seb",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "name": "user1",
+ "password": "123456"
+ }
+ },
+ "validate": [
+ {"eq": ["status_code", 201]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]},
+ {"eq": ["content.msg", "user created successfully."]}
+ ]
+ }
+ }
+]
\ No newline at end of file
diff --git a/docs/data/demo-quickstart-1.yml b/docs/data/demo-quickstart-1.yml
new file mode 100644
index 00000000..1874c75d
--- /dev/null
+++ b/docs/data/demo-quickstart-1.yml
@@ -0,0 +1,40 @@
+- config:
+ name: testcase description
+ variables: {}
+
+- test:
+ name: /api/get-token
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ app_version: 2.8.6
+ device_sn: FwgRiO7CNA50DSU
+ os_platform: ios
+ json:
+ sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98
+ method: POST
+ url: http://127.0.0.1:5000/api/get-token
+ validate:
+ - eq: [status_code, 200]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+
+- test:
+ name: /api/users/1000
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ device_sn: FwgRiO7CNA50DSU
+ token: baNLX1zhFYP11Seb
+ json:
+ name: user1
+ password: '123456'
+ method: POST
+ url: http://127.0.0.1:5000/api/users/1000
+ validate:
+ - eq: [status_code, 201]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+ - eq: [content.msg, user created successfully.]
\ No newline at end of file
diff --git a/docs/data/demo-quickstart-2.json b/docs/data/demo-quickstart-2.json
new file mode 100644
index 00000000..6c24da28
--- /dev/null
+++ b/docs/data/demo-quickstart-2.json
@@ -0,0 +1,60 @@
+[
+ {
+ "config": {
+ "name": "testcase description",
+ "variables": {}
+ }
+ },
+ {
+ "test": {
+ "name": "/api/get-token",
+ "request": {
+ "url": "http://127.0.0.1:5000/api/get-token",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "FwgRiO7CNA50DSU",
+ "os_platform": "ios",
+ "app_version": "2.8.6",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"
+ }
+ },
+ "extract": [
+ {"token": "content.token"}
+ ],
+ "validate": [
+ {"eq": ["status_code", 200]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]}
+ ]
+ }
+ },
+ {
+ "test": {
+ "name": "/api/users/1000",
+ "request": {
+ "url": "http://127.0.0.1:5000/api/users/1000",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "FwgRiO7CNA50DSU",
+ "token": "$token",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "name": "user1",
+ "password": "123456"
+ }
+ },
+ "validate": [
+ {"eq": ["status_code", 201]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]},
+ {"eq": ["content.msg", "user created successfully."]}
+ ]
+ }
+ }
+]
diff --git a/docs/data/demo-quickstart-2.yml b/docs/data/demo-quickstart-2.yml
new file mode 100644
index 00000000..17924f0d
--- /dev/null
+++ b/docs/data/demo-quickstart-2.yml
@@ -0,0 +1,42 @@
+- config:
+ name: testcase description
+ variables: {}
+
+- test:
+ name: /api/get-token
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ app_version: 2.8.6
+ device_sn: FwgRiO7CNA50DSU
+ os_platform: ios
+ json:
+ sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98
+ method: POST
+ url: http://127.0.0.1:5000/api/get-token
+ extract:
+ token: content.token
+ validate:
+ - eq: [status_code, 200]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+
+- test:
+ name: /api/users/1000
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ device_sn: FwgRiO7CNA50DSU
+ token: $token
+ json:
+ name: user1
+ password: '123456'
+ method: POST
+ url: http://127.0.0.1:5000/api/users/1000
+ validate:
+ - eq: [status_code, 201]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+ - eq: [content.msg, user created successfully.]
diff --git a/docs/data/demo-quickstart-3.json b/docs/data/demo-quickstart-3.json
new file mode 100644
index 00000000..fd276ca7
--- /dev/null
+++ b/docs/data/demo-quickstart-3.json
@@ -0,0 +1,61 @@
+[
+ {
+ "config": {
+ "name": "testcase description",
+ "base_url": "http://127.0.0.1:5000",
+ "variables": {}
+ }
+ },
+ {
+ "test": {
+ "name": "/api/get-token",
+ "request": {
+ "url": "/api/get-token",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "FwgRiO7CNA50DSU",
+ "os_platform": "ios",
+ "app_version": "2.8.6",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"
+ }
+ },
+ "extract": [
+ {"token": "content.token"}
+ ],
+ "validate": [
+ {"eq": ["status_code", 200]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]}
+ ]
+ }
+ },
+ {
+ "test": {
+ "name": "/api/users/1000",
+ "request": {
+ "url": "/api/users/1000",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "FwgRiO7CNA50DSU",
+ "token": "$token",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "name": "user1",
+ "password": "123456"
+ }
+ },
+ "validate": [
+ {"eq": ["status_code", 201]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]},
+ {"eq": ["content.msg", "user created successfully."]}
+ ]
+ }
+ }
+]
diff --git a/docs/data/demo-quickstart-3.yml b/docs/data/demo-quickstart-3.yml
new file mode 100644
index 00000000..654a383b
--- /dev/null
+++ b/docs/data/demo-quickstart-3.yml
@@ -0,0 +1,43 @@
+- config:
+ name: testcase description
+ base_url: http://127.0.0.1:5000
+ variables: {}
+
+- test:
+ name: /api/get-token
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ app_version: 2.8.6
+ device_sn: FwgRiO7CNA50DSU
+ os_platform: ios
+ json:
+ sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98
+ method: POST
+ url: /api/get-token
+ extract:
+ token: content.token
+ validate:
+ - eq: [status_code, 200]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+
+- test:
+ name: /api/users/1000
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ device_sn: FwgRiO7CNA50DSU
+ token: $token
+ json:
+ name: user1
+ password: '123456'
+ method: POST
+ url: /api/users/1000
+ validate:
+ - eq: [status_code, 201]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+ - eq: [content.msg, user created successfully.]
diff --git a/docs/data/demo-quickstart-4.json b/docs/data/demo-quickstart-4.json
new file mode 100644
index 00000000..ef03348e
--- /dev/null
+++ b/docs/data/demo-quickstart-4.json
@@ -0,0 +1,70 @@
+[
+ {
+ "config": {
+ "name": "testcase description",
+ "base_url": "http://127.0.0.1:5000",
+ "variables": {}
+ }
+ },
+ {
+ "test": {
+ "name": "/api/get-token",
+ "variables": {
+ "device_sn": "FwgRiO7CNA50DSU",
+ "os_platform": "ios",
+ "app_version": "2.8.6"
+ },
+ "request": {
+ "url": "/api/get-token",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "$device_sn",
+ "os_platform": "$os_platform",
+ "app_version": "$app_version",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"
+ }
+ },
+ "extract": [
+ {"token": "content.token"}
+ ],
+ "validate": [
+ {"eq": ["status_code", 200]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]}
+ ]
+ }
+ },
+ {
+ "test": {
+ "name": "/api/users/$user_id",
+ "variables": {
+ "device_sn": "FwgRiO7CNA50DSU",
+ "user_id": "1000"
+ },
+ "request": {
+ "url": "/api/users/$user_id",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "$device_sn",
+ "token": "$token",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "name": "user1",
+ "password": "123456"
+ }
+ },
+ "validate": [
+ {"eq": ["status_code", 201]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]},
+ {"eq": ["content.msg", "user created successfully."]}
+ ]
+ }
+ }
+]
\ No newline at end of file
diff --git a/docs/data/demo-quickstart-4.yml b/docs/data/demo-quickstart-4.yml
new file mode 100644
index 00000000..514b25f6
--- /dev/null
+++ b/docs/data/demo-quickstart-4.yml
@@ -0,0 +1,50 @@
+- config:
+ name: testcase description
+ base_url: http://127.0.0.1:5000
+ variables: {}
+
+- test:
+ name: /api/get-token
+ variables:
+ app_version: 2.8.6
+ device_sn: FwgRiO7CNA50DSU
+ os_platform: ios
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ app_version: $app_version
+ device_sn: $device_sn
+ os_platform: $os_platform
+ json:
+ sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98
+ method: POST
+ url: /api/get-token
+ extract:
+ token: content.token
+ validate:
+ - eq: [status_code, 200]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+
+- test:
+ name: /api/users/$user_id
+ variables:
+ device_sn: FwgRiO7CNA50DSU
+ user_id: 1000
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ device_sn: $device_sn
+ token: $token
+ json:
+ name: user1
+ password: '123456'
+ method: POST
+ url: /api/users/$user_id
+ validate:
+ - eq: [status_code, 201]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+ - eq: [content.msg, user created successfully.]
diff --git a/docs/data/demo-quickstart-5.json b/docs/data/demo-quickstart-5.json
new file mode 100644
index 00000000..4ede41f7
--- /dev/null
+++ b/docs/data/demo-quickstart-5.json
@@ -0,0 +1,70 @@
+[
+ {
+ "config": {
+ "name": "testcase description",
+ "base_url": "http://127.0.0.1:5000",
+ "variables": {
+ "device_sn": "FwgRiO7CNA50DSU"
+ }
+ }
+ },
+ {
+ "test": {
+ "name": "/api/get-token",
+ "variables": {
+ "os_platform": "ios",
+ "app_version": "2.8.6"
+ },
+ "request": {
+ "url": "/api/get-token",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "$device_sn",
+ "os_platform": "$os_platform",
+ "app_version": "$app_version",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"
+ }
+ },
+ "extract": [
+ {"token": "content.token"}
+ ],
+ "validate": [
+ {"eq": ["status_code", 200]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]}
+ ]
+ }
+ },
+ {
+ "test": {
+ "name": "/api/users/$user_id",
+ "variables": {
+ "user_id": "1000"
+ },
+ "request": {
+ "url": "/api/users/$user_id",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "$device_sn",
+ "token": "$token",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "name": "user1",
+ "password": "123456"
+ }
+ },
+ "validate": [
+ {"eq": ["status_code", 201]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]},
+ {"eq": ["content.msg", "user created successfully."]}
+ ]
+ }
+ }
+]
\ No newline at end of file
diff --git a/docs/data/demo-quickstart-5.yml b/docs/data/demo-quickstart-5.yml
new file mode 100644
index 00000000..98445b7f
--- /dev/null
+++ b/docs/data/demo-quickstart-5.yml
@@ -0,0 +1,49 @@
+- config:
+ name: testcase description
+ base_url: http://127.0.0.1:5000
+ variables:
+ device_sn: FwgRiO7CNA50DSU
+
+- test:
+ name: /api/get-token
+ variables:
+ app_version: 2.8.6
+ os_platform: ios
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ app_version: $app_version
+ device_sn: $device_sn
+ os_platform: $os_platform
+ json:
+ sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98
+ method: POST
+ url: /api/get-token
+ extract:
+ token: content.token
+ validate:
+ - eq: [status_code, 200]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+
+- test:
+ name: /api/users/$user_id
+ variables:
+ user_id: 1000
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ device_sn: $device_sn
+ token: $token
+ json:
+ name: user1
+ password: '123456'
+ method: POST
+ url: /api/users/$user_id
+ validate:
+ - eq: [status_code, 201]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+ - eq: [content.msg, user created successfully.]
diff --git a/docs/data/demo-quickstart-6.json b/docs/data/demo-quickstart-6.json
new file mode 100644
index 00000000..da126fca
--- /dev/null
+++ b/docs/data/demo-quickstart-6.json
@@ -0,0 +1,70 @@
+[
+ {
+ "config": {
+ "name": "testcase description",
+ "base_url": "http://127.0.0.1:5000",
+ "variables": {
+ "device_sn": "${gen_random_string(15)}"
+ }
+ }
+ },
+ {
+ "test": {
+ "name": "/api/get-token",
+ "variables": {
+ "os_platform": "ios",
+ "app_version": "2.8.6"
+ },
+ "request": {
+ "url": "/api/get-token",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "$device_sn",
+ "os_platform": "$os_platform",
+ "app_version": "$app_version",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "sign": "${get_sign($device_sn, $os_platform, $app_version)}"
+ }
+ },
+ "extract": [
+ {"token": "content.token"}
+ ],
+ "validate": [
+ {"eq": ["status_code", 200]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]}
+ ]
+ }
+ },
+ {
+ "test": {
+ "name": "/api/users/$user_id",
+ "variables": {
+ "user_id": "${gen_user_id()}"
+ },
+ "request": {
+ "url": "/api/users/$user_id",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "$device_sn",
+ "token": "$token",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "name": "user1",
+ "password": "123456"
+ }
+ },
+ "validate": [
+ {"eq": ["status_code", 201]},
+ {"eq": ["headers.Content-Type", "application/json"]},
+ {"eq": ["content.success", true]},
+ {"eq": ["content.msg", "user created successfully."]}
+ ]
+ }
+ }
+]
\ No newline at end of file
diff --git a/docs/data/demo-quickstart-6.yml b/docs/data/demo-quickstart-6.yml
new file mode 100644
index 00000000..b5acf937
--- /dev/null
+++ b/docs/data/demo-quickstart-6.yml
@@ -0,0 +1,49 @@
+- config:
+ name: testcase description
+ base_url: http://127.0.0.1:5000
+ variables:
+ device_sn: ${gen_random_string(15)}
+
+- test:
+ name: /api/get-token
+ variables:
+ app_version: 2.8.6
+ os_platform: ios
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ app_version: $app_version
+ device_sn: $device_sn
+ os_platform: $os_platform
+ json:
+ sign: ${get_sign($device_sn, $os_platform, $app_version)}
+ method: POST
+ url: /api/get-token
+ extract:
+ token: content.token
+ validate:
+ - eq: [status_code, 200]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+
+- test:
+ name: /api/users/$user_id
+ variables:
+ user_id: ${gen_user_id()}
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ device_sn: $device_sn
+ token: $token
+ json:
+ name: user1
+ password: '123456'
+ method: POST
+ url: /api/users/$user_id
+ validate:
+ - eq: [status_code, 201]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
+ - eq: [content.msg, user created successfully.]
diff --git a/docs/data/demo-quickstart-7.json b/docs/data/demo-quickstart-7.json
new file mode 100644
index 00000000..89d57f8d
--- /dev/null
+++ b/docs/data/demo-quickstart-7.json
@@ -0,0 +1,13 @@
+{
+ "config": {
+ "name": "create users with parameters"
+ },
+ "testcases": {
+ "create user $user_id": {
+ "testcase": "demo-quickstart-6.yml",
+ "parameters": {
+ "user_id": [1001, 1002, 1003, 1004]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/data/demo-quickstart-7.yml b/docs/data/demo-quickstart-7.yml
new file mode 100644
index 00000000..770dc45f
--- /dev/null
+++ b/docs/data/demo-quickstart-7.yml
@@ -0,0 +1,8 @@
+config:
+ name: testcase description
+
+testcases:
+ create user $user_id:
+ testcase: demo-quickstart-6.yml
+ parameters:
+ user_id: [1001, 1002, 1003, 1004]
diff --git a/docs/data/demo-quickstart.har b/docs/data/demo-quickstart.har
new file mode 100644
index 00000000..d4924ba4
--- /dev/null
+++ b/docs/data/demo-quickstart.har
@@ -0,0 +1,221 @@
+{
+ "log": {
+ "version": "1.2",
+ "creator": {
+ "name": "Charles Proxy",
+ "version": "4.2.1"
+ },
+ "entries": [
+ {
+ "startedDateTime": "2018-02-19T17:30:00.904+08:00",
+ "time": 3,
+ "request": {
+ "method": "POST",
+ "url": "http://127.0.0.1:5000/api/get-token",
+ "httpVersion": "HTTP/1.1",
+ "cookies": [],
+ "headers": [
+ {
+ "name": "Host",
+ "value": "127.0.0.1:5000"
+ },
+ {
+ "name": "User-Agent",
+ "value": "python-requests/2.18.4"
+ },
+ {
+ "name": "Accept-Encoding",
+ "value": "gzip, deflate"
+ },
+ {
+ "name": "Accept",
+ "value": "*/*"
+ },
+ {
+ "name": "Connection",
+ "value": "keep-alive"
+ },
+ {
+ "name": "device_sn",
+ "value": "FwgRiO7CNA50DSU"
+ },
+ {
+ "name": "os_platform",
+ "value": "ios"
+ },
+ {
+ "name": "app_version",
+ "value": "2.8.6"
+ },
+ {
+ "name": "Content-Length",
+ "value": "52"
+ },
+ {
+ "name": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "queryString": [],
+ "postData": {
+ "mimeType": "application/json",
+ "text": "{\"sign\": \"9c0c7e51c91ae963c833a4ccbab8d683c4a90c98\"}"
+ },
+ "headersSize": 299,
+ "bodySize": 52
+ },
+ "response": {
+ "_charlesStatus": "COMPLETE",
+ "status": 200,
+ "statusText": "OK",
+ "httpVersion": "HTTP/1.0",
+ "cookies": [],
+ "headers": [
+ {
+ "name": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "name": "Content-Length",
+ "value": "46"
+ },
+ {
+ "name": "Server",
+ "value": "Werkzeug/0.14.1 Python/3.6.4"
+ },
+ {
+ "name": "Date",
+ "value": "Mon, 19 Feb 2018 09:30:00 GMT"
+ },
+ {
+ "name": "Proxy-Connection",
+ "value": "Close"
+ }
+ ],
+ "content": {
+ "size": 46,
+ "mimeType": "application/json",
+ "text": "eyJzdWNjZXNzIjogdHJ1ZSwgInRva2VuIjogImJhTkxYMXpoRllQMTFTZWIifQ==",
+ "encoding": "base64"
+ },
+ "redirectURL": null,
+ "headersSize": 175,
+ "bodySize": 46
+ },
+ "serverIPAddress": "127.0.0.1",
+ "cache": {},
+ "timings": {
+ "dns": 1,
+ "connect": 0,
+ "ssl": -1,
+ "send": 0,
+ "wait": 1,
+ "receive": 1
+ }
+ },
+ {
+ "startedDateTime": "2018-02-19T17:30:00.911+08:00",
+ "time": 3,
+ "request": {
+ "method": "POST",
+ "url": "http://127.0.0.1:5000/api/users/1000",
+ "httpVersion": "HTTP/1.1",
+ "cookies": [],
+ "headers": [
+ {
+ "name": "Host",
+ "value": "127.0.0.1:5000"
+ },
+ {
+ "name": "User-Agent",
+ "value": "python-requests/2.18.4"
+ },
+ {
+ "name": "Accept-Encoding",
+ "value": "gzip, deflate"
+ },
+ {
+ "name": "Accept",
+ "value": "*/*"
+ },
+ {
+ "name": "Connection",
+ "value": "keep-alive"
+ },
+ {
+ "name": "device_sn",
+ "value": "FwgRiO7CNA50DSU"
+ },
+ {
+ "name": "token",
+ "value": "baNLX1zhFYP11Seb"
+ },
+ {
+ "name": "Content-Length",
+ "value": "39"
+ },
+ {
+ "name": "Content-Type",
+ "value": "application/json"
+ }
+ ],
+ "queryString": [],
+ "postData": {
+ "mimeType": "application/json",
+ "text": "{\"name\": \"user1\", \"password\": \"123456\"}"
+ },
+ "headersSize": 265,
+ "bodySize": 39
+ },
+ "response": {
+ "_charlesStatus": "COMPLETE",
+ "status": 201,
+ "statusText": "CREATED",
+ "httpVersion": "HTTP/1.0",
+ "cookies": [],
+ "headers": [
+ {
+ "name": "Content-Type",
+ "value": "application/json"
+ },
+ {
+ "name": "Content-Length",
+ "value": "54"
+ },
+ {
+ "name": "Server",
+ "value": "Werkzeug/0.14.1 Python/3.6.4"
+ },
+ {
+ "name": "Date",
+ "value": "Mon, 19 Feb 2018 09:30:00 GMT"
+ },
+ {
+ "name": "Proxy-Connection",
+ "value": "Close"
+ }
+ ],
+ "content": {
+ "size": 54,
+ "mimeType": "application/json",
+ "text": "eyJzdWNjZXNzIjogdHJ1ZSwgIm1zZyI6ICJ1c2VyIGNyZWF0ZWQgc3VjY2Vzc2Z1bGx5LiJ9",
+ "encoding": "base64"
+ },
+ "redirectURL": null,
+ "headersSize": 77,
+ "bodySize": 54
+ },
+ "serverIPAddress": "127.0.0.1",
+ "cache": {},
+ "timings": {
+ "dns": 0,
+ "connect": 0,
+ "ssl": -1,
+ "send": 0,
+ "wait": 3,
+ "receive": 0
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/docs/data/demo-quickstart.json b/docs/data/demo-quickstart.json
new file mode 100644
index 00000000..88933729
--- /dev/null
+++ b/docs/data/demo-quickstart.json
@@ -0,0 +1,98 @@
+[
+ {
+ "config": {
+ "name": "testcase description",
+ "variables": {}
+ }
+ },
+ {
+ "test": {
+ "name": "/api/get-token",
+ "request": {
+ "url": "http://127.0.0.1:5000/api/get-token",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "FwgRiO7CNA50DSU",
+ "os_platform": "ios",
+ "app_version": "2.8.6",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"
+ }
+ },
+ "validate": [
+ {
+ "eq": [
+ "status_code",
+ 200
+ ]
+ },
+ {
+ "eq": [
+ "headers.Content-Type",
+ "application/json"
+ ]
+ },
+ {
+ "eq": [
+ "content.success",
+ true
+ ]
+ },
+ {
+ "eq": [
+ "content.token",
+ "baNLX1zhFYP11Seb"
+ ]
+ }
+ ]
+ }
+ },
+ {
+ "test": {
+ "name": "/api/users/1000",
+ "request": {
+ "url": "http://127.0.0.1:5000/api/users/1000",
+ "method": "POST",
+ "headers": {
+ "User-Agent": "python-requests/2.18.4",
+ "device_sn": "FwgRiO7CNA50DSU",
+ "token": "baNLX1zhFYP11Seb",
+ "Content-Type": "application/json"
+ },
+ "json": {
+ "name": "user1",
+ "password": "123456"
+ }
+ },
+ "validate": [
+ {
+ "eq": [
+ "status_code",
+ 201
+ ]
+ },
+ {
+ "eq": [
+ "headers.Content-Type",
+ "application/json"
+ ]
+ },
+ {
+ "eq": [
+ "content.success",
+ true
+ ]
+ },
+ {
+ "eq": [
+ "content.msg",
+ "user created successfully."
+ ]
+ }
+ ]
+ }
+ }
+]
\ No newline at end of file
diff --git a/docs/data/demo-quickstart.yml b/docs/data/demo-quickstart.yml
new file mode 100644
index 00000000..6eb2f64c
--- /dev/null
+++ b/docs/data/demo-quickstart.yml
@@ -0,0 +1,55 @@
+- config:
+ name: testcase description
+ variables: {}
+- test:
+ name: /api/get-token
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ app_version: 2.8.6
+ device_sn: FwgRiO7CNA50DSU
+ os_platform: ios
+ json:
+ sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98
+ method: POST
+ url: http://127.0.0.1:5000/api/get-token
+ validate:
+ - eq:
+ - status_code
+ - 200
+ - eq:
+ - headers.Content-Type
+ - application/json
+ - eq:
+ - content.success
+ - true
+ - eq:
+ - content.token
+ - baNLX1zhFYP11Seb
+- test:
+ name: /api/users/1000
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ device_sn: FwgRiO7CNA50DSU
+ token: baNLX1zhFYP11Seb
+ json:
+ name: user1
+ password: '123456'
+ method: POST
+ url: http://127.0.0.1:5000/api/users/1000
+ validate:
+ - eq:
+ - status_code
+ - 201
+ - eq:
+ - headers.Content-Type
+ - application/json
+ - eq:
+ - content.success
+ - true
+ - eq:
+ - content.msg
+ - user created successfully.
diff --git a/docs/data/demo-testcase-get-token.yml b/docs/data/demo-testcase-get-token.yml
new file mode 100644
index 00000000..32db8d1e
--- /dev/null
+++ b/docs/data/demo-testcase-get-token.yml
@@ -0,0 +1,27 @@
+- config:
+ name: get token
+ base_url: http://127.0.0.1:5000
+ variables:
+ device_sn: ${gen_random_string(15)}
+ os_platform: 'ios'
+ app_version: '2.8.6'
+
+- test:
+ name: get token with $device_sn, $os_platform, $app_version
+ request:
+ headers:
+ Content-Type: application/json
+ User-Agent: python-requests/2.18.4
+ app_version: $app_version
+ device_sn: $device_sn
+ os_platform: $os_platform
+ json:
+ sign: ${get_sign($device_sn, $os_platform, $app_version)}
+ method: POST
+ url: /api/get-token
+ extract:
+ token: content.token
+ validate:
+ - eq: [status_code, 200]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
diff --git a/docs/data/demo_parameters.yml b/docs/data/demo_parameters.yml
new file mode 100644
index 00000000..76a8a4b9
--- /dev/null
+++ b/docs/data/demo_parameters.yml
@@ -0,0 +1,34 @@
+- config:
+ name: "user management testcase."
+ parameters:
+ - user_agent: ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
+ - app_version: ${P(app_version.csv)}
+ - os_platform: ${get_os_platform()}
+ variables:
+ - user_agent: 'iOS/10.3'
+ - device_sn: ${gen_random_string(15)}
+ - os_platform: 'ios'
+ - app_version: '2.8.6'
+ request:
+ base_url: http://127.0.0.1:5000
+ headers:
+ Content-Type: application/json
+ device_sn: $device_sn
+
+- test:
+ name: get token with $user_agent, $os_platform, $app_version
+ request:
+ url: /api/get-token
+ method: POST
+ headers:
+ app_version: $app_version
+ os_platform: $os_platform
+ user_agent: $user_agent
+ json:
+ sign: ${get_sign($user_agent, $device_sn, $os_platform, $app_version)}
+ extract:
+ - token: content.token
+ validate:
+ - eq: [status_code, 200]
+ - eq: [headers.Content-Type, application/json]
+ - eq: [content.success, true]
\ No newline at end of file
diff --git a/docs/data/testerhome-login.har b/docs/data/testerhome-login.har
new file mode 100644
index 00000000..f54e6707
--- /dev/null
+++ b/docs/data/testerhome-login.har
@@ -0,0 +1 @@
+{"log":{"version":"1.2","creator":{"name":"Charles Proxy","version":"4.2.6"},"entries":[{"startedDateTime":"2019-04-19T14:14:14.014+08:00","time":227,"request":{"method":"GET","url":"https://testerhome.com/account/sign_in","httpVersion":"HTTP/1.1","cookies":[{"name":"_ga","value":"GA1.2.162109905.1516957848"},{"name":"gsScrollPos-3842","value":"0"},{"name":"gsScrollPos-3871","value":""},{"name":"gsScrollPos-1094","value":"0"},{"name":"gsScrollPos-1837","value":"0"},{"name":"gsScrollPos-1993","value":""},{"name":"gsScrollPos-324","value":"0"},{"name":"gsScrollPos-861","value":"0"},{"name":"gsScrollPos-1568","value":"0"},{"name":"gsScrollPos-2909","value":"0"},{"name":"gsScrollPos-2429","value":""},{"name":"gsScrollPos-5380","value":"0"},{"name":"gsScrollPos-5417","value":"0"},{"name":"gsScrollPos-1937750581","value":""},{"name":"gsScrollPos-1937751827","value":"0"},{"name":"gsScrollPos-1937751846","value":"0"},{"name":"gsScrollPos-1937756244","value":"0"},{"name":"gsScrollPos-1937759407","value":"0"},{"name":"gsScrollPos-968","value":"0"},{"name":"gsScrollPos-3085","value":""},{"name":"hasSkipWelcomePage","value":"1"},{"name":"gsScrollPos-1874","value":"0"},{"name":"gsScrollPos-2049","value":"0"},{"name":"gsScrollPos-3406","value":""},{"name":"user_id","value":"NjEwOQ%3D%3D--a85617b1d508c153c6ac2d40bd49b25a1466988f"},{"name":"gsScrollPos-891","value":"0"},{"name":"gsScrollPos-931","value":""},{"name":"gsScrollPos-2436","value":"0"},{"name":"_gid","value":"GA1.2.113994526.1555584877"},{"name":"_homeland_session","value":"pMGgyE29RXzrANx5RkqLjCMIE%2FHB%2FxbtHPs1pETwS54JTS%2BaV7QSR10JxAXa6wXxVIKDfMclOhVyQF0ztWr51Z3pEDEZ8P5HRgW7UnnUXHU9LqyAA%2FLF8V3LptG6jYLODkS43KqwfgHQfq1oF9X%2FLDyuYhyfJH%2FQFJLanFfH7lq%2Bg6wJXSVBTvDDZh8m5wITIVhYd63fo8B5Eu3XkSbPpY%2B3PivBAyRiC5AjXEWnhDTDIGA%2BYXZ5hjGIOJRdhjhD1nF5OPq%2FNfG96u6yp5EBWbDknUicZ2YHGQ%3D%3D--G4cowQKL8hiscX9U--48zlnNPSvWTk2tgnQlIhzg%3D%3D"},{"name":"_gat","value":"1"}],"headers":[{"name":"Host","value":"testerhome.com"},{"name":"Connection","value":"keep-alive"},{"name":"Cache-Control","value":"max-age=0"},{"name":"Upgrade-Insecure-Requests","value":"1"},{"name":"User-Agent","value":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"},{"name":"Accept","value":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"},{"name":"Referer","value":"https://testerhome.com/notifications/personal"},{"name":"Accept-Encoding","value":"gzip, deflate, br"},{"name":"Accept-Language","value":"en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7"},{"name":"Cookie","value":"_ga=GA1.2.162109905.1516957848; gsScrollPos-3842=0; gsScrollPos-3871=; gsScrollPos-1094=0; gsScrollPos-1837=0; gsScrollPos-1993=; gsScrollPos-324=0; gsScrollPos-861=0; gsScrollPos-1568=0; gsScrollPos-2909=0; gsScrollPos-2429=; gsScrollPos-5380=0; gsScrollPos-5417=0; gsScrollPos-1937750581=; gsScrollPos-1937751827=0; gsScrollPos-1937751846=0; gsScrollPos-1937756244=0; gsScrollPos-1937759407=0; gsScrollPos-968=0; gsScrollPos-3085=; hasSkipWelcomePage=1; gsScrollPos-1874=0; gsScrollPos-2049=0; gsScrollPos-3406=; user_id=NjEwOQ%3D%3D--a85617b1d508c153c6ac2d40bd49b25a1466988f; gsScrollPos-891=0; gsScrollPos-931=; gsScrollPos-2436=0; _gid=GA1.2.113994526.1555584877; _homeland_session=pMGgyE29RXzrANx5RkqLjCMIE%2FHB%2FxbtHPs1pETwS54JTS%2BaV7QSR10JxAXa6wXxVIKDfMclOhVyQF0ztWr51Z3pEDEZ8P5HRgW7UnnUXHU9LqyAA%2FLF8V3LptG6jYLODkS43KqwfgHQfq1oF9X%2FLDyuYhyfJH%2FQFJLanFfH7lq%2Bg6wJXSVBTvDDZh8m5wITIVhYd63fo8B5Eu3XkSbPpY%2B3PivBAyRiC5AjXEWnhDTDIGA%2BYXZ5hjGIOJRdhjhD1nF5OPq%2FNfG96u6yp5EBWbDknUicZ2YHGQ%3D%3D--G4cowQKL8hiscX9U--48zlnNPSvWTk2tgnQlIhzg%3D%3D; _gat=1"},{"name":"If-None-Match","value":"W/\"bc9ae267fdcbd89bf1dfaea10dea2b0e\""}],"queryString":[],"headersSize":1666,"bodySize":0},"response":{"_charlesStatus":"COMPLETE","status":200,"statusText":"OK","httpVersion":"HTTP/1.1","cookies":[{"name":"_homeland_session","value":"%2BPqmS9%2B8BS0lpK1Q9Nzg5IkUR3B%2BECd1ApYtz33R4UNx60WgtFBIhojxj7lIlJniLVx2U%2BDTZm3wSK%2F85EjQNzjLlxKDMn%2FCkPR%2F9EOpyIhFV7NxFnrSFOAStblX8680tHPAI7uYY5bcR%2FF%2BuT4tAhM0uIRjMCIuFfSEFEvt%2BUnj7aVIkTzLjmmQMZ6EiiXjgwsseai2BDuRKH8Dhww0i%2FeizFdKEq2Uv1FoouIThFd0kjc9LpGnOIruhyE77h7XZw%2FoNOsGTZvH%2F%2BvLcRzBMbTGJGznvljXNg%3D%3D--NOyNIYFvHUu1Bf4g--ZP94D8QQDbRtiIVLWBidWQ%3D%3D","path":"/","domain":null,"expires":"Thu, 18 Jul 2019 06:14:16 -0000","httpOnly":true,"secure":true,"comment":null,"_maxAge":null}],"headers":[{"name":"Server","value":"nginx/1.10.2"},{"name":"Date","value":"Fri, 19 Apr 2019 06:14:16 GMT"},{"name":"Content-Type","value":"text/html; charset=utf-8"},{"name":"Transfer-Encoding","value":"chunked"},{"name":"Vary","value":"Accept-Encoding"},{"name":"X-Frame-Options","value":"SAMEORIGIN"},{"name":"X-XSS-Protection","value":"1; mode=block"},{"name":"X-Content-Type-Options","value":"nosniff"},{"name":"X-Download-Options","value":"noopen"},{"name":"X-Permitted-Cross-Domain-Policies","value":"none"},{"name":"Referrer-Policy","value":"strict-origin-when-cross-origin"},{"name":"ETag","value":"W/\"587c0b9f9cf24943552b421690023012\""},{"name":"Cache-Control","value":"max-age=0, private, must-revalidate"},{"name":"Content-Security-Policy","value":";"},{"name":"Set-Cookie","value":"_homeland_session=%2BPqmS9%2B8BS0lpK1Q9Nzg5IkUR3B%2BECd1ApYtz33R4UNx60WgtFBIhojxj7lIlJniLVx2U%2BDTZm3wSK%2F85EjQNzjLlxKDMn%2FCkPR%2F9EOpyIhFV7NxFnrSFOAStblX8680tHPAI7uYY5bcR%2FF%2BuT4tAhM0uIRjMCIuFfSEFEvt%2BUnj7aVIkTzLjmmQMZ6EiiXjgwsseai2BDuRKH8Dhww0i%2FeizFdKEq2Uv1FoouIThFd0kjc9LpGnOIruhyE77h7XZw%2FoNOsGTZvH%2F%2BvLcRzBMbTGJGznvljXNg%3D%3D--NOyNIYFvHUu1Bf4g--ZP94D8QQDbRtiIVLWBidWQ%3D%3D; path=/; expires=Thu, 18 Jul 2019 06:14:16 -0000; secure; HttpOnly"},{"name":"X-Request-Id","value":"d12cd1e4-2289-428f-8755-cabf6ad974ab"},{"name":"X-Runtime","value":"0.041729"},{"name":"Strict-Transport-Security","value":"max-age=15552000; includeSubDomains"},{"name":"Content-Encoding","value":"gzip"},{"name":"Connection","value":"keep-alive"}],"content":{"size":12610,"compression":8072,"mimeType":"text/html; charset=utf-8","text":"\n\n\n\n \n \n \n \n Sign In · TesterHome<\/title>\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \r\n \r\n\r\n