mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 07:26:55 +08:00
feat: implement upload keyword
This commit is contained in:
@@ -0,0 +1,89 @@
|
|||||||
|
config:
|
||||||
|
name: basic test with httpbin
|
||||||
|
base_url: https://httpbin.org/
|
||||||
|
|
||||||
|
teststeps:
|
||||||
|
-
|
||||||
|
name: headers
|
||||||
|
request:
|
||||||
|
url: /headers
|
||||||
|
method: GET
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
- eq: [body.headers.Host, "httpbin.org"]
|
||||||
|
|
||||||
|
-
|
||||||
|
name: user-agent
|
||||||
|
request:
|
||||||
|
url: /user-agent
|
||||||
|
method: GET
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
# - startswith: [body.user-agent, "python-requests"]
|
||||||
|
|
||||||
|
-
|
||||||
|
name: get without params
|
||||||
|
request:
|
||||||
|
url: /get
|
||||||
|
method: GET
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
- eq: [body.args, {}]
|
||||||
|
|
||||||
|
-
|
||||||
|
name: get with params in url
|
||||||
|
request:
|
||||||
|
url: /get?a=1&b=2
|
||||||
|
method: GET
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
- eq: [body.args, {'a': '1', 'b': '2'}]
|
||||||
|
|
||||||
|
-
|
||||||
|
name: get with params in params field
|
||||||
|
request:
|
||||||
|
url: /get
|
||||||
|
params:
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
method: GET
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
- eq: [body.args, {'a': '1', 'b': '2'}]
|
||||||
|
|
||||||
|
-
|
||||||
|
name: set cookie
|
||||||
|
request:
|
||||||
|
url: /cookies/set?name=value
|
||||||
|
method: GET
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
# - eq: [cookies.name, "value"]
|
||||||
|
|
||||||
|
-
|
||||||
|
name: extract cookie
|
||||||
|
request:
|
||||||
|
url: /cookies
|
||||||
|
method: GET
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
# - eq: [cookies.name, "value"]
|
||||||
|
|
||||||
|
-
|
||||||
|
name: post data
|
||||||
|
request:
|
||||||
|
url: /post
|
||||||
|
method: POST
|
||||||
|
headers:
|
||||||
|
Content-Type: application/json
|
||||||
|
data: abc
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
|
||||||
|
-
|
||||||
|
name: validate body length
|
||||||
|
request:
|
||||||
|
url: /spec.json
|
||||||
|
method: GET
|
||||||
|
validate:
|
||||||
|
- len_eq: ["body", 9]
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# NOTICE: Generated By HttpRunner. DO'NOT EDIT!
|
||||||
|
from httprunner import HttpRunner, TConfig, TStep
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseBasic(HttpRunner):
|
||||||
|
config = TConfig(
|
||||||
|
**{
|
||||||
|
"name": "basic test with httpbin",
|
||||||
|
"base_url": "https://httpbin.org/",
|
||||||
|
"path": "examples/httpbin/basic_test.py",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
teststeps = [
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "headers",
|
||||||
|
"request": {"url": "/headers", "method": "GET"},
|
||||||
|
"validate": [
|
||||||
|
{"eq": ["status_code", 200]},
|
||||||
|
{"eq": ["body.headers.Host", "httpbin.org"]},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "user-agent",
|
||||||
|
"request": {"url": "/user-agent", "method": "GET"},
|
||||||
|
"validate": [{"eq": ["status_code", 200]}],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "get without params",
|
||||||
|
"request": {"url": "/get", "method": "GET"},
|
||||||
|
"validate": [{"eq": ["status_code", 200]}, {"eq": ["body.args", {}]}],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "get with params in url",
|
||||||
|
"request": {"url": "/get?a=1&b=2", "method": "GET"},
|
||||||
|
"validate": [
|
||||||
|
{"eq": ["status_code", 200]},
|
||||||
|
{"eq": ["body.args", {"a": "1", "b": "2"}]},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "get with params in params field",
|
||||||
|
"request": {"url": "/get", "params": {"a": 1, "b": 2}, "method": "GET"},
|
||||||
|
"validate": [
|
||||||
|
{"eq": ["status_code", 200]},
|
||||||
|
{"eq": ["body.args", {"a": "1", "b": "2"}]},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "set cookie",
|
||||||
|
"request": {"url": "/cookies/set?name=value", "method": "GET"},
|
||||||
|
"validate": [{"eq": ["status_code", 200]}],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "extract cookie",
|
||||||
|
"request": {"url": "/cookies", "method": "GET"},
|
||||||
|
"validate": [{"eq": ["status_code", 200]}],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "post data",
|
||||||
|
"request": {
|
||||||
|
"url": "/post",
|
||||||
|
"method": "POST",
|
||||||
|
"headers": {"Content-Type": "application/json"},
|
||||||
|
"data": "abc",
|
||||||
|
},
|
||||||
|
"validate": [{"eq": ["status_code", 200]}],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "validate body length",
|
||||||
|
"request": {"url": "/spec.json", "method": "GET"},
|
||||||
|
"validate": [{"len_eq": ["body", 9]}],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
TestCaseBasic().test_start()
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
config:
|
||||||
|
name: test upload file with httpbin
|
||||||
|
base_url: ${get_httpbin_server()}
|
||||||
|
|
||||||
|
teststeps:
|
||||||
|
-
|
||||||
|
name: upload file
|
||||||
|
variables:
|
||||||
|
file_path: "test.env"
|
||||||
|
m_encoder: ${multipart_encoder(file=$file_path)}
|
||||||
|
request:
|
||||||
|
url: /post
|
||||||
|
method: POST
|
||||||
|
headers:
|
||||||
|
Content-Type: ${multipart_content_type($m_encoder)}
|
||||||
|
data: $m_encoder
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
- startswith: ["body.files.file", "UserName=test"]
|
||||||
|
|
||||||
|
-
|
||||||
|
name: upload file with keyword
|
||||||
|
request:
|
||||||
|
url: /post
|
||||||
|
method: POST
|
||||||
|
upload:
|
||||||
|
file: "test.env"
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
- startswith: ["body.files.file", "UserName=test"]
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# NOTICE: Generated By HttpRunner. DO'NOT EDIT!
|
||||||
|
from httprunner import HttpRunner, TConfig, TStep
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseUpload(HttpRunner):
|
||||||
|
config = TConfig(
|
||||||
|
**{
|
||||||
|
"name": "test upload file with httpbin",
|
||||||
|
"base_url": "${get_httpbin_server()}",
|
||||||
|
"path": "examples/httpbin/upload_test.py",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
teststeps = [
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "upload file",
|
||||||
|
"variables": {
|
||||||
|
"file_path": "test.env",
|
||||||
|
"m_encoder": "${multipart_encoder(file=$file_path)}",
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"url": "/post",
|
||||||
|
"method": "POST",
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "${multipart_content_type($m_encoder)}"
|
||||||
|
},
|
||||||
|
"data": "$m_encoder",
|
||||||
|
},
|
||||||
|
"validate": [
|
||||||
|
{"eq": ["status_code", 200]},
|
||||||
|
{"startswith": ["body.files.file", "UserName=test"]},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "upload file with keyword",
|
||||||
|
"request": {
|
||||||
|
"url": "/post",
|
||||||
|
"method": "POST",
|
||||||
|
"upload": {"file": "test.env"},
|
||||||
|
},
|
||||||
|
"validate": [
|
||||||
|
{"eq": ["status_code", 200]},
|
||||||
|
{"startswith": ["body.files.file", "UserName=test"]},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
TestCaseUpload().test_start()
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
config:
|
||||||
|
name: basic test with httpbin
|
||||||
|
base_url: http://httpbin.org/
|
||||||
|
|
||||||
|
teststeps:
|
||||||
|
-
|
||||||
|
name: validate response with json path
|
||||||
|
request:
|
||||||
|
url: /get
|
||||||
|
params:
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
method: GET
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
- eq: ["body.args.a", 1]
|
||||||
|
- eq: ["body.args.b", 2]
|
||||||
|
validate_script:
|
||||||
|
- "assert status_code == 200"
|
||||||
|
|
||||||
|
|
||||||
|
-
|
||||||
|
name: validate response with python script
|
||||||
|
request:
|
||||||
|
url: /get
|
||||||
|
params:
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
method: GET
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
validate_script:
|
||||||
|
- "assert status_code == 201"
|
||||||
|
- "a = response_json.get('args').get('a')"
|
||||||
|
- "assert a == '1'"
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# NOTICE: Generated By HttpRunner. DO'NOT EDIT!
|
||||||
|
from httprunner import HttpRunner, TConfig, TStep
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseValidate(HttpRunner):
|
||||||
|
config = TConfig(
|
||||||
|
**{
|
||||||
|
"name": "basic test with httpbin",
|
||||||
|
"base_url": "http://httpbin.org/",
|
||||||
|
"path": "examples/httpbin/validate_test.py",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
teststeps = [
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "validate response with json path",
|
||||||
|
"request": {"url": "/get", "params": {"a": 1, "b": 2}, "method": "GET"},
|
||||||
|
"validate": [
|
||||||
|
{"eq": ["status_code", 200]},
|
||||||
|
{"eq": ["body.args.a", 1]},
|
||||||
|
{"eq": ["body.args.b", 2]},
|
||||||
|
],
|
||||||
|
"validate_script": ["assert status_code == 200"],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TStep(
|
||||||
|
**{
|
||||||
|
"name": "validate response with python script",
|
||||||
|
"request": {"url": "/get", "params": {"a": 1, "b": 2}, "method": "GET"},
|
||||||
|
"validate": [{"eq": ["status_code", 200]}],
|
||||||
|
"validate_script": [
|
||||||
|
"assert status_code == 201",
|
||||||
|
"a = response_json.get('args').get('a')",
|
||||||
|
"assert a == '1'",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
TestCaseValidate().test_start()
|
||||||
+11
-11
@@ -36,22 +36,22 @@ def get_req_resp_record(resp_obj: Response) -> ReqRespData:
|
|||||||
logger.debug(msg)
|
logger.debug(msg)
|
||||||
|
|
||||||
# record actual request info
|
# record actual request info
|
||||||
|
request_headers = dict(resp_obj.request.headers)
|
||||||
|
request_body = resp_obj.request.body
|
||||||
|
|
||||||
|
if request_body:
|
||||||
|
request_content_type = lower_dict_keys(request_headers).get("content-type")
|
||||||
|
if request_content_type and "multipart/form-data" in request_content_type:
|
||||||
|
# upload file type
|
||||||
|
request_body = "upload file stream (OMITTED)"
|
||||||
|
|
||||||
request_data = RequestData(
|
request_data = RequestData(
|
||||||
method=resp_obj.request.method,
|
method=resp_obj.request.method,
|
||||||
url=resp_obj.request.url,
|
url=resp_obj.request.url,
|
||||||
headers=dict(resp_obj.request.headers),
|
headers=request_headers,
|
||||||
body=resp_obj.request.body,
|
body=request_body,
|
||||||
)
|
)
|
||||||
|
|
||||||
request_body = resp_obj.request.body
|
|
||||||
if request_body:
|
|
||||||
request_content_type = lower_dict_keys(request_data.headers).get("content-type")
|
|
||||||
if request_content_type and "multipart/form-data" in request_content_type:
|
|
||||||
# upload file type
|
|
||||||
request_data.body = "upload file stream (OMITTED)"
|
|
||||||
else:
|
|
||||||
request_data.body = request_body
|
|
||||||
|
|
||||||
# log request details in debug mode
|
# log request details in debug mode
|
||||||
log_print(request_data, "request")
|
log_print(request_data, "request")
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ For compatibility, you can also write upload test script in old way:
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from httprunner.parser import parse_variables_mapping
|
||||||
|
from httprunner.schema import TStep, FunctionsMapping
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import filetype
|
import filetype
|
||||||
from requests_toolbelt import MultipartEncoder
|
from requests_toolbelt import MultipartEncoder
|
||||||
@@ -57,16 +60,13 @@ $ pip install requests_toolbelt filetype
|
|||||||
print(msg)
|
print(msg)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
from httprunner.exceptions import ParamsError
|
|
||||||
|
|
||||||
|
def prepare_upload_step(step: TStep, functions: FunctionsMapping):
|
||||||
def prepare_upload_test(test_dict):
|
|
||||||
""" preprocess for upload test
|
""" preprocess for upload test
|
||||||
replace `upload` info with MultipartEncoder
|
replace `upload` info with MultipartEncoder
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
test_dict (dict):
|
step: teststep
|
||||||
|
|
||||||
{
|
{
|
||||||
"variables": {},
|
"variables": {},
|
||||||
"request": {
|
"request": {
|
||||||
@@ -81,26 +81,26 @@ def prepare_upload_test(test_dict):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
functions: functions mapping
|
||||||
|
|
||||||
"""
|
"""
|
||||||
upload_json = test_dict["request"].pop("upload", {})
|
if not step.request.upload:
|
||||||
if not upload_json:
|
return
|
||||||
raise ParamsError(f"invalid upload info: {upload_json}")
|
|
||||||
|
|
||||||
params_list = []
|
params_list = []
|
||||||
for key, value in upload_json.items():
|
for key, value in step.request.upload.items():
|
||||||
test_dict["variables"][key] = value
|
step.variables[key] = value
|
||||||
params_list.append(f"{key}=${key}")
|
params_list.append(f"{key}=${key}")
|
||||||
|
|
||||||
params_str = ", ".join(params_list)
|
params_str = ", ".join(params_list)
|
||||||
test_dict["variables"]["m_encoder"] = "${multipart_encoder(" + params_str + ")}"
|
step.variables["m_encoder"] = "${multipart_encoder(" + params_str + ")}"
|
||||||
|
|
||||||
test_dict["request"].setdefault("headers", {})
|
# parse variables
|
||||||
test_dict["request"]["headers"][
|
step.variables = parse_variables_mapping(step.variables, functions)
|
||||||
"Content-Type"
|
|
||||||
] = "${multipart_content_type($m_encoder)}"
|
|
||||||
|
|
||||||
test_dict["request"]["data"] = "$m_encoder"
|
step.request.headers["Content-Type"] = "${multipart_content_type($m_encoder)}"
|
||||||
|
|
||||||
|
step.request.data = "$m_encoder"
|
||||||
|
|
||||||
|
|
||||||
def multipart_encoder(**kwargs):
|
def multipart_encoder(**kwargs):
|
||||||
|
|||||||
@@ -125,4 +125,3 @@ class TestLoader(unittest.TestCase):
|
|||||||
loader.locate_file("examples/httpbin/", "debugtalk.py"),
|
loader.locate_file("examples/httpbin/", "debugtalk.py"),
|
||||||
os.path.join(os.getcwd(), "examples/httpbin/debugtalk.py"),
|
os.path.join(os.getcwd(), "examples/httpbin/debugtalk.py"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from loguru import logger
|
|||||||
from httprunner import utils, exceptions
|
from httprunner import utils, exceptions
|
||||||
from httprunner.client import HttpSession
|
from httprunner.client import HttpSession
|
||||||
from httprunner.exceptions import ValidationFailure, ParamsError
|
from httprunner.exceptions import ValidationFailure, ParamsError
|
||||||
|
from httprunner.ext.uploader import prepare_upload_step
|
||||||
from httprunner.loader import load_project_meta, load_testcase_file
|
from httprunner.loader import load_project_meta, load_testcase_file
|
||||||
from httprunner.parser import build_url, parse_data, parse_variables_mapping
|
from httprunner.parser import build_url, parse_data, parse_variables_mapping
|
||||||
from httprunner.response import ResponseObject
|
from httprunner.response import ResponseObject
|
||||||
@@ -53,7 +54,9 @@ class HttpRunner(object):
|
|||||||
step_data = StepData(name=step.name)
|
step_data = StepData(name=step.name)
|
||||||
|
|
||||||
# parse
|
# parse
|
||||||
|
prepare_upload_step(step, self.__project_meta.functions)
|
||||||
request_dict = step.request.dict()
|
request_dict = step.request.dict()
|
||||||
|
request_dict.pop("upload", None)
|
||||||
parsed_request_dict = parse_data(
|
parsed_request_dict = parse_data(
|
||||||
request_dict, step.variables, self.__project_meta.functions
|
request_dict, step.variables, self.__project_meta.functions
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,12 +13,8 @@ class TestHttpRunner(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
result = self.runner.get_summary()
|
result = self.runner.get_summary()
|
||||||
self.assertTrue(result.success)
|
self.assertTrue(result.success)
|
||||||
self.assertEqual(
|
self.assertEqual(result.name, "request methods testcase with variables")
|
||||||
result.name, "request methods testcase with variables"
|
self.assertEqual(result.step_datas[0].name, "get with params")
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
result.step_datas[0].name, "get with params"
|
|
||||||
)
|
|
||||||
self.assertEqual(len(result.step_datas), 3)
|
self.assertEqual(len(result.step_datas), 3)
|
||||||
|
|
||||||
def test_run_testcase_by_path_ref_testcase(self):
|
def test_run_testcase_by_path_ref_testcase(self):
|
||||||
@@ -27,10 +23,6 @@ class TestHttpRunner(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
result = self.runner.get_summary()
|
result = self.runner.get_summary()
|
||||||
self.assertTrue(result.success)
|
self.assertTrue(result.success)
|
||||||
self.assertEqual(
|
self.assertEqual(result.name, "request methods testcase: reference testcase")
|
||||||
result.name, "request methods testcase: reference testcase"
|
self.assertEqual(result.step_datas[0].name, "request with variables")
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
result.step_datas[0].name, "request with variables"
|
|
||||||
)
|
|
||||||
self.assertEqual(len(result.step_datas), 1)
|
self.assertEqual(len(result.step_datas), 1)
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class Request(BaseModel):
|
|||||||
timeout: int = 120
|
timeout: int = 120
|
||||||
allow_redirects: bool = True
|
allow_redirects: bool = True
|
||||||
verify: Verify = False
|
verify: Verify = False
|
||||||
|
upload: Dict = {} # used for upload files
|
||||||
|
|
||||||
|
|
||||||
class TStep(BaseModel):
|
class TStep(BaseModel):
|
||||||
@@ -107,7 +108,7 @@ class RequestData(BaseModel):
|
|||||||
url: Url
|
url: Url
|
||||||
headers: Headers = {}
|
headers: Headers = {}
|
||||||
# TODO: add cookies
|
# TODO: add cookies
|
||||||
body: Union[Text, Dict] = {}
|
body: Union[Text, bytes, Dict, None] = {}
|
||||||
|
|
||||||
|
|
||||||
class ResponseData(BaseModel):
|
class ResponseData(BaseModel):
|
||||||
@@ -137,6 +138,7 @@ class SessionData(BaseModel):
|
|||||||
|
|
||||||
class StepData(BaseModel):
|
class StepData(BaseModel):
|
||||||
"""teststep data, each step maybe corresponding to one request or one testcase"""
|
"""teststep data, each step maybe corresponding to one request or one testcase"""
|
||||||
|
|
||||||
success: bool = False
|
success: bool = False
|
||||||
name: Text = "" # teststep name
|
name: Text = "" # teststep name
|
||||||
data: Union[SessionData, List[SessionData]] = None
|
data: Union[SessionData, List[SessionData]] = None
|
||||||
|
|||||||
Reference in New Issue
Block a user