Merge pull request #918 from httprunner/v3

## 3.0.7 (2020-06-03)

**Added**

- feat: make pytest files in chain style
- feat: `hrun` supports run pytest files
- feat: get raw testcase model from pytest file

**Fixed**

- fix: convert jmespath.search result to int/float unintentionally
- fix: referenced testcase should not be run duplicately
- fix: requests.cookies.CookieConflictError, multiple cookies with name
- fix: missing exit code from pytest
- fix: skip invalid testcase/testsuite yaml/json file

**Changed**

- change: `har2case` generate pytest file by default
- docs: update sponsor info
This commit is contained in:
debugtalk
2020-06-03 22:31:02 +08:00
committed by GitHub
44 changed files with 1344 additions and 817 deletions

View File

@@ -24,7 +24,7 @@ jobs:
python -m pip install --upgrade pip
pip install poetry
poetry --version
poetry install -vv
poetry install -vv -E upload
- name: Test package installation
run: |
poetry build
@@ -36,7 +36,7 @@ jobs:
httprunner har2case -h
- name: Run smoketest - postman echo
run: |
hrun -s examples/postman_echo/request_methods
poetry run hrun examples/postman_echo/request_methods
- name: Run smoketest - httpbin
run: |
hrun -s examples/httpbin/
poetry run hrun examples/httpbin/

View File

@@ -1,5 +1,26 @@
# Release History
## 3.0.7 (2020-06-03)
**Added**
- feat: make pytest files in chain style
- feat: `hrun` supports run pytest files
- feat: get raw testcase model from pytest file
**Fixed**
- fix: convert jmespath.search result to int/float unintentionally
- fix: referenced testcase should not be run duplicately
- fix: requests.cookies.CookieConflictError, multiple cookies with name
- fix: missing exit code from pytest
- fix: skip invalid testcase/testsuite yaml/json file
**Changed**
- change: `har2case` generate pytest file by default
- docs: update sponsor info
## 3.0.6 (2020-05-29)
**Added**

View File

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

View File

@@ -1,105 +1,76 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# NOTICE: Generated By HttpRunner.
# FROM: examples/httpbin/basic.yml
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseBasic(HttpRunner):
config = TConfig(
**{
"name": "basic test with httpbin",
"base_url": "https://httpbin.org/",
"path": "examples/httpbin/basic_test.py",
"variables": {},
}
)
config = Config("basic test with httpbin").base_url("https://httpbin.org/")
teststeps = [
TStep(
**{
"name": "headers",
"request": {"url": "/headers", "method": "GET"},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.headers.Host", "httpbin.org"]},
],
}
Step(
RunRequest("headers")
.get("/headers")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.headers.Host", "httpbin.org")
),
TStep(
**{
"name": "user-agent",
"request": {"url": "/user-agent", "method": "GET"},
"validate": [
{"eq": ["status_code", 200]},
{"startswith": ['body."user-agent"', "python-requests"]},
],
}
Step(
RunRequest("user-agent")
.get("/user-agent")
.validate()
.assert_equal("status_code", 200)
.assert_startswith('body."user-agent"', "python-requests")
),
TStep(
**{
"name": "get without params",
"request": {"url": "/get", "method": "GET"},
"validate": [{"eq": ["status_code", 200]}, {"eq": ["body.args", {}]}],
}
Step(
RunRequest("get without params")
.get("/get")
.validate()
.assert_equal("status_code", 200)
.assert_equal("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"}]},
],
}
Step(
RunRequest("get with params in url")
.get("/get?a=1&b=2")
.validate()
.assert_equal("status_code", 200)
.assert_equal("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"}]},
],
}
Step(
RunRequest("get with params in params field")
.get("/get")
.with_params(**{"a": 1, "b": 2})
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.args", {"a": "1", "b": "2"})
),
TStep(
**{
"name": "set cookie",
"request": {"url": "/cookies/set?name=value", "method": "GET"},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.cookies.name", "value"]},
],
}
Step(
RunRequest("set cookie")
.get("/cookies/set?name=value")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.cookies.name", "value")
),
TStep(
**{
"name": "extract cookie",
"request": {"url": "/cookies", "method": "GET"},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.cookies.name", "value"]},
],
}
Step(
RunRequest("extract cookie")
.get("/cookies")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.cookies.name", "value")
),
TStep(
**{
"name": "post data",
"request": {
"url": "/post",
"method": "POST",
"headers": {"Content-Type": "application/json"},
"data": "abc",
},
"validate": [{"eq": ["status_code", 200]}],
}
Step(
RunRequest("post data")
.post("/post")
.with_headers(**{"Content-Type": "application/json"})
.with_data("abc")
.validate()
.assert_equal("status_code", 200)
),
TStep(
**{
"name": "validate body length",
"request": {"url": "/spec.json", "method": "GET"},
"validate": [{"len_eq": ["body", 9]}],
}
Step(
RunRequest("validate body length")
.get("/spec.json")
.validate()
.assert_length_equal("body", 9)
),
]

View File

@@ -1,48 +1,27 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# NOTICE: Generated By HttpRunner.
# FROM: examples/httpbin/hooks.yml
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseHooks(HttpRunner):
config = TConfig(
**{
"name": "basic test with httpbin",
"base_url": "${get_httpbin_server()}",
"setup_hooks": ["${hook_print(setup)}"],
"teardown_hooks": ["${hook_print(teardown)}"],
"path": "examples/httpbin/hooks_test.py",
"variables": {},
}
)
config = Config("basic test with httpbin").base_url("${get_httpbin_server()}")
teststeps = [
TStep(
**{
"name": "headers",
"variables": {"a": 123},
"request": {"url": "/headers", "method": "GET"},
"setup_hooks": [
"${setup_hook_add_kwargs($request)}",
"${setup_hook_remove_kwargs($request)}",
],
"teardown_hooks": ["${teardown_hook_sleep_N_secs($response, 1)}"],
"validate": [
{"eq": ["status_code", 200]},
{"contained_by": ["body.headers.Host", "${get_httpbin_server()}"]},
],
}
Step(
RunRequest("headers")
.with_variables(**{"a": 123})
.get("/headers")
.validate()
.assert_equal("status_code", 200)
.assert_contained_by("body.headers.Host", "${get_httpbin_server()}")
),
TStep(
**{
"name": "alter response",
"request": {"url": "/headers", "method": "GET"},
"teardown_hooks": ["${alter_response($response)}"],
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.headers.Host", "httpbin.org"]},
],
}
Step(
RunRequest("alter response")
.get("/headers")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.headers.Host", "httpbin.org")
),
]

View File

@@ -1,47 +1,36 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# NOTICE: Generated By HttpRunner.
# FROM: examples/httpbin/load_image.yml
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseLoadImage(HttpRunner):
config = TConfig(
**{
"name": "load images",
"base_url": "${get_httpbin_server()}",
"path": "examples/httpbin/load_image_test.py",
"variables": {},
}
)
config = Config("load images").base_url("${get_httpbin_server()}")
teststeps = [
TStep(
**{
"name": "get png image",
"request": {"url": "/image/png", "method": "GET"},
"validate": [{"eq": ["status_code", 200]}],
}
Step(
RunRequest("get png image")
.get("/image/png")
.validate()
.assert_equal("status_code", 200)
),
TStep(
**{
"name": "get jpeg image",
"request": {"url": "/image/jpeg", "method": "GET"},
"validate": [{"eq": ["status_code", 200]}],
}
Step(
RunRequest("get jpeg image")
.get("/image/jpeg")
.validate()
.assert_equal("status_code", 200)
),
TStep(
**{
"name": "get webp image",
"request": {"url": "/image/webp", "method": "GET"},
"validate": [{"eq": ["status_code", 200]}],
}
Step(
RunRequest("get webp image")
.get("/image/webp")
.validate()
.assert_equal("status_code", 200)
),
TStep(
**{
"name": "get svg image",
"request": {"url": "/image/svg", "method": "GET"},
"validate": [{"eq": ["status_code", 200]}],
}
Step(
RunRequest("get svg image")
.get("/image/svg")
.validate()
.assert_equal("status_code", 200)
),
]

View File

@@ -1,54 +1,35 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# NOTICE: Generated By HttpRunner.
# FROM: examples/httpbin/upload.yml
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseUpload(HttpRunner):
config = TConfig(
**{
"name": "test upload file with httpbin",
"base_url": "${get_httpbin_server()}",
"path": "examples/httpbin/upload_test.py",
"variables": {},
}
)
config = Config("test upload file with httpbin").base_url("${get_httpbin_server()}")
teststeps = [
TStep(
**{
"name": "upload file",
"variables": {
Step(
RunRequest("upload file")
.with_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"]},
],
}
}
)
.post("/post")
.with_headers(**{"Content-Type": "${multipart_content_type($m_encoder)}"})
.with_data("$m_encoder")
.validate()
.assert_equal("status_code", 200)
.assert_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"]},
],
}
Step(
RunRequest("upload file with keyword")
.post("/post")
.upload(**{"file": "test.env"})
.validate()
.assert_equal("status_code", 200)
.assert_startswith("body.files.file", "UserName=test")
),
]

View File

@@ -13,8 +13,8 @@ teststeps:
method: GET
validate:
- eq: ["status_code", 200]
- eq: ["body.args.a", 1]
- eq: ["body.args.b", 2]
- eq: ["body.args.a", "1"]
- eq: ["body.args.b", "2"]
validate_script:
- "assert status_code == 200"

View File

@@ -1,43 +1,28 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# NOTICE: Generated By HttpRunner.
# FROM: examples/httpbin/validate.yml
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseValidate(HttpRunner):
config = TConfig(
**{
"name": "basic test with httpbin",
"base_url": "http://httpbin.org/",
"path": "examples/httpbin/validate_test.py",
"variables": {},
}
)
config = Config("basic test with httpbin").base_url("http://httpbin.org/")
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"],
}
Step(
RunRequest("validate response with json path")
.get("/get")
.with_params(**{"a": 1, "b": 2})
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.args.a", "1")
.assert_equal("body.args.b", "2")
),
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'",
],
}
Step(
RunRequest("validate response with python script")
.get("/get")
.with_params(**{"a": 1, "b": 2})
.validate()
.assert_equal("status_code", 200)
),
]

View File

@@ -0,0 +1,34 @@
config:
name: "set & delete cookies."
base_url: "https://postman-echo.com"
verify: False
export: ["cookie_foo1", "cookie_foo3"]
teststeps:
-
name: set cookie foo1 & foo2 & foo3
request:
method: GET
url: /cookies/set
params:
foo1: bar1
foo2: bar2
headers:
User-Agent: HttpRunner/${get_httprunner_version()}
extract:
cookie_foo1: $.cookies.foo1
validate:
- eq: ["status_code", 200]
- eq: ["cookies.foo1", "bar1"]
-
name: delete cookie foo2
request:
method: GET
url: /cookies/delete?foo2
headers:
User-Agent: HttpRunner/${get_httprunner_version()}
validate:
- eq: ["status_code", 200]
- ne: ["$.cookies.foo1", "$foo1"]
- eq: ["$.cookies.foo1", "$cookie_foo1"]
- eq: ["$.cookies.foo3", "$cookie_foo3"]

View File

@@ -0,0 +1,54 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# FROM: examples/postman_echo/cookie_manipulation/hardcode.yml
from httprunner import HttpRunner, TConfig, TStep
class TestCaseHardcode(HttpRunner):
config = TConfig(
**{
"name": "set & delete cookies.",
"base_url": "https://postman-echo.com",
"verify": False,
"export": ["cookie_foo1", "cookie_foo3"],
"path": "examples/postman_echo/cookie_manipulation/hardcode_test.py",
}
)
teststeps = [
TStep(
**{
"name": "set cookie foo1 & foo2 & foo3",
"request": {
"method": "GET",
"url": "/cookies/set",
"params": {"foo1": "bar1", "foo2": "bar2"},
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"extract": {"cookie_foo1": "$.cookies.foo1"},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["cookies.foo1", "bar1"]},
],
}
),
TStep(
**{
"name": "delete cookie foo2",
"request": {
"method": "GET",
"url": "/cookies/delete?foo2",
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"validate": [
{"eq": ["status_code", 200]},
{"ne": ["$.cookies.foo1", "$foo1"]},
{"eq": ["$.cookies.foo1", "$cookie_foo1"]},
{"eq": ["$.cookies.foo3", "$cookie_foo3"]},
],
}
),
]
if __name__ == "__main__":
TestCaseHardcode().test_start()

View File

@@ -0,0 +1,62 @@
import unittest
import requests
from httprunner.runner import HttpRunner
from httprunner.schema import TConfig, TStep
class TestCaseSetDeleteCookies(unittest.TestCase):
config = TConfig(
**{
"name": "set & delete cookies.",
"base_url": "https://postman-echo.com",
"variables": {"foo1": "bar1", "foo2": "bar2"},
"verify": False,
"export": ["cookie_foo1", "cookie_foo3"],
}
)
teststeps = [
TStep(
**{
"name": "set cookie foo1 & foo2 & foo3",
"variables": {"foo3": "bar3"},
"request": {
"method": "GET",
"url": "/cookies/set",
"params": {"foo1": "bar111", "foo2": "$foo2", "foo3": "$foo3"},
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"extract": {
"cookie_foo1": "$.cookies.foo1",
"cookie_foo3": "$.cookies.foo3",
},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["$.cookies.foo3", "$foo3"]},
],
}
),
TStep(
**{
"name": "delete cookie foo2",
"request": {
"method": "GET",
"url": "/cookies/delete?foo2",
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"validate": [
{"eq": ["status_code", 200]},
{"ne": ["$.cookies.foo1", "$foo1"]},
{"eq": ["$.cookies.foo1", "$cookie_foo1"]},
{"eq": ["$.cookies.foo3", "$cookie_foo3"]},
],
}
),
]
def test_start(self):
s = requests.Session()
HttpRunner(self.config, self.teststeps, session=s).with_variables(
foo1="bar123", foo2="bar22"
).run()

View File

@@ -0,0 +1,59 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# FROM: examples/postman_echo/cookie_manipulation/set_delete_cookies.yml
from httprunner import HttpRunner, TConfig, TStep
class TestCaseSetDeleteCookies(HttpRunner):
config = TConfig(
**{
"name": "set & delete cookies.",
"variables": {"foo1": "bar1", "foo2": "bar2"},
"base_url": "https://postman-echo.com",
"verify": False,
"export": ["cookie_foo1", "cookie_foo3"],
"path": "examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py",
}
)
teststeps = [
TStep(
**{
"name": "set cookie foo1 & foo2 & foo3",
"variables": {"foo3": "bar3"},
"request": {
"method": "GET",
"url": "/cookies/set",
"params": {"foo1": "bar111", "foo2": "$foo2", "foo3": "$foo3"},
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"extract": {
"cookie_foo1": "$.cookies.foo1",
"cookie_foo3": "$.cookies.foo3",
},
"validate": [
{"eq": ["status_code", 200]},
{"ne": ["$.cookies.foo3", "$foo3"]},
],
}
),
TStep(
**{
"name": "delete cookie foo2",
"request": {
"method": "GET",
"url": "/cookies/delete?foo2",
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"validate": [
{"eq": ["status_code", 200]},
{"ne": ["$.cookies.foo1", "$foo1"]},
{"eq": ["$.cookies.foo1", "$cookie_foo1"]},
{"eq": ["$.cookies.foo3", "$cookie_foo3"]},
],
}
),
]
if __name__ == "__main__":
TestCaseSetDeleteCookies().test_start()

View File

@@ -2,10 +2,9 @@ import uuid
from typing import List
import pytest
from httprunner import Config, Step
from loguru import logger
from httprunner.schema import TConfig, TStep
@pytest.fixture(scope="session", autouse=True)
def session_fixture(request):
@@ -33,8 +32,8 @@ def session_fixture(request):
@pytest.fixture(scope="function", autouse=True)
def testcase_fixture(request):
"""setup and teardown each testcase"""
config: TConfig = request.cls.config
teststeps: List[TStep] = request.cls.teststeps
config: Config = request.cls.config
teststeps: List[Step] = request.cls.teststeps
logger.debug(f"setup testcase fixture: {config.name} - {request.module.__name__}")

View File

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

View File

@@ -1,87 +1,69 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/request_with_functions.yml
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithFunctions(HttpRunner):
config = TConfig(
**{
"name": "request with functions",
"variables": {"foo1": "session_bar1", "var1": "testsuite_val1"},
"base_url": "https://postman-echo.com",
"verify": False,
"path": "examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py",
}
config = (
Config("request with functions")
.variables(**{"foo1": "session_bar1", "var1": "testsuite_val1"})
.base_url("https://postman-echo.com")
.verify(False)
)
teststeps = [
TStep(
**{
"name": "get with params",
"variables": {
"foo1": "bar1",
"foo2": "session_bar2",
"sum_v": "${sum_two(1, 2)}",
},
"request": {
"method": "GET",
"url": "/get",
"params": {"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"},
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"extract": {"session_foo2": "body.args.foo2"},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.args.foo1", "session_bar1"]},
{"eq": ["body.args.sum_v", 3]},
{"eq": ["body.args.foo2", "session_bar2"]},
],
}
Step(
RunRequest("get with params")
.with_variables(
**{"foo1": "bar1", "foo2": "session_bar2", "sum_v": "${sum_two(1, 2)}"}
)
.get("/get")
.with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"})
.with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"})
.extract()
.with_jmespath("body.args.foo2", "session_foo2")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.args.foo1", "session_bar1")
.assert_equal("body.args.sum_v", "3")
.assert_equal("body.args.foo2", "session_bar2")
),
TStep(
**{
"name": "post raw text",
"variables": {"foo1": "hello world", "foo3": "$session_foo2"},
"request": {
"method": "POST",
"url": "/post",
"headers": {
"User-Agent": "HttpRunner/${get_httprunner_version()}",
"Content-Type": "text/plain",
},
"data": "This is expected to be sent back as part of response body: $foo1-$foo3.",
},
"validate": [
{"eq": ["status_code", 200]},
{
"eq": [
"body.data",
"This is expected to be sent back as part of response body: session_bar1-session_bar2.",
]
},
],
}
Step(
RunRequest("post raw text")
.with_variables(**{"foo1": "hello world", "foo3": "$session_foo2"})
.post("/post")
.with_headers(
**{
"User-Agent": "HttpRunner/${get_httprunner_version()}",
"Content-Type": "text/plain",
}
)
.with_data(
"This is expected to be sent back as part of response body: $foo1-$foo3."
)
.validate()
.assert_equal("status_code", 200)
.assert_equal(
"body.data",
"This is expected to be sent back as part of response body: session_bar1-session_bar2.",
)
),
TStep(
**{
"name": "post form data",
"variables": {"foo1": "bar1", "foo2": "bar2"},
"request": {
"method": "POST",
"url": "/post",
"headers": {
"User-Agent": "HttpRunner/${get_httprunner_version()}",
"Content-Type": "application/x-www-form-urlencoded",
},
"data": "foo1=$foo1&foo2=$foo2",
},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.form.foo1", "session_bar1"]},
{"eq": ["body.form.foo2", "bar2"]},
],
}
Step(
RunRequest("post form data")
.with_variables(**{"foo1": "bar1", "foo2": "bar2"})
.post("/post")
.with_headers(
**{
"User-Agent": "HttpRunner/${get_httprunner_version()}",
"Content-Type": "application/x-www-form-urlencoded",
}
)
.with_data("foo1=$foo1&foo2=$foo2")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.form.foo1", "session_bar1")
.assert_equal("body.form.foo2", "bar2")
),
]

View File

@@ -1,4 +1,4 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml
import os
@@ -6,7 +6,7 @@ import sys
sys.path.insert(0, os.getcwd())
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
from examples.postman_echo.request_methods.request_with_functions_test import (
TestCaseRequestWithFunctions as RequestWithFunctions,
@@ -14,23 +14,18 @@ from examples.postman_echo.request_methods.request_with_functions_test import (
class TestCaseRequestWithTestcaseReference(HttpRunner):
config = TConfig(
**{
"name": "request with referenced testcase",
"variables": {"foo1": "session_bar1", "var2": "testsuite_val2"},
"base_url": "https://postman-echo.com",
"verify": False,
"path": "examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py",
}
config = (
Config("request with referenced testcase")
.variables(**{"foo1": "session_bar1", "var2": "testsuite_val2"})
.base_url("https://postman-echo.com")
.verify(False)
)
teststeps = [
TStep(
**{
"name": "request with functions",
"variables": {"foo1": "override_bar1"},
"testcase": RequestWithFunctions,
}
Step(
RunTestCase("request with functions")
.with_variables(**{"foo1": "override_bar1"})
.call(RequestWithFunctions)
),
]

View File

@@ -1,77 +1,57 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/hardcode.yml
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseHardcode(HttpRunner):
config = TConfig(
**{
"name": "request methods testcase in hardcode",
"base_url": "https://postman-echo.com",
"verify": False,
"path": "examples/postman_echo/request_methods/hardcode_test.py",
"variables": {},
}
config = (
Config("request methods testcase in hardcode")
.base_url("https://postman-echo.com")
.verify(False)
)
teststeps = [
TStep(
**{
"name": "get with params",
"request": {
"method": "GET",
"url": "/get",
"params": {"foo1": "bar1", "foo2": "bar2"},
"headers": {"User-Agent": "HttpRunner/3.0"},
},
"validate": [{"eq": ["status_code", 200]}],
}
Step(
RunRequest("get with params")
.get("/get")
.with_params(**{"foo1": "bar1", "foo2": "bar2"})
.with_headers(**{"User-Agent": "HttpRunner/3.0"})
.validate()
.assert_equal("status_code", 200)
),
TStep(
**{
"name": "post raw text",
"request": {
"method": "POST",
"url": "/post",
"headers": {
"User-Agent": "HttpRunner/3.0",
"Content-Type": "text/plain",
},
"data": "This is expected to be sent back as part of response body.",
},
"validate": [{"eq": ["status_code", 200]}],
}
Step(
RunRequest("post raw text")
.post("/post")
.with_headers(
**{"User-Agent": "HttpRunner/3.0", "Content-Type": "text/plain"}
)
.with_data("This is expected to be sent back as part of response body.")
.validate()
.assert_equal("status_code", 200)
),
TStep(
**{
"name": "post form data",
"request": {
"method": "POST",
"url": "/post",
"headers": {
"User-Agent": "HttpRunner/3.0",
"Content-Type": "application/x-www-form-urlencoded",
},
"data": "foo1=bar1&foo2=bar2",
},
"validate": [{"eq": ["status_code", 200]}],
}
Step(
RunRequest("post form data")
.post("/post")
.with_headers(
**{
"User-Agent": "HttpRunner/3.0",
"Content-Type": "application/x-www-form-urlencoded",
}
)
.with_data("foo1=bar1&foo2=bar2")
.validate()
.assert_equal("status_code", 200)
),
TStep(
**{
"name": "put request",
"request": {
"method": "PUT",
"url": "/put",
"headers": {
"User-Agent": "HttpRunner/3.0",
"Content-Type": "text/plain",
},
"data": "This is expected to be sent back as part of response body.",
},
"validate": [{"eq": ["status_code", 200]}],
}
Step(
RunRequest("put request")
.put("/put")
.with_headers(
**{"User-Agent": "HttpRunner/3.0", "Content-Type": "text/plain"}
)
.with_data("This is expected to be sent back as part of response body.")
.validate()
.assert_equal("status_code", 200)
),
]

View File

@@ -26,7 +26,7 @@ teststeps:
validate:
- eq: ["status_code", 200]
- eq: ["body.args.foo1", "session_bar1"]
- eq: ["body.args.sum_v", 3]
- eq: ["body.args.sum_v", "3"]
- eq: ["body.args.foo2", "session_bar2"]
-
name: post raw text

View File

@@ -1,87 +1,69 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/request_with_functions.yml
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithFunctions(HttpRunner):
config = TConfig(
**{
"name": "request methods testcase with functions",
"variables": {"foo1": "session_bar1"},
"base_url": "https://postman-echo.com",
"verify": False,
"path": "examples/postman_echo/request_methods/request_with_functions_test.py",
}
config = (
Config("request methods testcase with functions")
.variables(**{"foo1": "session_bar1"})
.base_url("https://postman-echo.com")
.verify(False)
)
teststeps = [
TStep(
**{
"name": "get with params",
"variables": {
"foo1": "bar1",
"foo2": "session_bar2",
"sum_v": "${sum_two(1, 2)}",
},
"request": {
"method": "GET",
"url": "/get",
"params": {"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"},
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"extract": {"session_foo2": "body.args.foo2"},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.args.foo1", "session_bar1"]},
{"eq": ["body.args.sum_v", 3]},
{"eq": ["body.args.foo2", "session_bar2"]},
],
}
Step(
RunRequest("get with params")
.with_variables(
**{"foo1": "bar1", "foo2": "session_bar2", "sum_v": "${sum_two(1, 2)}"}
)
.get("/get")
.with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"})
.with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"})
.extract()
.with_jmespath("body.args.foo2", "session_foo2")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.args.foo1", "session_bar1")
.assert_equal("body.args.sum_v", "3")
.assert_equal("body.args.foo2", "session_bar2")
),
TStep(
**{
"name": "post raw text",
"variables": {"foo1": "hello world", "foo3": "$session_foo2"},
"request": {
"method": "POST",
"url": "/post",
"headers": {
"User-Agent": "HttpRunner/${get_httprunner_version()}",
"Content-Type": "text/plain",
},
"data": "This is expected to be sent back as part of response body: $foo1-$foo3.",
},
"validate": [
{"eq": ["status_code", 200]},
{
"eq": [
"body.data",
"This is expected to be sent back as part of response body: session_bar1-session_bar2.",
]
},
],
}
Step(
RunRequest("post raw text")
.with_variables(**{"foo1": "hello world", "foo3": "$session_foo2"})
.post("/post")
.with_headers(
**{
"User-Agent": "HttpRunner/${get_httprunner_version()}",
"Content-Type": "text/plain",
}
)
.with_data(
"This is expected to be sent back as part of response body: $foo1-$foo3."
)
.validate()
.assert_equal("status_code", 200)
.assert_equal(
"body.data",
"This is expected to be sent back as part of response body: session_bar1-session_bar2.",
)
),
TStep(
**{
"name": "post form data",
"variables": {"foo1": "bar1", "foo2": "bar2"},
"request": {
"method": "POST",
"url": "/post",
"headers": {
"User-Agent": "HttpRunner/${get_httprunner_version()}",
"Content-Type": "application/x-www-form-urlencoded",
},
"data": "foo1=$foo1&foo2=$foo2",
},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.form.foo1", "session_bar1"]},
{"eq": ["body.form.foo2", "bar2"]},
],
}
Step(
RunRequest("post form data")
.with_variables(**{"foo1": "bar1", "foo2": "bar2"})
.post("/post")
.with_headers(
**{
"User-Agent": "HttpRunner/${get_httprunner_version()}",
"Content-Type": "application/x-www-form-urlencoded",
}
)
.with_data("foo1=$foo1&foo2=$foo2")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.form.foo1", "session_bar1")
.assert_equal("body.form.foo2", "bar2")
),
]

View File

@@ -1,4 +1,4 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml
import os
@@ -6,7 +6,7 @@ import sys
sys.path.insert(0, os.getcwd())
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
from examples.postman_echo.request_methods.request_with_functions_test import (
TestCaseRequestWithFunctions as RequestWithFunctions,
@@ -14,23 +14,18 @@ from examples.postman_echo.request_methods.request_with_functions_test import (
class TestCaseRequestWithTestcaseReference(HttpRunner):
config = TConfig(
**{
"name": "request methods testcase: reference testcase",
"variables": {"foo1": "session_bar1"},
"base_url": "https://postman-echo.com",
"verify": False,
"path": "examples/postman_echo/request_methods/request_with_testcase_reference_test.py",
}
config = (
Config("request methods testcase: reference testcase")
.variables(**{"foo1": "session_bar1"})
.base_url("https://postman-echo.com")
.verify(False)
)
teststeps = [
TStep(
**{
"name": "request with functions",
"variables": {"foo1": "override_bar1"},
"testcase": RequestWithFunctions,
}
Step(
RunTestCase("request with functions")
.with_variables(**{"foo1": "override_bar1"})
.call(RequestWithFunctions)
),
]

View File

@@ -1,82 +1,63 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/request_with_variables.yml
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithVariables(HttpRunner):
config = TConfig(
**{
"name": "request methods testcase with variables",
"variables": {"foo1": "session_bar1"},
"base_url": "https://postman-echo.com",
"verify": False,
"path": "examples/postman_echo/request_methods/request_with_variables_test.py",
}
config = (
Config("request methods testcase with variables")
.variables(**{"foo1": "session_bar1"})
.base_url("https://postman-echo.com")
.verify(False)
)
teststeps = [
TStep(
**{
"name": "get with params",
"variables": {"foo1": "bar1", "foo2": "session_bar2"},
"request": {
"method": "GET",
"url": "/get",
"params": {"foo1": "$foo1", "foo2": "$foo2"},
"headers": {"User-Agent": "HttpRunner/3.0"},
},
"extract": {"session_foo2": "body.args.foo2"},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.args.foo1", "session_bar1"]},
{"eq": ["body.args.foo2", "session_bar2"]},
],
}
Step(
RunRequest("get with params")
.with_variables(**{"foo1": "bar1", "foo2": "session_bar2"})
.get("/get")
.with_params(**{"foo1": "$foo1", "foo2": "$foo2"})
.with_headers(**{"User-Agent": "HttpRunner/3.0"})
.extract()
.with_jmespath("body.args.foo2", "session_foo2")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.args.foo1", "session_bar1")
.assert_equal("body.args.foo2", "session_bar2")
),
TStep(
**{
"name": "post raw text",
"variables": {"foo1": "hello world", "foo3": "$session_foo2"},
"request": {
"method": "POST",
"url": "/post",
"headers": {
"User-Agent": "HttpRunner/3.0",
"Content-Type": "text/plain",
},
"data": "This is expected to be sent back as part of response body: $foo1-$foo3.",
},
"validate": [
{"eq": ["status_code", 200]},
{
"eq": [
"body.data",
"This is expected to be sent back as part of response body: session_bar1-session_bar2.",
]
},
],
}
Step(
RunRequest("post raw text")
.with_variables(**{"foo1": "hello world", "foo3": "$session_foo2"})
.post("/post")
.with_headers(
**{"User-Agent": "HttpRunner/3.0", "Content-Type": "text/plain"}
)
.with_data(
"This is expected to be sent back as part of response body: $foo1-$foo3."
)
.validate()
.assert_equal("status_code", 200)
.assert_equal(
"body.data",
"This is expected to be sent back as part of response body: session_bar1-session_bar2.",
)
),
TStep(
**{
"name": "post form data",
"variables": {"foo1": "bar1", "foo2": "bar2"},
"request": {
"method": "POST",
"url": "/post",
"headers": {
"User-Agent": "HttpRunner/3.0",
"Content-Type": "application/x-www-form-urlencoded",
},
"data": "foo1=$foo1&foo2=$foo2",
},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.form.foo1", "session_bar1"]},
{"eq": ["body.form.foo2", "bar2"]},
],
}
Step(
RunRequest("post form data")
.with_variables(**{"foo1": "bar1", "foo2": "bar2"})
.post("/post")
.with_headers(
**{
"User-Agent": "HttpRunner/3.0",
"Content-Type": "application/x-www-form-urlencoded",
}
)
.with_data("foo1=$foo1&foo2=$foo2")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.form.foo1", "session_bar1")
.assert_equal("body.form.foo2", "bar2")
),
]

View File

@@ -25,5 +25,5 @@ teststeps:
session_foo2: "body.args.foo2"
validate:
- eq: ["status_code", 200]
- eq: ["body.args.sum_v", 3]
- less_than: ["body.args.sum_v", "${sum_two(2, 2)}"]
- eq: ["body.args.sum_v", "3"]
# - less_than: ["body.args.sum_v", "${sum_two(2, 2)}"] TODO

View File

@@ -1,42 +1,31 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/validate_with_functions.yml
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseValidateWithFunctions(HttpRunner):
config = TConfig(
**{
"name": "request methods testcase: validate with functions",
"variables": {"foo1": "session_bar1"},
"base_url": "https://postman-echo.com",
"verify": False,
"path": "examples/postman_echo/request_methods/validate_with_functions_test.py",
}
config = (
Config("request methods testcase: validate with functions")
.variables(**{"foo1": "session_bar1"})
.base_url("https://postman-echo.com")
.verify(False)
)
teststeps = [
TStep(
**{
"name": "get with params",
"variables": {
"foo1": "bar1",
"foo2": "session_bar2",
"sum_v": "${sum_two(1, 2)}",
},
"request": {
"method": "GET",
"url": "/get",
"params": {"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"},
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"extract": {"session_foo2": "body.args.foo2"},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.args.sum_v", 3]},
{"less_than": ["body.args.sum_v", "${sum_two(2, 2)}"]},
],
}
Step(
RunRequest("get with params")
.with_variables(
**{"foo1": "bar1", "foo2": "session_bar2", "sum_v": "${sum_two(1, 2)}"}
)
.get("/get")
.with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"})
.with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"})
.extract()
.with_jmespath("body.args.foo2", "session_foo2")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.args.sum_v", "3")
),
]

View File

@@ -1,82 +1,63 @@
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
# NOTICE: Generated By HttpRunner.
# FROM: examples/postman_echo/request_methods/validate_with_variables.yml
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseValidateWithVariables(HttpRunner):
config = TConfig(
**{
"name": "request methods testcase: validate with variables",
"variables": {"foo1": "session_bar1"},
"base_url": "https://postman-echo.com",
"verify": False,
"path": "examples/postman_echo/request_methods/validate_with_variables_test.py",
}
config = (
Config("request methods testcase: validate with variables")
.variables(**{"foo1": "session_bar1"})
.base_url("https://postman-echo.com")
.verify(False)
)
teststeps = [
TStep(
**{
"name": "get with params",
"variables": {"foo1": "bar1", "foo2": "session_bar2"},
"request": {
"method": "GET",
"url": "/get",
"params": {"foo1": "$foo1", "foo2": "$foo2"},
"headers": {"User-Agent": "HttpRunner/3.0"},
},
"extract": {"session_foo2": "body.args.foo2"},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.args.foo1", "$foo1"]},
{"eq": ["body.args.foo2", "$foo2"]},
],
}
Step(
RunRequest("get with params")
.with_variables(**{"foo1": "bar1", "foo2": "session_bar2"})
.get("/get")
.with_params(**{"foo1": "$foo1", "foo2": "$foo2"})
.with_headers(**{"User-Agent": "HttpRunner/3.0"})
.extract()
.with_jmespath("body.args.foo2", "session_foo2")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.args.foo1", "$foo1")
.assert_equal("body.args.foo2", "$foo2")
),
TStep(
**{
"name": "post raw text",
"variables": {"foo1": "hello world", "foo3": "$session_foo2"},
"request": {
"method": "POST",
"url": "/post",
"headers": {
"User-Agent": "HttpRunner/3.0",
"Content-Type": "text/plain",
},
"data": "This is expected to be sent back as part of response body: $foo1-$foo3.",
},
"validate": [
{"eq": ["status_code", 200]},
{
"eq": [
"body.data",
"This is expected to be sent back as part of response body: session_bar1-$foo3.",
]
},
],
}
Step(
RunRequest("post raw text")
.with_variables(**{"foo1": "hello world", "foo3": "$session_foo2"})
.post("/post")
.with_headers(
**{"User-Agent": "HttpRunner/3.0", "Content-Type": "text/plain"}
)
.with_data(
"This is expected to be sent back as part of response body: $foo1-$foo3."
)
.validate()
.assert_equal("status_code", 200)
.assert_equal(
"body.data",
"This is expected to be sent back as part of response body: session_bar1-$foo3.",
)
),
TStep(
**{
"name": "post form data",
"variables": {"foo1": "bar1", "foo2": "bar2"},
"request": {
"method": "POST",
"url": "/post",
"headers": {
"User-Agent": "HttpRunner/3.0",
"Content-Type": "application/x-www-form-urlencoded",
},
"data": "foo1=$foo1&foo2=$foo2",
},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.form.foo1", "$foo1"]},
{"eq": ["body.form.foo2", "$foo2"]},
],
}
Step(
RunRequest("post form data")
.with_variables(**{"foo1": "bar1", "foo2": "bar2"})
.post("/post")
.with_headers(
**{
"User-Agent": "HttpRunner/3.0",
"Content-Type": "application/x-www-form-urlencoded",
}
)
.with_data("foo1=$foo1&foo2=$foo2")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.form.foo1", "$foo1")
.assert_equal("body.form.foo2", "$foo2")
),
]

View File

@@ -1,8 +1,9 @@
__version__ = "3.0.6"
__version__ = "3.0.7"
__description__ = "One-stop solution for HTTP(S) testing."
from httprunner.runner import HttpRunner
from httprunner.schema import TConfig, TStep
from httprunner.testcase import Config, Step, RunRequest, RunTestCase
__all__ = [
"__version__",
@@ -10,4 +11,8 @@ __all__ = [
"HttpRunner",
"TConfig",
"TStep",
"Config",
"Step",
"RunRequest",
"RunTestCase",
]

View File

@@ -5,27 +5,27 @@ Built-in validate comparators.
import re
def equals(check_value, expect_value):
def equal(check_value, expect_value):
assert check_value == expect_value
def less_than(check_value, expect_value):
assert check_value < expect_value
def less_than_or_equals(check_value, expect_value):
assert check_value <= expect_value
def greater_than(check_value, expect_value):
assert check_value > expect_value
def greater_than_or_equals(check_value, expect_value):
def less_than(check_value, expect_value):
assert check_value < expect_value
def greater_or_equals(check_value, expect_value):
assert check_value >= expect_value
def not_equals(check_value, expect_value):
def less_or_equals(check_value, expect_value):
assert check_value <= expect_value
def not_equal(check_value, expect_value):
assert check_value != expect_value
@@ -33,34 +33,29 @@ def string_equals(check_value, expect_value):
assert str(check_value) == str(expect_value)
def length_equals(check_value, expect_value):
def length_equal(check_value, expect_value):
assert isinstance(expect_value, int)
expect_len = _cast_to_int(expect_value)
assert len(check_value) == expect_len
assert len(check_value) == expect_value
def length_greater_than(check_value, expect_value):
assert isinstance(expect_value, int)
expect_len = _cast_to_int(expect_value)
assert len(check_value) > expect_len
assert isinstance(expect_value, (int, float))
assert len(check_value) > expect_value
def length_greater_than_or_equals(check_value, expect_value):
assert isinstance(expect_value, int)
expect_len = _cast_to_int(expect_value)
assert len(check_value) >= expect_len
def length_greater_or_equals(check_value, expect_value):
assert isinstance(expect_value, (int, float))
assert len(check_value) >= expect_value
def length_less_than(check_value, expect_value):
assert isinstance(expect_value, int)
expect_len = _cast_to_int(expect_value)
assert len(check_value) < expect_len
assert isinstance(expect_value, (int, float))
assert len(check_value) < expect_value
def length_less_than_or_equals(check_value, expect_value):
assert isinstance(expect_value, int)
expect_len = _cast_to_int(expect_value)
assert len(check_value) <= expect_len
def length_less_or_equals(check_value, expect_value):
assert isinstance(expect_value, (int, float))
assert len(check_value) <= expect_value
def contains(check_value, expect_value):
@@ -100,10 +95,3 @@ def startswith(check_value, expect_value):
def endswith(check_value, expect_value):
assert str(check_value).endswith(str(expect_value))
def _cast_to_int(expect_value):
try:
return int(expect_value)
except Exception:
raise AssertionError("%r can't cast to int" % str(expect_value))

View File

@@ -44,7 +44,7 @@ def main_run(extra_args):
sys.exit(1)
extra_args_new.extend(testcase_path_list)
pytest.main(extra_args_new)
sys.exit(pytest.main(extra_args_new))
def main():

View File

@@ -41,7 +41,7 @@ def get_req_resp_record(resp_obj: Response) -> ReqRespData:
# record actual request info
request_headers = dict(resp_obj.request.headers)
request_cookies = dict(resp_obj.request._cookies)
request_cookies = resp_obj.request._cookies.get_dict()
request_body = resp_obj.request.body
try:
request_body = json.loads(request_body)

View File

@@ -265,7 +265,9 @@ def session_fixture(request):
)
summary["stat"]["teststeps"]["failures"] += 1
summary["details"].append(testcase_summary.dict())
testcase_summary_json = testcase_summary.dict()
testcase_summary_json["records"] = testcase_summary_json.pop("step_datas")
summary["details"].append(testcase_summary_json)
summary_path = "{{SUMMARY_PATH_PLACEHOLDER}}"
summary_dir = os.path.dirname(summary_path)

View File

@@ -30,7 +30,14 @@ def init_har2case_parser(subparsers):
"--to-yaml",
dest="to_yaml",
action="store_true",
help="Convert to YAML format, if not specified, convert to JSON format by default.",
help="Convert to YAML format, if not specified, convert to pytest format by default.",
)
parser.add_argument(
"-2j",
"--to-json",
dest="to_json",
action="store_true",
help="Convert to JSON format, if not specified, convert to pytest format by default.",
)
parser.add_argument(
"--filter",
@@ -55,7 +62,13 @@ def main_har2case(args):
logger.error(f"HAR file not exists: {har_source_file}")
sys.exit(1)
output_file_type = "YML" if args.to_yaml else "JSON"
if args.to_yaml:
output_file_type = "YAML"
elif args.to_yaml:
output_file_type = "JSON"
else:
output_file_type = "pytest"
HarParser(har_source_file, args.filter, args.exclude).gen_testcase(output_file_type)
return 0

View File

@@ -7,6 +7,7 @@ import urllib.parse as urlparse
from loguru import logger
from httprunner.ext.har2case import utils
from httprunner.make import make_testcase, format_pytest_with_black
try:
from json.decoder import JSONDecodeError
@@ -329,17 +330,23 @@ class HarParser(object):
testcase = {"config": config, "teststeps": teststeps}
return testcase
def gen_testcase(self, file_type="JSON"):
def gen_testcase(self, file_type="pytest"):
logger.info(f"Start to generate testcase from {self.har_file_path}")
harfile = os.path.splitext(self.har_file_path)[0]
output_testcase_file = "{}.{}".format(harfile, file_type.lower())
testcase = self._make_testcase()
logger.debug("prepared testcase: {}".format(testcase))
if file_type == "JSON":
output_testcase_file = f"{harfile}.json"
utils.dump_json(testcase, output_testcase_file)
else:
elif file_type == "YAML":
output_testcase_file = f"{harfile}.yml"
utils.dump_yaml(testcase, output_testcase_file)
else:
# default to generate pytest file
testcase["config"]["path"] = self.har_file_path
output_testcase_file = make_testcase(testcase)
format_pytest_with_black(output_testcase_file)
logger.info(f"generated testcase: {output_testcase_file}")

View File

@@ -48,7 +48,6 @@ def _load_json_file(json_file: Text) -> Dict:
json_content = json.load(data_file)
except json.JSONDecodeError as ex:
err_msg = f"JSONDecodeError:\nfile: {json_file}\nerror: {ex}"
logger.error(err_msg)
raise exceptions.FileFormatError(err_msg)
return json_content
@@ -74,12 +73,11 @@ def load_test_file(test_file: Text) -> Dict:
def load_testcase(testcase: Dict) -> TestCase:
path = testcase["config"]["path"]
try:
# validate with pydantic TestCase model
testcase_obj = TestCase.parse_obj(testcase)
except ValidationError as ex:
err_msg = f"TestCase ValidationError:\nfile: {path}\nerror: {ex}"
err_msg = f"TestCase ValidationError:\nerror: {ex}\ncontent: {testcase}"
logger.error(err_msg)
raise exceptions.TestCaseFormatError(err_msg)
@@ -89,8 +87,8 @@ def load_testcase(testcase: Dict) -> TestCase:
def load_testcase_file(testcase_file: Text) -> TestCase:
"""load testcase file and validate with pydantic model"""
testcase_content = load_test_file(testcase_file)
testcase_content.setdefault("config", {})["path"] = testcase_file
testcase_obj = load_testcase(testcase_content)
testcase_obj.config.path = testcase_file
return testcase_obj
@@ -195,7 +193,7 @@ def load_csv_file(csv_file: Text) -> List[Dict]:
def load_folder_files(folder_path: Text, recursive: bool = True) -> List:
""" load folder path, return all files endswith yml/yaml/json in list.
""" load folder path, return all files endswith .yml/.yaml/.json/_test.py in list.
Args:
folder_path (str): specified folder path to load
@@ -220,7 +218,7 @@ def load_folder_files(folder_path: Text, recursive: bool = True) -> List:
filenames_list = []
for filename in filenames:
if not filename.endswith((".yml", ".yaml", ".json")):
if not filename.lower().endswith((".yml", ".yaml", ".json", "_test.py")):
continue
filenames_list.append(filename)

View File

@@ -16,13 +16,15 @@ from httprunner.loader import (
load_project_meta,
)
from httprunner.parser import parse_data
from httprunner.response import uniform_validator
""" cache converted pytest files, avoid duplicate making
"""
make_files_cache_set: Set = set()
pytest_files_set: Set = set()
__TEMPLATE__ = jinja2.Template(
"""# NOTICE: Generated By HttpRunner. DO NOT EDIT!
"""# NOTICE: Generated By HttpRunner.
# FROM: {{ testcase_path }}
{% if imports_list %}
import os
@@ -30,17 +32,17 @@ import sys
sys.path.insert(0, os.getcwd())
{% endif %}
from httprunner import HttpRunner, TConfig, TStep
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
{% for import_str in imports_list %}
{{ import_str }}
{% endfor %}
class {{ class_name }}(HttpRunner):
config = TConfig(**{{ config }})
config = {{ config_chain_style }}
teststeps = [
{% for teststep in teststeps %}
TStep(**{{ teststep }}),
{% for step_chain_style in teststeps_chain_style %}
{{ step_chain_style }},
{% endfor %}
]
@@ -96,7 +98,7 @@ def __ensure_testcase_module(path: Text) -> NoReturn:
return
with open(init_file, "w", encoding="utf-8") as f:
f.write("# NOTICE: Generated By HttpRunner. DO NOT EDIT!")
f.write("# NOTICE: Generated By HttpRunner. DO NOT EDIT!\n")
def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]:
@@ -109,7 +111,7 @@ def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]:
raw_file_name, file_suffix = os.path.splitext(os.path.basename(testcase_path))
file_suffix = file_suffix.lower()
if file_suffix not in [".json", ".yml", ".yaml"]:
if file_suffix not in [".json", ".yml", ".yaml", ".har"]:
raise exceptions.ParamsError(
"testcase file should have .yaml/.yml/.json suffix"
)
@@ -124,7 +126,7 @@ def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]:
return testcase_python_path, name_in_title_case
def __format_pytest_with_black(python_paths: List[Text]) -> NoReturn:
def format_pytest_with_black(*python_paths: Text) -> NoReturn:
logger.info("format pytest cases with black ...")
try:
subprocess.run(["black", *python_paths])
@@ -132,7 +134,112 @@ def __format_pytest_with_black(python_paths: List[Text]) -> NoReturn:
logger.error(ex)
def __make_testcase(testcase: Dict, dir_path: Text = None) -> NoReturn:
def make_config_chain_style(config: Dict) -> Text:
config_chain_style = f'Config("{config["name"]}")'
if config["variables"]:
variables = config["variables"]
config_chain_style += f".variables(**{variables})"
if "base_url" in config:
config_chain_style += f'.base_url("{config["base_url"]}")'
if "verify" in config:
config_chain_style += f'.verify({config["verify"]})'
return config_chain_style
def make_request_chain_style(request: Dict) -> Text:
method = request["method"].lower()
url = request["url"]
request_chain_style = f'.{method}("{url}")'
if "params" in request:
params = request["params"]
request_chain_style += f".with_params(**{params})"
if "headers" in request:
headers = request["headers"]
request_chain_style += f".with_headers(**{headers})"
if "cookies" in request:
cookies = request["cookies"]
request_chain_style += f".with_cookies(**{cookies})"
if "data" in request:
data = request["data"]
if isinstance(data, Text):
data = f'"{data}"'
request_chain_style += f".with_data({data})"
if "timeout" in request:
timeout = request["timeout"]
request_chain_style += f".set_timeout({timeout})"
if "verify" in request:
verify = request["verify"]
request_chain_style += f".set_verify({verify})"
if "allow_redirects" in request:
allow_redirects = request["allow_redirects"]
request_chain_style += f".set_allow_redirects({allow_redirects})"
if "upload" in request:
upload = request["upload"]
request_chain_style += f".upload(**{upload})"
return request_chain_style
def make_teststep_chain_style(teststep: Dict) -> Text:
if teststep.get("request"):
step_info = f'RunRequest("{teststep["name"]}")'
elif teststep.get("testcase"):
step_info = f'RunTestCase("{teststep["name"]}")'
else:
raise exceptions.TestCaseFormatError
if "variables" in teststep:
variables = teststep["variables"]
step_info += f".with_variables(**{variables})"
if teststep.get("request"):
step_info += make_request_chain_style(teststep["request"])
elif teststep.get("testcase"):
testcase = teststep["testcase"]
call_ref_testcase = f".call({testcase})"
step_info += call_ref_testcase
if "extract" in teststep:
step_info += ".extract()"
for extract_name, extract_path in teststep["extract"].items():
step_info += f'.with_jmespath("{extract_path}", "{extract_name}")'
if "validate" in teststep:
step_info += ".validate()"
for v in teststep["validate"]:
validator = uniform_validator(v)
assert_method = validator["assert"]
check = validator["check"]
if '"' in check:
# e.g. body."user-agent" => 'body."user-agent"'
check = f"'{check}'"
else:
check = f'"{check}"'
expect = validator["expect"]
if isinstance(expect, Text):
expect = f'"{expect}"'
step_info += f".assert_{assert_method}({check}, {expect})"
return f"Step({step_info})"
def make_testcase(
testcase: Dict, dir_path: Text = None, ref_flag: bool = False,
) -> Text:
"""convert valid testcase dict to pytest file path"""
# ensure compatibility with testcase format v2
testcase = ensure_testcase_v3(testcase)
@@ -151,7 +258,7 @@ def __make_testcase(testcase: Dict, dir_path: Text = None) -> NoReturn:
global make_files_cache_set
if testcase_python_path in make_files_cache_set:
return
return testcase_python_path
config = testcase["config"]
config["path"] = __ensure_cwd_relative(testcase_python_path)
@@ -174,13 +281,13 @@ def __make_testcase(testcase: Dict, dir_path: Text = None) -> NoReturn:
# make ref testcase pytest file
ref_testcase_path = __ensure_absolute(teststep["testcase"])
__make(ref_testcase_path)
__make(ref_testcase_path, ref_flag=True)
# prepare ref testcase class name
ref_testcase_python_path, ref_testcase_cls_name = convert_testcase_path(
ref_testcase_path
)
teststep["testcase"] = f"CLS_LB({ref_testcase_cls_name})CLS_RB"
teststep["testcase"] = ref_testcase_cls_name
# prepare import ref testcase
ref_testcase_python_path = ref_testcase_python_path[len(os.getcwd()) + 1 :]
@@ -193,12 +300,13 @@ def __make_testcase(testcase: Dict, dir_path: Text = None) -> NoReturn:
data = {
"testcase_path": __ensure_cwd_relative(testcase_path),
"class_name": f"TestCase{testcase_cls_name}",
"config": config,
"teststeps": teststeps,
"imports_list": imports_list,
"config_chain_style": make_config_chain_style(config),
"teststeps_chain_style": [
make_teststep_chain_style(step) for step in teststeps
],
}
content = __TEMPLATE__.render(data)
content = content.replace("'CLS_LB(", "").replace(")CLS_RB'", "")
with open(testcase_python_path, "w", encoding="utf-8") as f:
f.write(content)
@@ -206,10 +314,14 @@ def __make_testcase(testcase: Dict, dir_path: Text = None) -> NoReturn:
__ensure_testcase_module(testcase_python_path)
logger.info(f"generated testcase: {testcase_python_path}")
make_files_cache_set.add(__ensure_cwd_relative(testcase_python_path))
if not ref_flag:
make_files_cache_set.add(__ensure_cwd_relative(testcase_python_path))
return testcase_python_path
def __make_testsuite(testsuite: Dict) -> NoReturn:
def make_testsuite(testsuite: Dict) -> NoReturn:
"""convert valid testsuite dict to pytest folder with testcases"""
# validate testsuite format
load_testsuite(testsuite)
@@ -254,15 +366,16 @@ def __make_testsuite(testsuite: Dict) -> NoReturn:
testcase_dict["config"]["variables"].update(testsuite_variables)
# make testcase
__make_testcase(testcase_dict, testsuite_dir)
make_testcase(testcase_dict, testsuite_dir)
def __make(tests_path: Text) -> NoReturn:
def __make(tests_path: Text, ref_flag: bool = False) -> NoReturn:
""" make testcase(s) with testcase/testsuite/folder absolute path
generated pytest file path will be cached in make_files_cache_set
Args:
tests_path: should be in absolute path
ref_flag: flag if referenced test path
"""
test_files = []
@@ -275,6 +388,10 @@ def __make(tests_path: Text) -> NoReturn:
raise exceptions.TestcaseNotFound(f"Invalid tests path: {tests_path}")
for test_file in test_files:
if test_file.lower().endswith("_test.py"):
pytest_files_set.add(test_file)
continue
try:
test_content = load_test_file(test_file)
except (exceptions.FileNotFound, exceptions.FileFormatError) as ex:
@@ -290,34 +407,36 @@ def __make(tests_path: Text) -> NoReturn:
# testcase
if "teststeps" in test_content:
try:
__make_testcase(test_content)
make_testcase(test_content, ref_flag=ref_flag)
except exceptions.TestCaseFormatError:
continue
# testsuite
elif "testcases" in test_content:
try:
__make_testsuite(test_content)
make_testsuite(test_content)
except exceptions.TestSuiteFormatError:
continue
# invalid format
else:
raise exceptions.FileFormatError(
f"test file is neither testcase nor testsuite: {test_file}"
)
logger.warning(f"skip invalid testcase/testsuite file: {test_file}")
def main_make(tests_paths: List[Text]) -> List[Text]:
if not tests_paths:
return []
for tests_path in tests_paths:
if not os.path.isabs(tests_path):
tests_path = os.path.join(os.getcwd(), tests_path)
__make(tests_path)
testcase_path_list = list(make_files_cache_set)
__format_pytest_with_black(testcase_path_list)
return testcase_path_list
pytest_files_set.update(make_files_cache_set)
pytest_files_list = list(pytest_files_set)
format_pytest_with_black(*pytest_files_list)
return pytest_files_list
def init_make_parser(subparsers):

View File

@@ -12,45 +12,39 @@ from httprunner.schema import VariablesMapping, Validators, FunctionsMapping
def get_uniform_comparator(comparator: Text):
""" convert comparator alias to uniform name
"""
if comparator in ["eq", "equals", "==", "is"]:
return "equals"
if comparator in ["eq", "equals", "equal"]:
return "equal"
elif comparator in ["lt", "less_than"]:
return "less_than"
elif comparator in ["le", "less_than_or_equals"]:
return "less_than_or_equals"
elif comparator in ["le", "less_or_equals"]:
return "less_or_equals"
elif comparator in ["gt", "greater_than"]:
return "greater_than"
elif comparator in ["ge", "greater_than_or_equals"]:
return "greater_than_or_equals"
elif comparator in ["ne", "not_equals"]:
return "not_equals"
elif comparator in ["ge", "greater_or_equals"]:
return "greater_or_equals"
elif comparator in ["ne", "not_equal"]:
return "not_equal"
elif comparator in ["str_eq", "string_equals"]:
return "string_equals"
elif comparator in ["len_eq", "length_equals", "count_eq"]:
return "length_equals"
elif comparator in ["len_eq", "length_equal"]:
return "length_equal"
elif comparator in [
"len_gt",
"count_gt",
"length_greater_than",
"count_greater_than",
]:
return "length_greater_than"
elif comparator in [
"len_ge",
"count_ge",
"length_greater_than_or_equals",
"count_greater_than_or_equals",
"length_greater_or_equals",
]:
return "length_greater_than_or_equals"
elif comparator in ["len_lt", "count_lt", "length_less_than", "count_less_than"]:
return "length_greater_or_equals"
elif comparator in ["len_lt", "length_less_than"]:
return "length_less_than"
elif comparator in [
"len_le",
"count_le",
"length_less_than_or_equals",
"count_less_than_or_equals",
"length_less_or_equals",
]:
return "length_less_than_or_equals"
return "length_less_or_equals"
else:
return comparator
@@ -62,8 +56,8 @@ def uniform_validator(validator):
validator (dict): validator maybe in two formats:
format1: this is kept for compatibility with the previous versions.
{"check": "status_code", "assert": "eq", "expect": 201}
{"check": "$resp_body_success", "assert": "eq", "expect": True}
{"check": "status_code", "comparator": "eq", "expect": 201}
{"check": "$resp_body_success", "comparator": "eq", "expect": True}
format2: recommended new version, {assert: [check_item, expected_value]}
{'eq': ['status_code', 201]}
{'eq': ['$resp_body_success', True]}
@@ -169,11 +163,10 @@ class ResponseObject(object):
check_value = parse_data(
check_item, variables_mapping, functions_mapping
)
check_value = parse_string_value(check_value)
else:
check_value = jmespath.search(check_item, self.resp_obj_meta)
check_value = parse_string_value(check_value)
# comparator
assert_method = u_validator["assert"]
assert_func = get_mapping_function(assert_method, functions_mapping)

View File

@@ -2,7 +2,7 @@ import os
import time
import uuid
from datetime import datetime
from typing import List, Dict, Text
from typing import List, Dict, Text, NoReturn
try:
import allure
@@ -20,6 +20,7 @@ from httprunner.ext.uploader import prepare_upload_step
from httprunner.loader import load_project_meta, load_testcase_file
from httprunner.parser import build_url, parse_data, parse_variables_mapping
from httprunner.response import ResponseObject
from httprunner.testcase import Config, Step
from httprunner.schema import (
TConfig,
TStep,
@@ -34,10 +35,12 @@ from httprunner.schema import (
class HttpRunner(object):
config: TConfig
teststeps: List[TStep]
config: Config
teststeps: List[Step]
success: bool = True # indicate testcase execution result
__config: TConfig
__teststeps: List[TStep]
__project_meta: ProjectMeta = None
__case_id: Text = ""
__step_datas: List[StepData] = None
@@ -49,6 +52,19 @@ class HttpRunner(object):
# log
__log_path: Text = ""
def __init_tests__(self) -> NoReturn:
self.__config = self.config.perform()
self.__teststeps = []
for step in self.teststeps:
self.__teststeps.append(step.perform())
@property
def raw_testcase(self) -> TestCase:
if not hasattr(self, "__config"):
self.__init_tests__()
return TestCase(config=self.__config, teststeps=self.__teststeps)
def with_project_meta(self, project_meta: ProjectMeta) -> "HttpRunner":
self.__project_meta = project_meta
return self
@@ -65,7 +81,7 @@ class HttpRunner(object):
self.__session_variables = variables
return self
def __run_step_request(self, step: TStep):
def __run_step_request(self, step: TStep) -> StepData:
"""run teststep: request"""
step_data = StepData(name=step.name)
@@ -84,7 +100,7 @@ class HttpRunner(object):
# prepare arguments
method = parsed_request_dict.pop("method")
url_path = parsed_request_dict.pop("url")
url = build_url(self.config.base_url, url_path)
url = build_url(self.__config.base_url, url_path)
parsed_request_dict["json"] = parsed_request_dict.pop("req_json", {})
# request
@@ -142,7 +158,7 @@ class HttpRunner(object):
return step_data
def __run_step_testcase(self, step):
def __run_step_testcase(self, step: TStep) -> StepData:
"""run teststep: referenced testcase"""
step_data = StepData(name=step.name)
step_variables = step.variables
@@ -183,7 +199,7 @@ class HttpRunner(object):
return step_data
def __run_step(self, step: TStep):
def __run_step(self, step: TStep) -> Dict:
"""run teststep, teststep maybe a request or referenced testcase"""
logger.info(f"run step begin: {step.name} >>>>>>")
@@ -200,7 +216,7 @@ class HttpRunner(object):
logger.info(f"run step end: {step.name} <<<<<<\n")
return step_data.export
def __parse_config(self, config: TConfig):
def __parse_config(self, config: TConfig) -> NoReturn:
config.variables.update(self.__session_variables)
config.variables = parse_variables_mapping(
config.variables, self.__project_meta.functions
@@ -212,7 +228,7 @@ class HttpRunner(object):
config.base_url, config.variables, self.__project_meta.functions
)
def run_testcase(self, testcase: TestCase):
def run_testcase(self, testcase: TestCase) -> "HttpRunner":
"""run specified testcase
Examples:
@@ -220,21 +236,23 @@ class HttpRunner(object):
>>> HttpRunner().with_project_meta(project_meta).run_testcase(testcase_obj)
"""
self.config = testcase.config
self.teststeps = testcase.teststeps
self.__config = testcase.config
self.__teststeps = testcase.teststeps
# prepare
self.__project_meta = self.__project_meta or load_project_meta(self.config.path)
self.__parse_config(self.config)
self.__project_meta = self.__project_meta or load_project_meta(
self.__config.path
)
self.__parse_config(self.__config)
self.__start_at = time.time()
self.__step_datas: List[StepData] = []
self.__session = self.__session or HttpSession()
self.__session_variables = {}
# run teststeps
for step in self.teststeps:
for step in self.__teststeps:
# update with config variables
step.variables.update(self.config.variables)
step.variables.update(self.__config.variables)
# update with session variables extracted from pre step
step.variables.update(self.__session_variables)
# parse variables
@@ -267,7 +285,8 @@ class HttpRunner(object):
>>> TestCaseRequestWithFunctions().run()
"""
testcase_obj = TestCase(config=self.config, teststeps=self.teststeps)
self.__init_tests__()
testcase_obj = TestCase(config=self.__config, teststeps=self.__teststeps)
return self.run_testcase(testcase_obj)
def get_step_datas(self) -> List[StepData]:
@@ -275,7 +294,7 @@ class HttpRunner(object):
def get_export_variables(self) -> Dict:
export_vars_mapping = {}
for var_name in self.config.export:
for var_name in self.__config.export:
if var_name not in self.__session_variables:
raise ParamsError(
f"failed to export variable {var_name} from session variables {self.__session_variables}"
@@ -290,7 +309,7 @@ class HttpRunner(object):
start_at_timestamp = self.__start_at
start_at_iso_format = datetime.utcfromtimestamp(start_at_timestamp).isoformat()
return TestCaseSummary(
name=self.config.name,
name=self.__config.name,
success=self.success,
case_id=self.__case_id,
time=TestCaseTime(
@@ -299,15 +318,18 @@ class HttpRunner(object):
duration=self.__duration,
),
in_out=TestCaseInOut(
vars=self.config.variables, export=self.get_export_variables()
vars=self.__config.variables, export=self.get_export_variables()
),
log=self.__log_path,
step_datas=self.__step_datas,
)
def test_start(self):
def test_start(self) -> "HttpRunner":
"""main entrance, discovered by pytest"""
self.__project_meta = self.__project_meta or load_project_meta(self.config.path)
self.__init_tests__()
self.__project_meta = self.__project_meta or load_project_meta(
self.__config.path
)
self.__case_id = self.__case_id or str(uuid.uuid4())
self.__log_path = self.__log_path or os.path.join(
self.__project_meta.PWD, "logs", f"{self.__case_id}.run.log"
@@ -315,24 +337,24 @@ class HttpRunner(object):
log_handler = logger.add(self.__log_path, level="DEBUG")
# parse config name
variables = self.config.variables
variables = self.__config.variables
variables.update(self.__session_variables)
self.config.name = parse_data(
self.config.name, variables, self.__project_meta.functions
self.__config.name = parse_data(
self.__config.name, variables, self.__project_meta.functions
)
if USE_ALLURE:
# update allure report meta
allure.dynamic.title(self.config.name)
allure.dynamic.title(self.__config.name)
allure.dynamic.description(f"TestCase ID: {self.__case_id}")
logger.info(
f"Start to run testcase: {self.config.name}, TestCase ID: {self.__case_id}"
f"Start to run testcase: {self.__config.name}, TestCase ID: {self.__case_id}"
)
try:
return self.run_testcase(
TestCase(config=self.config, teststeps=self.teststeps)
TestCase(config=self.__config, teststeps=self.__teststeps)
)
finally:
logger.remove(log_handler)

View File

@@ -66,7 +66,7 @@ teststeps:
validate:
- eq: ["status_code", 200]
- eq: ["body.args.foo1", "session_bar1"]
- eq: ["body.args.sum_v", 3]
- eq: ["body.args.sum_v", "3"]
- eq: ["body.args.foo2", "session_bar2"]
-
name: post raw text

View File

@@ -29,8 +29,6 @@ class MethodEnum(Text, Enum):
HEAD = "HEAD"
OPTIONS = "OPTIONS"
PATCH = "PATCH"
CONNECT = "CONNECT"
TRACE = "TRACE"
class TConfig(BaseModel):
@@ -45,17 +43,17 @@ class TConfig(BaseModel):
path: Text = None
class Request(BaseModel):
class TRequest(BaseModel):
"""requests.Request model"""
method: MethodEnum = MethodEnum.GET
method: MethodEnum
url: Url
params: Dict[Text, Text] = {}
headers: Headers = {}
req_json: Dict = Field({}, alias="json")
req_json: Union[Dict, List] = Field({}, alias="json")
data: Union[Text, Dict[Text, Any]] = ""
cookies: Cookies = {}
timeout: int = 120
timeout: float = 120
allow_redirects: bool = True
verify: Verify = False
upload: Dict = {} # used for upload files
@@ -63,8 +61,8 @@ class Request(BaseModel):
class TStep(BaseModel):
name: Name
request: Request = None
testcase: Union[Text, Callable] = ""
request: Union[TRequest, None] = None
testcase: Union[Text, Callable, None] = None
variables: VariablesMapping = {}
setup_hooks: Hook = []
teardown_hooks: Hook = []

318
httprunner/testcase.py Normal file
View File

@@ -0,0 +1,318 @@
import inspect
from typing import Text, Any, Union, Callable
from httprunner.schema import (
TConfig,
TStep,
TRequest,
MethodEnum,
TestCase,
)
class Config(object):
def __init__(self, name: Text):
self.__name = name
self.__variables = {}
self.__base_url = ""
self.__verify = False
caller_frame = inspect.stack()[1]
self.__path = caller_frame.filename
@property
def name(self):
return self.__name
@property
def path(self):
return self.__path
def variables(self, **variables) -> "Config":
self.__variables.update(variables)
return self
def base_url(self, base_url: Text) -> "Config":
self.__base_url = base_url
return self
def verify(self, verify: bool) -> "Config":
self.__verify = verify
return self
def perform(self) -> TConfig:
return TConfig(
name=self.__name,
base_url=self.__base_url,
verify=self.__verify,
variables=self.__variables,
path=self.__path,
)
class StepValidation(object):
def __init__(self, step: TStep):
self.__t_step = step
def assert_equal(self, jmes_path: Text, expected_value: Any) -> "StepValidation":
self.__t_step.validators.append({"equal": [jmes_path, expected_value]})
return self
def assert_not_equal(
self, jmes_path: Text, expected_value: Any
) -> "StepValidation":
self.__t_step.validators.append({"not_equal": [jmes_path, expected_value]})
return self
def assert_greater_than(
self, jmes_path: Text, expected_value: Union[int, float]
) -> "StepValidation":
self.__t_step.validators.append({"greater_than": [jmes_path, expected_value]})
return self
def assert_less_than(
self, jmes_path: Text, expected_value: Union[int, float]
) -> "StepValidation":
self.__t_step.validators.append({"less_than": [jmes_path, expected_value]})
return self
def assert_greater_or_equals(
self, jmes_path: Text, expected_value: Union[int, float]
) -> "StepValidation":
self.__t_step.validators.append(
{"greater_or_equals": [jmes_path, expected_value]}
)
return self
def assert_less_or_equals(
self, jmes_path: Text, expected_value: Union[int, float]
) -> "StepValidation":
self.__t_step.validators.append({"less_or_equals": [jmes_path, expected_value]})
return self
def assert_length_equal(
self, jmes_path: Text, expected_value: int
) -> "StepValidation":
self.__t_step.validators.append({"length_equal": [jmes_path, expected_value]})
return self
def assert_length_greater_than(
self, jmes_path: Text, expected_value: int
) -> "StepValidation":
self.__t_step.validators.append(
{"length_greater_than": [jmes_path, expected_value]}
)
return self
def assert_length_less_than(
self, jmes_path: Text, expected_value: int
) -> "StepValidation":
self.__t_step.validators.append(
{"length_less_than": [jmes_path, expected_value]}
)
return self
def assert_length_greater_or_equals(
self, jmes_path: Text, expected_value: int
) -> "StepValidation":
self.__t_step.validators.append(
{"length_greater_or_equals": [jmes_path, expected_value]}
)
return self
def assert_length_less_or_equals(
self, jmes_path: Text, expected_value: int
) -> "StepValidation":
self.__t_step.validators.append(
{"length_less_or_equals": [jmes_path, expected_value]}
)
return self
def assert_string_equals(
self, jmes_path: Text, expected_value: int
) -> "StepValidation":
self.__t_step.validators.append({"string_equals": [jmes_path, expected_value]})
return self
def assert_startswith(
self, jmes_path: Text, expected_value: Text
) -> "StepValidation":
self.__t_step.validators.append({"startswith": [jmes_path, expected_value]})
return self
def assert_endswith(
self, jmes_path: Text, expected_value: Text
) -> "StepValidation":
self.__t_step.validators.append({"endswith": [jmes_path, expected_value]})
return self
def assert_regex_match(
self, jmes_path: Text, expected_value: Text
) -> "StepValidation":
self.__t_step.validators.append({"regex_match": [jmes_path, expected_value]})
return self
def assert_contains(self, jmes_path: Text, expected_value: Any) -> "StepValidation":
self.__t_step.validators.append({"contains": [jmes_path, expected_value]})
return self
def assert_contained_by(
self, jmes_path: Text, expected_value: Any
) -> "StepValidation":
self.__t_step.validators.append({"contained_by": [jmes_path, expected_value]})
return self
def assert_type_match(
self, jmes_path: Text, expected_value: Text
) -> "StepValidation":
self.__t_step.validators.append({"type_match": [jmes_path, expected_value]})
return self
def perform(self) -> TStep:
return self.__t_step
class StepExtraction(object):
def __init__(self, step: TStep):
self.__t_step = step
def with_jmespath(self, jmes_path: Text, var_name: Text) -> "StepExtraction":
self.__t_step.extract[var_name] = jmes_path
return self
# def with_regex(self):
# # TODO: extract response html with regex
# pass
#
# def with_jsonpath(self):
# # TODO: extract response json with jsonpath
# pass
def validate(self) -> StepValidation:
return StepValidation(self.__t_step)
def perform(self) -> TStep:
return self.__t_step
class RequestWithOptionalArgs(object):
def __init__(self, step: TStep):
self.__t_step = step
def with_params(self, **params) -> "RequestWithOptionalArgs":
self.__t_step.request.params.update(params)
return self
def with_headers(self, **headers) -> "RequestWithOptionalArgs":
self.__t_step.request.headers.update(headers)
return self
def with_cookies(self, **cookies) -> "RequestWithOptionalArgs":
self.__t_step.request.cookies.update(cookies)
return self
def with_data(self, data) -> "RequestWithOptionalArgs":
self.__t_step.request.data = data
return self
def set_timeout(self, timeout: float) -> "RequestWithOptionalArgs":
self.__t_step.request.timeout = timeout
return self
def set_verify(self, verify: bool) -> "RequestWithOptionalArgs":
self.__t_step.request.verify = verify
return self
def set_allow_redirects(self, allow_redirects: bool) -> "RequestWithOptionalArgs":
self.__t_step.request.allow_redirects = allow_redirects
return self
def upload(self, **file_info) -> "RequestWithOptionalArgs":
self.__t_step.request.upload.update(file_info)
return self
# def hooks(self):
# pass
def extract(self) -> StepExtraction:
return StepExtraction(self.__t_step)
def validate(self) -> StepValidation:
return StepValidation(self.__t_step)
def perform(self) -> TStep:
return self.__t_step
class RunRequest(object):
def __init__(self, name: Text):
self.__t_step = TStep(name=name)
def with_variables(self, **variables) -> "RunRequest":
self.__t_step.variables.update(variables)
return self
def get(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.GET, url=url)
return RequestWithOptionalArgs(self.__t_step)
def post(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.POST, url=url)
return RequestWithOptionalArgs(self.__t_step)
def put(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.PUT, url=url)
return RequestWithOptionalArgs(self.__t_step)
def head(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.HEAD, url=url)
return RequestWithOptionalArgs(self.__t_step)
def delete(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.DELETE, url=url)
return RequestWithOptionalArgs(self.__t_step)
def options(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.OPTIONS, url=url)
return RequestWithOptionalArgs(self.__t_step)
def patch(self, url: Text) -> RequestWithOptionalArgs:
self.__t_step.request = TRequest(method=MethodEnum.PATCH, url=url)
return RequestWithOptionalArgs(self.__t_step)
class RunTestCase(object):
def __init__(self, name: Text):
self.__t_step = TStep(name=name)
def with_variables(self, **variables) -> "RunTestCase":
self.__t_step.variables.update(variables)
return self
def call(self, testcase: Callable):
self.__t_step.testcase = testcase
return self
def perform(self) -> TStep:
return self.__t_step
class Step(object):
def __init__(
self,
step: Union[
StepValidation, StepExtraction, RequestWithOptionalArgs, RunTestCase
],
):
self.__t_step = step.perform()
@property
def request(self) -> TRequest:
return self.__t_step.request
@property
def testcase(self) -> TestCase:
return self.__t_step.testcase
def perform(self) -> TStep:
return self.__t_step

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "httprunner"
version = "3.0.6"
version = "3.0.7"
description = "One-stop solution for HTTP(S) testing."
license = "Apache-2.0"
readme = "README.md"

View File

@@ -34,7 +34,7 @@ class TestHar(TestHar2CaseUtils):
self.assertIn("validate", teststeps[0])
def test_gen_testcase_yaml(self):
yaml_file = os.path.join(os.path.dirname(__file__), "data", "demo.yaml")
yaml_file = os.path.join(os.path.dirname(__file__), "data", "demo.yml")
self.har_parser.gen_testcase(file_type="YAML")
self.assertTrue(os.path.isfile(yaml_file))

View File

@@ -1,9 +1,16 @@
import unittest
from httprunner.make import main_make, convert_testcase_path, make_files_cache_set
from httprunner.make import (
main_make,
convert_testcase_path,
make_files_cache_set,
make_config_chain_style,
make_teststep_chain_style,
pytest_files_set,
)
class TestLoader(unittest.TestCase):
class TestMake(unittest.TestCase):
def test_make_testcase(self):
path = ["examples/postman_echo/request_methods/request_with_variables.yml"]
testcase_python_list = main_make(path)
@@ -17,8 +24,9 @@ class TestLoader(unittest.TestCase):
"examples/postman_echo/request_methods/request_with_testcase_reference.yml"
]
make_files_cache_set.clear()
pytest_files_set.clear()
testcase_python_list = main_make(path)
self.assertEqual(len(testcase_python_list), 2)
self.assertEqual(len(testcase_python_list), 1)
self.assertIn(
"examples/postman_echo/request_methods/request_with_testcase_reference_test.py",
testcase_python_list,
@@ -37,7 +45,7 @@ from examples.postman_echo.request_methods.request_with_functions_test import (
content,
)
self.assertIn(
'"testcase": RequestWithFunctions,', content,
".call(RequestWithFunctions)", content,
)
def test_make_testcase_folder(self):
@@ -85,8 +93,9 @@ from examples.postman_echo.request_methods.request_with_functions_test import (
def test_make_testsuite(self):
path = ["examples/postman_echo/request_methods/demo_testsuite.yml"]
make_files_cache_set.clear()
pytest_files_set.clear()
testcase_python_list = main_make(path)
self.assertEqual(len(testcase_python_list), 3)
self.assertEqual(len(testcase_python_list), 2)
self.assertIn(
"examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py",
testcase_python_list,
@@ -95,7 +104,42 @@ from examples.postman_echo.request_methods.request_with_functions_test import (
"examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py",
testcase_python_list,
)
self.assertIn(
"examples/postman_echo/request_methods/request_with_functions_test.py",
testcase_python_list,
def test_make_config_chain_style(self):
config = {
"name": "request methods testcase: validate with functions",
"variables": {"foo1": "bar1", "foo2": 22},
"base_url": "https://postman-echo.com",
"verify": False,
"path": "examples/postman_echo/request_methods/validate_with_functions_test.py",
}
self.assertEqual(
make_config_chain_style(config),
"""Config("request methods testcase: validate with functions").variables(**{'foo1': 'bar1', 'foo2': 22}).base_url("https://postman-echo.com").verify(False)""",
)
def test_make_teststep_chain_style(self):
step = {
"name": "get with params",
"variables": {"foo1": "bar1", "foo2": 123, "sum_v": "${sum_two(1, 2)}",},
"request": {
"method": "GET",
"url": "/get",
"params": {"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"},
"headers": {"User-Agent": "HttpRunner/${get_httprunner_version()}"},
},
"testcase": "CLS_LB(TestCaseDemo)CLS_RB",
"extract": {
"session_foo1": "body.args.foo1",
"session_foo2": "body.args.foo2",
},
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["body.args.sum_v", "3"]},
],
}
teststep_chain_style = make_teststep_chain_style(step)
self.assertEqual(
teststep_chain_style,
"""Step(RunRequest("get with params").with_variables(**{'foo1': 'bar1', 'foo2': 123, 'sum_v': '${sum_two(1, 2)}'}).get("/get").with_params(**{'foo1': '$foo1', 'foo2': '$foo2', 'sum_v': '$sum_v'}).with_headers(**{'User-Agent': 'HttpRunner/${get_httprunner_version()}'}).extract().with_jmespath("body.args.foo1", "session_foo1").with_jmespath("body.args.foo2", "session_foo2").validate().assert_equal("status_code", 200).assert_equal("body.args.sum_v", "3"))""",
)

View File

@@ -12,33 +12,32 @@ class TestUtils(unittest.TestCase):
self.assertIn("abc", os.environ)
self.assertEqual(os.environ["abc"], "123")
def current_validators(self):
def test_validators(self):
from httprunner.builtin import comparators
functions_mapping = loader.load_module_functions(comparators)
functions_mapping["equals"](None, None)
functions_mapping["equals"](1, 1)
functions_mapping["equals"]("abc", "abc")
functions_mapping["equal"](None, None)
functions_mapping["equal"](1, 1)
functions_mapping["equal"]("abc", "abc")
with self.assertRaises(AssertionError):
functions_mapping["equals"]("123", 123)
functions_mapping["equal"]("123", 123)
functions_mapping["less_than"](1, 2)
functions_mapping["less_than_or_equals"](2, 2)
functions_mapping["less_or_equals"](2, 2)
functions_mapping["greater_than"](2, 1)
functions_mapping["greater_than_or_equals"](2, 2)
functions_mapping["greater_or_equals"](2, 2)
functions_mapping["not_equals"](123, "123")
functions_mapping["not_equal"](123, "123")
functions_mapping["length_equals"]("123", 3)
# Because the Numbers in a CSV file are by default treated as strings,
# you need to convert them to Numbers, and we'll test that out here.
functions_mapping["length_equals"]("123", "3")
functions_mapping["length_equal"]("123", 3)
with self.assertRaises(AssertionError):
functions_mapping["length_equals"]("123", "abc")
functions_mapping["length_equal"]("123", "3")
with self.assertRaises(AssertionError):
functions_mapping["length_equal"]("123", "abc")
functions_mapping["length_greater_than"]("123", 2)
functions_mapping["length_greater_than_or_equals"]("123", 3)
functions_mapping["length_greater_or_equals"]("123", 3)
functions_mapping["contains"]("123abc456", "3ab")
functions_mapping["contains"](["1", "2"], "1")