mirror of
https://github.com/httprunner/httprunner.git
synced 2026-08-28 11:36:56 +08:00
change: bump verison
This commit is contained in:
+176
@@ -0,0 +1,176 @@
|
|||||||
|
import io
|
||||||
|
import json
|
||||||
|
import pprint
|
||||||
|
import re
|
||||||
|
import textwrap
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from mitmproxy import http
|
||||||
|
from mitmproxy.utils import strutils
|
||||||
|
|
||||||
|
|
||||||
|
def curl_command(flow: http.HTTPFlow) -> str:
|
||||||
|
data = "curl "
|
||||||
|
|
||||||
|
request = flow.request.copy()
|
||||||
|
request.decode(strict=False)
|
||||||
|
|
||||||
|
for k, v in request.headers.items(multi=True):
|
||||||
|
data += "-H '%s:%s' " % (k, v)
|
||||||
|
|
||||||
|
if request.method != "GET":
|
||||||
|
data += "-X %s " % request.method
|
||||||
|
|
||||||
|
data += "'%s'" % request.url
|
||||||
|
|
||||||
|
if request.content:
|
||||||
|
data += " --data-binary '%s'" % strutils.bytes_to_escaped_str(
|
||||||
|
request.content, escape_single_quotes=True
|
||||||
|
)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def python_arg(arg: str, val: Any) -> str:
|
||||||
|
if not val:
|
||||||
|
return ""
|
||||||
|
if arg:
|
||||||
|
arg += "="
|
||||||
|
arg_str = "{}{},\n".format(arg, pprint.pformat(val, 79 - len(arg)))
|
||||||
|
return textwrap.indent(arg_str, " " * 4)
|
||||||
|
|
||||||
|
|
||||||
|
def python_code(flow: http.HTTPFlow):
|
||||||
|
code = io.StringIO()
|
||||||
|
|
||||||
|
def writearg(arg, val):
|
||||||
|
code.write(python_arg(arg, val))
|
||||||
|
|
||||||
|
code.write("import requests\n")
|
||||||
|
code.write("\n")
|
||||||
|
if flow.request.method.lower() in ("get", "post", "put", "head", "delete", "patch"):
|
||||||
|
code.write("response = requests.{}(\n".format(flow.request.method.lower()))
|
||||||
|
else:
|
||||||
|
code.write("response = requests.request(\n")
|
||||||
|
writearg("", flow.request.method)
|
||||||
|
url_without_query = flow.request.url.split("?", 1)[0]
|
||||||
|
writearg("", url_without_query)
|
||||||
|
|
||||||
|
writearg("params", list(flow.request.query.fields))
|
||||||
|
|
||||||
|
headers = flow.request.headers.copy()
|
||||||
|
# requests adds those by default.
|
||||||
|
for x in (":authority", "host", "content-length"):
|
||||||
|
headers.pop(x, None)
|
||||||
|
writearg("headers", dict(headers))
|
||||||
|
try:
|
||||||
|
if "json" not in flow.request.headers.get("content-type", ""):
|
||||||
|
raise ValueError()
|
||||||
|
writearg("json", json.loads(flow.request.text))
|
||||||
|
except ValueError:
|
||||||
|
writearg("data", flow.request.content)
|
||||||
|
|
||||||
|
code.seek(code.tell() - 2) # remove last comma
|
||||||
|
code.write("\n)\n")
|
||||||
|
code.write("\n")
|
||||||
|
code.write("print(response.text)")
|
||||||
|
|
||||||
|
return code.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def locust_code(flow):
|
||||||
|
code = textwrap.dedent(
|
||||||
|
"""
|
||||||
|
from locust import HttpLocust, TaskSet, task
|
||||||
|
class UserBehavior(TaskSet):
|
||||||
|
def on_start(self):
|
||||||
|
''' on_start is called when a Locust start before any task is scheduled '''
|
||||||
|
self.{name}()
|
||||||
|
@task()
|
||||||
|
def {name}(self):
|
||||||
|
url = self.locust.host + '{path}'
|
||||||
|
{headers}{params}{data}
|
||||||
|
self.response = self.client.request(
|
||||||
|
method='{method}',
|
||||||
|
url=url,{args}
|
||||||
|
)
|
||||||
|
### Additional tasks can go here ###
|
||||||
|
class WebsiteUser(HttpLocust):
|
||||||
|
task_set = UserBehavior
|
||||||
|
min_wait = 1000
|
||||||
|
max_wait = 3000
|
||||||
|
"""
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
name = re.sub("\W|^(?=\d)", "_", flow.request.path.strip("/").split("?", 1)[0])
|
||||||
|
if not name:
|
||||||
|
new_name = "_".join([str(flow.request.host), str(flow.request.timestamp_start)])
|
||||||
|
name = re.sub("\W|^(?=\d)", "_", new_name)
|
||||||
|
|
||||||
|
path_without_query = flow.request.path.split("?")[0]
|
||||||
|
|
||||||
|
args = ""
|
||||||
|
headers = ""
|
||||||
|
|
||||||
|
def conv(x):
|
||||||
|
return strutils.bytes_to_escaped_str(x, escape_single_quotes=True)
|
||||||
|
|
||||||
|
if flow.request.headers:
|
||||||
|
lines = [
|
||||||
|
(conv(k), conv(v))
|
||||||
|
for k, v in flow.request.headers.fields
|
||||||
|
if conv(k).lower() not in [":authority", "host", "cookie"]
|
||||||
|
]
|
||||||
|
lines = [" '%s': '%s',\n" % (k, v) for k, v in lines]
|
||||||
|
headers += "\n headers = {\n%s }\n" % "".join(lines)
|
||||||
|
args += "\n headers=headers,"
|
||||||
|
|
||||||
|
params = ""
|
||||||
|
if flow.request.query:
|
||||||
|
lines = [
|
||||||
|
" %s: %s,\n" % (repr(k), repr(v))
|
||||||
|
for k, v in flow.request.query.collect()
|
||||||
|
]
|
||||||
|
params = "\n params = {\n%s }\n" % "".join(lines)
|
||||||
|
args += "\n params=params,"
|
||||||
|
|
||||||
|
data = ""
|
||||||
|
if flow.request.content:
|
||||||
|
data = "\n data = '''%s'''\n" % conv(flow.request.content)
|
||||||
|
args += "\n data=data,"
|
||||||
|
|
||||||
|
code = code.format(
|
||||||
|
name=name,
|
||||||
|
path=path_without_query,
|
||||||
|
headers=headers,
|
||||||
|
params=params,
|
||||||
|
data=data,
|
||||||
|
method=flow.request.method,
|
||||||
|
args=args,
|
||||||
|
)
|
||||||
|
|
||||||
|
return code
|
||||||
|
|
||||||
|
|
||||||
|
def locust_task(flow):
|
||||||
|
code = locust_code(flow)
|
||||||
|
start_task = len(code.split("@task")[0]) - 4
|
||||||
|
end_task = -19 - len(code.split("### Additional")[1])
|
||||||
|
task_code = code[start_task:end_task]
|
||||||
|
|
||||||
|
return task_code
|
||||||
|
|
||||||
|
|
||||||
|
def url(flow):
|
||||||
|
return flow.request.url
|
||||||
|
|
||||||
|
|
||||||
|
EXPORTERS = [
|
||||||
|
("content", "c", None),
|
||||||
|
("headers+content", "h", None),
|
||||||
|
("url", "u", url),
|
||||||
|
("as curl command", "r", curl_command),
|
||||||
|
("as python code", "p", python_code),
|
||||||
|
("as locust code", "l", locust_code),
|
||||||
|
("as locust task", "t", locust_task),
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# NOTE: Generated By hrp v4.1.4, DO NOT EDIT!
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from debugtalk import *
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import funppy
|
||||||
|
funppy.register("get_user_agent", get_user_agent)
|
||||||
|
funppy.register("sleep", sleep)
|
||||||
|
funppy.register("sum", sum)
|
||||||
|
funppy.register("sum_ints", sum_ints)
|
||||||
|
funppy.register("sum_two_int", sum_two_int)
|
||||||
|
funppy.register("sum_two_string", sum_two_string)
|
||||||
|
funppy.register("sum_strings", sum_strings)
|
||||||
|
funppy.register("concatenate", concatenate)
|
||||||
|
funppy.register("setup_hook_example", setup_hook_example)
|
||||||
|
funppy.register("teardown_hook_example", teardown_hook_example)
|
||||||
|
funppy.serve()
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
base_url=https://postman-echo.com
|
||||||
|
USERNAME=debugtalk
|
||||||
|
PASSWORD=123456
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
reports/
|
||||||
|
*.so
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
.DS_Store
|
||||||
|
output/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.python-version
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# plugin
|
||||||
|
debugtalk.bin
|
||||||
|
debugtalk.so
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
# commented out function will be filtered
|
||||||
|
# def get_headers():
|
||||||
|
# return {"User-Agent": "hrp"}
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_agent():
|
||||||
|
return "hrp/funppy"
|
||||||
|
|
||||||
|
|
||||||
|
def sleep(n_secs):
|
||||||
|
time.sleep(n_secs)
|
||||||
|
|
||||||
|
|
||||||
|
def sum(*args):
|
||||||
|
result = 0
|
||||||
|
for arg in args:
|
||||||
|
result += arg
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def sum_ints(*args: List[int]) -> int:
|
||||||
|
result = 0
|
||||||
|
for arg in args:
|
||||||
|
result += arg
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def sum_two_int(a: int, b: int) -> int:
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
|
||||||
|
def sum_two_string(a: str, b: str) -> str:
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
|
||||||
|
def sum_strings(*args: List[str]) -> str:
|
||||||
|
result = ""
|
||||||
|
for arg in args:
|
||||||
|
result += arg
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def concatenate(*args: List[str]) -> str:
|
||||||
|
result = ""
|
||||||
|
for arg in args:
|
||||||
|
result += str(arg)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def setup_hook_example(name):
|
||||||
|
logging.warning("setup_hook_example")
|
||||||
|
return f"setup_hook_example: {name}"
|
||||||
|
|
||||||
|
|
||||||
|
def teardown_hook_example(name):
|
||||||
|
logging.warning("teardown_hook_example")
|
||||||
|
return f"teardown_hook_example: {name}"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"project_name": "demo",
|
||||||
|
"create_time": "2022-06-23T11:15:39.635136+08:00",
|
||||||
|
"hrp_version": "v4.1.4"
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"name": "demo with complex mechanisms",
|
||||||
|
"base_url": "https://postman-echo.com",
|
||||||
|
"variables": {
|
||||||
|
"a": "${sum(10, 2.3)}",
|
||||||
|
"b": 3.45,
|
||||||
|
"n": "${sum_ints(1, 2, 2)}",
|
||||||
|
"varFoo1": "${gen_random_string($n)}",
|
||||||
|
"varFoo2": "${max($a, $b)}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"teststeps": [
|
||||||
|
{
|
||||||
|
"name": "transaction 1 start",
|
||||||
|
"transaction": {
|
||||||
|
"name": "tran1",
|
||||||
|
"type": "start"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "get with params",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "/get",
|
||||||
|
"params": {
|
||||||
|
"foo1": "$varFoo1",
|
||||||
|
"foo2": "$varFoo2"
|
||||||
|
},
|
||||||
|
"headers": {
|
||||||
|
"User-Agent": "HttpRunnerPlus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"variables": {
|
||||||
|
"b": 34.5,
|
||||||
|
"n": 3,
|
||||||
|
"name": "get with params",
|
||||||
|
"varFoo2": "${max($a, $b)}"
|
||||||
|
},
|
||||||
|
"setup_hooks": [
|
||||||
|
"${setup_hook_example($name)}"
|
||||||
|
],
|
||||||
|
"teardown_hooks": [
|
||||||
|
"${teardown_hook_example($name)}"
|
||||||
|
],
|
||||||
|
"extract": {
|
||||||
|
"varFoo1": "body.args.foo1"
|
||||||
|
},
|
||||||
|
"validate": [
|
||||||
|
{
|
||||||
|
"check": "status_code",
|
||||||
|
"assert": "equals",
|
||||||
|
"expect": 200,
|
||||||
|
"msg": "check response status code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "headers.\"Content-Type\"",
|
||||||
|
"assert": "startswith",
|
||||||
|
"expect": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "body.args.foo1",
|
||||||
|
"assert": "length_equals",
|
||||||
|
"expect": 5,
|
||||||
|
"msg": "check args foo1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "$varFoo1",
|
||||||
|
"assert": "length_equals",
|
||||||
|
"expect": 5,
|
||||||
|
"msg": "check args foo1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "body.args.foo2",
|
||||||
|
"assert": "equals",
|
||||||
|
"expect": "34.5",
|
||||||
|
"msg": "check args foo2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "transaction 1 end",
|
||||||
|
"transaction": {
|
||||||
|
"name": "tran1",
|
||||||
|
"type": "end"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "post json data",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "/post",
|
||||||
|
"body": {
|
||||||
|
"foo1": "$varFoo1",
|
||||||
|
"foo2": "${max($a, $b)}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"validate": [
|
||||||
|
{
|
||||||
|
"check": "status_code",
|
||||||
|
"assert": "equals",
|
||||||
|
"expect": 200,
|
||||||
|
"msg": "check status code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "body.json.foo1",
|
||||||
|
"assert": "length_equals",
|
||||||
|
"expect": 5,
|
||||||
|
"msg": "check args foo1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "body.json.foo2",
|
||||||
|
"assert": "equals",
|
||||||
|
"expect": 12.3,
|
||||||
|
"msg": "check args foo2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "post form data",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "/post",
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"foo1": "$varFoo1",
|
||||||
|
"foo2": "${max($a, $b)}",
|
||||||
|
"time": "${get_timestamp()}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract": {
|
||||||
|
"varTime": "body.form.time"
|
||||||
|
},
|
||||||
|
"validate": [
|
||||||
|
{
|
||||||
|
"check": "status_code",
|
||||||
|
"assert": "equals",
|
||||||
|
"expect": 200,
|
||||||
|
"msg": "check status code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "body.form.foo1",
|
||||||
|
"assert": "length_equals",
|
||||||
|
"expect": 5,
|
||||||
|
"msg": "check args foo1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "body.form.foo2",
|
||||||
|
"assert": "equals",
|
||||||
|
"expect": "12.3",
|
||||||
|
"msg": "check args foo2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "get with timestamp",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "/get",
|
||||||
|
"params": {
|
||||||
|
"time": "$varTime"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"validate": [
|
||||||
|
{
|
||||||
|
"check": "body.args.time",
|
||||||
|
"assert": "length_equals",
|
||||||
|
"expect": 13,
|
||||||
|
"msg": "check extracted var timestamp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
config:
|
||||||
|
name: "request methods testcase: reference testcase"
|
||||||
|
variables:
|
||||||
|
foo1: testsuite_config_bar1
|
||||||
|
expect_foo1: testsuite_config_bar1
|
||||||
|
expect_foo2: config_bar2
|
||||||
|
base_url: "https://postman-echo.com"
|
||||||
|
verify: False
|
||||||
|
|
||||||
|
teststeps:
|
||||||
|
-
|
||||||
|
name: request with functions
|
||||||
|
variables:
|
||||||
|
foo1: testcase_ref_bar1
|
||||||
|
expect_foo1: testcase_ref_bar1
|
||||||
|
testcase: testcases/requests.yml
|
||||||
|
export:
|
||||||
|
- foo3
|
||||||
|
-
|
||||||
|
name: post form data
|
||||||
|
variables:
|
||||||
|
foo1: bar1
|
||||||
|
request:
|
||||||
|
method: POST
|
||||||
|
url: /post
|
||||||
|
headers:
|
||||||
|
User-Agent: ${get_user_agent()}
|
||||||
|
Content-Type: "application/x-www-form-urlencoded"
|
||||||
|
body: "foo1=$foo1&foo2=$foo3"
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
- eq: ["body.form.foo1", "bar1"]
|
||||||
|
- eq: ["body.form.foo2", "bar21"]
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"name": "request methods testcase with functions",
|
||||||
|
"variables": {
|
||||||
|
"foo1": "config_bar1",
|
||||||
|
"foo2": "config_bar2",
|
||||||
|
"expect_foo1": "config_bar1",
|
||||||
|
"expect_foo2": "config_bar2"
|
||||||
|
},
|
||||||
|
"headers": {
|
||||||
|
"User-Agent": "${get_user_agent()}"
|
||||||
|
},
|
||||||
|
"base_url": "https://postman-echo.com",
|
||||||
|
"verify": false,
|
||||||
|
"export": [
|
||||||
|
"foo3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"teststeps": [
|
||||||
|
{
|
||||||
|
"name": "get with params",
|
||||||
|
"variables": {
|
||||||
|
"foo1": "${ENV(USERNAME)}",
|
||||||
|
"foo2": "bar21",
|
||||||
|
"sum_v": "${sum_two_int(10000000, 20000000)}"
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "/get",
|
||||||
|
"params": {
|
||||||
|
"foo1": "$foo1",
|
||||||
|
"foo2": "$foo2",
|
||||||
|
"sum_v": "$sum_v"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract": {
|
||||||
|
"foo3": "body.args.foo2"
|
||||||
|
},
|
||||||
|
"validate": [
|
||||||
|
{
|
||||||
|
"check": "status_code",
|
||||||
|
"assert": "equal",
|
||||||
|
"expect": 200,
|
||||||
|
"msg": "check status_code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "body.args.foo1",
|
||||||
|
"assert": "equal",
|
||||||
|
"expect": "debugtalk",
|
||||||
|
"msg": "check body.args.foo1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "body.args.sum_v",
|
||||||
|
"assert": "equal",
|
||||||
|
"expect": "30000000",
|
||||||
|
"msg": "check body.args.sum_v"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "body.args.foo2",
|
||||||
|
"assert": "equal",
|
||||||
|
"expect": "bar21",
|
||||||
|
"msg": "check body.args.foo2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "post raw text",
|
||||||
|
"variables": {
|
||||||
|
"foo1": "bar12",
|
||||||
|
"foo3": "bar32"
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "/post",
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "text/plain"
|
||||||
|
},
|
||||||
|
"body": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3."
|
||||||
|
},
|
||||||
|
"validate": [
|
||||||
|
{
|
||||||
|
"check": "status_code",
|
||||||
|
"assert": "equal",
|
||||||
|
"expect": 200,
|
||||||
|
"msg": "check status_code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "body.data",
|
||||||
|
"assert": "equal",
|
||||||
|
"expect": "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.",
|
||||||
|
"msg": "check body.data"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "post form data",
|
||||||
|
"variables": {
|
||||||
|
"foo2": "bar23"
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "/post",
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
"body": "foo1=$foo1&foo2=$foo2&foo3=$foo3"
|
||||||
|
},
|
||||||
|
"validate": [
|
||||||
|
{
|
||||||
|
"check": "status_code",
|
||||||
|
"assert": "equal",
|
||||||
|
"expect": 200,
|
||||||
|
"msg": "check status_code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "body.form.foo1",
|
||||||
|
"assert": "equal",
|
||||||
|
"expect": "$expect_foo1",
|
||||||
|
"msg": "check body.form.foo1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "body.form.foo2",
|
||||||
|
"assert": "equal",
|
||||||
|
"expect": "bar23",
|
||||||
|
"msg": "check body.form.foo2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"check": "body.form.foo3",
|
||||||
|
"assert": "equal",
|
||||||
|
"expect": "bar21",
|
||||||
|
"msg": "check body.form.foo3"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
config:
|
||||||
|
name: "request methods testcase with functions"
|
||||||
|
variables:
|
||||||
|
foo1: config_bar1
|
||||||
|
foo2: config_bar2
|
||||||
|
expect_foo1: config_bar1
|
||||||
|
expect_foo2: config_bar2
|
||||||
|
headers:
|
||||||
|
User-Agent: ${get_user_agent()}
|
||||||
|
verify: False
|
||||||
|
export: ["foo3"]
|
||||||
|
|
||||||
|
teststeps:
|
||||||
|
-
|
||||||
|
name: get with params
|
||||||
|
variables:
|
||||||
|
foo1: ${ENV(USERNAME)}
|
||||||
|
foo2: bar21
|
||||||
|
sum_v: "${sum_two_int(10000000, 20000000)}"
|
||||||
|
request:
|
||||||
|
method: GET
|
||||||
|
url: $base_url/get
|
||||||
|
params:
|
||||||
|
foo1: $foo1
|
||||||
|
foo2: $foo2
|
||||||
|
sum_v: $sum_v
|
||||||
|
extract:
|
||||||
|
foo3: "body.args.foo2"
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
- eq: ["body.args.foo1", "debugtalk"]
|
||||||
|
- eq: ["body.args.sum_v", "30000000"]
|
||||||
|
- eq: ["body.args.foo2", "bar21"]
|
||||||
|
-
|
||||||
|
name: post raw text
|
||||||
|
variables:
|
||||||
|
foo1: "bar12"
|
||||||
|
foo3: "bar32"
|
||||||
|
request:
|
||||||
|
method: POST
|
||||||
|
url: $base_url/post
|
||||||
|
headers:
|
||||||
|
Content-Type: "text/plain"
|
||||||
|
body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3."
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
- eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."]
|
||||||
|
-
|
||||||
|
name: post form data
|
||||||
|
variables:
|
||||||
|
foo2: bar23
|
||||||
|
request:
|
||||||
|
method: POST
|
||||||
|
url: $base_url/post
|
||||||
|
headers:
|
||||||
|
Content-Type: "application/x-www-form-urlencoded"
|
||||||
|
body: "foo1=$foo1&foo2=$foo2&foo3=$foo3"
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
- eq: ["body.form.foo1", "$expect_foo1"]
|
||||||
|
- eq: ["body.form.foo2", "bar23"]
|
||||||
|
- eq: ["body.form.foo3", "bar21"]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"name": "postman collection demo"
|
||||||
|
},
|
||||||
|
"teststeps": [
|
||||||
|
{
|
||||||
|
"name": "folder1 - folder2 - Get with params",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "https://postman-echo.com/get",
|
||||||
|
"params": {
|
||||||
|
"k1": "v1",
|
||||||
|
"k2": "v2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder3 - Post form-data",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "https://postman-echo.com/post",
|
||||||
|
"upload": {
|
||||||
|
"intro_key": "intro.txt",
|
||||||
|
"k1": "v1",
|
||||||
|
"k2": "v2",
|
||||||
|
"logo_key": "logo.jpeg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder3 - Post x-www-form-urlencoded",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "https://postman-echo.com/post",
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"k1": "v1",
|
||||||
|
"k2": "v2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder3 - Post raw json",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "https://postman-echo.com/post",
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"k1": "v1",
|
||||||
|
"k2": "v2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "folder3 - Post raw text",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": "https://postman-echo.com/post",
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "text/plain"
|
||||||
|
},
|
||||||
|
"body": "have a nice day"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Get request headers",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "https://postman-echo.com/headers",
|
||||||
|
"headers": {
|
||||||
|
"Connection": "close",
|
||||||
|
"User-Agent": "HttpRunner"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# NOTE: Generated By HttpRunner v4.1.4
|
||||||
|
# FROM: postman/postman_collection_test.json
|
||||||
|
from httprunner import HttpRunner, Config, Step, RunRequest
|
||||||
|
|
||||||
|
|
||||||
|
class TestCasePostmanCollectionTest(HttpRunner):
|
||||||
|
|
||||||
|
config = Config("postman collection demo")
|
||||||
|
|
||||||
|
teststeps = [
|
||||||
|
Step(
|
||||||
|
RunRequest("folder1 - folder2 - Get with params")
|
||||||
|
.get("https://postman-echo.com/get")
|
||||||
|
.with_params(**{"k1": "v1", "k2": "v2"})
|
||||||
|
),
|
||||||
|
Step(
|
||||||
|
RunRequest("folder3 - Post form-data")
|
||||||
|
.post("https://postman-echo.com/post")
|
||||||
|
.upload(
|
||||||
|
**{
|
||||||
|
"intro_key": "intro.txt",
|
||||||
|
"k1": "v1",
|
||||||
|
"k2": "v2",
|
||||||
|
"logo_key": "logo.jpeg",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Step(
|
||||||
|
RunRequest("folder3 - Post x-www-form-urlencoded")
|
||||||
|
.post("https://postman-echo.com/post")
|
||||||
|
.with_headers(**{"Content-Type": "application/x-www-form-urlencoded"})
|
||||||
|
.with_data({"k1": "v1", "k2": "v2"})
|
||||||
|
),
|
||||||
|
Step(
|
||||||
|
RunRequest("folder3 - Post raw json")
|
||||||
|
.post("https://postman-echo.com/post")
|
||||||
|
.with_headers(**{"Content-Type": "application/json"})
|
||||||
|
.with_json({"k1": "v1", "k2": "v2"})
|
||||||
|
),
|
||||||
|
Step(
|
||||||
|
RunRequest("folder3 - Post raw text")
|
||||||
|
.post("https://postman-echo.com/post")
|
||||||
|
.with_headers(**{"Content-Type": "text/plain"})
|
||||||
|
.with_data("have a nice day")
|
||||||
|
),
|
||||||
|
Step(
|
||||||
|
RunRequest("Get request headers")
|
||||||
|
.get("https://postman-echo.com/headers")
|
||||||
|
.with_headers(**{"Connection": "close", "User-Agent": "HttpRunner"})
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
TestCasePostmanCollectionTest().test_start()
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# NOTE: Generated By hrp v4.2.0, DO NOT EDIT!
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from debugtalk import *
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import funppy
|
||||||
|
funppy.register("get_httprunner_version", get_httprunner_version)
|
||||||
|
funppy.register("sum_two", sum_two)
|
||||||
|
funppy.register("get_testcase_config_variables", get_testcase_config_variables)
|
||||||
|
funppy.register("get_testsuite_config_variables", get_testsuite_config_variables)
|
||||||
|
funppy.register("get_app_version", get_app_version)
|
||||||
|
funppy.register("calculate_two_nums", calculate_two_nums)
|
||||||
|
funppy.register("fake_rand_count", fake_rand_count)
|
||||||
|
funppy.serve()
|
||||||
@@ -88,3 +88,4 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// replace github.com/httprunner/funplugin => ../funplugin
|
// replace github.com/httprunner/funplugin => ../funplugin
|
||||||
|
replace github.com/electricbubble/gidevice => github.com/debugtalk/gidevice v0.6.3-0.20221008035433-d79086a752a7
|
||||||
|
|||||||
@@ -96,12 +96,12 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/debugtalk/gidevice v0.6.3-0.20221008035433-d79086a752a7 h1:rxaa937c9aj3Yu4M2UZb5CLAgmPu5XXpXQEfKKSWkEw=
|
||||||
|
github.com/debugtalk/gidevice v0.6.3-0.20221008035433-d79086a752a7/go.mod h1:bRHL2M9qgeEKju8KRvKMZUVEg7t5zMnTiG3SJ3QDH5o=
|
||||||
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||||
github.com/electricbubble/gadb v0.0.7 h1:fxvVLVNs3IFKuYAEXDF2tDZUjT9jNCltoTSirjM5dgo=
|
github.com/electricbubble/gadb v0.0.7 h1:fxvVLVNs3IFKuYAEXDF2tDZUjT9jNCltoTSirjM5dgo=
|
||||||
github.com/electricbubble/gadb v0.0.7/go.mod h1:3293YJ6OWHv/Q6NA5dwSbK43MbmYm8+Vz2d7h5J3IA8=
|
github.com/electricbubble/gadb v0.0.7/go.mod h1:3293YJ6OWHv/Q6NA5dwSbK43MbmYm8+Vz2d7h5J3IA8=
|
||||||
github.com/electricbubble/gidevice v0.6.2 h1:eIeCHH7Xn5fTwnUv3qL8c7L4anKIHtjlTBkgr1LDVTc=
|
|
||||||
github.com/electricbubble/gidevice v0.6.2/go.mod h1:bRHL2M9qgeEKju8KRvKMZUVEg7t5zMnTiG3SJ3QDH5o=
|
|
||||||
github.com/electricbubble/opencv-helper v0.0.3 h1:p0sHTUPPPm8GqzVUtYH+wQbJoguzotUXVRAS7Ibk7nI=
|
github.com/electricbubble/opencv-helper v0.0.3 h1:p0sHTUPPPm8GqzVUtYH+wQbJoguzotUXVRAS7Ibk7nI=
|
||||||
github.com/electricbubble/opencv-helper v0.0.3/go.mod h1:VHB21p5xsIjXUsUleWSaKGJosRsRAO7cuJoZKf7uCcc=
|
github.com/electricbubble/opencv-helper v0.0.3/go.mod h1:VHB21p5xsIjXUsUleWSaKGJosRsRAO7cuJoZKf7uCcc=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
|||||||
+297
@@ -0,0 +1,297 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Example Google style docstrings.
|
||||||
|
|
||||||
|
This module demonstrates documentation as specified by the `Google Python
|
||||||
|
Style Guide`_. Docstrings may extend over multiple lines. Sections are created
|
||||||
|
with a section header and a colon followed by a block of indented text.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
Examples can be given using either the ``Example`` or ``Examples``
|
||||||
|
sections. Sections support any reStructuredText formatting, including
|
||||||
|
literal blocks::
|
||||||
|
|
||||||
|
$ python example_google.py
|
||||||
|
|
||||||
|
Section breaks are created by resuming unindented text. Section breaks
|
||||||
|
are also implicitly created anytime a new section starts.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
module_level_variable1 (int): Module level variables may be documented in
|
||||||
|
either the ``Attributes`` section of the module docstring, or in an
|
||||||
|
inline docstring immediately following the variable.
|
||||||
|
|
||||||
|
Either form is acceptable, but the two should not be mixed. Choose
|
||||||
|
one convention to document module level variables and be consistent
|
||||||
|
with it.
|
||||||
|
|
||||||
|
Todo:
|
||||||
|
* For module TODOs
|
||||||
|
* You have to also use ``sphinx.ext.todo`` extension
|
||||||
|
|
||||||
|
.. _Google Python Style Guide:
|
||||||
|
http://google.github.io/styleguide/pyguide.html
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
module_level_variable1 = 12345
|
||||||
|
|
||||||
|
module_level_variable2 = 98765
|
||||||
|
"""int: Module level variable documented inline.
|
||||||
|
|
||||||
|
The docstring may span multiple lines. The type may optionally be specified
|
||||||
|
on the first line, separated by a colon.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def function_with_types_in_docstring(param1, param2):
|
||||||
|
"""Example function with types documented in the docstring.
|
||||||
|
|
||||||
|
`PEP 484`_ type annotations are supported. If attribute, parameter, and
|
||||||
|
return types are annotated according to `PEP 484`_, they do not need to be
|
||||||
|
included in the docstring:
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param1 (int): The first parameter.
|
||||||
|
param2 (str): The second parameter.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: The return value. True for success, False otherwise.
|
||||||
|
|
||||||
|
.. _PEP 484:
|
||||||
|
https://www.python.org/dev/peps/pep-0484/
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def function_with_pep484_type_annotations(param1: int, param2: str) -> bool:
|
||||||
|
"""Example function with PEP 484 type annotations.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param1: The first parameter.
|
||||||
|
param2: The second parameter.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The return value. True for success, False otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def module_level_function(param1, param2=None, *args, **kwargs):
|
||||||
|
"""This is an example of a module level function.
|
||||||
|
|
||||||
|
Function parameters should be documented in the ``Args`` section. The name
|
||||||
|
of each parameter is required. The type and description of each parameter
|
||||||
|
is optional, but should be included if not obvious.
|
||||||
|
|
||||||
|
If \*args or \*\*kwargs are accepted,
|
||||||
|
they should be listed as ``*args`` and ``**kwargs``.
|
||||||
|
|
||||||
|
The format for a parameter is::
|
||||||
|
|
||||||
|
name (type): description
|
||||||
|
The description may span multiple lines. Following
|
||||||
|
lines should be indented. The "(type)" is optional.
|
||||||
|
|
||||||
|
Multiple paragraphs are supported in parameter
|
||||||
|
descriptions.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param1 (int): The first parameter.
|
||||||
|
param2 (:obj:`str`, optional): The second parameter. Defaults to None.
|
||||||
|
Second line of description should be indented.
|
||||||
|
*args: Variable length argument list.
|
||||||
|
**kwargs: Arbitrary keyword arguments.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False otherwise.
|
||||||
|
|
||||||
|
The return type is optional and may be specified at the beginning of
|
||||||
|
the ``Returns`` section followed by a colon.
|
||||||
|
|
||||||
|
The ``Returns`` section may span multiple lines and paragraphs.
|
||||||
|
Following lines should be indented to match the first line.
|
||||||
|
|
||||||
|
The ``Returns`` section supports any reStructuredText formatting,
|
||||||
|
including literal blocks::
|
||||||
|
|
||||||
|
{
|
||||||
|
'param1': param1,
|
||||||
|
'param2': param2
|
||||||
|
}
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AttributeError: The ``Raises`` section is a list of all exceptions
|
||||||
|
that are relevant to the interface.
|
||||||
|
ValueError: If `param2` is equal to `param1`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if param1 == param2:
|
||||||
|
raise ValueError("param1 may not be equal to param2")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def example_generator(n):
|
||||||
|
"""Generators have a ``Yields`` section instead of a ``Returns`` section.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
n (int): The upper limit of the range to generate, from 0 to `n` - 1.
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
int: The next number in the range of 0 to `n` - 1.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Examples should be written in doctest format, and should illustrate how
|
||||||
|
to use the function.
|
||||||
|
|
||||||
|
>>> print([i for i in example_generator(4)])
|
||||||
|
[0, 1, 2, 3]
|
||||||
|
|
||||||
|
"""
|
||||||
|
for i in range(n):
|
||||||
|
yield i
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleError(Exception):
|
||||||
|
"""Exceptions are documented in the same way as classes.
|
||||||
|
|
||||||
|
The __init__ method may be documented in either the class level
|
||||||
|
docstring, or as a docstring on the __init__ method itself.
|
||||||
|
|
||||||
|
Either form is acceptable, but the two should not be mixed. Choose one
|
||||||
|
convention to document the __init__ method and be consistent with it.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Do not include the `self` parameter in the ``Args`` section.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg (str): Human readable string describing the exception.
|
||||||
|
code (:obj:`int`, optional): Error code.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
msg (str): Human readable string describing the exception.
|
||||||
|
code (int): Exception error code.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, msg, code):
|
||||||
|
self.msg = msg
|
||||||
|
self.code = code
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleClass(object):
|
||||||
|
"""The summary line for a class docstring should fit on one line.
|
||||||
|
|
||||||
|
If the class has public attributes, they may be documented here
|
||||||
|
in an ``Attributes`` section and follow the same formatting as a
|
||||||
|
function's ``Args`` section. Alternatively, attributes may be documented
|
||||||
|
inline with the attribute's declaration (see __init__ method below).
|
||||||
|
|
||||||
|
Properties created with the ``@property`` decorator should be documented
|
||||||
|
in the property's getter method.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
attr1 (str): Description of `attr1`.
|
||||||
|
attr2 (:obj:`int`, optional): Description of `attr2`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, param1, param2, param3):
|
||||||
|
"""Example of docstring on the __init__ method.
|
||||||
|
|
||||||
|
The __init__ method may be documented in either the class level
|
||||||
|
docstring, or as a docstring on the __init__ method itself.
|
||||||
|
|
||||||
|
Either form is acceptable, but the two should not be mixed. Choose one
|
||||||
|
convention to document the __init__ method and be consistent with it.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Do not include the `self` parameter in the ``Args`` section.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param1 (str): Description of `param1`.
|
||||||
|
param2 (:obj:`int`, optional): Description of `param2`. Multiple
|
||||||
|
lines are supported.
|
||||||
|
param3 (:obj:`list` of :obj:`str`): Description of `param3`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.attr1 = param1
|
||||||
|
self.attr2 = param2
|
||||||
|
self.attr3 = param3 #: Doc comment *inline* with attribute
|
||||||
|
|
||||||
|
#: list of str: Doc comment *before* attribute, with type specified
|
||||||
|
self.attr4 = ["attr4"]
|
||||||
|
|
||||||
|
self.attr5 = None
|
||||||
|
"""str: Docstring *after* attribute, with type specified."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def readonly_property(self):
|
||||||
|
"""str: Properties should be documented in their getter method."""
|
||||||
|
return "readonly_property"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def readwrite_property(self):
|
||||||
|
""":obj:`list` of :obj:`str`: Properties with both a getter and setter
|
||||||
|
should only be documented in their getter method.
|
||||||
|
|
||||||
|
If the setter method contains notable behavior, it should be
|
||||||
|
mentioned here.
|
||||||
|
"""
|
||||||
|
return ["readwrite_property"]
|
||||||
|
|
||||||
|
@readwrite_property.setter
|
||||||
|
def readwrite_property(self, value):
|
||||||
|
value
|
||||||
|
|
||||||
|
def example_method(self, param1, param2):
|
||||||
|
"""Class methods are similar to regular functions.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Do not include the `self` parameter in the ``Args`` section.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param1: The first parameter.
|
||||||
|
param2: The second parameter.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if successful, False otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __special__(self):
|
||||||
|
"""By default special members with docstrings are not included.
|
||||||
|
|
||||||
|
Special members are any methods or attributes that start with and
|
||||||
|
end with a double underscore. Any special member with a docstring
|
||||||
|
will be included in the output, if
|
||||||
|
``napoleon_include_special_with_doc`` is set to True.
|
||||||
|
|
||||||
|
This behavior can be enabled by changing the following setting in
|
||||||
|
Sphinx's conf.py::
|
||||||
|
|
||||||
|
napoleon_include_special_with_doc = True
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __special_without_docstring__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _private(self):
|
||||||
|
"""By default private members are not included.
|
||||||
|
|
||||||
|
Private members are any methods or attributes that start with an
|
||||||
|
underscore and are *not* special. By default they are not included
|
||||||
|
in the output.
|
||||||
|
|
||||||
|
This behavior can be changed such that private members *are* included
|
||||||
|
by changing the following setting in Sphinx's conf.py::
|
||||||
|
|
||||||
|
napoleon_include_private_with_doc = True
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _private_without_docstring(self):
|
||||||
|
pass
|
||||||
@@ -1 +1 @@
|
|||||||
v4.3.0-beta-09302036
|
v4.3.0-beta-10081235
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = "v4.3.0-beta-09302036"
|
__version__ = "v4.3.0-beta-10081235"
|
||||||
__description__ = "One-stop solution for HTTP(S) testing."
|
__description__ = "One-stop solution for HTTP(S) testing."
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
from typing import Text
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
import uiautomator2 as u2
|
||||||
|
|
||||||
|
from httprunner.models import IStep, StepResult, TStep, TStepAndroidUI
|
||||||
|
from httprunner.runner import HttpRunner
|
||||||
|
|
||||||
|
|
||||||
|
def run_android_ui(runner: HttpRunner, step: TStep) -> StepResult:
|
||||||
|
step_result = StepResult(
|
||||||
|
name=step.name,
|
||||||
|
step_type="android_ui",
|
||||||
|
success=False,
|
||||||
|
)
|
||||||
|
logger.info(f"run android ui action: {step.android.method}, param: {step.android.param}")
|
||||||
|
|
||||||
|
return step_result
|
||||||
|
|
||||||
|
|
||||||
|
class StepAndroidControl(IStep):
|
||||||
|
|
||||||
|
def __init__(self, step: TStep):
|
||||||
|
self.__step = step
|
||||||
|
|
||||||
|
def start_app(self, package_name: Text) -> "StepAndroidControl":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def stop_app(self, package_name: Text) -> "StepAndroidControl":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def start_watcher(self) -> "StepAndroidControl":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def stop_watcher(self) -> "StepAndroidControl":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def start_camera(self) -> "StepAndroidControl":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def stop_camera(self) -> "StepAndroidControl":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def start_record(self) -> "StepAndroidControl":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def stop_record(self) -> "StepAndroidControl":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def struct(self) -> TStep:
|
||||||
|
return self.__step
|
||||||
|
|
||||||
|
def name(self) -> Text:
|
||||||
|
return self.__step.name
|
||||||
|
|
||||||
|
def type(self) -> Text:
|
||||||
|
return "android-control"
|
||||||
|
|
||||||
|
def run(self, runner: HttpRunner):
|
||||||
|
return run_android_ui(runner, self.__step)
|
||||||
|
|
||||||
|
|
||||||
|
class StepAndroidUI(IStep):
|
||||||
|
|
||||||
|
def __init__(self, step: TStep):
|
||||||
|
self.__step = step
|
||||||
|
|
||||||
|
def press_back(self) -> "StepAndroidUI":
|
||||||
|
self.__step.android.method = "press"
|
||||||
|
self.__step.android.param = "back"
|
||||||
|
return self
|
||||||
|
|
||||||
|
def press_home(self) -> "StepAndroidUI":
|
||||||
|
self.__step.android.method = "press"
|
||||||
|
self.__step.android.param = "home"
|
||||||
|
return self
|
||||||
|
|
||||||
|
def sleep(self, time: int) -> "StepAndroidUI":
|
||||||
|
self.__step.android.method = "sleep"
|
||||||
|
self.__step.android.param = time
|
||||||
|
return self
|
||||||
|
|
||||||
|
def swipe_up(self) -> "StepAndroidUI":
|
||||||
|
self.__step.android.method = "swipe"
|
||||||
|
self.__step.android.param = [0.25, 0.5, 0.75, 0.5]
|
||||||
|
return self
|
||||||
|
|
||||||
|
def swipe_down(self) -> "StepAndroidUI":
|
||||||
|
self.__step.android.method = "swipe"
|
||||||
|
self.__step.android.param = [0.75, 0.5, 0.25, 0.5]
|
||||||
|
return self
|
||||||
|
|
||||||
|
def swipe_left(self) -> "StepAndroidUI":
|
||||||
|
self.__step.android.method = "swipe"
|
||||||
|
self.__step.android.param = [0.5, 0.75, 0.5, 0.25]
|
||||||
|
return self
|
||||||
|
|
||||||
|
def swipe_right(self) -> "StepAndroidUI":
|
||||||
|
self.__step.android.method = "swipe"
|
||||||
|
self.__step.android.param = [0.5, 0.25, 0.5, 0.75]
|
||||||
|
return self
|
||||||
|
|
||||||
|
def swipe(self, from_x: float, from_y: float, to_x: float, to_y: float) -> "StepAndroidUI":
|
||||||
|
self.__step.android.method = "swipe"
|
||||||
|
self.__step.android.param = [from_x, from_y, to_x, to_y]
|
||||||
|
return self
|
||||||
|
|
||||||
|
def click(self, text: Text) -> "StepAndroidUI":
|
||||||
|
self.__step.android.method = "click"
|
||||||
|
self.__step.android.param = text
|
||||||
|
return self
|
||||||
|
|
||||||
|
def struct(self) -> TStep:
|
||||||
|
return self.__step
|
||||||
|
|
||||||
|
def name(self) -> Text:
|
||||||
|
return self.__step.name
|
||||||
|
|
||||||
|
def type(self) -> Text:
|
||||||
|
return "android-ui"
|
||||||
|
|
||||||
|
def run(self, runner: HttpRunner):
|
||||||
|
return run_android_ui(runner, self.__step)
|
||||||
|
|
||||||
|
|
||||||
|
class RunAndroidUI(object):
|
||||||
|
|
||||||
|
def __init__(self, name: Text):
|
||||||
|
self.__step = TStep(name=name)
|
||||||
|
self.__step.android = TStepAndroidUI()
|
||||||
|
|
||||||
|
def control(self) -> StepAndroidControl:
|
||||||
|
return StepAndroidControl(self.__step)
|
||||||
|
|
||||||
|
def ui(self) -> StepAndroidUI:
|
||||||
|
return StepAndroidUI(self.__step)
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
from httprunner import HttpRunner, Config, Step, RunAndroidUI
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseAndroidDemo(HttpRunner):
|
||||||
|
|
||||||
|
config = (
|
||||||
|
Config("demo for android UI test")
|
||||||
|
.variables(
|
||||||
|
**{
|
||||||
|
"foo1": "config_bar1",
|
||||||
|
"foo2": "config_bar2",
|
||||||
|
"expect_foo1": "config_bar1",
|
||||||
|
"expect_foo2": "config_bar2",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.android()
|
||||||
|
.serial("xxx")
|
||||||
|
.package_name("xxx")
|
||||||
|
.install_apk("xxx")
|
||||||
|
)
|
||||||
|
|
||||||
|
teststeps = [
|
||||||
|
# Step(
|
||||||
|
# RunAndroidUI("start app").control().start_app("com.ss.android.ugc.aweme")
|
||||||
|
# ),
|
||||||
|
Step(
|
||||||
|
RunAndroidUI("back home").ui().press_home()
|
||||||
|
),
|
||||||
|
Step(
|
||||||
|
RunAndroidUI("back home").control().start_app()
|
||||||
|
),
|
||||||
|
Step(
|
||||||
|
RunAndroidUI("swipe up").ui().swipe_up()
|
||||||
|
),
|
||||||
|
Step(
|
||||||
|
RunAndroidUI("swipe up").ui().swipe_up()
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
TestCaseAndroidDemo().test_start()
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/electricbubble/gadb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
adbClient, err := gadb.NewClient()
|
||||||
|
checkErr(err, "fail to connect adb server")
|
||||||
|
|
||||||
|
devices, err := adbClient.DeviceList()
|
||||||
|
checkErr(err)
|
||||||
|
|
||||||
|
if len(devices) == 0 {
|
||||||
|
log.Fatalln("list of devices is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
dev := devices[0]
|
||||||
|
|
||||||
|
userHomeDir, _ := os.UserHomeDir()
|
||||||
|
apk, err := os.Open(userHomeDir + "/Desktop/xuexi_android_10002068.apk")
|
||||||
|
checkErr(err)
|
||||||
|
|
||||||
|
log.Println("starting to push apk")
|
||||||
|
|
||||||
|
remotePath := "/data/local/tmp/xuexi_android_10002068.apk"
|
||||||
|
err = dev.PushFile(apk, remotePath)
|
||||||
|
checkErr(err, "adb push")
|
||||||
|
|
||||||
|
log.Println("push completed")
|
||||||
|
|
||||||
|
log.Println("starting to install apk")
|
||||||
|
|
||||||
|
shellOutput, err := dev.RunShellCommand("pm install", remotePath)
|
||||||
|
checkErr(err, "pm install")
|
||||||
|
if !strings.Contains(shellOutput, "Success") {
|
||||||
|
log.Fatalln("fail to install: ", shellOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("install completed")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkErr(err error, msg ...string) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var output string
|
||||||
|
if len(msg) != 0 {
|
||||||
|
output = msg[0] + " "
|
||||||
|
}
|
||||||
|
output += err.Error()
|
||||||
|
log.Fatalln(output)
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "httprunner"
|
name = "httprunner"
|
||||||
version = "v4.3.0-beta-09302036"
|
version = "v4.3.0-beta-10081235"
|
||||||
description = "One-stop solution for HTTP(S) testing."
|
description = "One-stop solution for HTTP(S) testing."
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|||||||
Reference in New Issue
Block a user