diff --git a/.github/workflows/smoketest.yml b/.github/workflows/smoketest.yml
index 1d629d59..e08ea6e5 100644
--- a/.github/workflows/smoketest.yml
+++ b/.github/workflows/smoketest.yml
@@ -66,10 +66,14 @@ jobs:
uses: actions/checkout@v2
- name: Build hrp binary
run: make build
- - name: Run smoketest - postman echo
- run: ./output/hrp boom examples/hrp/postman-echo.json --spawn-count 10 --spawn-rate 10 --loop-count 10
- - name: Run smoketest - data driven with parameterize mechanism
+ - name: Run smoketest - run with parameters
+ run: ./output/hrp run examples/hrp/parameters_test.json
+ - name: Run smoketest - boom with parameters
run: ./output/hrp boom examples/hrp/parameters_test.json --spawn-count 10 --spawn-rate 10 --loop-count 10
- - name: Run smoketest - rendezvous
+ - name: Run smoketest - boom with rendezvous
run: |
./output/hrp boom examples/hrp/rendezvous_test.json --spawn-count 10 --spawn-rate 10 --loop-count 10
+ - name: Run hrp convert --pytest
+ run: ./output/hrp convert examples/postman_echo/request_methods/
+ - name: Run hrp pytest
+ run: ./output/hrp pytest examples/postman_echo/request_methods/
diff --git a/README.en.md b/README.en.md
index 47eab79d..511a02cd 100644
--- a/README.en.md
+++ b/README.en.md
@@ -83,7 +83,7 @@ monitoring (DEM) test types. Enjoy! ✨ 🚀 ✨
License: Apache-2.0
Website: https://httprunner.com
Github: https://github.com/httprunner/httprunner
-Copyright 2021 debugtalk
+Copyright 2017 debugtalk
Usage:
hrp [command]
diff --git a/README.md b/README.md
index c623efa0..b701e953 100644
--- a/README.md
+++ b/README.md
@@ -76,7 +76,7 @@ monitoring (DEM) test types. Enjoy! ✨ 🚀 ✨
License: Apache-2.0
Website: https://httprunner.com
Github: https://github.com/httprunner/httprunner
-Copyright 2021 debugtalk
+Copyright 2017 debugtalk
Usage:
hrp [command]
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 504da451..d6f3369e 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,11 +1,12 @@
# Release History
-## v4.0.0-alpha
+## v4.0.0-beta (2022-04-24)
- refactor: merge [hrp] into httprunner v4, which will include golang and python dual engine
- refactor: redesign `IStep` to make step extensible to support implementing new protocols and test types
- feat: disable GA events report by setting environment `DISABLE_GA=true`
- feat: disable sentry reports by setting environment `DISABLE_SENTRY=true`
+- feat: prepare python3 venv in `~/.hrp/venv` before running
**go version**
@@ -13,6 +14,7 @@
- feat: support run testcases in specified folder path, including testcases in sub folders
- feat: support HTTP/2 protocol
- feat: support WebSocket protocol
+- feat: convert YAML/JSON testcases to pytest scripts with `hrp convert`
- change: integrate [sentry sdk][sentry sdk] for panic reporting and analysis
- change: lock funplugin version when creating scaffold project
- fix: call referenced api/testcase with relative path
@@ -163,6 +165,10 @@
- test: add CI test with [github actions][github-actions]
- test: integrate [sentry sdk][sentry sdk] for event reporting and analysis
+## 3.1.11 (2022-04-24)
+
+- fix #1273: ImportError by cannot import name '_unicodefun' from 'click'
+
## 3.1.10 (2022-04-18)
- fix #1249: catch exceptions when requesting with disabling allow_redirects
diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md
index 1a3960a4..4a0c55d0 100644
--- a/docs/cmd/hrp.md
+++ b/docs/cmd/hrp.md
@@ -19,7 +19,7 @@ monitoring (DEM) test types. Enjoy! ✨ 🚀 ✨
License: Apache-2.0
Website: https://httprunner.com
Github: https://github.com/httprunner/httprunner
-Copyright 2021 debugtalk
+Copyright 2017 debugtalk
### Options
@@ -30,9 +30,10 @@ Copyright 2021 debugtalk
### SEE ALSO
* [hrp boom](hrp_boom.md) - run load test with boomer
+* [hrp convert](hrp_convert.md) - convert JSON/YAML testcases to pytest/gotest scripts
* [hrp har2case](hrp_har2case.md) - convert HAR to json/yaml testcase files
* [hrp pytest](hrp_pytest.md) - run API test with pytest
* [hrp run](hrp_run.md) - run API test with go engine
* [hrp startproject](hrp_startproject.md) - create a scaffold project
-###### Auto generated by spf13/cobra on 17-Apr-2022
+###### Auto generated by spf13/cobra on 24-Apr-2022
diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md
index 7be66d74..ebc6bf4c 100644
--- a/docs/cmd/hrp_boom.md
+++ b/docs/cmd/hrp_boom.md
@@ -41,4 +41,4 @@ hrp boom [flags]
* [hrp](hrp.md) - Next-Generation API Testing Solution.
-###### Auto generated by spf13/cobra on 17-Apr-2022
+###### Auto generated by spf13/cobra on 24-Apr-2022
diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md
new file mode 100644
index 00000000..6ec6b49e
--- /dev/null
+++ b/docs/cmd/hrp_convert.md
@@ -0,0 +1,21 @@
+## hrp convert
+
+convert JSON/YAML testcases to pytest/gotest scripts
+
+```
+hrp convert $path... [flags]
+```
+
+### Options
+
+```
+ --gotest convert to gotest scripts (TODO)
+ -h, --help help for convert
+ --pytest convert to pytest scripts (default true)
+```
+
+### SEE ALSO
+
+* [hrp](hrp.md) - Next-Generation API Testing Solution.
+
+###### Auto generated by spf13/cobra on 24-Apr-2022
diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md
index 729b1226..0d15ec54 100644
--- a/docs/cmd/hrp_har2case.md
+++ b/docs/cmd/hrp_har2case.md
@@ -24,4 +24,4 @@ hrp har2case $har_path... [flags]
* [hrp](hrp.md) - Next-Generation API Testing Solution.
-###### Auto generated by spf13/cobra on 17-Apr-2022
+###### Auto generated by spf13/cobra on 24-Apr-2022
diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md
index 5a2363be..bc0c2437 100644
--- a/docs/cmd/hrp_pytest.md
+++ b/docs/cmd/hrp_pytest.md
@@ -16,4 +16,4 @@ hrp pytest $path ... [flags]
* [hrp](hrp.md) - Next-Generation API Testing Solution.
-###### Auto generated by spf13/cobra on 17-Apr-2022
+###### Auto generated by spf13/cobra on 24-Apr-2022
diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md
index 7ad8d4d4..d7581b4c 100644
--- a/docs/cmd/hrp_run.md
+++ b/docs/cmd/hrp_run.md
@@ -34,4 +34,4 @@ hrp run $path... [flags]
* [hrp](hrp.md) - Next-Generation API Testing Solution.
-###### Auto generated by spf13/cobra on 17-Apr-2022
+###### Auto generated by spf13/cobra on 24-Apr-2022
diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md
index e33d46d6..3402b1c9 100644
--- a/docs/cmd/hrp_startproject.md
+++ b/docs/cmd/hrp_startproject.md
@@ -9,6 +9,7 @@ hrp startproject $project_name [flags]
### Options
```
+ -f, --force force to overwrite existing project
--go generate hashicorp go plugin
-h, --help help for startproject
--ignore-plugin ignore function plugin
@@ -19,4 +20,4 @@ hrp startproject $project_name [flags]
* [hrp](hrp.md) - Next-Generation API Testing Solution.
-###### Auto generated by spf13/cobra on 17-Apr-2022
+###### Auto generated by spf13/cobra on 24-Apr-2022
diff --git a/examples/data/a_b_c/T1_test.py b/examples/data/a_b_c/T1_test.py
index db722b78..d3273df7 100644
--- a/examples/data/a_b_c/T1_test.py
+++ b/examples/data/a_b_c/T1_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: a-b.c/1.yml
diff --git a/examples/data/a_b_c/T2_3_test.py b/examples/data/a_b_c/T2_3_test.py
index eec4401b..a225b4cb 100644
--- a/examples/data/a_b_c/T2_3_test.py
+++ b/examples/data/a_b_c/T2_3_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: a-b.c/2 3.yml
diff --git a/examples/demo-with-go-plugin/plugin/debugtalk.go b/examples/demo-with-go-plugin/plugin/debugtalk.go
index f99a1321..dbb37554 100644
--- a/examples/demo-with-go-plugin/plugin/debugtalk.go
+++ b/examples/demo-with-go-plugin/plugin/debugtalk.go
@@ -42,7 +42,7 @@ func TeardownHookExample(args string) string {
}
func GetVersion() string {
- return "v4.0.0-alpha"
+ return "v4.0.0-beta"
}
func main() {
diff --git a/examples/demo-with-go-plugin/plugin/go.mod b/examples/demo-with-go-plugin/plugin/go.mod
index a8aafa0f..941628d8 100644
--- a/examples/demo-with-go-plugin/plugin/go.mod
+++ b/examples/demo-with-go-plugin/plugin/go.mod
@@ -2,4 +2,4 @@ module plugin
go 1.16
-require github.com/httprunner/funplugin v0.4.2 // indirect
+require github.com/httprunner/funplugin v0.4.3 // indirect
diff --git a/examples/demo-with-go-plugin/plugin/go.sum b/examples/demo-with-go-plugin/plugin/go.sum
index 85aa768d..93a4421a 100644
--- a/examples/demo-with-go-plugin/plugin/go.sum
+++ b/examples/demo-with-go-plugin/plugin/go.sum
@@ -58,8 +58,8 @@ github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v
github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
-github.com/httprunner/funplugin v0.4.2 h1:iDeg3GVCKdimgZQ40xq0kxHqhL/DQmRxs3DRjzOpUuo=
-github.com/httprunner/funplugin v0.4.2/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc=
+github.com/httprunner/funplugin v0.4.3 h1:mxdxQh54NZLQnK/FXZxpZV0rhqZQzckrWKEnBW5w2Vg=
+github.com/httprunner/funplugin v0.4.3/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc=
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
diff --git a/examples/demo-with-py-plugin/debugtalk.py b/examples/demo-with-py-plugin/debugtalk.py
index 743e7f62..180725e0 100644
--- a/examples/demo-with-py-plugin/debugtalk.py
+++ b/examples/demo-with-py-plugin/debugtalk.py
@@ -6,7 +6,7 @@ import funppy
def get_httprunner_version():
- return "v4.0.0-alpha"
+ return "v4.0.0-beta"
def sleep(n_secs):
@@ -59,7 +59,7 @@ def teardown_hook_example(name):
return f"teardown_hook_example: {name}"
-if __name__ == '__main__':
+if __name__ == "__main__":
funppy.register("get_httprunner_version", get_httprunner_version)
funppy.register("sum", sum)
funppy.register("sum_ints", sum_ints)
diff --git a/examples/httpbin/basic_test.py b/examples/httpbin/basic_test.py
index eec02f1f..d13c87ac 100644
--- a/examples/httpbin/basic_test.py
+++ b/examples/httpbin/basic_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: basic.yml
diff --git a/examples/httpbin/debugtalk.py b/examples/httpbin/debugtalk.py
index ef78aee0..f949e84b 100644
--- a/examples/httpbin/debugtalk.py
+++ b/examples/httpbin/debugtalk.py
@@ -36,8 +36,8 @@ def sum_two(m, n):
def sum_status_code(status_code, expect_sum):
- """ sum status code digits
- e.g. 400 => 4, 201 => 3
+ """sum status code digits
+ e.g. 400 => 4, 201 => 3
"""
sum_value = 0
for digit in str(status_code):
@@ -54,8 +54,7 @@ os.environ["TEST_ENV"] = "PRODUCTION"
def skip_test_in_production_env():
- """ skip this test in production environment
- """
+ """skip this test in production environment"""
return os.environ["TEST_ENV"] == "PRODUCTION"
@@ -97,8 +96,7 @@ def setup_hook_remove_kwargs(request):
def teardown_hook_sleep_N_secs(response, n_secs):
- """ sleep n seconds after request
- """
+ """sleep n seconds after request"""
if response.status_code == 200:
time.sleep(0.1)
else:
diff --git a/examples/httpbin/hooks_test.py b/examples/httpbin/hooks_test.py
index 0a696d8f..96302d35 100644
--- a/examples/httpbin/hooks_test.py
+++ b/examples/httpbin/hooks_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: hooks.yml
diff --git a/examples/httpbin/load_image_test.py b/examples/httpbin/load_image_test.py
index d00e0e22..a43f5e75 100644
--- a/examples/httpbin/load_image_test.py
+++ b/examples/httpbin/load_image_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: load_image.yml
diff --git a/examples/httpbin/upload_test.py b/examples/httpbin/upload_test.py
index f9ef8406..394ecbb6 100644
--- a/examples/httpbin/upload_test.py
+++ b/examples/httpbin/upload_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: upload.yml
diff --git a/examples/httpbin/validate_test.py b/examples/httpbin/validate_test.py
index fe1c2b76..ae948113 100644
--- a/examples/httpbin/validate_test.py
+++ b/examples/httpbin/validate_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: validate.yml
diff --git a/examples/postman_echo/conftest.py b/examples/postman_echo/conftest.py
index 88a30859..c894700f 100644
--- a/examples/postman_echo/conftest.py
+++ b/examples/postman_echo/conftest.py
@@ -54,8 +54,7 @@ def session_fixture(request):
summary["details"].append(testcase_summary_json)
summary_path = os.path.join(
- os.getcwd(),
- "examples/postman_echo/logs/request_methods/hardcode.summary.json"
+ os.getcwd(), "examples/postman_echo/logs/request_methods/hardcode.summary.json"
)
summary_dir = os.path.dirname(summary_path)
os.makedirs(summary_dir, exist_ok=True)
diff --git a/examples/postman_echo/cookie_manipulation/hardcode_test.py b/examples/postman_echo/cookie_manipulation/hardcode_test.py
index b69b0e40..f8d58a4d 100644
--- a/examples/postman_echo/cookie_manipulation/hardcode_test.py
+++ b/examples/postman_echo/cookie_manipulation/hardcode_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v3.1.7
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: cookie_manipulation/hardcode.yml
diff --git a/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py b/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py
index 77347c7a..371a5372 100644
--- a/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py
+++ b/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v3.1.7
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: cookie_manipulation/set_delete_cookies.yml
diff --git a/examples/postman_echo/request_methods/demo_testsuite.yml b/examples/postman_echo/request_methods/demo_testsuite.yml
deleted file mode 100644
index 4c367a6b..00000000
--- a/examples/postman_echo/request_methods/demo_testsuite.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-config:
- name: "demo testsuite"
- variables: ${get_testsuite_config_variables()}
-
-testcases:
--
- name: request with functions
- testcase: request_methods/request_with_functions.yml
- weight: 2
- variables:
- foo1: testcase_ref_bar11
- expect_foo1: testcase_ref_bar11
- expect_foo2: testsuite_config_bar2
--
- name: request with referenced testcase
- testcase: request_methods/request_with_testcase_reference.yml
- weight: 3
- variables:
- foo1: testcase_ref_bar12
- expect_foo1: testcase_ref_bar12
- foo2: testcase_ref_bar22
- expect_foo2: testcase_ref_bar22
diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/__init__.py b/examples/postman_echo/request_methods/demo_testsuite_yml/__init__.py
deleted file mode 100644
index 70cfba53..00000000
--- a/examples/postman_echo/request_methods/demo_testsuite_yml/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# NOTICE: Generated By HttpRunner. DO NOT EDIT!
diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py
deleted file mode 100644
index 97593703..00000000
--- a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_functions_test.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
-# FROM: request_methods/request_with_functions.yml
-
-
-from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
-
-
-class TestCaseRequestWithFunctions(HttpRunner):
-
- config = (
- Config("request with functions")
- .variables(
- **{
- "foo1": "testcase_ref_bar11",
- "foo2": "testsuite_config_bar2",
- "expect_foo1": "testcase_ref_bar11",
- "expect_foo2": "testsuite_config_bar2",
- }
- )
- .base_url("https://postman-echo.com")
- .verify(False)
- .export(*["foo3"])
- )
-
- teststeps = [
- Step(
- RunRequest("get with params")
- .with_variables(
- **{"foo1": "bar11", "foo2": "bar21", "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", "foo3")
- .validate()
- .assert_equal("status_code", 200)
- .assert_equal("body.args.foo1", "bar11")
- .assert_equal("body.args.sum_v", "3")
- .assert_equal("body.args.foo2", "bar21")
- ),
- Step(
- RunRequest("post raw text")
- .with_variables(**{"foo1": "bar12", "foo3": "bar32"})
- .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-$foo2-$foo3."
- )
- .validate()
- .assert_equal("status_code", 200)
- .assert_equal(
- "body.data",
- "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.",
- )
- .assert_type_match("body.json", "None")
- .assert_type_match("body.json", "NoneType")
- .assert_type_match("body.json", None)
- ),
- Step(
- RunRequest("post form data")
- .with_variables(**{"foo2": "bar23"})
- .post("/post")
- .with_headers(
- **{
- "User-Agent": "HttpRunner/${get_httprunner_version()}",
- "Content-Type": "application/x-www-form-urlencoded",
- }
- )
- .with_data("foo1=$foo1&foo2=$foo2&foo3=$foo3")
- .validate()
- .assert_equal("status_code", 200, "response status code should be 200")
- .assert_equal("body.form.foo1", "$expect_foo1")
- .assert_equal("body.form.foo2", "bar23")
- .assert_equal("body.form.foo3", "bar21")
- ),
- ]
-
-
-if __name__ == "__main__":
- TestCaseRequestWithFunctions().test_start()
diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py
deleted file mode 100644
index 54d76e41..00000000
--- a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
-# FROM: request_methods/request_with_testcase_reference.yml
-
-
-import sys
-from pathlib import Path
-
-sys.path.insert(0, str(Path(__file__).parent.parent))
-
-
-from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
-
-from request_methods.request_with_functions_test import (
- TestCaseRequestWithFunctions as RequestWithFunctions,
-)
-
-
-class TestCaseRequestWithTestcaseReference(HttpRunner):
-
- config = (
- Config("request with referenced testcase")
- .variables(
- **{
- "foo1": "testcase_ref_bar12",
- "expect_foo1": "testcase_ref_bar12",
- "expect_foo2": "testcase_ref_bar22",
- "foo2": "testcase_ref_bar22",
- }
- )
- .base_url("https://postman-echo.com")
- .verify(False)
- )
-
- teststeps = [
- Step(
- RunTestCase("request with functions")
- .with_variables(
- **{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"}
- )
- .setup_hook("${sleep(0.1)}")
- .call(RequestWithFunctions)
- .teardown_hook("${sleep(0.2)}")
- .export(*["foo3"])
- ),
- Step(
- RunRequest("post form data")
- .with_variables(**{"foo1": "bar1"})
- .post("/post")
- .with_headers(
- **{
- "User-Agent": "HttpRunner/${get_httprunner_version()}",
- "Content-Type": "application/x-www-form-urlencoded",
- }
- )
- .with_data("foo1=$foo1&foo2=$foo3")
- .validate()
- .assert_equal("status_code", 200)
- .assert_equal("body.form.foo1", "bar1")
- .assert_equal("body.form.foo2", "bar21")
- ),
- ]
-
-
-if __name__ == "__main__":
- TestCaseRequestWithTestcaseReference().test_start()
diff --git a/examples/postman_echo/request_methods/hardcode_test.py b/examples/postman_echo/request_methods/hardcode_test.py
index ba492426..a2b49836 100644
--- a/examples/postman_echo/request_methods/hardcode_test.py
+++ b/examples/postman_echo/request_methods/hardcode_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: request_methods/hardcode.yml
diff --git a/examples/postman_echo/request_methods/request_with_functions_test.py b/examples/postman_echo/request_methods/request_with_functions_test.py
index 90f14f36..8688765f 100644
--- a/examples/postman_echo/request_methods/request_with_functions_test.py
+++ b/examples/postman_echo/request_methods/request_with_functions_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: request_methods/request_with_functions.yml
diff --git a/examples/postman_echo/request_methods/request_with_parameters_test.py b/examples/postman_echo/request_methods/request_with_parameters_test.py
index 5a538af5..5c6271a8 100644
--- a/examples/postman_echo/request_methods/request_with_parameters_test.py
+++ b/examples/postman_echo/request_methods/request_with_parameters_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: request_methods/request_with_parameters.yml
diff --git a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py
index 71305153..6a55b63a 100644
--- a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py
+++ b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: request_methods/request_with_testcase_reference.yml
diff --git a/examples/postman_echo/request_methods/request_with_variables_test.py b/examples/postman_echo/request_methods/request_with_variables_test.py
index 61d9382b..0c7c0f77 100644
--- a/examples/postman_echo/request_methods/request_with_variables_test.py
+++ b/examples/postman_echo/request_methods/request_with_variables_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: request_methods/request_with_variables.yml
diff --git a/examples/postman_echo/request_methods/validate_with_functions_test.py b/examples/postman_echo/request_methods/validate_with_functions_test.py
index d2f58894..2ad1b58f 100644
--- a/examples/postman_echo/request_methods/validate_with_functions_test.py
+++ b/examples/postman_echo/request_methods/validate_with_functions_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: request_methods/validate_with_functions.yml
diff --git a/examples/postman_echo/request_methods/validate_with_variables_test.py b/examples/postman_echo/request_methods/validate_with_variables_test.py
index eaa20726..26fa76bc 100644
--- a/examples/postman_echo/request_methods/validate_with_variables_test.py
+++ b/examples/postman_echo/request_methods/validate_with_variables_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: request_methods/validate_with_variables.yml
diff --git a/go.mod b/go.mod
index 52540f0c..49c7f16b 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
github.com/getsentry/sentry-go v0.13.0
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.4.1
- github.com/httprunner/funplugin v0.4.2
+ github.com/httprunner/funplugin v0.4.3
github.com/jinzhu/copier v0.3.2
github.com/jmespath/go-jmespath v0.4.0
github.com/json-iterator/go v1.1.12
diff --git a/go.sum b/go.sum
index ad484835..9647213c 100644
--- a/go.sum
+++ b/go.sum
@@ -241,8 +241,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
-github.com/httprunner/funplugin v0.4.2 h1:iDeg3GVCKdimgZQ40xq0kxHqhL/DQmRxs3DRjzOpUuo=
-github.com/httprunner/funplugin v0.4.2/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc=
+github.com/httprunner/funplugin v0.4.3 h1:mxdxQh54NZLQnK/FXZxpZV0rhqZQzckrWKEnBW5w2Vg=
+github.com/httprunner/funplugin v0.4.3/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
diff --git a/hrp/boomer.go b/hrp/boomer.go
index d639f981..271f00d7 100644
--- a/hrp/boomer.go
+++ b/hrp/boomer.go
@@ -46,7 +46,7 @@ func (b *HRPBoomer) Run(testcases ...ITestCase) {
var taskSlice []*boomer.Task
// load all testcases
- testCases, err := loadTestCases(testcases...)
+ testCases, err := LoadTestCases(testcases...)
if err != nil {
log.Error().Err(err).Msg("failed to load testcases")
os.Exit(1)
diff --git a/hrp/cmd/convert.go b/hrp/cmd/convert.go
new file mode 100644
index 00000000..7ec89187
--- /dev/null
+++ b/hrp/cmd/convert.go
@@ -0,0 +1,48 @@
+package cmd
+
+import (
+ "errors"
+ "os"
+
+ "github.com/rs/zerolog/log"
+ "github.com/spf13/cobra"
+
+ "github.com/httprunner/httprunner/hrp/internal/convert"
+)
+
+var convertCmd = &cobra.Command{
+ Use: "convert $path...",
+ Short: "convert JSON/YAML testcases to pytest/gotest scripts",
+ Args: cobra.ExactValidArgs(1),
+ PreRun: func(cmd *cobra.Command, args []string) {
+ setLogLevel(logLevel)
+ },
+ RunE: func(cmd *cobra.Command, args []string) error {
+ if !pytestFlag && !gotestFlag {
+ return errors.New("please specify convertion type")
+ }
+
+ var err error
+ if gotestFlag {
+ err = convert.Convert2TestScripts("gotest", args...)
+ } else {
+ err = convert.Convert2TestScripts("pytest", args...)
+ }
+ if err != nil {
+ log.Error().Err(err).Msg("convert test scripts failed")
+ os.Exit(1)
+ }
+ return nil
+ },
+}
+
+var (
+ pytestFlag bool
+ gotestFlag bool
+)
+
+func init() {
+ rootCmd.AddCommand(convertCmd)
+ convertCmd.Flags().BoolVar(&pytestFlag, "pytest", true, "convert to pytest scripts")
+ convertCmd.Flags().BoolVar(&gotestFlag, "gotest", false, "convert to gotest scripts (TODO)")
+}
diff --git a/hrp/cmd/root.go b/hrp/cmd/root.go
index 9128d37e..c19775d9 100644
--- a/hrp/cmd/root.go
+++ b/hrp/cmd/root.go
@@ -31,7 +31,7 @@ monitoring (DEM) test types. Enjoy! ✨ 🚀 ✨
License: Apache-2.0
Website: https://httprunner.com
Github: https://github.com/httprunner/httprunner
-Copyright 2021 debugtalk`,
+Copyright 2017 debugtalk`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
var noColor = false
if runtime.GOOS == "windows" {
diff --git a/hrp/cmd/scaffold.go b/hrp/cmd/scaffold.go
index 0d065839..5f9eb945 100644
--- a/hrp/cmd/scaffold.go
+++ b/hrp/cmd/scaffold.go
@@ -19,7 +19,7 @@ var scaffoldCmd = &cobra.Command{
},
RunE: func(cmd *cobra.Command, args []string) error {
if !ignorePlugin && !genPythonPlugin && !genGoPlugin {
- return errors.New("please select function plugin type")
+ return errors.New("please specify function plugin type")
}
var pluginType scaffold.PluginType
@@ -31,7 +31,7 @@ var scaffoldCmd = &cobra.Command{
pluginType = scaffold.Py // default
}
- err := scaffold.CreateScaffold(args[0], pluginType)
+ err := scaffold.CreateScaffold(args[0], pluginType, force)
if err != nil {
log.Error().Err(err).Msg("create scaffold project failed")
os.Exit(1)
@@ -45,10 +45,12 @@ var (
ignorePlugin bool
genPythonPlugin bool
genGoPlugin bool
+ force bool
)
func init() {
rootCmd.AddCommand(scaffoldCmd)
+ scaffoldCmd.Flags().BoolVarP(&force, "force", "f", false, "force to overwrite existing project")
scaffoldCmd.Flags().BoolVar(&genPythonPlugin, "py", true, "generate hashicorp python plugin")
scaffoldCmd.Flags().BoolVar(&genGoPlugin, "go", false, "generate hashicorp go plugin")
scaffoldCmd.Flags().BoolVar(&ignorePlugin, "ignore-plugin", false, "ignore function plugin")
diff --git a/hrp/internal/builtin/assertion.go b/hrp/internal/builtin/assertion.go
index 01eb3157..96fd4310 100644
--- a/hrp/internal/builtin/assertion.go
+++ b/hrp/internal/builtin/assertion.go
@@ -9,9 +9,9 @@ import (
)
var Assertions = map[string]func(t assert.TestingT, actual interface{}, expected interface{}, msgAndArgs ...interface{}) bool{
- "eq": assert.EqualValues,
- "equals": assert.EqualValues,
- "equal": assert.EqualValues,
+ "eq": EqualValues,
+ "equals": EqualValues,
+ "equal": EqualValues,
"lt": assert.Less,
"less_than": assert.Less,
"le": assert.LessOrEqual,
@@ -20,8 +20,8 @@ var Assertions = map[string]func(t assert.TestingT, actual interface{}, expected
"greater_than": assert.Greater,
"ge": assert.GreaterOrEqual,
"greater_or_equals": assert.GreaterOrEqual,
- "ne": assert.NotEqual,
- "not_equal": assert.NotEqual,
+ "ne": NotEqual,
+ "not_equal": NotEqual,
"contains": assert.Contains,
"type_match": assert.IsType,
// custom assertions
@@ -48,6 +48,14 @@ var Assertions = map[string]func(t assert.TestingT, actual interface{}, expected
"regex_match": RegexMatch,
}
+func EqualValues(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
+ return assert.EqualValues(t, expected, actual, msgAndArgs)
+}
+
+func NotEqual(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
+ return assert.NotEqual(t, expected, actual, msgAndArgs)
+}
+
// StartsWith check if string starts with substring
func StartsWith(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool {
if !assert.IsType(t, "string", actual, fmt.Sprintf("actual is %v", actual)) {
diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go
index 7bf7a943..de2486a8 100644
--- a/hrp/internal/builtin/utils.go
+++ b/hrp/internal/builtin/utils.go
@@ -17,6 +17,7 @@ import (
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v3"
+ "github.com/httprunner/funplugin/shared"
"github.com/httprunner/httprunner/hrp/internal/json"
)
@@ -76,16 +77,59 @@ func FormatResponse(raw interface{}) interface{} {
return formattedResponse
}
-func ExecCommand(cmd *exec.Cmd, cwd string) error {
- log.Info().Str("cmd", cmd.String()).Str("cwd", cwd).Msg("exec command")
- cmd.Dir = cwd
- output, err := cmd.CombinedOutput()
- out := strings.TrimSpace(string(output))
+func EnsurePython3Venv(packages ...string) (string, error) {
+ // create python venv
+ home, err := os.UserHomeDir()
if err != nil {
- log.Error().Err(err).Str("output", out).Msg("exec command failed")
- } else if len(out) != 0 {
- log.Info().Str("output", out).Msg("exec command success")
+ return "", errors.Wrap(err, "get user home dir failed")
}
+ venvDir := filepath.Join(home, ".hrp", "venv")
+ python3, err := shared.EnsurePython3Venv(venvDir, packages...)
+ if err != nil {
+ return "", errors.Wrap(err, "ensure python venv failed")
+ }
+
+ return python3, nil
+}
+
+func ExecCommandInDir(cmd *exec.Cmd, dir string) error {
+ log.Info().Str("cmd", cmd.String()).Str("dir", dir).Msg("exec command")
+ cmd.Dir = dir
+
+ // print output with colors
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ err := cmd.Run()
+ if err != nil {
+ log.Error().Err(err).Msg("exec command failed")
+ return err
+ }
+
+ return nil
+}
+
+func ExecCommand(cmdName string, args ...string) error {
+ cmd := exec.Command(cmdName, args...)
+ log.Info().Str("cmd", cmd.String()).Msg("exec command")
+
+ // print output with colors
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+
+ // add cmd dir path to PATH
+ PATH := fmt.Sprintf("%s:%s", filepath.Dir(cmdName), os.Getenv("PATH"))
+ if err := os.Setenv("PATH", PATH); err != nil {
+ log.Error().Err(err).Msg("failed to add cmd dir path to $PATH")
+ return err
+ }
+
+ err := cmd.Run()
+ if err != nil {
+ log.Error().Err(err).Msg("exec command failed")
+ return err
+ }
+
return err
}
diff --git a/hrp/internal/convert/main.go b/hrp/internal/convert/main.go
new file mode 100644
index 00000000..a52a0fab
--- /dev/null
+++ b/hrp/internal/convert/main.go
@@ -0,0 +1,118 @@
+package convert
+
+import (
+ _ "embed"
+ "fmt"
+ "os"
+
+ "github.com/rs/zerolog/log"
+
+ "github.com/httprunner/httprunner/hrp"
+ "github.com/httprunner/httprunner/hrp/internal/builtin"
+ "github.com/httprunner/httprunner/hrp/internal/sdk"
+)
+
+func Convert2TestScripts(destType string, paths ...string) error {
+ // report event
+ sdk.SendEvent(sdk.EventTracking{
+ Category: "ConvertTests",
+ Action: fmt.Sprintf("hrp convert --%s", destType),
+ })
+
+ if destType == "gotest" {
+ return convert2GoTestScripts(paths...)
+ } else {
+ // default to pytest
+ return convert2PyTestScripts(paths...)
+ }
+}
+
+func convert2PyTestScripts(paths ...string) error {
+ python3, err := builtin.EnsurePython3Venv("httprunner")
+ if err != nil {
+ return err
+ }
+
+ args := append([]string{"-m", "httprunner", "make"}, paths...)
+ return builtin.ExecCommand(python3, args...)
+}
+
+func convert2GoTestScripts(paths ...string) error {
+ log.Warn().Msg("convert to gotest scripts is not supported yet")
+ os.Exit(1)
+
+ // TODO
+ var testCasePaths []hrp.ITestCase
+ for _, path := range paths {
+ testCasePath := hrp.TestCasePath(path)
+ testCasePaths = append(testCasePaths, &testCasePath)
+ }
+
+ testCases, err := hrp.LoadTestCases(testCasePaths...)
+ if err != nil {
+ log.Error().Err(err).Msg("failed to load testcases")
+ return err
+ }
+
+ var pytestPaths []string
+ for _, testCase := range testCases {
+ tc := testCase.ToTCase()
+ converter := CaseConverter{
+ TCase: tc,
+ }
+ pytestPath, err := converter.ToPyTest()
+ if err != nil {
+ log.Error().Err(err).
+ Str("originPath", tc.Config.Path).
+ Msg("convert to pytest failed")
+ continue
+ }
+ log.Info().
+ Str("pytestPath", pytestPath).
+ Str("originPath", tc.Config.Path).
+ Msg("convert to pytest success")
+ pytestPaths = append(pytestPaths, pytestPath)
+ }
+
+ // format pytest scripts with black
+ python3, err := builtin.EnsurePython3Venv("black")
+ if err != nil {
+ return err
+ }
+ args := append([]string{"-m", "black"}, pytestPaths...)
+ return builtin.ExecCommand(python3, args...)
+}
+
+//go:embed testcase.tmpl
+var testcaseTemplate string
+
+type CaseConverter struct {
+ *hrp.TCase
+}
+
+func (c *CaseConverter) ToPyTest() (string, error) {
+ script := convertConfig(c.TCase.Config)
+ println(script)
+ return script, nil
+}
+
+func (c *CaseConverter) ToGoTest() (string, error) {
+ return "", nil
+}
+
+func convertConfig(config *hrp.TConfig) string {
+ script := fmt.Sprintf("Config('%s')", config.Name)
+
+ if config.Variables != nil {
+ script += fmt.Sprintf(".variables(**{%v})", config.Variables)
+ }
+ if config.BaseURL != "" {
+ script += fmt.Sprintf(".base_url('%s')", config.BaseURL)
+ }
+ if config.Export != nil {
+ script += fmt.Sprintf(".export(*%v)", config.Export)
+ }
+ script += fmt.Sprintf(".verify(%v)", config.Verify)
+
+ return script
+}
diff --git a/hrp/internal/convert/testcase.tmpl b/hrp/internal/convert/testcase.tmpl
new file mode 100644
index 00000000..783a1bc3
--- /dev/null
+++ b/hrp/internal/convert/testcase.tmpl
@@ -0,0 +1,38 @@
+# NOTE: Generated By HttpRunner v{{ version }}
+# FROM: {{ testcase_path }}
+
+{% if imports_list and diff_levels > 0 %}
+import sys
+from pathlib import Path
+sys.path.insert(0, str(Path(__file__){% for _ in range(diff_levels) %}.parent{% endfor %}))
+{% endif %}
+
+{% if parameters %}
+import pytest
+from httprunner import Parameters
+{% endif %}
+
+from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
+{% for import_str in imports_list %}
+{{ import_str }}
+{% endfor %}
+
+class {{ class_name }}(HttpRunner):
+
+ {% if parameters %}
+ @pytest.mark.parametrize("param", Parameters({{parameters}}))
+ def test_start(self, param):
+ super().test_start(param)
+ {% endif %}
+
+ config = {{ config_chain_style }}
+
+ teststeps = [
+ {% for step_chain_style in teststeps_chain_style %}
+ {{ step_chain_style }},
+ {% endfor %}
+ ]
+
+
+if __name__ == "__main__":
+ {{ class_name }}().test_start()
diff --git a/hrp/internal/pytest/main.go b/hrp/internal/pytest/main.go
index 7304de99..a70ead1e 100644
--- a/hrp/internal/pytest/main.go
+++ b/hrp/internal/pytest/main.go
@@ -1,23 +1,21 @@
package pytest
import (
- "os/exec"
- "strings"
-
- "github.com/pkg/errors"
- "github.com/rs/zerolog/log"
+ "github.com/httprunner/httprunner/hrp/internal/builtin"
+ "github.com/httprunner/httprunner/hrp/internal/sdk"
)
func RunPytest(args []string) error {
- cmd := exec.Command("pytest", args...)
- log.Info().Str("cmd", cmd.String()).Msg("run pytest")
+ sdk.SendEvent(sdk.EventTracking{
+ Category: "RunAPITests",
+ Action: "hrp pytest",
+ })
- output, err := cmd.CombinedOutput()
+ python3, err := builtin.EnsurePython3Venv("httprunner")
if err != nil {
- return errors.Wrap(err, "pytest running failed")
+ return err
}
- out := strings.TrimSpace(string(output))
- println(out)
- return nil
+ args = append([]string{"-m", "httprunner", "run"}, args...)
+ return builtin.ExecCommand(python3, args...)
}
diff --git a/hrp/internal/scaffold/examples_test.go b/hrp/internal/scaffold/examples_test.go
index 22daa768..3ec85cb5 100644
--- a/hrp/internal/scaffold/examples_test.go
+++ b/hrp/internal/scaffold/examples_test.go
@@ -1,28 +1,24 @@
package scaffold
import (
- "os"
"testing"
)
func TestGenDemoExamples(t *testing.T) {
dir := "../../../examples/demo-with-go-plugin"
- os.RemoveAll(dir)
- err := CreateScaffold(dir, Go)
+ err := CreateScaffold(dir, Go, true)
if err != nil {
t.Fatal()
}
dir = "../../../examples/demo-with-py-plugin"
- os.RemoveAll(dir)
- err = CreateScaffold(dir, Py)
+ err = CreateScaffold(dir, Py, true)
if err != nil {
t.Fatal()
}
dir = "../../../examples/demo-without-plugin"
- os.RemoveAll(dir)
- err = CreateScaffold(dir, Ignore)
+ err = CreateScaffold(dir, Ignore, true)
if err != nil {
t.Fatal()
}
diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go
index b7c1c0ad..92ff9ddf 100644
--- a/hrp/internal/scaffold/main.go
+++ b/hrp/internal/scaffold/main.go
@@ -42,25 +42,32 @@ func CopyFile(templateFile, targetFile string) error {
return nil
}
-func CreateScaffold(projectName string, pluginType PluginType) error {
+func CreateScaffold(projectName string, pluginType PluginType, force bool) error {
// report event
sdk.SendEvent(sdk.EventTracking{
Category: "Scaffold",
Action: "hrp startproject",
})
- // check if projectName exists
- if _, err := os.Stat(projectName); err == nil {
- log.Warn().Str("projectName", projectName).
- Msg("project name already exists, please specify a new one.")
- return fmt.Errorf("project name already exists")
- }
-
log.Info().
Str("projectName", projectName).
Str("pluginType", string(pluginType)).
+ Bool("force", force).
Msg("create new scaffold project")
+ // check if projectName exists
+ if _, err := os.Stat(projectName); err == nil {
+ if !force {
+ log.Warn().Str("projectName", projectName).
+ Msg("project name already exists, please specify a new one.")
+ return fmt.Errorf("project name already exists")
+ }
+
+ log.Warn().Str("projectName", projectName).
+ Msg("project name already exists, remove first !!!")
+ os.RemoveAll(projectName)
+ }
+
// create project folders
if err := builtin.CreateFolder(projectName); err != nil {
return err
@@ -133,7 +140,7 @@ func CreateScaffold(projectName string, pluginType PluginType) error {
func createGoPlugin(projectName string) error {
log.Info().Msg("start to create hashicorp go plugin")
// check go sdk
- if err := builtin.ExecCommand(exec.Command("go", "version"), projectName); err != nil {
+ if err := builtin.ExecCommandInDir(exec.Command("go", "version"), projectName); err != nil {
return errors.Wrap(err, "go sdk not installed")
}
@@ -149,19 +156,19 @@ func createGoPlugin(projectName string) error {
}
// create go mod
- if err := builtin.ExecCommand(exec.Command("go", "mod", "init", "plugin"), pluginDir); err != nil {
+ if err := builtin.ExecCommandInDir(exec.Command("go", "mod", "init", "plugin"), pluginDir); err != nil {
return err
}
// download plugin dependency
// funplugin version should be locked
funplugin := fmt.Sprintf("github.com/httprunner/funplugin@%s", shared.Version)
- if err := builtin.ExecCommand(exec.Command("go", "get", funplugin), pluginDir); err != nil {
+ if err := builtin.ExecCommandInDir(exec.Command("go", "get", funplugin), pluginDir); err != nil {
return err
}
// build plugin debugtalk.bin
- if err := builtin.ExecCommand(exec.Command("go", "build", "-o", filepath.Join("..", "debugtalk.bin"), "debugtalk.go"), pluginDir); err != nil {
+ if err := builtin.ExecCommandInDir(exec.Command("go", "build", "-o", filepath.Join("..", "debugtalk.bin"), "debugtalk.go"), pluginDir); err != nil {
return err
}
@@ -178,15 +185,9 @@ func createPythonPlugin(projectName string) error {
return errors.Wrap(err, "copy file failed")
}
- // create python venv
- home, err := os.UserHomeDir()
+ _, err = builtin.EnsurePython3Venv(fmt.Sprintf("funppy==%s", shared.Version))
if err != nil {
- return errors.Wrap(err, "get user home dir failed")
- }
- venvDir := filepath.Join(home, ".hrp", "venv")
- _, err = shared.EnsurePython3Venv(venvDir)
- if err != nil {
- return errors.Wrap(err, "ensure python venv failed")
+ return err
}
return nil
diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.go b/hrp/internal/scaffold/templates/plugin/debugtalk.go
index f99a1321..dbb37554 100644
--- a/hrp/internal/scaffold/templates/plugin/debugtalk.go
+++ b/hrp/internal/scaffold/templates/plugin/debugtalk.go
@@ -42,7 +42,7 @@ func TeardownHookExample(args string) string {
}
func GetVersion() string {
- return "v4.0.0-alpha"
+ return "v4.0.0-beta"
}
func main() {
diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.py b/hrp/internal/scaffold/templates/plugin/debugtalk.py
index 743e7f62..180725e0 100644
--- a/hrp/internal/scaffold/templates/plugin/debugtalk.py
+++ b/hrp/internal/scaffold/templates/plugin/debugtalk.py
@@ -6,7 +6,7 @@ import funppy
def get_httprunner_version():
- return "v4.0.0-alpha"
+ return "v4.0.0-beta"
def sleep(n_secs):
@@ -59,7 +59,7 @@ def teardown_hook_example(name):
return f"teardown_hook_example: {name}"
-if __name__ == '__main__':
+if __name__ == "__main__":
funppy.register("get_httprunner_version", get_httprunner_version)
funppy.register("sum", sum)
funppy.register("sum_ints", sum_ints)
diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py
index e8707a57..deae0a81 100644
--- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py
+++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: testcases/demo_ref_testcase.yml
diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py
index 526961e3..5ce8a6d8 100644
--- a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py
+++ b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py
@@ -1,4 +1,4 @@
-# NOTE: Generated By HttpRunner v4.0.0-alpha
+# NOTE: Generated By HttpRunner v4.0.0-beta
# FROM: testcases/demo_requests.yml
diff --git a/hrp/internal/version/init.go b/hrp/internal/version/init.go
index 720fe5c6..ca433250 100644
--- a/hrp/internal/version/init.go
+++ b/hrp/internal/version/init.go
@@ -1,3 +1,3 @@
package version
-const VERSION = "v4.0.0-alpha"
+const VERSION = "v4.0.0-beta"
diff --git a/hrp/runner.go b/hrp/runner.go
index e73ae616..d21b8beb 100644
--- a/hrp/runner.go
+++ b/hrp/runner.go
@@ -151,8 +151,9 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error {
s := newOutSummary()
// load all testcases
- testCases, err := loadTestCases(testcases...)
+ testCases, err := LoadTestCases(testcases...)
if err != nil {
+ log.Error().Err(err).Msg("failed to load testcases")
return err
}
diff --git a/hrp/runner_test.go b/hrp/runner_test.go
index 9510a975..25eaca68 100644
--- a/hrp/runner_test.go
+++ b/hrp/runner_test.go
@@ -2,20 +2,21 @@ package hrp
import (
"os"
- "os/exec"
"testing"
"time"
- "github.com/httprunner/httprunner/hrp/internal/scaffold"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
+
+ "github.com/httprunner/httprunner/hrp/internal/builtin"
+ "github.com/httprunner/httprunner/hrp/internal/scaffold"
)
func buildHashicorpGoPlugin() {
log.Info().Msg("[init] build hashicorp go plugin")
- cmd := exec.Command("go", "build",
+ err := builtin.ExecCommand("go", "build",
"-o", templatesDir+"debugtalk.bin", templatesDir+"plugin/debugtalk.go")
- if err := cmd.Run(); err != nil {
+ if err != nil {
log.Error().Err(err).Msg("build hashicorp go plugin failed")
os.Exit(1)
}
@@ -202,7 +203,7 @@ func TestRunCaseWithRefAPI(t *testing.T) {
func TestLoadTestCases(t *testing.T) {
// load test cases from folder path
tc := TestCasePath("../examples/demo-with-py-plugin/testcases/")
- testCases, err := loadTestCases(&tc)
+ testCases, err := LoadTestCases(&tc)
if !assert.Nil(t, err) {
t.Fatal()
}
@@ -212,7 +213,7 @@ func TestLoadTestCases(t *testing.T) {
// load test cases from folder path, including sub folders
tc = TestCasePath("../examples/demo-with-py-plugin/")
- testCases, err = loadTestCases(&tc)
+ testCases, err = LoadTestCases(&tc)
if !assert.Nil(t, err) {
t.Fatal()
}
@@ -222,7 +223,7 @@ func TestLoadTestCases(t *testing.T) {
// load test cases from single file path
tc = demoTestCaseWithPluginJSONPath
- testCases, err = loadTestCases(&tc)
+ testCases, err = LoadTestCases(&tc)
if !assert.Nil(t, err) {
t.Fatal()
}
@@ -234,7 +235,7 @@ func TestLoadTestCases(t *testing.T) {
testcase := &TestCase{
Config: NewConfig("TestCase").SetWeight(3),
}
- testCases, err = loadTestCases(testcase)
+ testCases, err = LoadTestCases(testcase)
if !assert.Nil(t, err) {
t.Fatal()
}
diff --git a/hrp/testcase.go b/hrp/testcase.go
index 815480d7..ce6a8106 100644
--- a/hrp/testcase.go
+++ b/hrp/testcase.go
@@ -240,7 +240,7 @@ func convertCheckExpr(checkExpr string) string {
return strings.Join(checkItems, ".")
}
-func loadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) {
+func LoadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) {
testCases := make([]*TestCase, 0)
for _, iTestCase := range iTestCases {
diff --git a/httprunner/__init__.py b/httprunner/__init__.py
index 5c6dcfe5..1329604f 100644
--- a/httprunner/__init__.py
+++ b/httprunner/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "4.0.0-alpha"
+__version__ = "4.0.0-beta"
__description__ = "One-stop solution for HTTP(S) testing."
from httprunner.config import Config
diff --git a/httprunner/builtin/functions.py b/httprunner/builtin/functions.py
index 2a7c68ca..00f964fd 100644
--- a/httprunner/builtin/functions.py
+++ b/httprunner/builtin/functions.py
@@ -11,16 +11,14 @@ from httprunner.exceptions import ParamsError
def gen_random_string(str_len):
- """ generate random string with specified length
- """
+ """generate random string with specified length"""
return "".join(
random.choice(string.ascii_letters + string.digits) for _ in range(str_len)
)
def get_timestamp(str_len=13):
- """ get timestamp string, length can only between 0 and 16
- """
+ """get timestamp string, length can only between 0 and 16"""
if isinstance(str_len, int) and 0 < str_len < 17:
return str(time.time()).replace(".", "")[:str_len]
@@ -28,12 +26,10 @@ def get_timestamp(str_len=13):
def get_current_date(fmt="%Y-%m-%d"):
- """ get current date, default format is %Y-%m-%d
- """
+ """get current date, default format is %Y-%m-%d"""
return datetime.datetime.now().strftime(fmt)
def sleep(n_secs):
- """ sleep n seconds
- """
+ """sleep n seconds"""
time.sleep(n_secs)
diff --git a/httprunner/cli.py b/httprunner/cli.py
index 1aa29eac..b26dc5be 100644
--- a/httprunner/cli.py
+++ b/httprunner/cli.py
@@ -9,7 +9,7 @@ from loguru import logger
from httprunner import __description__, __version__
from httprunner.compat import ensure_cli_args
from httprunner.make import init_make_parser, main_make
-from httprunner.utils import ga_client, init_sentry_sdk
+from httprunner.utils import ga_client, init_logger, init_sentry_sdk
init_sentry_sdk()
@@ -55,8 +55,9 @@ def main_run(extra_args) -> enum.IntEnum:
def main():
- """ API test: parse command line options and run commands.
- """
+ """API test: parse command line options and run commands."""
+ init_logger()
+
parser = argparse.ArgumentParser(description=__description__)
parser.add_argument(
"-V", "--version", dest="version", action="store_true", help="show version"
@@ -109,8 +110,8 @@ def main():
def main_hrun_alias():
- """ command alias
- hrun = httprunner run
+ """command alias
+ hrun = httprunner run
"""
if len(sys.argv) == 2:
if sys.argv[1] in ["-V", "--version"]:
@@ -129,8 +130,8 @@ def main_hrun_alias():
def main_make_alias():
- """ command alias
- hmake = httprunner make
+ """command alias
+ hmake = httprunner make
"""
sys.argv.insert(1, "make")
main()
diff --git a/httprunner/client.py b/httprunner/client.py
index 8ac38f0e..8e45425c 100644
--- a/httprunner/client.py
+++ b/httprunner/client.py
@@ -27,8 +27,7 @@ class ApiResponse(Response):
def get_req_resp_record(resp_obj: Response) -> ReqRespData:
- """ get request and response info from Response() object.
- """
+ """get request and response info from Response() object."""
def log_print(req_or_resp, r_type):
msg = f"\n================== {r_type} details ==================\n"
diff --git a/httprunner/client_test.py b/httprunner/client_test.py
index 2ea5a6b1..467d4246 100644
--- a/httprunner/client_test.py
+++ b/httprunner/client_test.py
@@ -27,7 +27,8 @@ class TestHttpSession(unittest.TestCase):
self.session.request(
"get",
"http://httpbin.org/redirect-to?url=https%3A%2F%2Fgithub.com",
- allow_redirects=True)
+ allow_redirects=True,
+ )
address = self.session.data.address
self.assertNotEqual(address.server_ip, "N/A")
self.assertEqual(address.server_port, 443)
@@ -38,7 +39,8 @@ class TestHttpSession(unittest.TestCase):
self.session.request(
"get",
"https://httpbin.org/redirect-to?url=https%3A%2F%2Fgithub.com",
- allow_redirects=True)
+ allow_redirects=True,
+ )
address = self.session.data.address
self.assertNotEqual(address.server_ip, "N/A")
self.assertEqual(address.server_port, 443)
@@ -49,7 +51,8 @@ class TestHttpSession(unittest.TestCase):
self.session.request(
"get",
"http://httpbin.org/redirect-to?url=https%3A%2F%2Fgithub.com",
- allow_redirects=False)
+ allow_redirects=False,
+ )
address = self.session.data.address
self.assertEqual(address.server_ip, "N/A")
self.assertEqual(address.server_port, 0)
@@ -60,7 +63,8 @@ class TestHttpSession(unittest.TestCase):
self.session.request(
"get",
"https://httpbin.org/redirect-to?url=https%3A%2F%2Fgithub.com",
- allow_redirects=False)
+ allow_redirects=False,
+ )
address = self.session.data.address
self.assertEqual(address.server_ip, "N/A")
self.assertEqual(address.server_port, 0)
diff --git a/httprunner/compat.py b/httprunner/compat.py
index fb78b39b..c7352f6a 100644
--- a/httprunner/compat.py
+++ b/httprunner/compat.py
@@ -14,25 +14,12 @@ from httprunner.utils import sort_dict_by_custom_order
def convert_variables(
- raw_variables: Union[Dict, List, Text], test_path: Text
+ raw_variables: Union[Dict, Text], test_path: Text
) -> Dict[Text, Any]:
if isinstance(raw_variables, Dict):
return raw_variables
- if isinstance(raw_variables, List):
- # [{"var1": 1}, {"var2": 2}]
- variables: Dict[Text, Any] = {}
- for var_item in raw_variables:
- if not isinstance(var_item, Dict) or len(var_item) != 1:
- raise exceptions.TestCaseFormatError(
- f"Invalid variables format: {raw_variables}"
- )
-
- variables.update(var_item)
-
- return variables
-
elif isinstance(raw_variables, Text):
# get variables by function, e.g. ${get_variables()}
project_meta = load_project_meta(test_path)
@@ -79,7 +66,7 @@ def _convert_jmespath(raw: Text) -> Text:
def _convert_extractors(extractors: Union[List, Dict]) -> Dict:
- """ convert extract list(v2) to dict(v3)
+ """convert extract list(v2) to dict(v3)
Args:
extractors: [{"varA": "content.varA"}, {"varB": "json.varB"}]
@@ -251,8 +238,7 @@ def ensure_testcase_v3(test_content: Dict) -> Dict:
def ensure_cli_args(args: List) -> List:
- """ ensure compatibility with deprecated cli args in v2
- """
+ """ensure compatibility with deprecated cli args in v2"""
# remove deprecated --failfast
if "--failfast" in args:
logger.warning("remove deprecated argument: --failfast")
@@ -301,13 +287,13 @@ from httprunner.utils import get_platform, ExtendJSONEncoder
@pytest.fixture(scope="session", autouse=True)
def session_fixture(request):
"""setup and teardown each task"""
- logger.info(f"start running testcases ...")
+ logger.info("start running testcases ...")
start_at = time.time()
yield
- logger.info(f"task finished, generate task summary for --save-tests")
+ logger.info("task finished, generate task summary for --save-tests")
summary = {
"success": True,
@@ -386,8 +372,7 @@ def session_fixture(request):
def ensure_path_sep(path: Text) -> Text:
- """ ensure compatibility with different path separators of Linux and Windows
- """
+ """ensure compatibility with different path separators of Linux and Windows"""
if "/" in path:
path = os.sep.join(path.split("/"))
diff --git a/httprunner/compat_test.py b/httprunner/compat_test.py
index 661d4ad8..391133a1 100644
--- a/httprunner/compat_test.py
+++ b/httprunner/compat_test.py
@@ -9,11 +9,6 @@ class TestCompat(unittest.TestCase):
loader.project_meta = None
def test_convert_variables(self):
- raw_variables = [{"var1": 1}, {"var2": "val2"}]
- self.assertEqual(
- compat.convert_variables(raw_variables, "examples/data/a-b.c/1.yml"),
- {"var1": 1, "var2": "val2"},
- )
raw_variables = {"var1": 1, "var2": "val2"}
self.assertEqual(
compat.convert_variables(raw_variables, "examples/data/a-b.c/1.yml"),
@@ -38,7 +33,7 @@ class TestCompat(unittest.TestCase):
compat._convert_jmespath("headers.Content-Type"), 'headers."Content-Type"'
)
self.assertEqual(
- compat._convert_jmespath('headers.User-Agent'), 'headers."User-Agent"'
+ compat._convert_jmespath("headers.User-Agent"), 'headers."User-Agent"'
)
self.assertEqual(
compat._convert_jmespath('headers."Content-Type"'), 'headers."Content-Type"'
diff --git a/httprunner/config.py b/httprunner/config.py
index 0f372975..ea594b5f 100644
--- a/httprunner/config.py
+++ b/httprunner/config.py
@@ -5,7 +5,6 @@ from httprunner.models import TConfig, TConfigThrift
class ConfigThrift(object):
-
def __init__(self, config: TConfig) -> None:
self.__config = config
self.__config.thrift = TConfigThrift()
@@ -31,13 +30,9 @@ class ConfigThrift(object):
class Config(object):
-
def __init__(self, name: Text) -> None:
caller_frame = inspect.stack()[1]
- self.__config = TConfig(
- name=name,
- path=caller_frame.filename
- )
+ self.__config = TConfig(name=name, path=caller_frame.filename)
@property
def name(self) -> Text:
diff --git a/httprunner/exceptions.py b/httprunner/exceptions.py
index 6559fd87..495922d7 100644
--- a/httprunner/exceptions.py
+++ b/httprunner/exceptions.py
@@ -85,5 +85,4 @@ class TestcaseNotFound(NotFoundError):
class SummaryEmpty(MyBaseError):
- """ test result summary data is empty
- """
+ """test result summary data is empty"""
diff --git a/httprunner/ext/uploader/__init__.py b/httprunner/ext/uploader/__init__.py
index 95aad33a..c284f7b3 100644
--- a/httprunner/ext/uploader/__init__.py
+++ b/httprunner/ext/uploader/__init__.py
@@ -76,7 +76,7 @@ def ensure_upload_ready():
def prepare_upload_step(step: TStep, functions: FunctionsMapping):
- """ preprocess for upload test
+ """preprocess for upload test
replace `upload` info with MultipartEncoder
Args:
@@ -102,9 +102,7 @@ def prepare_upload_step(step: TStep, functions: FunctionsMapping):
return
# parse upload info
- step.request.upload = parse_data(
- step.request.upload, step.variables, functions
- )
+ step.request.upload = parse_data(step.request.upload, step.variables, functions)
ensure_upload_ready()
params_list = []
@@ -124,7 +122,7 @@ def prepare_upload_step(step: TStep, functions: FunctionsMapping):
def multipart_encoder(**kwargs):
- """ initialize MultipartEncoder with uploading fields.
+ """initialize MultipartEncoder with uploading fields.
Returns:
MultipartEncoder: initialized MultipartEncoder object
@@ -169,7 +167,7 @@ def multipart_encoder(**kwargs):
def multipart_content_type(m_encoder) -> Text:
- """ prepare Content-Type for request headers
+ """prepare Content-Type for request headers
Args:
m_encoder: MultipartEncoder object
diff --git a/httprunner/loader.py b/httprunner/loader.py
index 6c81fd82..49dc849a 100644
--- a/httprunner/loader.py
+++ b/httprunner/loader.py
@@ -11,14 +11,13 @@ from loguru import logger
from pydantic import ValidationError
from httprunner import builtin, exceptions, utils
-from httprunner.models import ProjectMeta, TestCase, TestSuite
+from httprunner.models import ProjectMeta, TestCase
project_meta: Union[ProjectMeta, None] = None
def _load_yaml_file(yaml_file: Text) -> Dict:
- """ load yaml file and check file content format
- """
+ """load yaml file and check file content format"""
with open(yaml_file, mode="rb") as stream:
try:
yaml_content = yaml.load(stream, Loader=yaml.FullLoader)
@@ -31,8 +30,7 @@ def _load_yaml_file(yaml_file: Text) -> Dict:
def _load_json_file(json_file: Text) -> Dict:
- """ load json file and check file content format
- """
+ """load json file and check file content format"""
with open(json_file, mode="rb") as data_file:
try:
json_content = json.load(data_file)
@@ -81,20 +79,8 @@ def load_testcase_file(testcase_file: Text) -> TestCase:
return testcase_obj
-def load_testsuite(testsuite: Dict) -> TestSuite:
- path = testsuite["config"]["path"]
- try:
- # validate with pydantic TestCase model
- testsuite_obj = TestSuite.parse_obj(testsuite)
- except ValidationError as ex:
- err_msg = f"TestSuite ValidationError:\nfile: {path}\nerror: {ex}"
- raise exceptions.TestSuiteFormatError(err_msg)
-
- return testsuite_obj
-
-
def load_dot_env_file(dot_env_path: Text) -> Dict:
- """ load .env file.
+ """load .env file.
Args:
dot_env_path (str): .env file path
@@ -140,7 +126,7 @@ def load_dot_env_file(dot_env_path: Text) -> Dict:
def load_csv_file(csv_file: Text) -> List[Dict]:
- """ load csv file and check file content format
+ """load csv file and check file content format
Args:
csv_file (str): csv file path, csv file content is like below:
@@ -186,7 +172,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/_test.py 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
@@ -227,7 +213,7 @@ def load_folder_files(folder_path: Text, recursive: bool = True) -> List:
def load_module_functions(module) -> Dict[Text, Callable]:
- """ load python module functions.
+ """load python module functions.
Args:
module: python module
@@ -251,13 +237,12 @@ def load_module_functions(module) -> Dict[Text, Callable]:
def load_builtin_functions() -> Dict[Text, Callable]:
- """ load builtin module functions
- """
+ """load builtin module functions"""
return load_module_functions(builtin)
def locate_file(start_path: Text, file_name: Text) -> Text:
- """ locate filename and return absolute file path.
+ """locate filename and return absolute file path.
searching will be recursive upward until system root dir.
Args:
@@ -295,7 +280,7 @@ def locate_file(start_path: Text, file_name: Text) -> Text:
def locate_debugtalk_py(start_path: Text) -> Text:
- """ locate debugtalk.py file
+ """locate debugtalk.py file
Args:
start_path (str): start locating path,
@@ -315,7 +300,7 @@ def locate_debugtalk_py(start_path: Text) -> Text:
def locate_project_root_directory(test_path: Text) -> Tuple[Text, Text]:
- """ locate debugtalk.py path as project root directory
+ """locate debugtalk.py path as project root directory
Args:
test_path: specified testfile path
@@ -352,7 +337,7 @@ def locate_project_root_directory(test_path: Text) -> Tuple[Text, Text]:
def load_debugtalk_functions() -> Dict[Text, Callable]:
- """ load project debugtalk.py module functions
+ """load project debugtalk.py module functions
debugtalk.py should be located in project root directory.
Returns:
@@ -376,7 +361,7 @@ def load_debugtalk_functions() -> Dict[Text, Callable]:
def load_project_meta(test_path: Text, reload: bool = False) -> ProjectMeta:
- """ load testcases, .env, debugtalk.py functions.
+ """load testcases, .env, debugtalk.py functions.
testcases folder is relative to project_root_directory
by default, project_meta will be loaded only once, unless set reload to true.
@@ -428,7 +413,7 @@ def load_project_meta(test_path: Text, reload: bool = False) -> ProjectMeta:
def convert_relative_project_root_dir(abs_path: Text) -> Text:
- """ convert absolute path to relative path, based on project_meta.RootDir
+ """convert absolute path to relative path, based on project_meta.RootDir
Args:
abs_path: absolute path
@@ -444,4 +429,4 @@ def convert_relative_project_root_dir(abs_path: Text) -> Text:
f"project_meta.RootDir: {_project_meta.RootDir}"
)
- return abs_path[len(_project_meta.RootDir) + 1:]
+ return abs_path[len(_project_meta.RootDir) + 1 :]
diff --git a/httprunner/loader_test.py b/httprunner/loader_test.py
index 95449f43..7b09d87b 100644
--- a/httprunner/loader_test.py
+++ b/httprunner/loader_test.py
@@ -97,7 +97,11 @@ class TestLoader(unittest.TestCase):
)
def test_load_env_path_not_exist(self):
- dot_env_path = os.path.join(os.getcwd(), "tests", "data",)
+ dot_env_path = os.path.join(
+ os.getcwd(),
+ "tests",
+ "data",
+ )
env_variables_mapping = loader.load_dot_env_file(dot_env_path)
self.assertEqual(env_variables_mapping, {})
diff --git a/httprunner/make.py b/httprunner/make.py
index e540e450..c2b3d3b2 100644
--- a/httprunner/make.py
+++ b/httprunner/make.py
@@ -6,17 +6,23 @@ from typing import Dict, List, Set, Text, Tuple
import jinja2
from loguru import logger
-from sentry_sdk import capture_exception
from httprunner import __version__, exceptions
-from httprunner.compat import (convert_variables, ensure_path_sep,
- ensure_testcase_v3, ensure_testcase_v3_api)
-from httprunner.loader import (convert_relative_project_root_dir,
- load_folder_files, load_project_meta,
- load_test_file, load_testcase, load_testsuite)
+from httprunner.compat import (
+ convert_variables,
+ ensure_path_sep,
+ ensure_testcase_v3,
+ ensure_testcase_v3_api,
+)
+from httprunner.loader import (
+ convert_relative_project_root_dir,
+ load_folder_files,
+ load_project_meta,
+ load_test_file,
+ load_testcase,
+)
from httprunner.response import uniform_validator
-from httprunner.utils import (ga_client, is_support_multiprocessing,
- merge_variables)
+from httprunner.utils import ga_client, is_support_multiprocessing
""" cache converted pytest files, avoid duplicate making
"""
@@ -72,10 +78,10 @@ if __name__ == "__main__":
def __ensure_absolute(path: Text) -> Text:
if path.startswith("./"):
# Linux/Darwin, hrun ./test.yml
- path = path[len("./"):]
+ path = path[2:]
elif path.startswith(".\\"):
# Windows, hrun .\\test.yml
- path = path[len(".\\"):]
+ path = path[3:]
path = ensure_path_sep(path)
project_meta = load_project_meta(path)
@@ -93,7 +99,7 @@ def __ensure_absolute(path: Text) -> Text:
def ensure_file_abs_path_valid(file_abs_path: Text) -> Text:
- """ ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space
+ """ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space
Args:
file_abs_path: absolute file path
@@ -134,8 +140,7 @@ def ensure_file_abs_path_valid(file_abs_path: Text) -> Text:
def __ensure_testcase_module(path: Text):
- """ ensure pytest files are in python module, generate __init__.py on demand
- """
+ """ensure pytest files are in python module, generate __init__.py on demand"""
init_file = os.path.join(os.path.dirname(path), "__init__.py")
if os.path.isfile(init_file):
return
@@ -169,7 +174,6 @@ def format_pytest_with_black(*python_paths: Text):
)
[subprocess.run(["black", path]) for path in python_paths]
except subprocess.CalledProcessError as ex:
- capture_exception(ex)
logger.error(ex)
sys.exit(1)
except OSError:
@@ -433,61 +437,8 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
return testcase_python_abs_path
-def make_testsuite(testsuite: Dict):
- """convert valid testsuite dict to pytest folder with testcases"""
- # validate testsuite format
- load_testsuite(testsuite)
-
- testsuite_config = testsuite["config"]
- testsuite_path = testsuite_config["path"]
- testsuite_variables = convert_variables(
- testsuite_config.get("variables", {}), testsuite_path
- )
-
- logger.info(f"start to make testsuite: {testsuite_path}")
-
- # create directory with testsuite file name, put its testcases under this directory
- testsuite_path = ensure_file_abs_path_valid(testsuite_path)
- testsuite_dir, file_suffix = os.path.splitext(testsuite_path)
- # demo_testsuite.yml => demo_testsuite_yml
- testsuite_dir = f"{testsuite_dir}_{file_suffix.lstrip('.')}"
-
- for testcase in testsuite["testcases"]:
- # get referenced testcase content
- testcase_file = testcase["testcase"]
- testcase_path = __ensure_absolute(testcase_file)
- testcase_dict = load_test_file(testcase_path)
- testcase_dict.setdefault("config", {})
- testcase_dict["config"]["path"] = testcase_path
-
- # override testcase name
- testcase_dict["config"]["name"] = testcase["name"]
- # override base_url
- base_url = testsuite_config.get("base_url") or testcase.get("base_url")
- if base_url:
- testcase_dict["config"]["base_url"] = base_url
- # override verify
- if "verify" in testsuite_config:
- testcase_dict["config"]["verify"] = testsuite_config["verify"]
- # override variables
- # testsuite testcase variables > testsuite config variables
- testcase_variables = convert_variables(
- testcase.get("variables", {}), testcase_path
- )
- testcase_variables = merge_variables(testcase_variables, testsuite_variables)
- # testsuite testcase variables > testcase config variables
- testcase_dict["config"]["variables"] = convert_variables(
- testcase_dict["config"].get("variables", {}), testcase_path
- )
- testcase_dict["config"]["variables"].update(testcase_variables)
-
- # make testcase
- testcase_pytest_path = make_testcase(testcase_dict, testsuite_dir)
- pytest_files_run_set.add(testcase_pytest_path)
-
-
def __make(tests_path: Text):
- """ make testcase(s) with testcase/testsuite/folder absolute path
+ """make testcase(s) with testcase/folder absolute path
generated pytest file path will be cached in pytest_files_made_cache_mapping
Args:
@@ -528,13 +479,12 @@ def __make(tests_path: Text):
if "config" not in test_content:
logger.warning(
- f"Invalid testcase/testsuite file: {test_file}\n"
- f"reason: missing config part."
+ f"Invalid testcase file: {test_file}\nreason: missing config part."
)
continue
elif not isinstance(test_content["config"], Dict):
logger.warning(
- f"Invalid testcase/testsuite file: {test_file}\n"
+ f"Invalid testcase file: {test_file}\n"
f"reason: config should be dict type, got {test_content['config']}"
)
continue
@@ -542,33 +492,19 @@ def __make(tests_path: Text):
# ensure path absolute
test_content.setdefault("config", {})["path"] = test_file
- # testcase
- if "teststeps" in test_content:
- try:
- testcase_pytest_path = make_testcase(test_content)
- pytest_files_run_set.add(testcase_pytest_path)
- except exceptions.TestCaseFormatError as ex:
- logger.warning(
- f"Invalid testcase file: {test_file}\n{type(ex).__name__}: {ex}"
- )
- continue
-
- # testsuite
- elif "testcases" in test_content:
- try:
- make_testsuite(test_content)
- except exceptions.TestSuiteFormatError as ex:
- logger.warning(
- f"Invalid testsuite file: {test_file}\n{type(ex).__name__}: {ex}"
- )
- continue
-
# invalid format
- else:
+ if "teststeps" not in test_content:
+ logger.warning(f"Invalid testcase file: {test_file}")
+
+ # testcase
+ try:
+ testcase_pytest_path = make_testcase(test_content)
+ pytest_files_run_set.add(testcase_pytest_path)
+ except exceptions.TestCaseFormatError as ex:
logger.warning(
- f"Invalid test file: {test_file}\n"
- f"reason: file content is neither testcase nor testsuite"
+ f"Invalid testcase file: {test_file}\n{type(ex).__name__}: {ex}"
)
+ continue
def main_make(tests_paths: List[Text]) -> List[Text]:
@@ -596,10 +532,10 @@ def main_make(tests_paths: List[Text]) -> List[Text]:
def init_make_parser(subparsers):
- """ make testcases: parse command line options and run commands.
- """
+ """make testcases: parse command line options and run commands."""
parser = subparsers.add_parser(
- "make", help="Convert YAML/JSON testcases to pytest cases.",
+ "make",
+ help="Convert YAML/JSON testcases to pytest cases.",
)
parser.add_argument(
"testcase_path", nargs="*", help="Specify YAML/JSON testcase file/folder path"
diff --git a/httprunner/make_test.py b/httprunner/make_test.py
index b6a40f20..f3a80325 100644
--- a/httprunner/make_test.py
+++ b/httprunner/make_test.py
@@ -73,7 +73,8 @@ from request_methods.request_with_functions_test import (
content,
)
self.assertIn(
- ".call(RequestWithFunctions)", content,
+ ".call(RequestWithFunctions)",
+ content,
)
def test_make_testcase_folder(self):
@@ -94,9 +95,7 @@ from request_methods.request_with_functions_test import (
def test_ensure_file_path_valid(self):
self.assertEqual(
- ensure_file_abs_path_valid(
- os.path.join(self.data_dir, "a-b.c", "2 3.yml")
- ),
+ ensure_file_abs_path_valid(os.path.join(self.data_dir, "a-b.c", "2 3.yml")),
os.path.join(self.data_dir, "a_b_c", "T2_3.yml"),
)
loader.project_meta = None
@@ -113,67 +112,31 @@ from request_methods.request_with_functions_test import (
)
loader.project_meta = None
self.assertEqual(
- ensure_file_abs_path_valid(os.getcwd()), os.getcwd(),
+ ensure_file_abs_path_valid(os.getcwd()),
+ os.getcwd(),
)
loader.project_meta = None
self.assertEqual(
- ensure_file_abs_path_valid(
- os.path.join(self.data_dir, ".csv")
- ),
+ ensure_file_abs_path_valid(os.path.join(self.data_dir, ".csv")),
os.path.join(self.data_dir, ".csv"),
)
def test_convert_testcase_path(self):
self.assertEqual(
- convert_testcase_path(
- os.path.join(self.data_dir, "a-b.c", "2 3.yml")
- ),
+ convert_testcase_path(os.path.join(self.data_dir, "a-b.c", "2 3.yml")),
(
os.path.join(self.data_dir, "a_b_c", "T2_3_test.py"),
"T23",
),
)
self.assertEqual(
- convert_testcase_path(
- os.path.join(self.data_dir, "a-b.c", "中文case.yml")
- ),
+ convert_testcase_path(os.path.join(self.data_dir, "a-b.c", "中文case.yml")),
(
os.path.join(self.data_dir, "a_b_c", "中文case_test.py"),
"中文Case",
),
)
- def test_make_testsuite(self):
- path = ["examples/postman_echo/request_methods/demo_testsuite.yml"]
- testcase_python_list = main_make(path)
- self.assertEqual(len(testcase_python_list), 2)
- self.assertIn(
- os.path.join(
- os.getcwd(),
- os.path.join(
- "examples",
- "postman_echo",
- "request_methods",
- "demo_testsuite_yml",
- "request_with_functions_test.py",
- ),
- ),
- testcase_python_list,
- )
- self.assertIn(
- os.path.join(
- os.getcwd(),
- os.path.join(
- "examples",
- "postman_echo",
- "request_methods",
- "demo_testsuite_yml",
- "request_with_testcase_reference_test.py",
- ),
- ),
- testcase_python_list,
- )
-
def test_make_config_chain_style(self):
config = {
"name": "request methods testcase: validate with functions",
@@ -190,7 +153,11 @@ from request_methods.request_with_functions_test import (
def test_make_teststep_chain_style(self):
step = {
"name": "get with params",
- "variables": {"foo1": "bar1", "foo2": 123, "sum_v": "${sum_two(1, 2)}",},
+ "variables": {
+ "foo1": "bar1",
+ "foo2": 123,
+ "sum_v": "${sum_two(1, 2)}",
+ },
"request": {
"method": "GET",
"url": "/get",
diff --git a/httprunner/models.py b/httprunner/models.py
index 8c8ced42..d43a3a94 100644
--- a/httprunner/models.py
+++ b/httprunner/models.py
@@ -97,7 +97,9 @@ class ProjectMeta(BaseModel):
dot_env_path: Text = "" # .env file path
functions: FunctionsMapping = {} # functions defined in debugtalk.py
env: Env = {}
- RootDir: Text = os.getcwd() # project root directory (ensure absolute), the path debugtalk.py located
+ RootDir: Text = (
+ os.getcwd()
+ ) # project root directory (ensure absolute), the path debugtalk.py located
class TestsMapping(BaseModel):
@@ -166,21 +168,20 @@ class SessionData(BaseModel):
class StepResult(BaseModel):
"""teststep data, each step maybe corresponding to one request or one testcase"""
- name: Text = "" # teststep name
- step_type: Text = "" # teststep type, request or testcase
+ name: Text = "" # teststep name
+ step_type: Text = "" # teststep type, request or testcase
success: bool = False
- data: Union[SessionData, List['StepResult']] = None
- elapsed: float = 0.0 # teststep elapsed time
- content_size: float = 0 # response content size
+ data: Union[SessionData, List["StepResult"]] = None
+ elapsed: float = 0.0 # teststep elapsed time
+ content_size: float = 0 # response content size
export_vars: VariablesMapping = {}
- attachment: Text = "" # teststep attachment
+ attachment: Text = "" # teststep attachment
StepResult.update_forward_refs()
class IStep(object):
-
def name(self) -> str:
raise NotImplementedError
@@ -211,18 +212,6 @@ class PlatformInfo(BaseModel):
platform: Text
-class TestCaseRef(BaseModel):
- name: Text
- base_url: Text = ""
- testcase: Text
- variables: VariablesMapping = {}
-
-
-class TestSuite(BaseModel):
- config: TConfig
- testcases: List[TestCaseRef]
-
-
class Stat(BaseModel):
total: int = 0
success: int = 0
diff --git a/httprunner/parser.py b/httprunner/parser.py
index 7bf6e9ef..e1adb7be 100644
--- a/httprunner/parser.py
+++ b/httprunner/parser.py
@@ -6,7 +6,6 @@ from typing import Any, Callable, Dict, List, Set, Text
from urllib.parse import urlparse
from loguru import logger
-from sentry_sdk import capture_exception
from httprunner import exceptions, loader, utils
from httprunner.models import FunctionsMapping, VariablesMapping
@@ -21,7 +20,7 @@ function_regex_compile = re.compile(r"\$\{([a-zA-Z_]\w*)\(([\$\w\.\-/\s=,]*)\)\}
def parse_string_value(str_value: Text) -> Any:
- """ parse string to number if possible
+ """parse string to number if possible
e.g. "123" => 123
"12.2" => 12.3
"abc" => "abc"
@@ -37,7 +36,7 @@ def parse_string_value(str_value: Text) -> Any:
def build_url(base_url, step_url):
- """ prepend url with base_url unless it's already an absolute URL """
+ """prepend url with base_url unless it's already an absolute URL"""
o_step_url = urlparse(step_url)
if o_step_url.netloc != "":
# step url is absolute url
@@ -50,14 +49,16 @@ def build_url(base_url, step_url):
raise exceptions.ParamsError("base url missed!")
path = o_base_url.path.rstrip("/") + "/" + o_step_url.path.lstrip("/")
- o_step_url = o_step_url._replace(scheme=o_base_url.scheme) \
- ._replace(netloc=o_base_url.netloc) \
+ o_step_url = (
+ o_step_url._replace(scheme=o_base_url.scheme)
+ ._replace(netloc=o_base_url.netloc)
._replace(path=path)
+ )
return o_step_url.geturl()
def regex_findall_variables(raw_string: Text) -> List[Text]:
- """ extract all variable names from content, which is in format $variable
+ """extract all variable names from content, which is in format $variable
Args:
raw_string (str): string content
@@ -116,7 +117,7 @@ def regex_findall_variables(raw_string: Text) -> List[Text]:
def regex_findall_functions(content: Text) -> List[Text]:
- """ extract all functions from string content, which are in format ${fun()}
+ """extract all functions from string content, which are in format ${fun()}
Args:
content (str): string content
@@ -144,13 +145,12 @@ def regex_findall_functions(content: Text) -> List[Text]:
try:
return function_regex_compile.findall(content)
except TypeError as ex:
- capture_exception(ex)
+ logger.error(f"regex findall functions error: {ex}")
return []
def extract_variables(content: Any) -> Set:
- """ extract all variables in content recursively.
- """
+ """extract all variables in content recursively."""
if isinstance(content, (list, set, tuple)):
variables = set()
for item in content:
@@ -170,7 +170,7 @@ def extract_variables(content: Any) -> Set:
def parse_function_params(params: Text) -> Dict:
- """ parse function params to args and kwargs.
+ """parse function params to args and kwargs.
Args:
params (str): function param in string
@@ -221,7 +221,7 @@ def parse_function_params(params: Text) -> Dict:
def get_mapping_variable(
variable_name: Text, variables_mapping: VariablesMapping
) -> Any:
- """ get variable from variables_mapping.
+ """get variable from variables_mapping.
Args:
variable_name (str): variable name
@@ -246,7 +246,7 @@ def get_mapping_variable(
def get_mapping_function(
function_name: Text, functions_mapping: FunctionsMapping
) -> Callable:
- """ get function from functions_mapping,
+ """get function from functions_mapping,
if not found, then try to check if builtin function.
Args:
@@ -296,7 +296,7 @@ def parse_string(
variables_mapping: VariablesMapping,
functions_mapping: FunctionsMapping,
) -> Any:
- """ parse string content with variables and functions mapping.
+ """parse string content with variables and functions mapping.
Args:
raw_string: raw string content to be parsed.
@@ -403,8 +403,8 @@ def parse_data(
variables_mapping: VariablesMapping = None,
functions_mapping: FunctionsMapping = None,
) -> Any:
- """ parse raw data with evaluated variables mapping.
- Notice: variables_mapping should not contain any variable or function.
+ """parse raw data with evaluated variables mapping.
+ Notice: variables_mapping should not contain any variable or function.
"""
if isinstance(raw_data, str):
# content in string format may contains variables and functions
@@ -476,8 +476,10 @@ def parse_variables_mapping(
return parsed_variables
-def parse_parameters(parameters: Dict,) -> List[Dict]:
- """ parse parameters and generate cartesian product.
+def parse_parameters(
+ parameters: Dict,
+) -> List[Dict]:
+ """parse parameters and generate cartesian product.
Args:
parameters (Dict) parameters: parameter name and value mapping
@@ -584,17 +586,20 @@ def parse_parameters(parameters: Dict,) -> List[Dict]:
class Parser(object):
-
def __init__(self, functions_mapping: FunctionsMapping = None) -> None:
self.functions_mapping = functions_mapping
- def parse_string(self, raw_string: Text, variables_mapping: VariablesMapping) -> Any:
+ def parse_string(
+ self, raw_string: Text, variables_mapping: VariablesMapping
+ ) -> Any:
return parse_string(raw_string, variables_mapping, self.functions_mapping)
def parse_variables(self, variables_mapping: VariablesMapping) -> VariablesMapping:
return parse_variables_mapping(variables_mapping, self.functions_mapping)
- def parse_data(self, raw_data: Any, variables_mapping: VariablesMapping = None) -> Any:
+ def parse_data(
+ self, raw_data: Any, variables_mapping: VariablesMapping = None
+ ) -> Any:
return parse_data(raw_data, variables_mapping, self.functions_mapping)
def get_mapping_function(self, func_name: Text) -> Callable:
diff --git a/httprunner/parser_test.py b/httprunner/parser_test.py
index eaed0f78..2ac75422 100644
--- a/httprunner/parser_test.py
+++ b/httprunner/parser_test.py
@@ -8,7 +8,6 @@ from httprunner.loader import load_project_meta
class TestParserBasic(unittest.TestCase):
-
def test_build_url(self):
url = parser.build_url("https://postman-echo.com", "/get")
self.assertEqual(url, "https://postman-echo.com/get")
diff --git a/httprunner/response.py b/httprunner/response.py
index 7ee2006f..bac5632b 100644
--- a/httprunner/response.py
+++ b/httprunner/response.py
@@ -12,8 +12,7 @@ from httprunner.parser import Parser, parse_string_value
def get_uniform_comparator(comparator: Text):
- """ convert comparator alias to uniform name
- """
+ """convert comparator alias to uniform name"""
if comparator in ["eq", "equals", "equal"]:
return "equal"
elif comparator in ["lt", "less_than"]:
@@ -52,7 +51,7 @@ def get_uniform_comparator(comparator: Text):
def uniform_validator(validator):
- """ unify validator
+ """unify validator
Args:
validator (dict): validator maybe in two formats:
@@ -116,7 +115,7 @@ def uniform_validator(validator):
class ResponseObject(object):
def __init__(self, resp_obj: requests.Response, parser: Parser):
- """ initialize with a requests.Response object
+ """initialize with a requests.Response object
Args:
resp_obj (instance): requests.Response instance
@@ -168,20 +167,19 @@ class ResponseObject(object):
return check_value
- def extract(self,
- extractors: Dict[Text, Text],
- variables_mapping: VariablesMapping = None,
- ) -> Dict[Text, Any]:
+ def extract(
+ self,
+ extractors: Dict[Text, Text],
+ variables_mapping: VariablesMapping = None,
+ ) -> Dict[Text, Any]:
if not extractors:
return {}
extract_mapping = {}
for key, field in extractors.items():
- if '$' in field:
+ if "$" in field:
# field contains variable or function
- field = self.parser.parse_data(
- field, variables_mapping
- )
+ field = self.parser.parse_data(field, variables_mapping)
field_value = self._search_jmespath(field)
extract_mapping[key] = field_value
@@ -214,9 +212,7 @@ class ResponseObject(object):
check_item = u_validator["check"]
if "$" in check_item:
# check_item is variable or function
- check_item = self.parser.parse_data(
- check_item, variables_mapping
- )
+ check_item = self.parser.parse_data(check_item, variables_mapping)
check_item = parse_string_value(check_item)
if check_item and isinstance(check_item, Text):
diff --git a/httprunner/response_test.py b/httprunner/response_test.py
index 8b292f46..7ab7a6fb 100644
--- a/httprunner/response_test.py
+++ b/httprunner/response_test.py
@@ -19,16 +19,13 @@ class TestResponse(unittest.TestCase):
]
},
)
- parser = Parser(functions_mapping={
- 'get_name': lambda: 'name',
- "get_num": lambda x: x
- })
+ parser = Parser(
+ functions_mapping={"get_name": lambda: "name", "get_num": lambda x: x}
+ )
self.resp_obj = ResponseObject(resp, parser)
def test_extract(self):
- variables_mapping = {
- 'body': 'body'
- }
+ variables_mapping = {"body": "body"}
extract_mapping = self.resp_obj.extract(
{
"var_1": "body.json.locations[0]",
@@ -64,6 +61,9 @@ class TestResponse(unittest.TestCase):
def test_validate_functions(self):
variables_mapping = {"index": 1}
self.resp_obj.validate(
- [{"eq": ["${get_num(0)}", 0]}, {"eq": ["${get_num($index)}", 1]},],
+ [
+ {"eq": ["${get_num(0)}", 0]},
+ {"eq": ["${get_num($index)}", 1]},
+ ],
variables_mapping=variables_mapping,
)
diff --git a/httprunner/runner.py b/httprunner/runner.py
index 53d4786c..af69ec61 100644
--- a/httprunner/runner.py
+++ b/httprunner/runner.py
@@ -12,14 +12,22 @@ except ModuleNotFoundError:
USE_ALLURE = False
from loguru import logger
+
from httprunner.client import HttpSession
from httprunner.config import Config
from httprunner.exceptions import ParamsError, ValidationFailure
from httprunner.loader import load_project_meta
-from httprunner.models import (ProjectMeta, StepResult, TConfig, TestCaseInOut,
- TestCaseSummary, TestCaseTime, VariablesMapping)
+from httprunner.models import (
+ ProjectMeta,
+ StepResult,
+ TConfig,
+ TestCaseInOut,
+ TestCaseSummary,
+ TestCaseTime,
+ VariablesMapping,
+)
from httprunner.parser import Parser
-from httprunner.utils import merge_variables
+from httprunner.utils import LOGGER_FORMAT, init_logger, merge_variables
class SessionRunner(object):
@@ -43,6 +51,7 @@ class SessionRunner(object):
__log_path: Text = ""
def __init(self):
+ init_logger()
self.__config = self.config.struct()
self.__session_variables = {}
self.__start_at = 0
@@ -53,9 +62,7 @@ class SessionRunner(object):
)
self.case_id = self.case_id or str(uuid.uuid4())
self.root_dir = self.root_dir or self.__project_meta.RootDir
- self.__log_path = os.path.join(
- self.root_dir, "logs", f"{self.case_id}.run.log"
- )
+ self.__log_path = os.path.join(self.root_dir, "logs", f"{self.case_id}.run.log")
self.__step_results.clear()
self.session = self.session or HttpSession()
@@ -85,9 +92,7 @@ class SessionRunner(object):
self.__config.variables.update(self.__session_variables)
if param:
self.__config.variables.update(param)
- self.__config.variables = self.parser.parse_variables(
- self.__config.variables
- )
+ self.__config.variables = self.parser.parse_variables(self.__config.variables)
# parse config name
self.__config.name = self.parser.parse_data(
@@ -174,10 +179,12 @@ class SessionRunner(object):
raise
else:
logger.warning(
- f"run step {step.name()} validation failed,wait {step.retry_interval} sec and try again")
+ f"run step {step.name()} validation failed,wait {step.retry_interval} sec and try again"
+ )
time.sleep(step.retry_interval)
logger.info(
- f"run step retry ({i+1}/{step.retry_times} time): {step.name()} >>>>>>")
+ f"run step retry ({i+1}/{step.retry_times} time): {step.name()} >>>>>>"
+ )
# save extracted variables to session variables
self.__session_variables.update(step_result.export_vars)
@@ -188,6 +195,7 @@ class SessionRunner(object):
def test_start(self, param: Dict = None) -> "SessionRunner":
"""main entrance, discovered by pytest"""
+ print("\n")
self.__init()
self.__parse_config(param)
@@ -200,14 +208,13 @@ class SessionRunner(object):
f"Start to run testcase: {self.__config.name}, TestCase ID: {self.case_id}"
)
- log_handler = logger.add(self.__log_path, level="DEBUG")
+ logger.add(self.__log_path, format=LOGGER_FORMAT, level="DEBUG")
self.__start_at = time.time()
try:
# run step in sequential order
for step in self.teststeps:
self.__run_step(step)
finally:
- logger.remove(log_handler)
logger.info(f"generate testcase log: {self.__log_path}")
self.__duration = time.time() - self.__start_at
diff --git a/httprunner/step.py b/httprunner/step.py
index c349de3e..a5221f16 100644
--- a/httprunner/step.py
+++ b/httprunner/step.py
@@ -2,12 +2,15 @@ from typing import Union
from httprunner.models import StepResult, TRequest, TStep, TestCase
from httprunner.runner import HttpRunner
-from httprunner.step_request import RequestWithOptionalArgs, StepRequestExtraction, StepRequestValidation
+from httprunner.step_request import (
+ RequestWithOptionalArgs,
+ StepRequestExtraction,
+ StepRequestValidation,
+)
from httprunner.step_testcase import StepRefCase
class Step(object):
-
def __init__(
self,
step: Union[
diff --git a/httprunner/step_request.py b/httprunner/step_request.py
index a681e448..9299cba2 100644
--- a/httprunner/step_request.py
+++ b/httprunner/step_request.py
@@ -6,15 +6,24 @@ from loguru import logger
from httprunner import utils
from httprunner.exceptions import ValidationFailure
from httprunner.ext.uploader import prepare_upload_step
-from httprunner.models import (Hooks, IStep, MethodEnum, StepResult, TRequest,
- TStep, VariablesMapping)
+from httprunner.models import (
+ Hooks,
+ IStep,
+ MethodEnum,
+ StepResult,
+ TRequest,
+ TStep,
+ VariablesMapping,
+)
from httprunner.parser import build_url
from httprunner.response import ResponseObject
from httprunner.runner import HttpRunner
-def call_hooks(runner: HttpRunner, hooks: Hooks, step_variables: VariablesMapping, hook_msg: Text):
- """ call hook actions.
+def call_hooks(
+ runner: HttpRunner, hooks: Hooks, step_variables: VariablesMapping, hook_msg: Text
+):
+ """call hook actions.
Args:
hooks (list): each hook in hooks list maybe in two format.
@@ -46,9 +55,7 @@ def call_hooks(runner: HttpRunner, hooks: Hooks, step_variables: VariablesMappin
elif isinstance(hook, Dict) and len(hook) == 1:
# format 2: {"var": "${func()}"}
var_name, hook_content = list(hook.items())[0]
- hook_content_eval = runner.parser.parse_data(
- hook_content, step_variables
- )
+ hook_content_eval = runner.parser.parse_data(hook_content, step_variables)
logger.debug(
f"call hook function: {hook_content}, got value: {hook_content_eval}"
)
@@ -73,9 +80,7 @@ def run_step_request(runner: HttpRunner, step: TStep) -> StepResult:
prepare_upload_step(step, functions)
request_dict = step.request.dict()
request_dict.pop("upload", None)
- parsed_request_dict = runner.parser.parse_data(
- request_dict, step.variables
- )
+ parsed_request_dict = runner.parser.parse_data(request_dict, step.variables)
parsed_request_dict["headers"].setdefault(
"HRUN-Request-ID",
f"HRUN-{runner.case_id}-{str(int(time.time() * 1000))[-6:]}",
@@ -136,9 +141,7 @@ def run_step_request(runner: HttpRunner, step: TStep) -> StepResult:
# validate
validators = step.validators
try:
- resp_obj.validate(
- validators, variables_mapping
- )
+ resp_obj.validate(validators, variables_mapping)
step_result.success = True
except ValidationFailure:
log_req_resp_details()
@@ -162,9 +165,7 @@ class StepRequestValidation(IStep):
def assert_equal(
self, jmes_path: Text, expected_value: Any, message: Text = ""
) -> "StepRequestValidation":
- self.__step.validators.append(
- {"equal": [jmes_path, expected_value, message]}
- )
+ self.__step.validators.append({"equal": [jmes_path, expected_value, message]})
return self
def assert_not_equal(
@@ -418,7 +419,6 @@ class RequestWithOptionalArgs(IStep):
class RunRequest(object):
-
def __init__(self, name: Text):
self.__step = TStep(name=name)
diff --git a/httprunner/step_request_test.py b/httprunner/step_request_test.py
index 7a54e494..58164acc 100644
--- a/httprunner/step_request_test.py
+++ b/httprunner/step_request_test.py
@@ -1,10 +1,11 @@
import unittest
-from examples.postman_echo.request_methods.request_with_functions_test import TestCaseRequestWithFunctions
+from examples.postman_echo.request_methods.request_with_functions_test import (
+ TestCaseRequestWithFunctions,
+)
class TestRunRequest(unittest.TestCase):
-
def test_run_request(self):
runner = TestCaseRequestWithFunctions().test_start()
summary = runner.get_summary()
diff --git a/httprunner/step_testcase.py b/httprunner/step_testcase.py
index b61285c5..c011168f 100644
--- a/httprunner/step_testcase.py
+++ b/httprunner/step_testcase.py
@@ -22,11 +22,9 @@ def run_step_testcase(runner: HttpRunner, step: TStep) -> StepResult:
# step.testcase is a referenced testcase, e.g. RequestWithFunctions
ref_case_runner = step.testcase()
- ref_case_runner.with_session(runner.session) \
- .with_case_id(runner.case_id) \
- .with_variables(step_variables) \
- .with_export(step_export) \
- .test_start()
+ ref_case_runner.with_session(runner.session).with_case_id(
+ runner.case_id
+ ).with_variables(step_variables).with_export(step_export).test_start()
# teardown hooks
if step.teardown_hooks:
diff --git a/httprunner/step_testcase_test.py b/httprunner/step_testcase_test.py
index 27a7301c..9f64e0c0 100644
--- a/httprunner/step_testcase_test.py
+++ b/httprunner/step_testcase_test.py
@@ -2,19 +2,22 @@ import unittest
from httprunner.runner import HttpRunner
from httprunner.step_testcase import RunTestCase
-from examples.postman_echo.request_methods.request_with_functions_test import TestCaseRequestWithFunctions
+from examples.postman_echo.request_methods.request_with_functions_test import (
+ TestCaseRequestWithFunctions,
+)
class TestRunTestCase(unittest.TestCase):
-
def setUp(self):
self.runner = HttpRunner()
def test_run_testcase_by_path(self):
- step_result = RunTestCase("run referenced testcase").call(
- TestCaseRequestWithFunctions
- ).run(self.runner)
+ step_result = (
+ RunTestCase("run referenced testcase")
+ .call(TestCaseRequestWithFunctions)
+ .run(self.runner)
+ )
self.assertTrue(step_result.success)
self.assertEqual(step_result.name, "run referenced testcase")
self.assertEqual(len(step_result.data), 3)
diff --git a/httprunner/utils.py b/httprunner/utils.py
index 1acfca25..3a42f3b3 100644
--- a/httprunner/utils.py
+++ b/httprunner/utils.py
@@ -5,6 +5,7 @@ import json
import os
import os.path
import platform
+import sys
import uuid
from multiprocessing import Queue
from typing import Any, Dict, List, Text
@@ -31,18 +32,20 @@ def init_sentry_sdk():
class GAClient(object):
- version = '1' # GA API Version
- report_url = 'https://www.google-analytics.com/collect'
- report_debug_url = 'https://www.google-analytics.com/debug/collect' # used for debug
+ version = "1" # GA API Version
+ report_url = "https://www.google-analytics.com/collect"
+ report_debug_url = (
+ "https://www.google-analytics.com/debug/collect" # used for debug
+ )
def __init__(self, tracking_id: Text):
self.http_client = requests.Session()
self.label = f"v{__version__}"
self.common_params = {
- 'v': self.version,
- 'tid': tracking_id, # Tracking ID / Property ID, XX-XXXXXXX-X
- 'cid': uuid.getnode(), # Anonymous Client ID
- 'ua': f'HttpRunner/{__version__}',
+ "v": self.version,
+ "tid": tracking_id, # Tracking ID / Property ID, XX-XXXXXXX-X
+ "cid": uuid.getnode(), # Anonymous Client ID
+ "ua": f"HttpRunner/{__version__}",
}
# do not send GA events in CI environment
self.__is_ci = os.getenv("DISABLE_GA") == "true"
@@ -52,16 +55,16 @@ class GAClient(object):
return
data = {
- 't': 'event', # Event hit type = event
- 'ec': category, # Required. Event Category.
- 'ea': action, # Required. Event Action.
- 'el': self.label, # Optional. Event label, used as version.
- 'ev': value, # Optional. Event value, must be non-negative integer
+ "t": "event", # Event hit type = event
+ "ec": category, # Required. Event Category.
+ "ea": action, # Required. Event Action.
+ "el": self.label, # Optional. Event label, used as version.
+ "ev": value, # Optional. Event value, must be non-negative integer
}
data.update(self.common_params)
try:
self.http_client.post(self.report_url, data=data, timeout=5)
- except Exception: # ProxyError, SSLError, ConnectionError
+ except Exception: # ProxyError, SSLError, ConnectionError
pass
def track_user_timing(self, category: Text, variable: Text, duration: int):
@@ -69,16 +72,16 @@ class GAClient(object):
return
data = {
- 't': 'timing', # Event hit type = timing
- 'utc': category, # Required. user timing category. e.g. jsonLoader
- 'utv': variable, # Required. timing variable. e.g. load
- 'utt': duration, # Required. time took duration.
- 'utl': self.label, # Optional. user timing label, used as version.
+ "t": "timing", # Event hit type = timing
+ "utc": category, # Required. user timing category. e.g. jsonLoader
+ "utv": variable, # Required. timing variable. e.g. load
+ "utt": duration, # Required. time took duration.
+ "utl": self.label, # Optional. user timing label, used as version.
}
data.update(self.common_params)
try:
self.http_client.post(self.report_url, data=data, timeout=5)
- except Exception: # ProxyError, SSLError, ConnectionError
+ except Exception: # ProxyError, SSLError, ConnectionError
pass
@@ -86,23 +89,21 @@ ga_client = GAClient("UA-114587036-1")
def set_os_environ(variables_mapping):
- """ set variables mapping to os.environ
- """
+ """set variables mapping to os.environ"""
for variable in variables_mapping:
os.environ[variable] = variables_mapping[variable]
logger.debug(f"Set OS environment variable: {variable}")
def unset_os_environ(variables_mapping):
- """ unset variables mapping to os.environ
- """
+ """unset variables mapping to os.environ"""
for variable in variables_mapping:
os.environ.pop(variable)
logger.debug(f"Unset OS environment variable: {variable}")
def get_os_environ(variable_name):
- """ get value of environment variable.
+ """get value of environment variable.
Args:
variable_name(str): variable name
@@ -121,7 +122,7 @@ def get_os_environ(variable_name):
def lower_dict_keys(origin_dict):
- """ convert keys in dict to lower case
+ """convert keys in dict to lower case
Args:
origin_dict (dict): mapping data structure
@@ -156,7 +157,7 @@ def lower_dict_keys(origin_dict):
def print_info(info_mapping):
- """ print info in mapping.
+ """print info in mapping.
Args:
info_mapping (dict): input(variables) or output mapping.
@@ -201,8 +202,7 @@ def print_info(info_mapping):
def omit_long_data(body, omit_len=512):
- """ omit too long str/bytes
- """
+ """omit too long str/bytes"""
if not isinstance(body, (str, bytes)):
return body
@@ -243,8 +243,7 @@ def sort_dict_by_custom_order(raw_dict: Dict, custom_order: List):
class ExtendJSONEncoder(json.JSONEncoder):
- """ especially used to safely dump json data with python object, such as MultipartEncoder
- """
+ """especially used to safely dump json data with python object, such as MultipartEncoder"""
def default(self, obj):
try:
@@ -256,8 +255,7 @@ class ExtendJSONEncoder(json.JSONEncoder):
def merge_variables(
variables: VariablesMapping, variables_to_be_overridden: VariablesMapping
) -> VariablesMapping:
- """ merge two variables mapping, the first variables have higher priority
- """
+ """merge two variables mapping, the first variables have higher priority"""
step_new_variables = {}
for key, value in variables.items():
if f"${key}" == value or "${" + key + "}" == value:
@@ -282,7 +280,7 @@ def is_support_multiprocessing() -> bool:
def gen_cartesian_product(*args: List[Dict]) -> List[Dict]:
- """ generate cartesian product for lists
+ """generate cartesian product for lists
Args:
args (list of list): lists to be generated with cartesian product
@@ -320,3 +318,12 @@ def gen_cartesian_product(*args: List[Dict]) -> List[Dict]:
product_list.append(product_item_dict)
return product_list
+
+
+LOGGER_FORMAT = "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {message}"
+
+
+def init_logger():
+ # set log level to INFO
+ logger.remove()
+ logger.add(sys.stderr, format=LOGGER_FORMAT, level="INFO")
diff --git a/poetry.lock b/poetry.lock
index 7b9eea4b..41ec159b 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -34,19 +34,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "tsinghua"
-[[package]]
-name = "appdirs"
-version = "1.4.4"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.source]
-type = "legacy"
-url = "https://pypi.tuna.tsinghua.edu.cn/simple"
-reference = "tsinghua"
-
[[package]]
name = "atomicwrites"
version = "1.4.0"
@@ -81,23 +68,26 @@ reference = "tsinghua"
[[package]]
name = "black"
-version = "19.10b0"
+version = "22.3.0"
description = "The uncompromising code formatter."
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.6.2"
[package.dependencies]
-appdirs = "*"
-attrs = ">=18.1.0"
-click = ">=6.5"
-pathspec = ">=0.6,<1"
-regex = "*"
-toml = ">=0.9.4"
-typed-ast = ">=1.4.0"
+click = ">=8.0.0"
+mypy-extensions = ">=0.4.3"
+pathspec = ">=0.9.0"
+platformdirs = ">=2"
+tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
+typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
[package.extras]
-d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.7.4)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2)"]
[package.source]
type = "legacy"
@@ -315,6 +305,19 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "tsinghua"
+[[package]]
+name = "mypy-extensions"
+version = "0.4.3"
+description = "Experimental type system extensions for programs checked with the mypy typechecker."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.source]
+type = "legacy"
+url = "https://pypi.tuna.tsinghua.edu.cn/simple"
+reference = "tsinghua"
+
[[package]]
name = "packaging"
version = "21.3"
@@ -344,6 +347,23 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "tsinghua"
+[[package]]
+name = "platformdirs"
+version = "2.5.2"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
+test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
+
+[package.source]
+type = "legacy"
+url = "https://pypi.tuna.tsinghua.edu.cn/simple"
+reference = "tsinghua"
+
[[package]]
name = "pluggy"
version = "1.0.0"
@@ -486,19 +506,6 @@ type = "legacy"
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
reference = "tsinghua"
-[[package]]
-name = "regex"
-version = "2022.3.15"
-description = "Alternative regular expression module, to replace re."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.source]
-type = "legacy"
-url = "https://pypi.tuna.tsinghua.edu.cn/simple"
-reference = "tsinghua"
-
[[package]]
name = "requests"
version = "2.27.1"
@@ -692,7 +699,7 @@ upload = ["requests-toolbelt", "filetype"]
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
-content-hash = "ce51964d1daf419593be8b6d6f003069bf8626d922b950432e2c6f9a8093d9e6"
+content-hash = "8dc444117b1b9a55f00d25b86e69a26d9ceaac2d331731b676415fd4a019f57a"
[metadata.files]
allure-pytest = [
@@ -703,10 +710,6 @@ allure-python-commons = [
{file = "allure-python-commons-2.9.45.tar.gz", hash = "sha256:c238d28aeac35e8c7c517d8a2327e25ae5bbf2c30b5e2313d20ef11d75f5549d"},
{file = "allure_python_commons-2.9.45-py3-none-any.whl", hash = "sha256:3572f0526db3946fb14470c58b0b41d343483aad91d37d414e4641815e13691a"},
]
-appdirs = [
- {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
- {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
-]
atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
@@ -716,8 +719,29 @@ attrs = [
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
]
black = [
- {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
- {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
+ {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"},
+ {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"},
+ {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"},
+ {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"},
+ {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"},
+ {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"},
+ {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"},
+ {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"},
+ {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"},
+ {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"},
+ {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"},
+ {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"},
+ {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"},
+ {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"},
+ {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"},
+ {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"},
+ {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"},
+ {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"},
+ {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"},
+ {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"},
+ {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"},
+ {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"},
+ {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"},
]
brotli = [
{file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"},
@@ -888,6 +912,10 @@ markupsafe = [
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
]
+mypy-extensions = [
+ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
+ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
+]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
@@ -896,6 +924,10 @@ pathspec = [
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
]
+platformdirs = [
+ {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
+ {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
+]
pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
@@ -975,82 +1007,6 @@ pyyaml = [
{file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
]
-regex = [
- {file = "regex-2022.3.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:42eb13b93765c6698a5ab3bcd318d8c39bb42e5fa8a7fcf7d8d98923f3babdb1"},
- {file = "regex-2022.3.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9beb03ff6fe509d6455971c2489dceb31687b38781206bcec8e68bdfcf5f1db2"},
- {file = "regex-2022.3.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0a5a1fdc9f148a8827d55b05425801acebeeefc9e86065c7ac8b8cc740a91ff"},
- {file = "regex-2022.3.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb374a2a4dba7c4be0b19dc7b1adc50e6c2c26c3369ac629f50f3c198f3743a4"},
- {file = "regex-2022.3.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c33ce0c665dd325200209340a88438ba7a470bd5f09f7424e520e1a3ff835b52"},
- {file = "regex-2022.3.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04c09b9651fa814eeeb38e029dc1ae83149203e4eeb94e52bb868fadf64852bc"},
- {file = "regex-2022.3.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab5d89cfaf71807da93c131bb7a19c3e19eaefd613d14f3bce4e97de830b15df"},
- {file = "regex-2022.3.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e2630ae470d6a9f8e4967388c1eda4762706f5750ecf387785e0df63a4cc5af"},
- {file = "regex-2022.3.15-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:df037c01d68d1958dad3463e2881d3638a0d6693483f58ad41001aa53a83fcea"},
- {file = "regex-2022.3.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:940570c1a305bac10e8b2bc934b85a7709c649317dd16520471e85660275083a"},
- {file = "regex-2022.3.15-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7f63877c87552992894ea1444378b9c3a1d80819880ae226bb30b04789c0828c"},
- {file = "regex-2022.3.15-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:3e265b388cc80c7c9c01bb4f26c9e536c40b2c05b7231fbb347381a2e1c8bf43"},
- {file = "regex-2022.3.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:058054c7a54428d5c3e3739ac1e363dc9347d15e64833817797dc4f01fb94bb8"},
- {file = "regex-2022.3.15-cp310-cp310-win32.whl", hash = "sha256:76435a92e444e5b8f346aed76801db1c1e5176c4c7e17daba074fbb46cb8d783"},
- {file = "regex-2022.3.15-cp310-cp310-win_amd64.whl", hash = "sha256:174d964bc683b1e8b0970e1325f75e6242786a92a22cedb2a6ec3e4ae25358bd"},
- {file = "regex-2022.3.15-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6e1d8ed9e61f37881c8db383a124829a6e8114a69bd3377a25aecaeb9b3538f8"},
- {file = "regex-2022.3.15-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b52771f05cff7517f7067fef19ffe545b1f05959e440d42247a17cd9bddae11b"},
- {file = "regex-2022.3.15-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:673f5a393d603c34477dbad70db30025ccd23996a2d0916e942aac91cc42b31a"},
- {file = "regex-2022.3.15-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8923e1c5231549fee78ff9b2914fad25f2e3517572bb34bfaa3aea682a758683"},
- {file = "regex-2022.3.15-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:764e66a0e382829f6ad3bbce0987153080a511c19eb3d2f8ead3f766d14433ac"},
- {file = "regex-2022.3.15-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd00859291658fe1fda48a99559fb34da891c50385b0bfb35b808f98956ef1e7"},
- {file = "regex-2022.3.15-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aa2ce79f3889720b46e0aaba338148a1069aea55fda2c29e0626b4db20d9fcb7"},
- {file = "regex-2022.3.15-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:34bb30c095342797608727baf5c8aa122406aa5edfa12107b8e08eb432d4c5d7"},
- {file = "regex-2022.3.15-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:25ecb1dffc5e409ca42f01a2b2437f93024ff1612c1e7983bad9ee191a5e8828"},
- {file = "regex-2022.3.15-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:aa5eedfc2461c16a092a2fabc5895f159915f25731740c9152a1b00f4bcf629a"},
- {file = "regex-2022.3.15-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:7d1a6e403ac8f1d91d8f51c441c3f99367488ed822bda2b40836690d5d0059f5"},
- {file = "regex-2022.3.15-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:3e4d710ff6539026e49f15a3797c6b1053573c2b65210373ef0eec24480b900b"},
- {file = "regex-2022.3.15-cp36-cp36m-win32.whl", hash = "sha256:0100f0ded953b6b17f18207907159ba9be3159649ad2d9b15535a74de70359d3"},
- {file = "regex-2022.3.15-cp36-cp36m-win_amd64.whl", hash = "sha256:f320c070dea3f20c11213e56dbbd7294c05743417cde01392148964b7bc2d31a"},
- {file = "regex-2022.3.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fc8c7958d14e8270171b3d72792b609c057ec0fa17d507729835b5cff6b7f69a"},
- {file = "regex-2022.3.15-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ca6dcd17f537e9f3793cdde20ac6076af51b2bd8ad5fe69fa54373b17b48d3c"},
- {file = "regex-2022.3.15-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0214ff6dff1b5a4b4740cfe6e47f2c4c92ba2938fca7abbea1359036305c132f"},
- {file = "regex-2022.3.15-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a98ae493e4e80b3ded6503ff087a8492db058e9c68de371ac3df78e88360b374"},
- {file = "regex-2022.3.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b1cc70e31aacc152a12b39245974c8fccf313187eead559ee5966d50e1b5817"},
- {file = "regex-2022.3.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4829db3737480a9d5bfb1c0320c4ee13736f555f53a056aacc874f140e98f64"},
- {file = "regex-2022.3.15-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:303b15a3d32bf5fe5a73288c316bac5807587f193ceee4eb6d96ee38663789fa"},
- {file = "regex-2022.3.15-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:dc7b7c16a519d924c50876fb152af661a20749dcbf653c8759e715c1a7a95b18"},
- {file = "regex-2022.3.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ce3057777a14a9a1399b81eca6a6bfc9612047811234398b84c54aeff6d536ea"},
- {file = "regex-2022.3.15-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:48081b6bff550fe10bcc20c01cf6c83dbca2ccf74eeacbfac240264775fd7ecf"},
- {file = "regex-2022.3.15-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dcbb7665a9db9f8d7642171152c45da60e16c4f706191d66a1dc47ec9f820aed"},
- {file = "regex-2022.3.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c155a1a80c5e7a8fa1d9bb1bf3c8a953532b53ab1196092749bafb9d3a7cbb60"},
- {file = "regex-2022.3.15-cp37-cp37m-win32.whl", hash = "sha256:04b5ee2b6d29b4a99d38a6469aa1db65bb79d283186e8460542c517da195a8f6"},
- {file = "regex-2022.3.15-cp37-cp37m-win_amd64.whl", hash = "sha256:797437e6024dc1589163675ae82f303103063a0a580c6fd8d0b9a0a6708da29e"},
- {file = "regex-2022.3.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8afcd1c2297bc989dceaa0379ba15a6df16da69493635e53431d2d0c30356086"},
- {file = "regex-2022.3.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0066a6631c92774391f2ea0f90268f0d82fffe39cb946f0f9c6b382a1c61a5e5"},
- {file = "regex-2022.3.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8248f19a878c72d8c0a785a2cd45d69432e443c9f10ab924c29adda77b324ae"},
- {file = "regex-2022.3.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d1f3ea0d1924feb4cf6afb2699259f658a08ac6f8f3a4a806661c2dfcd66db1"},
- {file = "regex-2022.3.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:794a6bc66c43db8ed06698fc32aaeaac5c4812d9f825e9589e56f311da7becd9"},
- {file = "regex-2022.3.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d1445824944e642ffa54c4f512da17a953699c563a356d8b8cbdad26d3b7598"},
- {file = "regex-2022.3.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f553a1190ae6cd26e553a79f6b6cfba7b8f304da2071052fa33469da075ea625"},
- {file = "regex-2022.3.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:75a5e6ce18982f0713c4bac0704bf3f65eed9b277edd3fb9d2b0ff1815943327"},
- {file = "regex-2022.3.15-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f16cf7e4e1bf88fecf7f41da4061f181a6170e179d956420f84e700fb8a3fd6b"},
- {file = "regex-2022.3.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dad3991f0678facca1a0831ec1ddece2eb4d1dd0f5150acb9440f73a3b863907"},
- {file = "regex-2022.3.15-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:491fc754428514750ab21c2d294486223ce7385446f2c2f5df87ddbed32979ae"},
- {file = "regex-2022.3.15-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:6504c22c173bb74075d7479852356bb7ca80e28c8e548d4d630a104f231e04fb"},
- {file = "regex-2022.3.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:01c913cf573d1da0b34c9001a94977273b5ee2fe4cb222a5d5b320f3a9d1a835"},
- {file = "regex-2022.3.15-cp38-cp38-win32.whl", hash = "sha256:029e9e7e0d4d7c3446aa92474cbb07dafb0b2ef1d5ca8365f059998c010600e6"},
- {file = "regex-2022.3.15-cp38-cp38-win_amd64.whl", hash = "sha256:947a8525c0a95ba8dc873191f9017d1b1e3024d4dc757f694e0af3026e34044a"},
- {file = "regex-2022.3.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:591d4fba554f24bfa0421ba040cd199210a24301f923ed4b628e1e15a1001ff4"},
- {file = "regex-2022.3.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9809404528a999cf02a400ee5677c81959bc5cb938fdc696b62eb40214e3632"},
- {file = "regex-2022.3.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f08a7e4d62ea2a45557f561eea87c907222575ca2134180b6974f8ac81e24f06"},
- {file = "regex-2022.3.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a86cac984da35377ca9ac5e2e0589bd11b3aebb61801204bd99c41fac516f0d"},
- {file = "regex-2022.3.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:286908cbe86b1a0240a867aecfe26a439b16a1f585d2de133540549831f8e774"},
- {file = "regex-2022.3.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b7494df3fdcc95a1f76cf134d00b54962dd83189520fd35b8fcd474c0aa616d"},
- {file = "regex-2022.3.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b1ceede92400b3acfebc1425937454aaf2c62cd5261a3fabd560c61e74f6da3"},
- {file = "regex-2022.3.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0317eb6331146c524751354ebef76a7a531853d7207a4d760dfb5f553137a2a4"},
- {file = "regex-2022.3.15-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9c144405220c5ad3f5deab4c77f3e80d52e83804a6b48b6bed3d81a9a0238e4c"},
- {file = "regex-2022.3.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5b2e24f3ae03af3d8e8e6d824c891fea0ca9035c5d06ac194a2700373861a15c"},
- {file = "regex-2022.3.15-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f2c53f3af011393ab5ed9ab640fa0876757498aac188f782a0c620e33faa2a3d"},
- {file = "regex-2022.3.15-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:060f9066d2177905203516c62c8ea0066c16c7342971d54204d4e51b13dfbe2e"},
- {file = "regex-2022.3.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:530a3a16e57bd3ea0dff5ec2695c09632c9d6c549f5869d6cf639f5f7153fb9c"},
- {file = "regex-2022.3.15-cp39-cp39-win32.whl", hash = "sha256:78ce90c50d0ec970bd0002462430e00d1ecfd1255218d52d08b3a143fe4bde18"},
- {file = "regex-2022.3.15-cp39-cp39-win_amd64.whl", hash = "sha256:c5adc854764732dbd95a713f2e6c3e914e17f2ccdc331b9ecb777484c31f73b6"},
- {file = "regex-2022.3.15.tar.gz", hash = "sha256:0a7b75cc7bb4cc0334380053e4671c560e31272c9d2d5a6c4b8e9ae2c9bd0f82"},
-]
requests = [
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
diff --git a/pyproject.toml b/pyproject.toml
index 745deb07..9c098cf6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,12 +1,12 @@
[tool.poetry]
name = "httprunner"
-version = "4.0.0-alpha"
+version = "4.0.0-beta"
description = "One-stop solution for HTTP(S) testing."
license = "Apache-2.0"
readme = "README.md"
authors = ["debugtalk "]
-homepage = "https://github.com/httprunner/httprunner"
+homepage = "https://httprunner.com"
repository = "https://github.com/httprunner/httprunner"
documentation = "https://httprunner.com/docs"
@@ -35,7 +35,7 @@ pyyaml = "^5.4.1"
pydantic = "~1.8" # >=1.8.0 <1.9.0
loguru = "^0.4.1"
jmespath = "^0.9.5"
-black = "^19.10b0"
+black = "^22.3.0"
pytest = "^7.1.1"
pytest-html = "^3.1.1"
sentry-sdk = "^0.14.4"
@@ -44,6 +44,7 @@ requests-toolbelt = {version = "^0.9.1", optional = true}
filetype = {version = "^1.0.7", optional = true}
Brotli = "^1.0.9"
jinja2 = "^3.0.3"
+toml = "^0.10.2"
[tool.poetry.extras]
allure = ["allure-pytest"] # pip install "httprunner[allure]", poetry install -E allure
diff --git a/scripts/install.sh b/scripts/install.sh
index d26b93b6..dfc11491 100644
--- a/scripts/install.sh
+++ b/scripts/install.sh
@@ -2,7 +2,7 @@
# install hrp with one shell command
# bash -c "$(curl -ksSL https://httprunner.oss-cn-beijing.aliyuncs.com/install.sh)"
-LATEST_VERSION="v4.0.0-alpha"
+LATEST_VERSION="v4.0.0-beta"
set -e