mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-07 06:32:43 +08:00
change: bump verison
This commit is contained in:
176
converter.py
Normal file
176
converter.py
Normal file
@@ -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),
|
||||
]
|
||||
23
demo/.debugtalk_gen.py
Normal file
23
demo/.debugtalk_gen.py
Normal file
@@ -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()
|
||||
3
demo/.env
Normal file
3
demo/.env
Normal file
@@ -0,0 +1,3 @@
|
||||
base_url=https://postman-echo.com
|
||||
USERNAME=debugtalk
|
||||
PASSWORD=123456
|
||||
14
demo/.gitignore
vendored
Normal file
14
demo/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
reports/
|
||||
*.so
|
||||
.vscode/
|
||||
.idea/
|
||||
.DS_Store
|
||||
output/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.python-version
|
||||
logs/
|
||||
|
||||
# plugin
|
||||
debugtalk.bin
|
||||
debugtalk.so
|
||||
62
demo/debugtalk.py
Normal file
62
demo/debugtalk.py
Normal file
@@ -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
demo/har/.keep
Normal file
0
demo/har/.keep
Normal file
5
demo/proj.json
Normal file
5
demo/proj.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"project_name": "demo",
|
||||
"create_time": "2022-06-23T11:15:39.635136+08:00",
|
||||
"hrp_version": "v4.1.4"
|
||||
}
|
||||
176
demo/testcases/demo.json
Normal file
176
demo/testcases/demo.json
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
33
demo/testcases/ref_testcase.yml
Normal file
33
demo/testcases/ref_testcase.yml
Normal file
@@ -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"]
|
||||
136
demo/testcases/requests.json
Normal file
136
demo/testcases/requests.json
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
62
demo/testcases/requests.yml
Normal file
62
demo/testcases/requests.yml
Normal file
@@ -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"]
|
||||
1
examples/data/postman/__init__.py
Normal file
1
examples/data/postman/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
|
||||
81
examples/data/postman/postman_collection_test.json
Normal file
81
examples/data/postman/postman_collection_test.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
55
examples/data/postman/postman_collection_test_test.py
Normal file
55
examples/data/postman/postman_collection_test_test.py
Normal file
@@ -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()
|
||||
20
examples/postman_echo/.debugtalk_gen.py
Normal file
20
examples/postman_echo/.debugtalk_gen.py
Normal file
@@ -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()
|
||||
1
go.mod
1
go.mod
@@ -88,3 +88,4 @@ require (
|
||||
)
|
||||
|
||||
// replace github.com/httprunner/funplugin => ../funplugin
|
||||
replace github.com/electricbubble/gidevice => github.com/debugtalk/gidevice v0.6.3-0.20221008035433-d79086a752a7
|
||||
|
||||
4
go.sum
4
go.sum
@@ -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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||
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/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/go.mod h1:VHB21p5xsIjXUsUleWSaKGJosRsRAO7cuJoZKf7uCcc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
|
||||
297
google_style.py
Normal file
297
google_style.py
Normal file
@@ -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."
|
||||
|
||||
|
||||
|
||||
136
httprunner/step_android.py
Normal file
136
httprunner/step_android.py
Normal file
@@ -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)
|
||||
42
httprunner/step_android_test.py
Normal file
42
httprunner/step_android_test.py
Normal file
@@ -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()
|
||||
59
main.go
Normal file
59
main.go
Normal file
@@ -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,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "httprunner"
|
||||
version = "v4.3.0-beta-09302036"
|
||||
version = "v4.3.0-beta-10081235"
|
||||
description = "One-stop solution for HTTP(S) testing."
|
||||
license = "Apache-2.0"
|
||||
readme = "README.md"
|
||||
|
||||
Reference in New Issue
Block a user