change: bump verison

This commit is contained in:
debugtalk
2022-10-08 12:36:25 +08:00
parent 84c88d7dde
commit ed15e31805
24 changed files with 1387 additions and 5 deletions

176
converter.py Normal file
View 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
View 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
View File

@@ -0,0 +1,3 @@
base_url=https://postman-echo.com
USERNAME=debugtalk
PASSWORD=123456

14
demo/.gitignore vendored Normal file
View 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
View 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
View File

5
demo/proj.json Normal file
View 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
View 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"
}
]
}
]
}

View 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"]

View 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"
}
]
}
]
}

View 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"]

View File

@@ -0,0 +1 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!

View 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"
}
}
}
]
}

View 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()

View 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
View File

@@ -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
View File

@@ -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
View 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

View File

@@ -1 +1 @@
v4.3.0-beta-09302036
v4.3.0-beta-10081235

View File

@@ -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
View 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)

View 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
View 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)
}

View File

@@ -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"