mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-13 08:59:44 +08:00
Merge pull request #1211 from httprunner/refactor
refactor: move features from python version to go version
This commit is contained in:
22
.github/workflows/hrp-scaffold.yml
vendored
22
.github/workflows/hrp-scaffold.yml
vendored
@@ -25,8 +25,11 @@ jobs:
|
||||
run: make build
|
||||
- name: Run start project
|
||||
run: ./output/hrp startproject demo
|
||||
- name: Run demo tests
|
||||
run: ./output/hrp run demo/testcases/demo.json demo/testcases/demo.yaml
|
||||
- name: Run generated demo tests
|
||||
run: ./output/hrp run demo/testcases/demo_with_funplugin.json demo/testcases/demo_requests.yml demo/testcases/demo_ref_testcase.yml
|
||||
- name: Run demo in examples
|
||||
run: |
|
||||
./output/hrp run examples/demo-with-py-plugin/testcases/demo_with_funplugin.json
|
||||
|
||||
scaffold-with-go-plugin:
|
||||
strategy:
|
||||
@@ -47,8 +50,12 @@ jobs:
|
||||
run: make build
|
||||
- name: Run start project
|
||||
run: ./output/hrp startproject demo --go
|
||||
- name: Run demo tests
|
||||
run: ./output/hrp run demo/testcases/demo.json demo/testcases/demo.yaml
|
||||
- name: Run generated demo tests
|
||||
run: ./output/hrp run demo/testcases/demo_with_funplugin.json demo/testcases/demo_requests.yml demo/testcases/demo_ref_testcase.yml
|
||||
- name: Run demo in examples
|
||||
run: |
|
||||
go build -o examples/demo-with-go-plugin/debugtalk.bin examples/demo-with-go-plugin/plugin/debugtalk.go
|
||||
./output/hrp run examples/demo-with-go-plugin/testcases/demo_with_funplugin.json
|
||||
|
||||
scaffold-without-custom-plugin:
|
||||
strategy:
|
||||
@@ -69,5 +76,8 @@ jobs:
|
||||
run: make build
|
||||
- name: Run start project
|
||||
run: ./output/hrp startproject demo --ignore-plugin
|
||||
- name: Run demo tests
|
||||
run: ./output/hrp run demo/testcases/demo.json demo/testcases/demo.yaml
|
||||
- name: Run generated demo tests
|
||||
run: ./output/hrp run demo/testcases/demo_without_funplugin.json
|
||||
- name: Run demo in examples
|
||||
run: |
|
||||
./output/hrp run examples/demo-without-plugin/testcases/demo_without_funplugin.json
|
||||
|
||||
3
.github/workflows/smoketest.yml
vendored
3
.github/workflows/smoketest.yml
vendored
@@ -36,10 +36,7 @@ jobs:
|
||||
- name: Test commands
|
||||
run: |
|
||||
poetry run hrun -V
|
||||
poetry run har2case -h
|
||||
poetry run httprunner run -h
|
||||
poetry run httprunner startproject -h
|
||||
poetry run httprunner har2case -h
|
||||
- name: Run smoketest - postman echo
|
||||
run: |
|
||||
poetry run hrun examples/postman_echo/request_methods
|
||||
|
||||
1
.github/workflows/unittest.yml
vendored
1
.github/workflows/unittest.yml
vendored
@@ -33,7 +33,6 @@ jobs:
|
||||
poetry run httprunner
|
||||
poetry run hmake
|
||||
poetry run hrun
|
||||
poetry run har2case
|
||||
poetry run coverage run --source=httprunner -m pytest httprunner
|
||||
- name: coverage report
|
||||
run: |
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
# Release History
|
||||
|
||||
## 4.0.0
|
||||
## v4.0.0-alpha
|
||||
|
||||
- refactor: merge [hrp] into httprunner repo
|
||||
- fix: call referenced api/testcase with relative path
|
||||
- fix: ignore exceptions when reporting GA events
|
||||
- refactor: merge [hrp] into httprunner v4, which will include golang and python dual engine
|
||||
|
||||
**go version**
|
||||
|
||||
- feat: add `--profile` flag for har2case to support overwrite headers/cookies with specified yaml/json profile file
|
||||
- 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
|
||||
|
||||
**python version**
|
||||
|
||||
- change: remove startproject, move all features to go version, replace with `hrp startproject`
|
||||
- change: remove har2case, move all features to go version, replace with `hrp run`
|
||||
- change: remove locust, you should run load tests with go version, replace with `hrp boom`
|
||||
- change: remove fastapi and uvicorn dependencies
|
||||
- fix: ignore exceptions when reporting GA events
|
||||
- fix: remove misuse of NoReturn in Python typing
|
||||
|
||||
## hrp-v0.8.0 (2022-03-22)
|
||||
|
||||
|
||||
@@ -33,4 +33,4 @@ Copyright 2021 debugtalk
|
||||
* [hrp run](hrp_run.md) - run API test
|
||||
* [hrp startproject](hrp_startproject.md) - create a scaffold project
|
||||
|
||||
###### Auto generated by spf13/cobra on 23-Mar-2022
|
||||
###### Auto generated by spf13/cobra on 26-Mar-2022
|
||||
|
||||
@@ -41,4 +41,4 @@ hrp boom [flags]
|
||||
|
||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||
|
||||
###### Auto generated by spf13/cobra on 23-Mar-2022
|
||||
###### Auto generated by spf13/cobra on 26-Mar-2022
|
||||
|
||||
@@ -15,6 +15,7 @@ hrp har2case $har_path... [flags]
|
||||
```
|
||||
-h, --help help for har2case
|
||||
-d, --output-dir string specify output directory, default to the same dir with har file
|
||||
-p, --profile string specify profile path to override headers and cookies
|
||||
-j, --to-json convert to JSON format (default true)
|
||||
-y, --to-yaml convert to YAML format
|
||||
```
|
||||
@@ -23,4 +24,4 @@ hrp har2case $har_path... [flags]
|
||||
|
||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||
|
||||
###### Auto generated by spf13/cobra on 23-Mar-2022
|
||||
###### Auto generated by spf13/cobra on 26-Mar-2022
|
||||
|
||||
@@ -34,4 +34,4 @@ hrp run $path... [flags]
|
||||
|
||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||
|
||||
###### Auto generated by spf13/cobra on 23-Mar-2022
|
||||
###### Auto generated by spf13/cobra on 26-Mar-2022
|
||||
|
||||
@@ -19,4 +19,4 @@ hrp startproject $project_name [flags]
|
||||
|
||||
* [hrp](hrp.md) - One-stop solution for HTTP(S) testing.
|
||||
|
||||
###### Auto generated by spf13/cobra on 23-Mar-2022
|
||||
###### Auto generated by spf13/cobra on 26-Mar-2022
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
headers:
|
||||
Content-Type: "application/x-www-form-urlencoded"
|
||||
cookies:
|
||||
CASTGC: "TGT"
|
||||
UserName: "debugtalk"
|
||||
15
examples/demo-with-go-plugin/.gitignore
vendored
Normal file
15
examples/demo-with-go-plugin/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
.env
|
||||
reports/
|
||||
*.so
|
||||
.vscode/
|
||||
.idea/
|
||||
.DS_Store
|
||||
output/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.python-version
|
||||
logs/
|
||||
|
||||
# plugin
|
||||
debugtalk.bin
|
||||
debugtalk.so
|
||||
@@ -2,12 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.Println("plugin init function called")
|
||||
}
|
||||
"github.com/httprunner/funplugin/fungo"
|
||||
)
|
||||
|
||||
func SumTwoInt(a, b int) int {
|
||||
return a + b
|
||||
@@ -36,26 +33,6 @@ func Sum(args ...interface{}) (interface{}, error) {
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
func SumTwoString(a, b string) string {
|
||||
return a + b
|
||||
}
|
||||
|
||||
func SumStrings(s ...string) string {
|
||||
var sum string
|
||||
for _, arg := range s {
|
||||
sum += arg
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func Concatenate(args ...interface{}) (interface{}, error) {
|
||||
var result string
|
||||
for _, arg := range args {
|
||||
result += fmt.Sprintf("%v", arg)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func SetupHookExample(args string) string {
|
||||
return fmt.Sprintf("step name: %v, setup...", args)
|
||||
}
|
||||
@@ -63,3 +40,18 @@ func SetupHookExample(args string) string {
|
||||
func TeardownHookExample(args string) string {
|
||||
return fmt.Sprintf("step name: %v, teardown...", args)
|
||||
}
|
||||
|
||||
func GetVersion() string {
|
||||
return "v4.0.0-alpha"
|
||||
}
|
||||
|
||||
func main() {
|
||||
fungo.Register("get_httprunner_version", GetVersion)
|
||||
fungo.Register("sum_ints", SumInts)
|
||||
fungo.Register("sum_two_int", SumTwoInt)
|
||||
fungo.Register("sum_two", SumTwoInt)
|
||||
fungo.Register("sum", Sum)
|
||||
fungo.Register("setup_hook_example", SetupHookExample)
|
||||
fungo.Register("teardown_hook_example", TeardownHookExample)
|
||||
fungo.Serve()
|
||||
}
|
||||
5
examples/demo-with-go-plugin/plugin/go.mod
Normal file
5
examples/demo-with-go-plugin/plugin/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module plugin
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/httprunner/funplugin v0.4.2 // indirect
|
||||
196
examples/demo-with-go-plugin/plugin/go.sum
Normal file
196
examples/demo-with-go-plugin/plugin/go.sum
Normal file
@@ -0,0 +1,196 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v1.1.0 h1:QsGcniKx5/LuX2eYoeL+Np3UKYPNaN7YKpTh29h8rbw=
|
||||
github.com/hashicorp/go-hclog v1.1.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM=
|
||||
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/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=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc=
|
||||
github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 h1:ErU+UA6wxadoU8nWrsy5MZUVBs75K17zUCsUCIfrXCE=
|
||||
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
33
examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml
Normal file
33
examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
config:
|
||||
name: "request methods testcase: reference testcase"
|
||||
variables:
|
||||
foo1: testsuite_config_bar1
|
||||
expect_foo1: testsuite_config_bar1
|
||||
expect_foo2: config_bar2
|
||||
base_url: "https://postman-echo.com"
|
||||
verify: False
|
||||
|
||||
teststeps:
|
||||
-
|
||||
name: request with functions
|
||||
variables:
|
||||
foo1: testcase_ref_bar1
|
||||
expect_foo1: testcase_ref_bar1
|
||||
testcase: testcases/demo_requests.yml
|
||||
export:
|
||||
- foo3
|
||||
-
|
||||
name: post form data
|
||||
variables:
|
||||
foo1: bar1
|
||||
request:
|
||||
method: POST
|
||||
url: /post
|
||||
headers:
|
||||
User-Agent: HttpRunner/${get_httprunner_version()}
|
||||
Content-Type: "application/x-www-form-urlencoded"
|
||||
data: "foo1=$foo1&foo2=$foo3"
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["body.form.foo1", "bar1"]
|
||||
- eq: ["body.form.foo2", "bar21"]
|
||||
65
examples/demo-with-go-plugin/testcases/demo_requests.yml
Normal file
65
examples/demo-with-go-plugin/testcases/demo_requests.yml
Normal file
@@ -0,0 +1,65 @@
|
||||
config:
|
||||
name: "request methods testcase with functions"
|
||||
variables:
|
||||
foo1: config_bar1
|
||||
foo2: config_bar2
|
||||
expect_foo1: config_bar1
|
||||
expect_foo2: config_bar2
|
||||
base_url: "https://postman-echo.com"
|
||||
verify: False
|
||||
export: ["foo3"]
|
||||
|
||||
teststeps:
|
||||
-
|
||||
name: get with params
|
||||
variables:
|
||||
foo1: bar11
|
||||
foo2: bar21
|
||||
sum_v: "${sum_two(1, 2)}"
|
||||
request:
|
||||
method: GET
|
||||
url: /get
|
||||
params:
|
||||
foo1: $foo1
|
||||
foo2: $foo2
|
||||
sum_v: $sum_v
|
||||
headers:
|
||||
User-Agent: HttpRunner/${get_httprunner_version()}
|
||||
extract:
|
||||
foo3: "body.args.foo2"
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["body.args.foo1", "bar11"]
|
||||
- eq: ["body.args.sum_v", "3"]
|
||||
- eq: ["body.args.foo2", "bar21"]
|
||||
-
|
||||
name: post raw text
|
||||
variables:
|
||||
foo1: "bar12"
|
||||
foo3: "bar32"
|
||||
request:
|
||||
method: POST
|
||||
url: /post
|
||||
headers:
|
||||
User-Agent: HttpRunner/${get_httprunner_version()}
|
||||
Content-Type: "text/plain"
|
||||
data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3."
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."]
|
||||
-
|
||||
name: post form data
|
||||
variables:
|
||||
foo2: bar23
|
||||
request:
|
||||
method: POST
|
||||
url: /post
|
||||
headers:
|
||||
User-Agent: HttpRunner/${get_httprunner_version()}
|
||||
Content-Type: "application/x-www-form-urlencoded"
|
||||
data: "foo1=$foo1&foo2=$foo2&foo3=$foo3"
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["body.form.foo1", "$expect_foo1"]
|
||||
- eq: ["body.form.foo2", "bar23"]
|
||||
- eq: ["body.form.foo3", "bar21"]
|
||||
15
examples/demo-with-py-plugin/.gitignore
vendored
Normal file
15
examples/demo-with-py-plugin/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
.env
|
||||
reports/
|
||||
*.so
|
||||
.vscode/
|
||||
.idea/
|
||||
.DS_Store
|
||||
output/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.python-version
|
||||
logs/
|
||||
|
||||
# plugin
|
||||
debugtalk.bin
|
||||
debugtalk.so
|
||||
@@ -1,53 +1,71 @@
|
||||
import logging
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
import funppy
|
||||
|
||||
|
||||
def get_httprunner_version():
|
||||
return "v4.0.0-alpha"
|
||||
|
||||
|
||||
def sleep(n_secs):
|
||||
time.sleep(n_secs)
|
||||
|
||||
|
||||
def sum(*args):
|
||||
result = 0
|
||||
for arg in args:
|
||||
result += arg
|
||||
return result
|
||||
|
||||
|
||||
def sum_ints(*args: List[int]) -> int:
|
||||
result = 0
|
||||
for arg in args:
|
||||
result += arg
|
||||
return result
|
||||
|
||||
|
||||
def sum_two_int(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
|
||||
def sum_two_string(a: str, b: str) -> str:
|
||||
return a + b
|
||||
|
||||
|
||||
def sum_strings(*args: List[str]) -> str:
|
||||
result = ""
|
||||
for arg in args:
|
||||
result += arg
|
||||
return result
|
||||
|
||||
|
||||
def concatenate(*args: List[str]) -> str:
|
||||
result = ""
|
||||
for arg in args:
|
||||
result += str(arg)
|
||||
return result
|
||||
|
||||
|
||||
def setup_hook_example(name):
|
||||
logging.warning("setup_hook_example")
|
||||
return f"setup_hook_example: {name}"
|
||||
|
||||
|
||||
def teardown_hook_example(name):
|
||||
logging.warning("teardown_hook_example")
|
||||
return f"teardown_hook_example: {name}"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
funppy.register("get_httprunner_version", get_httprunner_version)
|
||||
funppy.register("sum", sum)
|
||||
funppy.register("sum_ints", sum_ints)
|
||||
funppy.register("concatenate", concatenate)
|
||||
funppy.register("sum_two_int", sum_two_int)
|
||||
funppy.register("sum_two", sum_two_int)
|
||||
funppy.register("sum_two_string", sum_two_string)
|
||||
funppy.register("sum_strings", sum_strings)
|
||||
funppy.register("setup_hook_example", setup_hook_example)
|
||||
33
examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml
Normal file
33
examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
config:
|
||||
name: "request methods testcase: reference testcase"
|
||||
variables:
|
||||
foo1: testsuite_config_bar1
|
||||
expect_foo1: testsuite_config_bar1
|
||||
expect_foo2: config_bar2
|
||||
base_url: "https://postman-echo.com"
|
||||
verify: False
|
||||
|
||||
teststeps:
|
||||
-
|
||||
name: request with functions
|
||||
variables:
|
||||
foo1: testcase_ref_bar1
|
||||
expect_foo1: testcase_ref_bar1
|
||||
testcase: testcases/demo_requests.yml
|
||||
export:
|
||||
- foo3
|
||||
-
|
||||
name: post form data
|
||||
variables:
|
||||
foo1: bar1
|
||||
request:
|
||||
method: POST
|
||||
url: /post
|
||||
headers:
|
||||
User-Agent: HttpRunner/${get_httprunner_version()}
|
||||
Content-Type: "application/x-www-form-urlencoded"
|
||||
data: "foo1=$foo1&foo2=$foo3"
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["body.form.foo1", "bar1"]
|
||||
- eq: ["body.form.foo2", "bar21"]
|
||||
65
examples/demo-with-py-plugin/testcases/demo_requests.yml
Normal file
65
examples/demo-with-py-plugin/testcases/demo_requests.yml
Normal file
@@ -0,0 +1,65 @@
|
||||
config:
|
||||
name: "request methods testcase with functions"
|
||||
variables:
|
||||
foo1: config_bar1
|
||||
foo2: config_bar2
|
||||
expect_foo1: config_bar1
|
||||
expect_foo2: config_bar2
|
||||
base_url: "https://postman-echo.com"
|
||||
verify: False
|
||||
export: ["foo3"]
|
||||
|
||||
teststeps:
|
||||
-
|
||||
name: get with params
|
||||
variables:
|
||||
foo1: bar11
|
||||
foo2: bar21
|
||||
sum_v: "${sum_two(1, 2)}"
|
||||
request:
|
||||
method: GET
|
||||
url: /get
|
||||
params:
|
||||
foo1: $foo1
|
||||
foo2: $foo2
|
||||
sum_v: $sum_v
|
||||
headers:
|
||||
User-Agent: HttpRunner/${get_httprunner_version()}
|
||||
extract:
|
||||
foo3: "body.args.foo2"
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["body.args.foo1", "bar11"]
|
||||
- eq: ["body.args.sum_v", "3"]
|
||||
- eq: ["body.args.foo2", "bar21"]
|
||||
-
|
||||
name: post raw text
|
||||
variables:
|
||||
foo1: "bar12"
|
||||
foo3: "bar32"
|
||||
request:
|
||||
method: POST
|
||||
url: /post
|
||||
headers:
|
||||
User-Agent: HttpRunner/${get_httprunner_version()}
|
||||
Content-Type: "text/plain"
|
||||
data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3."
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."]
|
||||
-
|
||||
name: post form data
|
||||
variables:
|
||||
foo2: bar23
|
||||
request:
|
||||
method: POST
|
||||
url: /post
|
||||
headers:
|
||||
User-Agent: HttpRunner/${get_httprunner_version()}
|
||||
Content-Type: "application/x-www-form-urlencoded"
|
||||
data: "foo1=$foo1&foo2=$foo2&foo3=$foo3"
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["body.form.foo1", "$expect_foo1"]
|
||||
- eq: ["body.form.foo2", "bar23"]
|
||||
- eq: ["body.form.foo3", "bar21"]
|
||||
176
examples/demo-with-py-plugin/testcases/demo_with_funplugin.json
Normal file
176
examples/demo-with-py-plugin/testcases/demo_with_funplugin.json
Normal file
@@ -0,0 +1,176 @@
|
||||
{
|
||||
"config": {
|
||||
"name": "demo with complex mechanisms",
|
||||
"base_url": "https://postman-echo.com",
|
||||
"variables": {
|
||||
"a": "${sum(10, 2.3)}",
|
||||
"b": 3.45,
|
||||
"n": "${sum_ints(1, 2, 2)}",
|
||||
"varFoo1": "${gen_random_string($n)}",
|
||||
"varFoo2": "${max($a, $b)}"
|
||||
}
|
||||
},
|
||||
"teststeps": [
|
||||
{
|
||||
"name": "transaction 1 start",
|
||||
"transaction": {
|
||||
"name": "tran1",
|
||||
"type": "start"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get with params",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/get",
|
||||
"params": {
|
||||
"foo1": "$varFoo1",
|
||||
"foo2": "$varFoo2"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "HttpRunnerPlus"
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"b": 34.5,
|
||||
"n": 3,
|
||||
"name": "get with params",
|
||||
"varFoo2": "${max($a, $b)}"
|
||||
},
|
||||
"setup_hooks": [
|
||||
"${setup_hook_example($name)}"
|
||||
],
|
||||
"teardown_hooks": [
|
||||
"${teardown_hook_example($name)}"
|
||||
],
|
||||
"extract": {
|
||||
"varFoo1": "body.args.foo1"
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "status_code",
|
||||
"assert": "equals",
|
||||
"expect": 200,
|
||||
"msg": "check response status code"
|
||||
},
|
||||
{
|
||||
"check": "headers.\"Content-Type\"",
|
||||
"assert": "startswith",
|
||||
"expect": "application/json"
|
||||
},
|
||||
{
|
||||
"check": "body.args.foo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "$varFoo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "body.args.foo2",
|
||||
"assert": "equals",
|
||||
"expect": "34.5",
|
||||
"msg": "check args foo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "transaction 1 end",
|
||||
"transaction": {
|
||||
"name": "tran1",
|
||||
"type": "end"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "post json data",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "/post",
|
||||
"body": {
|
||||
"foo1": "$varFoo1",
|
||||
"foo2": "${max($a, $b)}"
|
||||
}
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "status_code",
|
||||
"assert": "equals",
|
||||
"expect": 200,
|
||||
"msg": "check status code"
|
||||
},
|
||||
{
|
||||
"check": "body.json.foo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "body.json.foo2",
|
||||
"assert": "equals",
|
||||
"expect": 12.3,
|
||||
"msg": "check args foo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "post form data",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "/post",
|
||||
"headers": {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
|
||||
},
|
||||
"body": {
|
||||
"foo1": "$varFoo1",
|
||||
"foo2": "${max($a, $b)}",
|
||||
"time": "${get_timestamp()}"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"varTime": "body.form.time"
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "status_code",
|
||||
"assert": "equals",
|
||||
"expect": 200,
|
||||
"msg": "check status code"
|
||||
},
|
||||
{
|
||||
"check": "body.form.foo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "body.form.foo2",
|
||||
"assert": "equals",
|
||||
"expect": "12.3",
|
||||
"msg": "check args foo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "get with timestamp",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/get",
|
||||
"params": {
|
||||
"time": "$varTime"
|
||||
}
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "body.args.time",
|
||||
"assert": "length_equals",
|
||||
"expect": 13,
|
||||
"msg": "check extracted var timestamp"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
15
examples/demo-without-plugin/.gitignore
vendored
Normal file
15
examples/demo-without-plugin/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
.env
|
||||
reports/
|
||||
*.so
|
||||
.vscode/
|
||||
.idea/
|
||||
.DS_Store
|
||||
output/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.python-version
|
||||
logs/
|
||||
|
||||
# plugin
|
||||
debugtalk.bin
|
||||
debugtalk.so
|
||||
0
examples/demo-without-plugin/har/.keep
Normal file
0
examples/demo-without-plugin/har/.keep
Normal file
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"config": {
|
||||
"name": "demo without custom function plugin",
|
||||
"base_url": "https://postman-echo.com",
|
||||
"variables": {
|
||||
"a": 12.3,
|
||||
"b": 3.45,
|
||||
"n": 5,
|
||||
"varFoo1": "${gen_random_string($n)}",
|
||||
"varFoo2": "${max($a, $b)}"
|
||||
}
|
||||
},
|
||||
"teststeps": [
|
||||
{
|
||||
"name": "transaction 1 start",
|
||||
"transaction": {
|
||||
"name": "tran1",
|
||||
"type": "start"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get with params",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/get",
|
||||
"params": {
|
||||
"foo1": "$varFoo1",
|
||||
"foo2": "$varFoo2"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "HttpRunnerPlus"
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"b": 34.5,
|
||||
"n": 3,
|
||||
"name": "get with params",
|
||||
"varFoo2": "${max($a, $b)}"
|
||||
},
|
||||
"extract": {
|
||||
"varFoo1": "body.args.foo1"
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "status_code",
|
||||
"assert": "equals",
|
||||
"expect": 200,
|
||||
"msg": "check response status code"
|
||||
},
|
||||
{
|
||||
"check": "headers.\"Content-Type\"",
|
||||
"assert": "startswith",
|
||||
"expect": "application/json"
|
||||
},
|
||||
{
|
||||
"check": "body.args.foo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "$varFoo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "body.args.foo2",
|
||||
"assert": "equals",
|
||||
"expect": "34.5",
|
||||
"msg": "check args foo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "transaction 1 end",
|
||||
"transaction": {
|
||||
"name": "tran1",
|
||||
"type": "end"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "post json data",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "/post",
|
||||
"body": {
|
||||
"foo1": "$varFoo1",
|
||||
"foo2": "${max($a, $b)}"
|
||||
}
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "status_code",
|
||||
"assert": "equals",
|
||||
"expect": 200,
|
||||
"msg": "check status code"
|
||||
},
|
||||
{
|
||||
"check": "body.json.foo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "body.json.foo2",
|
||||
"assert": "equals",
|
||||
"expect": 12.3,
|
||||
"msg": "check args foo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "post form data",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "/post",
|
||||
"headers": {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
|
||||
},
|
||||
"body": {
|
||||
"foo1": "$varFoo1",
|
||||
"foo2": "${max($a, $b)}",
|
||||
"time": "${get_timestamp()}"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"varTime": "body.form.time"
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "status_code",
|
||||
"assert": "equals",
|
||||
"expect": 200,
|
||||
"msg": "check status code"
|
||||
},
|
||||
{
|
||||
"check": "body.form.foo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "body.form.foo2",
|
||||
"assert": "equals",
|
||||
"expect": "12.3",
|
||||
"msg": "check args foo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "get with timestamp",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/get",
|
||||
"params": {
|
||||
"time": "$varTime"
|
||||
}
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "body.args.time",
|
||||
"assert": "length_equals",
|
||||
"expect": 13,
|
||||
"msg": "check extracted var timestamp"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/httprunner/httprunner/hrp"
|
||||
)
|
||||
|
||||
// generated by examples/hrp/har/demo.har using HttpRunner v3.1.6
|
||||
var (
|
||||
demoHttpRunnerJSONPath hrp.TestCasePath = "demo_httprunner.json"
|
||||
demoHttpRunnerYAMLPath hrp.TestCasePath = "demo_httprunner.yaml"
|
||||
)
|
||||
|
||||
func TestCompatTestCase(t *testing.T) {
|
||||
err := hrp.NewRunner(t).Run(&demoHttpRunnerJSONPath)
|
||||
if err != nil {
|
||||
t.Fatalf("run testcase error: %v", err)
|
||||
}
|
||||
|
||||
err = hrp.NewRunner(t).Run(&demoHttpRunnerYAMLPath)
|
||||
if err != nil {
|
||||
t.Fatalf("run testcase error: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"name": "testcase description",
|
||||
"variables": {},
|
||||
"verify": false
|
||||
},
|
||||
"teststeps": [
|
||||
{
|
||||
"name": "/get",
|
||||
"request": {
|
||||
"url": "https://postman-echo.com/get",
|
||||
"params": {
|
||||
"foo1": "HDnY8",
|
||||
"foo2": "34.5"
|
||||
},
|
||||
"method": "GET",
|
||||
"headers": {
|
||||
"Host": "postman-echo.com",
|
||||
"User-Agent": "HttpRunnerPlus",
|
||||
"Accept-Encoding": "gzip"
|
||||
}
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"eq": [
|
||||
"status_code",
|
||||
200
|
||||
]
|
||||
},
|
||||
{
|
||||
"eq": [
|
||||
"headers.Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"eq": [
|
||||
"body.url",
|
||||
"https://postman-echo.com/get?foo1=HDnY8&foo2=34.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "/post",
|
||||
"request": {
|
||||
"url": "https://postman-echo.com/post",
|
||||
"method": "POST",
|
||||
"cookies": {
|
||||
"sails.sid": "s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk"
|
||||
},
|
||||
"headers": {
|
||||
"Host": "postman-echo.com",
|
||||
"User-Agent": "Go-http-client/1.1",
|
||||
"Content-Length": "28",
|
||||
"Content-Type": "application/json; charset=UTF-8",
|
||||
"Cookie": "sails.sid=s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk",
|
||||
"Accept-Encoding": "gzip"
|
||||
},
|
||||
"json": {
|
||||
"foo1": "HDnY8",
|
||||
"foo2": 12.3
|
||||
}
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"eq": [
|
||||
"status_code",
|
||||
200
|
||||
]
|
||||
},
|
||||
{
|
||||
"eq": [
|
||||
"headers.Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"eq": [
|
||||
"body.url",
|
||||
"https://postman-echo.com/post"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "/post",
|
||||
"request": {
|
||||
"url": "https://postman-echo.com/post",
|
||||
"method": "POST",
|
||||
"cookies": {
|
||||
"sails.sid": "s%3AS5e7w0zQ0xAsCwh9L8T6R7QLYCO7_gtD.r8%2B2w9IWqEIfuVkrZjnxzm2xADIk34zKAWXRPapr%2FAw"
|
||||
},
|
||||
"headers": {
|
||||
"Host": "postman-echo.com",
|
||||
"User-Agent": "Go-http-client/1.1",
|
||||
"Content-Length": "20",
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"Cookie": "sails.sid=s%3AS5e7w0zQ0xAsCwh9L8T6R7QLYCO7_gtD.r8%2B2w9IWqEIfuVkrZjnxzm2xADIk34zKAWXRPapr%2FAw",
|
||||
"Accept-Encoding": "gzip"
|
||||
},
|
||||
"data": {
|
||||
"foo1": "HDnY8",
|
||||
"foo2": "12.3"
|
||||
}
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"eq": [
|
||||
"status_code",
|
||||
200
|
||||
]
|
||||
},
|
||||
{
|
||||
"eq": [
|
||||
"headers.Content-Type",
|
||||
"application/json; charset=utf-8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"eq": [
|
||||
"body.data",
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"eq": [
|
||||
"body.url",
|
||||
"https://postman-echo.com/post"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
config:
|
||||
name: testcase description
|
||||
variables: {}
|
||||
verify: false
|
||||
teststeps:
|
||||
- name: /get
|
||||
request:
|
||||
headers:
|
||||
Accept-Encoding: gzip
|
||||
Host: postman-echo.com
|
||||
User-Agent: HttpRunnerPlus
|
||||
method: GET
|
||||
params:
|
||||
foo1: HDnY8
|
||||
foo2: '34.5'
|
||||
url: https://postman-echo.com/get
|
||||
validate:
|
||||
- eq:
|
||||
- status_code
|
||||
- 200
|
||||
- eq:
|
||||
- headers.Content-Type
|
||||
- application/json; charset=utf-8
|
||||
- eq:
|
||||
- body.url
|
||||
- https://postman-echo.com/get?foo1=HDnY8&foo2=34.5
|
||||
- name: /post
|
||||
request:
|
||||
cookies:
|
||||
sails.sid: s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk
|
||||
headers:
|
||||
Accept-Encoding: gzip
|
||||
Content-Length: '28'
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
Cookie: sails.sid=s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk
|
||||
Host: postman-echo.com
|
||||
User-Agent: Go-http-client/1.1
|
||||
json:
|
||||
foo1: HDnY8
|
||||
foo2: 12.3
|
||||
method: POST
|
||||
url: https://postman-echo.com/post
|
||||
validate:
|
||||
- eq:
|
||||
- status_code
|
||||
- 200
|
||||
- eq:
|
||||
- headers.Content-Type
|
||||
- application/json; charset=utf-8
|
||||
- eq:
|
||||
- body.url
|
||||
- https://postman-echo.com/post
|
||||
- name: /post
|
||||
request:
|
||||
cookies:
|
||||
sails.sid: s%3AS5e7w0zQ0xAsCwh9L8T6R7QLYCO7_gtD.r8%2B2w9IWqEIfuVkrZjnxzm2xADIk34zKAWXRPapr%2FAw
|
||||
data:
|
||||
foo1: HDnY8
|
||||
foo2: '12.3'
|
||||
headers:
|
||||
Accept-Encoding: gzip
|
||||
Content-Length: '20'
|
||||
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
|
||||
Cookie: sails.sid=s%3AS5e7w0zQ0xAsCwh9L8T6R7QLYCO7_gtD.r8%2B2w9IWqEIfuVkrZjnxzm2xADIk34zKAWXRPapr%2FAw
|
||||
Host: postman-echo.com
|
||||
User-Agent: Go-http-client/1.1
|
||||
method: POST
|
||||
url: https://postman-echo.com/post
|
||||
validate:
|
||||
- eq:
|
||||
- status_code
|
||||
- 200
|
||||
- eq:
|
||||
- headers.Content-Type
|
||||
- application/json; charset=utf-8
|
||||
- eq:
|
||||
- body.data
|
||||
- ''
|
||||
- eq:
|
||||
- body.url
|
||||
- https://postman-echo.com/post
|
||||
@@ -1,63 +0,0 @@
|
||||
# NOTE: Generated By HttpRunner v3.1.6
|
||||
# FROM: hrp/examples/demo.json
|
||||
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
|
||||
|
||||
class TestCaseDemo(HttpRunner):
|
||||
|
||||
config = (
|
||||
Config("demo with complex mechanisms")
|
||||
.variables(
|
||||
**{
|
||||
"a": 12.3,
|
||||
"b": 3.45,
|
||||
"n": 5,
|
||||
"varFoo1": "${gen_random_string($n)}",
|
||||
"varFoo2": "${max($a, $b)}",
|
||||
}
|
||||
)
|
||||
.base_url("https://postman-echo.com")
|
||||
)
|
||||
|
||||
teststeps = [
|
||||
Step(
|
||||
RunRequest("get with params")
|
||||
.with_variables(**{"b": 34.5, "n": 3, "varFoo2": "${max($a, $b)}"})
|
||||
.get("/get")
|
||||
.with_params(**{"foo1": "$varFoo1", "foo2": "$varFoo2"})
|
||||
.with_headers(**{"User-Agent": "HttpRunnerPlus"})
|
||||
.extract()
|
||||
.with_jmespath("body.args.foo1", "varFoo1")
|
||||
.validate()
|
||||
.assert_equal("status_code", 200)
|
||||
.assert_equal('headers."Content-Type"', "application/json")
|
||||
.assert_equal("body.args.foo1", 5)
|
||||
.assert_equal("$varFoo1", 5)
|
||||
.assert_equal("body.args.foo2", "34.5")
|
||||
),
|
||||
Step(
|
||||
RunRequest("post json data")
|
||||
.post("/post")
|
||||
.validate()
|
||||
.assert_equal("status_code", 200)
|
||||
.assert_equal("body.json.foo1", 5)
|
||||
.assert_equal("body.json.foo2", 12.3)
|
||||
),
|
||||
Step(
|
||||
RunRequest("post form data")
|
||||
.post("/post")
|
||||
.with_headers(
|
||||
**{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
|
||||
)
|
||||
.validate()
|
||||
.assert_equal("status_code", 200)
|
||||
.assert_equal("body.form.foo1", 5)
|
||||
.assert_equal("body.form.foo2", "12.3")
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
TestCaseDemo().test_start()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"name": "testcase description",
|
||||
"variables": {},
|
||||
"verify": false
|
||||
},
|
||||
"teststeps": [
|
||||
{
|
||||
"name": "/get",
|
||||
"request": {
|
||||
"url": "http://httpbin.org/get",
|
||||
"method": "GET",
|
||||
"headers": {
|
||||
"Host": "httpbin.org",
|
||||
"Connection": "keep-alive",
|
||||
"accept": "application/json",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.50",
|
||||
"Referer": "http://httpbin.org/",
|
||||
"Accept-Encoding": "gzip, deflate",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"
|
||||
}
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "status_code",
|
||||
"assert": "equals",
|
||||
"expect": 200,
|
||||
"msg": "assert response status code"
|
||||
},
|
||||
{
|
||||
"check": "headers.\"Content-Type\"",
|
||||
"assert": "equals",
|
||||
"expect": "application/json",
|
||||
"msg": "assert response header Content-Type"
|
||||
},
|
||||
{
|
||||
"check": "body.origin",
|
||||
"assert": "equals",
|
||||
"expect": "117.176.133.109",
|
||||
"msg": "assert response body origin"
|
||||
},
|
||||
{
|
||||
"check": "body.url",
|
||||
"assert": "equals",
|
||||
"expect": "http://httpbin.org/get",
|
||||
"msg": "assert response body url"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/httprunner/funplugin/fungo"
|
||||
)
|
||||
|
||||
// register functions and build to plugin binary
|
||||
func main() {
|
||||
fungo.Register("sum_ints", SumInts)
|
||||
fungo.Register("sum_two_int", SumTwoInt)
|
||||
fungo.Register("sum", Sum)
|
||||
fungo.Register("sum_two_string", SumTwoString)
|
||||
fungo.Register("sum_strings", SumStrings)
|
||||
fungo.Register("concatenate", Concatenate)
|
||||
fungo.Register("setup_hook_example", SetupHookExample)
|
||||
fungo.Register("teardown_hook_example", TeardownHookExample)
|
||||
fungo.Serve()
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
config:
|
||||
name: 'api test demo'
|
||||
variables:
|
||||
user_agent: iOS/10.3
|
||||
device_sn: TESTCASE_SETUP_XXX
|
||||
os_platform: ios
|
||||
app_version: 2.8.6
|
||||
base_url: 'https://postman-echo.com'
|
||||
herader:
|
||||
- Accept: '*/*'
|
||||
Accept-Encoding: 'gzip, deflate, br'
|
||||
Cache-Control: no-cache
|
||||
Connection: keep-alive
|
||||
Host: postman-echo.com
|
||||
User-Agent: PostmanRuntime/7.28.4
|
||||
verify: false
|
||||
export:
|
||||
- session_token
|
||||
teststeps:
|
||||
- name: 'test api /get'
|
||||
api: api/get.json
|
||||
variables:
|
||||
user_agent: iOS/10.4
|
||||
device_sn: $device_sn
|
||||
os_platform: ios
|
||||
app_version: 2.8.7
|
||||
extract:
|
||||
session_token: 'body.headers."postman-token"'
|
||||
- name: 'test api /post'
|
||||
api: api/post.json
|
||||
variables:
|
||||
user_agent: iOS/10.5
|
||||
device_sn: $device_sn
|
||||
os_platform: ios
|
||||
app_version: 2.8.9
|
||||
validate:
|
||||
- { eq: [ status_code, 200 ] }
|
||||
- { eq: [ body.headers.postman-token, ea19464c-ddd4-4724-abe9-5e2b254c2723 ] }
|
||||
- name: 'test api /put'
|
||||
api: api/put.json
|
||||
variables:
|
||||
user_agent: iOS/10.6
|
||||
device_sn: $device_sn
|
||||
os_platform: ios
|
||||
app_version: 2.8.10
|
||||
extract:
|
||||
session_token: 'body.headers."postman-token"'
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"name": "reference testcase test",
|
||||
"base_url": "https://postman-echo.com",
|
||||
"variables": {
|
||||
"os_platform": "ios"
|
||||
}
|
||||
},
|
||||
"teststeps": [
|
||||
{
|
||||
"name": "run demo_httprunner.json",
|
||||
"testcase": "demo_httprunner.json",
|
||||
"variables": {
|
||||
"os_platform": "$os_platform"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
config:
|
||||
name: "reference testcase test"
|
||||
base_url: "https://postman-echo.com"
|
||||
variables:
|
||||
os_platform: 'ios'
|
||||
|
||||
teststeps:
|
||||
- name: run demo_httprunner.yaml
|
||||
testcase: demo_httprunner.yaml
|
||||
variables:
|
||||
os_platform: $os_platform
|
||||
@@ -1,40 +0,0 @@
|
||||
config:
|
||||
name: "think time test demo"
|
||||
variables:
|
||||
app_version: v1
|
||||
user_agent: iOS/10.3
|
||||
base_url: "https://postman-echo.com"
|
||||
think_time:
|
||||
strategy: random_percentage
|
||||
setting:
|
||||
min_percentage: 1.0
|
||||
max_percentage: 1.5
|
||||
limit: 4
|
||||
verify: False
|
||||
|
||||
teststeps:
|
||||
- name: get with params
|
||||
request:
|
||||
method: GET
|
||||
url: /get
|
||||
headers:
|
||||
User-Agent: $user_agent,$app_version
|
||||
validate:
|
||||
- check: status_code
|
||||
assert: equals
|
||||
expect: 200
|
||||
msg: check status code
|
||||
- name: think time 1
|
||||
think_time:
|
||||
time: 3
|
||||
- name: post with params
|
||||
request:
|
||||
method: POST
|
||||
url: /post
|
||||
headers:
|
||||
User-Agent: $user_agent,$app_version
|
||||
validate:
|
||||
- check: status_code
|
||||
assert: equals
|
||||
expect: 200
|
||||
msg: check status code
|
||||
2
go.mod
2
go.mod
@@ -7,7 +7,7 @@ require (
|
||||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/getsentry/sentry-go v0.13.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/httprunner/funplugin v0.4.0
|
||||
github.com/httprunner/funplugin v0.4.2
|
||||
github.com/jinzhu/copier v0.3.2
|
||||
github.com/jmespath/go-jmespath v0.4.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
|
||||
4
go.sum
4
go.sum
@@ -240,8 +240,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.0 h1:jSptZ6Ki0Dh3uvpLDbmxE6kSqVv0FHaQnHs0Qt+6SS8=
|
||||
github.com/httprunner/funplugin v0.4.0/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc=
|
||||
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/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=
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
)
|
||||
|
||||
func TestBoomerStandaloneRun(t *testing.T) {
|
||||
buildHashicorpPlugin()
|
||||
defer removeHashicorpPlugin()
|
||||
buildHashicorpGoPlugin()
|
||||
defer removeHashicorpGoPlugin()
|
||||
|
||||
testcase1 := &TestCase{
|
||||
Config: NewConfig("TestCase1").SetBaseURL("http://httpbin.org"),
|
||||
@@ -25,7 +25,7 @@ func TestBoomerStandaloneRun(t *testing.T) {
|
||||
NewStep("TestCase3").CallRefCase(&TestCase{Config: NewConfig("TestCase3")}),
|
||||
},
|
||||
}
|
||||
testcase2 := &demoTestCaseJSONPath
|
||||
testcase2 := &demoTestCaseWithPluginJSONPath
|
||||
|
||||
b := NewBoomer(2, 1)
|
||||
go b.Run(testcase1, testcase2)
|
||||
|
||||
@@ -34,6 +34,12 @@ var har2caseCmd = &cobra.Command{
|
||||
if outputDir != "" {
|
||||
har.SetOutputDir(outputDir)
|
||||
}
|
||||
|
||||
// specify profile
|
||||
if profilePath != "" {
|
||||
har.SetProfile(profilePath)
|
||||
}
|
||||
|
||||
// generate json/yaml files
|
||||
if genYAMLFlag {
|
||||
outputPath, err = har.GenYAML()
|
||||
@@ -54,6 +60,7 @@ var (
|
||||
genJSONFlag bool
|
||||
genYAMLFlag bool
|
||||
outputDir string
|
||||
profilePath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -61,4 +68,5 @@ func init() {
|
||||
har2caseCmd.Flags().BoolVarP(&genJSONFlag, "to-json", "j", true, "convert to JSON format")
|
||||
har2caseCmd.Flags().BoolVarP(&genYAMLFlag, "to-yaml", "y", false, "convert to YAML format")
|
||||
har2caseCmd.Flags().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file")
|
||||
har2caseCmd.Flags().StringVarP(&profilePath, "profile", "p", "", "specify profile path to override headers and cookies")
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ var scaffoldCmd = &cobra.Command{
|
||||
} else {
|
||||
pluginType = scaffold.Py // default
|
||||
}
|
||||
|
||||
err := scaffold.CreateScaffold(args[0], pluginType)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("create scaffold project failed")
|
||||
|
||||
111
hrp/convert.go
111
hrp/convert.go
@@ -1,56 +1,17 @@
|
||||
package hrp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/httprunner/httprunner/hrp/internal/json"
|
||||
"github.com/httprunner/httprunner/hrp/internal/builtin"
|
||||
)
|
||||
|
||||
func loadFromJSON(path string, structObj interface{}) error {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
log.Error().Str("path", path).Err(err).Msg("convert absolute path failed")
|
||||
return err
|
||||
}
|
||||
log.Info().Str("path", path).Msg("load json")
|
||||
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("load json path failed")
|
||||
return err
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(bytes.NewReader(file))
|
||||
decoder.UseNumber()
|
||||
err = decoder.Decode(structObj)
|
||||
return err
|
||||
}
|
||||
|
||||
func loadFromYAML(path string, structObj interface{}) error {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
log.Error().Str("path", path).Err(err).Msg("convert absolute path failed")
|
||||
return err
|
||||
}
|
||||
log.Info().Str("path", path).Msg("load yaml")
|
||||
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("load yaml path failed")
|
||||
return err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(file, structObj)
|
||||
return err
|
||||
}
|
||||
|
||||
func convertCompatValidator(Validators []interface{}) (err error) {
|
||||
for i, iValidator := range Validators {
|
||||
validatorMap := iValidator.(map[string]interface{})
|
||||
@@ -133,9 +94,21 @@ func (tc *TCase) ToTestCase() (*TestCase, error) {
|
||||
testCase := &TestCase{
|
||||
Config: tc.Config,
|
||||
}
|
||||
|
||||
// locate project root dir by plugin path
|
||||
projectRootDir, err := getProjectRootDirPath(testCase.Config.Path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get project root dir")
|
||||
}
|
||||
log.Info().Str("dir", projectRootDir).Msg("located project root dir")
|
||||
|
||||
for _, step := range tc.TestSteps {
|
||||
if step.APIPath != "" {
|
||||
path := filepath.Join(filepath.Dir(testCase.Config.Path), step.APIPath)
|
||||
path := filepath.Join(projectRootDir, step.APIPath)
|
||||
if !builtin.IsFilePathExists(path) {
|
||||
return nil, errors.New("referenced api file not found: " + path)
|
||||
}
|
||||
|
||||
refAPI := APIPath(path)
|
||||
step.APIContent = &refAPI
|
||||
apiContent, err := step.APIContent.ToAPI()
|
||||
@@ -147,7 +120,11 @@ func (tc *TCase) ToTestCase() (*TestCase, error) {
|
||||
step: step,
|
||||
})
|
||||
} else if step.TestCasePath != "" {
|
||||
path := filepath.Join(filepath.Dir(testCase.Config.Path), step.TestCasePath)
|
||||
path := filepath.Join(projectRootDir, step.TestCasePath)
|
||||
if !builtin.IsFilePathExists(path) {
|
||||
return nil, errors.New("referenced testcase file not found: " + path)
|
||||
}
|
||||
|
||||
refTestCase := TestCasePath(path)
|
||||
step.TestCaseContent = &refTestCase
|
||||
tc, err := step.TestCaseContent.ToTestCase()
|
||||
@@ -181,29 +158,30 @@ func (tc *TCase) ToTestCase() (*TestCase, error) {
|
||||
return testCase, nil
|
||||
}
|
||||
|
||||
var ErrUnsupportedFileExt = fmt.Errorf("unsupported testcase file extension")
|
||||
func getProjectRootDirPath(path string) (rootDir string, err error) {
|
||||
pluginPath, err := locatePlugin(path)
|
||||
if err == nil {
|
||||
rootDir = filepath.Dir(pluginPath)
|
||||
return
|
||||
}
|
||||
|
||||
// failed to locate project root dir
|
||||
// maybe project plugin debugtalk.xx is not exist
|
||||
// use current dir instead
|
||||
return os.Getwd()
|
||||
}
|
||||
|
||||
// APIPath implements IAPI interface.
|
||||
type APIPath string
|
||||
|
||||
func (path *APIPath) ToString() string {
|
||||
func (path *APIPath) GetPath() string {
|
||||
return fmt.Sprintf("%v", *path)
|
||||
}
|
||||
|
||||
func (path *APIPath) ToAPI() (*API, error) {
|
||||
api := &API{}
|
||||
var err error
|
||||
|
||||
apiPath := path.ToString()
|
||||
ext := filepath.Ext(apiPath)
|
||||
switch ext {
|
||||
case ".json":
|
||||
err = loadFromJSON(apiPath, api)
|
||||
case ".yaml", ".yml":
|
||||
err = loadFromYAML(apiPath, api)
|
||||
default:
|
||||
err = ErrUnsupportedFileExt
|
||||
}
|
||||
apiPath := path.GetPath()
|
||||
err := builtin.LoadFile(apiPath, api)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -214,32 +192,23 @@ func (path *APIPath) ToAPI() (*API, error) {
|
||||
// TestCasePath implements ITestCase interface.
|
||||
type TestCasePath string
|
||||
|
||||
func (path *TestCasePath) ToString() string {
|
||||
func (path *TestCasePath) GetPath() string {
|
||||
return fmt.Sprintf("%v", *path)
|
||||
}
|
||||
|
||||
func (path *TestCasePath) ToTestCase() (*TestCase, error) {
|
||||
tc := &TCase{}
|
||||
var err error
|
||||
|
||||
casePath := path.ToString()
|
||||
ext := filepath.Ext(casePath)
|
||||
switch ext {
|
||||
case ".json":
|
||||
err = loadFromJSON(casePath, tc)
|
||||
case ".yaml", ".yml":
|
||||
err = loadFromYAML(casePath, tc)
|
||||
default:
|
||||
err = ErrUnsupportedFileExt
|
||||
}
|
||||
casePath := path.GetPath()
|
||||
err := builtin.LoadFile(casePath, tc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = convertCompatTestCase(tc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tc.Config.Path = path.ToString()
|
||||
tc.Config.Path = casePath
|
||||
testcase, err := tc.ToTestCase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -4,25 +4,184 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/httprunner/httprunner/hrp/internal/builtin"
|
||||
)
|
||||
|
||||
const (
|
||||
templatesDir = "internal/scaffold/templates/"
|
||||
hrpExamplesDir = "../examples/hrp"
|
||||
)
|
||||
|
||||
var (
|
||||
demoTestCaseJSONPath TestCasePath = "../examples/hrp/demo.json"
|
||||
demoTestCaseYAMLPath TestCasePath = "../examples/hrp/demo.yaml"
|
||||
demoRefAPIYAMLPath TestCasePath = "../examples/hrp/ref_api_test.yaml"
|
||||
demoRefTestCaseJSONPath TestCasePath = "../examples/hrp/ref_testcase_test.json"
|
||||
demoThinkTimeJsonPath TestCasePath = "../examples/hrp/think_time_test.json"
|
||||
demoAPIYAMLPath APIPath = "../examples/hrp/api/put.yml"
|
||||
demoTestCaseWithPluginJSONPath TestCasePath = templatesDir + "testcases/demo_with_funplugin.json"
|
||||
demoTestCaseWithPluginYAMLPath TestCasePath = templatesDir + "testcases/demo_with_funplugin.yaml"
|
||||
demoTestCaseWithoutPluginJSONPath TestCasePath = templatesDir + "testcases/demo_without_funplugin.json"
|
||||
demoTestCaseWithoutPluginYAMLPath TestCasePath = templatesDir + "testcases/demo_without_funplugin.yaml"
|
||||
demoTestCaseWithRefAPIPath TestCasePath = templatesDir + "testcases/demo_ref_api.json"
|
||||
demoAPIGETPath APIPath = templatesDir + "/api/get.yml"
|
||||
)
|
||||
|
||||
var (
|
||||
demoTestCaseWithThinkTimePath TestCasePath = hrpExamplesDir + "/think_time_test.json"
|
||||
)
|
||||
|
||||
var demoTestCaseWithPlugin = &TestCase{
|
||||
Config: NewConfig("demo with complex mechanisms").
|
||||
SetBaseURL("https://postman-echo.com").
|
||||
WithVariables(map[string]interface{}{ // global level variables
|
||||
"n": "${sum_ints(1, 2, 2)}",
|
||||
"a": "${sum(10, 2.3)}",
|
||||
"b": 3.45,
|
||||
"varFoo1": "${gen_random_string($n)}",
|
||||
"varFoo2": "${max($a, $b)}", // 12.3; eval with built-in function
|
||||
}),
|
||||
TestSteps: []IStep{
|
||||
NewStep("transaction 1 start").StartTransaction("tran1"), // start transaction
|
||||
NewStep("get with params").
|
||||
WithVariables(map[string]interface{}{ // step level variables
|
||||
"n": 3, // inherit config level variables if not set in step level, a/varFoo1
|
||||
"b": 34.5, // override config level variable if existed, n/b/varFoo2
|
||||
"varFoo2": "${max($a, $b)}", // 34.5; override variable b and eval again
|
||||
"name": "get with params",
|
||||
}).
|
||||
SetupHook("${setup_hook_example($name)}").
|
||||
GET("/get").
|
||||
TeardownHook("${teardown_hook_example($name)}").
|
||||
WithParams(map[string]interface{}{"foo1": "$varFoo1", "foo2": "$varFoo2"}). // request with params
|
||||
WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). // request with headers
|
||||
Extract().
|
||||
WithJmesPath("body.args.foo1", "varFoo1"). // extract variable with jmespath
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check response status code"). // validate response status code
|
||||
AssertStartsWith("headers.\"Content-Type\"", "application/json", ""). // validate response header
|
||||
AssertLengthEqual("body.args.foo1", 5, "check args foo1"). // validate response body with jmespath
|
||||
AssertLengthEqual("$varFoo1", 5, "check args foo1"). // assert with extracted variable from current step
|
||||
AssertEqual("body.args.foo2", "34.5", "check args foo2"), // notice: request params value will be converted to string
|
||||
NewStep("transaction 1 end").EndTransaction("tran1"), // end transaction
|
||||
NewStep("post json data").
|
||||
POST("/post").
|
||||
WithBody(map[string]interface{}{
|
||||
"foo1": "$varFoo1", // reference former extracted variable
|
||||
"foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here
|
||||
}).
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check status code").
|
||||
AssertLengthEqual("body.json.foo1", 5, "check args foo1").
|
||||
AssertEqual("body.json.foo2", 12.3, "check args foo2"),
|
||||
NewStep("post form data").
|
||||
POST("/post").
|
||||
WithHeaders(map[string]string{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}).
|
||||
WithBody(map[string]interface{}{
|
||||
"foo1": "$varFoo1", // reference former extracted variable
|
||||
"foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here
|
||||
"time": "${get_timestamp()}",
|
||||
}).
|
||||
Extract().
|
||||
WithJmesPath("body.form.time", "varTime").
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check status code").
|
||||
AssertLengthEqual("body.form.foo1", 5, "check args foo1").
|
||||
AssertEqual("body.form.foo2", "12.3", "check args foo2"), // form data will be converted to string
|
||||
NewStep("get with timestamp").
|
||||
GET("/get").WithParams(map[string]interface{}{"time": "$varTime"}).
|
||||
Validate().
|
||||
AssertLengthEqual("body.args.time", 13, "check extracted var timestamp"),
|
||||
},
|
||||
}
|
||||
|
||||
var demoTestCaseWithoutPlugin = &TestCase{
|
||||
Config: NewConfig("demo without custom function plugin").
|
||||
SetBaseURL("https://postman-echo.com").
|
||||
WithVariables(map[string]interface{}{ // global level variables
|
||||
"n": 5,
|
||||
"a": 12.3,
|
||||
"b": 3.45,
|
||||
"varFoo1": "${gen_random_string($n)}",
|
||||
"varFoo2": "${max($a, $b)}", // 12.3; eval with built-in function
|
||||
}),
|
||||
TestSteps: []IStep{
|
||||
NewStep("transaction 1 start").StartTransaction("tran1"), // start transaction
|
||||
NewStep("get with params").
|
||||
WithVariables(map[string]interface{}{ // step level variables
|
||||
"n": 3, // inherit config level variables if not set in step level, a/varFoo1
|
||||
"b": 34.5, // override config level variable if existed, n/b/varFoo2
|
||||
"varFoo2": "${max($a, $b)}", // 34.5; override variable b and eval again
|
||||
"name": "get with params",
|
||||
}).
|
||||
GET("/get").
|
||||
WithParams(map[string]interface{}{"foo1": "$varFoo1", "foo2": "$varFoo2"}). // request with params
|
||||
WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). // request with headers
|
||||
Extract().
|
||||
WithJmesPath("body.args.foo1", "varFoo1"). // extract variable with jmespath
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check response status code"). // validate response status code
|
||||
AssertStartsWith("headers.\"Content-Type\"", "application/json", ""). // validate response header
|
||||
AssertLengthEqual("body.args.foo1", 5, "check args foo1"). // validate response body with jmespath
|
||||
AssertLengthEqual("$varFoo1", 5, "check args foo1"). // assert with extracted variable from current step
|
||||
AssertEqual("body.args.foo2", "34.5", "check args foo2"), // notice: request params value will be converted to string
|
||||
NewStep("transaction 1 end").EndTransaction("tran1"), // end transaction
|
||||
NewStep("post json data").
|
||||
POST("/post").
|
||||
WithBody(map[string]interface{}{
|
||||
"foo1": "$varFoo1", // reference former extracted variable
|
||||
"foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here
|
||||
}).
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check status code").
|
||||
AssertLengthEqual("body.json.foo1", 5, "check args foo1").
|
||||
AssertEqual("body.json.foo2", 12.3, "check args foo2"),
|
||||
NewStep("post form data").
|
||||
POST("/post").
|
||||
WithHeaders(map[string]string{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}).
|
||||
WithBody(map[string]interface{}{
|
||||
"foo1": "$varFoo1", // reference former extracted variable
|
||||
"foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here
|
||||
"time": "${get_timestamp()}",
|
||||
}).
|
||||
Extract().
|
||||
WithJmesPath("body.form.time", "varTime").
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check status code").
|
||||
AssertLengthEqual("body.form.foo1", 5, "check args foo1").
|
||||
AssertEqual("body.form.foo2", "12.3", "check args foo2"), // form data will be converted to string
|
||||
NewStep("get with timestamp").
|
||||
GET("/get").WithParams(map[string]interface{}{"time": "$varTime"}).
|
||||
Validate().
|
||||
AssertLengthEqual("body.args.time", 13, "check extracted var timestamp"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestGenDemoTestCase(t *testing.T) {
|
||||
tCase, _ := demoTestCaseWithPlugin.ToTCase()
|
||||
err := builtin.Dump2JSON(tCase, demoTestCaseWithPluginJSONPath.GetPath())
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
err = builtin.Dump2YAML(tCase, demoTestCaseWithPluginYAMLPath.GetPath())
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
tCase, _ = demoTestCaseWithoutPlugin.ToTCase()
|
||||
err = builtin.Dump2JSON(tCase, demoTestCaseWithoutPluginJSONPath.GetPath())
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
err = builtin.Dump2YAML(tCase, demoTestCaseWithoutPluginYAMLPath.GetPath())
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadCase(t *testing.T) {
|
||||
tcJSON := &TCase{}
|
||||
tcYAML := &TCase{}
|
||||
err := loadFromJSON(demoTestCaseJSONPath.ToString(), tcJSON)
|
||||
err := builtin.LoadFile(demoTestCaseWithPluginJSONPath.GetPath(), tcJSON)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
err = loadFromYAML(demoTestCaseYAMLPath.ToString(), tcYAML)
|
||||
err = builtin.LoadFile(demoTestCaseWithPluginYAMLPath.GetPath(), tcYAML)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
@@ -41,7 +200,7 @@ func TestLoadCase(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_convertCheckExpr(t *testing.T) {
|
||||
func TestConvertCheckExpr(t *testing.T) {
|
||||
exprs := []struct {
|
||||
before string
|
||||
after string
|
||||
|
||||
@@ -1,26 +1,11 @@
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/csv"
|
||||
"encoding/hex"
|
||||
builtinJSON "encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/httprunner/httprunner/hrp/internal/json"
|
||||
)
|
||||
|
||||
var Functions = map[string]interface{}{
|
||||
@@ -61,200 +46,3 @@ func MD5(str string) string {
|
||||
hasher.Write([]byte(str))
|
||||
return hex.EncodeToString(hasher.Sum(nil))
|
||||
}
|
||||
|
||||
func loadFromCSV(path string) []map[string]interface{} {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
log.Error().Str("path", path).Err(err).Msg("convert absolute path failed")
|
||||
panic(err)
|
||||
}
|
||||
log.Info().Str("path", path).Msg("load csv file")
|
||||
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("load csv file failed")
|
||||
panic(err)
|
||||
}
|
||||
r := csv.NewReader(strings.NewReader(string(file)))
|
||||
content, err := r.ReadAll()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("parse csv file failed")
|
||||
panic(err)
|
||||
}
|
||||
var result []map[string]interface{}
|
||||
for i := 1; i < len(content); i++ {
|
||||
row := make(map[string]interface{})
|
||||
for j := 0; j < len(content[i]); j++ {
|
||||
row[content[0][j]] = content[i][j]
|
||||
}
|
||||
result = append(result, row)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func Dump2JSON(data interface{}, path string) error {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("convert absolute path failed")
|
||||
return err
|
||||
}
|
||||
log.Info().Str("path", path).Msg("dump data to json")
|
||||
file, _ := json.MarshalIndent(data, "", " ")
|
||||
err = os.WriteFile(path, file, 0644)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("dump json path failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Dump2YAML(data interface{}, path string) error {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("convert absolute path failed")
|
||||
return err
|
||||
}
|
||||
log.Info().Str("path", path).Msg("dump data to yaml")
|
||||
|
||||
// init yaml encoder
|
||||
buffer := new(bytes.Buffer)
|
||||
encoder := yaml.NewEncoder(buffer)
|
||||
encoder.SetIndent(4)
|
||||
|
||||
// encode
|
||||
err = encoder.Encode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(path, buffer.Bytes(), 0644)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("dump yaml path failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FormatResponse(raw interface{}) interface{} {
|
||||
formattedResponse := make(map[string]interface{})
|
||||
for key, value := range raw.(map[string]interface{}) {
|
||||
// convert value to json
|
||||
if key == "body" {
|
||||
b, _ := json.MarshalIndent(&value, "", " ")
|
||||
value = string(b)
|
||||
}
|
||||
formattedResponse[key] = value
|
||||
}
|
||||
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))
|
||||
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 err
|
||||
}
|
||||
|
||||
func CreateFolder(folderPath string) error {
|
||||
log.Info().Str("path", folderPath).Msg("create folder")
|
||||
err := os.MkdirAll(folderPath, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("create folder failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateFile(filePath string, data string) error {
|
||||
log.Info().Str("path", filePath).Msg("create file")
|
||||
err := os.WriteFile(filePath, []byte(data), 0o644)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("create file failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isFilePathExists returns true if path exists, whether path is file or dir
|
||||
func isPathExists(path string) bool {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isFilePathExists returns true if path exists and path is file
|
||||
func isFilePathExists(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
// path not exists
|
||||
return false
|
||||
}
|
||||
|
||||
// path exists
|
||||
if info.IsDir() {
|
||||
// path is dir, not file
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func EnsureFolderExists(folderPath string) error {
|
||||
if !isPathExists(folderPath) {
|
||||
err := CreateFolder(folderPath)
|
||||
return err
|
||||
} else if isFilePathExists(folderPath) {
|
||||
return fmt.Errorf("path %v should be directory", folderPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetRandomNumber(min, max int) int {
|
||||
if min > max {
|
||||
return 0
|
||||
}
|
||||
r := rand.Intn(max - min + 1)
|
||||
return min + r
|
||||
}
|
||||
|
||||
func Interface2Float64(i interface{}) (float64, error) {
|
||||
switch i.(type) {
|
||||
case int:
|
||||
return float64(i.(int)), nil
|
||||
case int32:
|
||||
return float64(i.(int32)), nil
|
||||
case int64:
|
||||
return float64(i.(int64)), nil
|
||||
case float32:
|
||||
return float64(i.(float32)), nil
|
||||
case float64:
|
||||
return i.(float64), nil
|
||||
case string:
|
||||
intVar, err := strconv.Atoi(i.(string))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return float64(intVar), err
|
||||
}
|
||||
// json.Number
|
||||
value, ok := i.(builtinJSON.Number)
|
||||
if ok {
|
||||
return value.Float64()
|
||||
}
|
||||
return 0, errors.New("failed to convert interface to float64")
|
||||
}
|
||||
|
||||
252
hrp/internal/builtin/utils.go
Normal file
252
hrp/internal/builtin/utils.go
Normal file
@@ -0,0 +1,252 @@
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
builtinJSON "encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/httprunner/httprunner/hrp/internal/json"
|
||||
)
|
||||
|
||||
func Dump2JSON(data interface{}, path string) error {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("convert absolute path failed")
|
||||
return err
|
||||
}
|
||||
log.Info().Str("path", path).Msg("dump data to json")
|
||||
file, _ := json.MarshalIndent(data, "", " ")
|
||||
err = os.WriteFile(path, file, 0644)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("dump json path failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Dump2YAML(data interface{}, path string) error {
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("convert absolute path failed")
|
||||
return err
|
||||
}
|
||||
log.Info().Str("path", path).Msg("dump data to yaml")
|
||||
|
||||
// init yaml encoder
|
||||
buffer := new(bytes.Buffer)
|
||||
encoder := yaml.NewEncoder(buffer)
|
||||
encoder.SetIndent(4)
|
||||
|
||||
// encode
|
||||
err = encoder.Encode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(path, buffer.Bytes(), 0644)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("dump yaml path failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FormatResponse(raw interface{}) interface{} {
|
||||
formattedResponse := make(map[string]interface{})
|
||||
for key, value := range raw.(map[string]interface{}) {
|
||||
// convert value to json
|
||||
if key == "body" {
|
||||
b, _ := json.MarshalIndent(&value, "", " ")
|
||||
value = string(b)
|
||||
}
|
||||
formattedResponse[key] = value
|
||||
}
|
||||
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))
|
||||
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 err
|
||||
}
|
||||
|
||||
func CreateFolder(folderPath string) error {
|
||||
log.Info().Str("path", folderPath).Msg("create folder")
|
||||
err := os.MkdirAll(folderPath, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("create folder failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateFile(filePath string, data string) error {
|
||||
log.Info().Str("path", filePath).Msg("create file")
|
||||
err := os.WriteFile(filePath, []byte(data), 0o644)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("create file failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsPathExists returns true if path exists, whether path is file or dir
|
||||
func IsPathExists(path string) bool {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsFilePathExists returns true if path exists and path is file
|
||||
func IsFilePathExists(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
// path not exists
|
||||
return false
|
||||
}
|
||||
|
||||
// path exists
|
||||
if info.IsDir() {
|
||||
// path is dir, not file
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func EnsureFolderExists(folderPath string) error {
|
||||
if !IsPathExists(folderPath) {
|
||||
err := CreateFolder(folderPath)
|
||||
return err
|
||||
} else if IsFilePathExists(folderPath) {
|
||||
return fmt.Errorf("path %v should be directory", folderPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetRandomNumber(min, max int) int {
|
||||
if min > max {
|
||||
return 0
|
||||
}
|
||||
r := rand.Intn(max - min + 1)
|
||||
return min + r
|
||||
}
|
||||
|
||||
func Interface2Float64(i interface{}) (float64, error) {
|
||||
switch i.(type) {
|
||||
case int:
|
||||
return float64(i.(int)), nil
|
||||
case int32:
|
||||
return float64(i.(int32)), nil
|
||||
case int64:
|
||||
return float64(i.(int64)), nil
|
||||
case float32:
|
||||
return float64(i.(float32)), nil
|
||||
case float64:
|
||||
return i.(float64), nil
|
||||
case string:
|
||||
intVar, err := strconv.Atoi(i.(string))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return float64(intVar), err
|
||||
}
|
||||
// json.Number
|
||||
value, ok := i.(builtinJSON.Number)
|
||||
if ok {
|
||||
return value.Float64()
|
||||
}
|
||||
return 0, errors.New("failed to convert interface to float64")
|
||||
}
|
||||
|
||||
var ErrUnsupportedFileExt = fmt.Errorf("unsupported file extension")
|
||||
|
||||
// LoadFile loads file content with file extension and assigns to structObj
|
||||
func LoadFile(path string, structObj interface{}) (err error) {
|
||||
log.Info().Str("path", path).Msg("load file")
|
||||
file, err := readFile(path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read file failed")
|
||||
}
|
||||
|
||||
ext := filepath.Ext(path)
|
||||
switch ext {
|
||||
case ".json", ".har":
|
||||
decoder := json.NewDecoder(bytes.NewReader(file))
|
||||
decoder.UseNumber()
|
||||
err = decoder.Decode(structObj)
|
||||
case ".yaml", ".yml":
|
||||
err = yaml.Unmarshal(file, structObj)
|
||||
default:
|
||||
err = ErrUnsupportedFileExt
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func loadFromCSV(path string) []map[string]interface{} {
|
||||
log.Info().Str("path", path).Msg("load csv file")
|
||||
file, err := readFile(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("read csv file failed")
|
||||
panic(err)
|
||||
}
|
||||
|
||||
r := csv.NewReader(strings.NewReader(string(file)))
|
||||
content, err := r.ReadAll()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("parse csv file failed")
|
||||
panic(err)
|
||||
}
|
||||
var result []map[string]interface{}
|
||||
for i := 1; i < len(content); i++ {
|
||||
row := make(map[string]interface{})
|
||||
for j := 0; j < len(content[i]); j++ {
|
||||
row[content[0][j]] = content[i][j]
|
||||
}
|
||||
result = append(result, row)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func readFile(path string) ([]byte, error) {
|
||||
var err error
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("path", path).Msg("convert absolute path failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("read file failed")
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
@@ -3,9 +3,7 @@ package har2case
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -34,10 +32,22 @@ type har struct {
|
||||
path string
|
||||
filterStr string
|
||||
excludeStr string
|
||||
profile map[string]interface{}
|
||||
outputDir string
|
||||
}
|
||||
|
||||
func (h *har) SetProfile(path string) {
|
||||
log.Info().Str("path", path).Msg("set profile")
|
||||
h.profile = make(map[string]interface{})
|
||||
err := builtin.LoadFile(path, h.profile)
|
||||
if err != nil {
|
||||
log.Warn().Str("path", path).
|
||||
Msg("invalid profile format, ignore!")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *har) SetOutputDir(dir string) {
|
||||
log.Info().Str("dir", dir).Msg("set output directory")
|
||||
h.outputDir = dir
|
||||
}
|
||||
|
||||
@@ -93,23 +103,11 @@ func (h *har) makeTestCase() (*hrp.TCase, error) {
|
||||
}
|
||||
|
||||
func (h *har) load() (*Har, error) {
|
||||
fp, err := os.Open(h.path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open: %w", err)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(fp)
|
||||
fp.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read: %w", err)
|
||||
}
|
||||
|
||||
har := &Har{}
|
||||
err = json.Unmarshal(data, har)
|
||||
err := builtin.LoadFile(h.path, har)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("json.Unmarshal error: %w", err)
|
||||
return nil, errors.Wrap(err, "load har failed")
|
||||
}
|
||||
|
||||
return har, nil
|
||||
}
|
||||
|
||||
@@ -147,6 +145,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) {
|
||||
Request: &hrp.Request{},
|
||||
Validators: make([]interface{}, 0),
|
||||
},
|
||||
profile: h.profile,
|
||||
}
|
||||
if err := step.makeRequestMethod(entry); err != nil {
|
||||
return nil, err
|
||||
@@ -174,6 +173,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) {
|
||||
|
||||
type tStep struct {
|
||||
hrp.TStep
|
||||
profile map[string]interface{}
|
||||
}
|
||||
|
||||
func (s *tStep) makeRequestMethod(entry *Entry) error {
|
||||
@@ -182,7 +182,6 @@ func (s *tStep) makeRequestMethod(entry *Entry) error {
|
||||
}
|
||||
|
||||
func (s *tStep) makeRequestURL(entry *Entry) error {
|
||||
|
||||
u, err := url.Parse(entry.Request.URL)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("make request url failed")
|
||||
@@ -202,6 +201,21 @@ func (s *tStep) makeRequestParams(entry *Entry) error {
|
||||
|
||||
func (s *tStep) makeRequestCookies(entry *Entry) error {
|
||||
s.Request.Cookies = make(map[string]string)
|
||||
cookies, ok := s.profile["cookies"]
|
||||
if ok {
|
||||
// use cookies from profile
|
||||
cookies, ok := cookies.(map[string]interface{})
|
||||
if ok {
|
||||
for k, v := range cookies {
|
||||
s.Request.Cookies[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
log.Warn().Interface("cookies", cookies).
|
||||
Msg("cookies from profile is not a map, ignore!")
|
||||
}
|
||||
|
||||
// use cookies from har
|
||||
for _, cookie := range entry.Request.Cookies {
|
||||
s.Request.Cookies[cookie.Name] = cookie.Value
|
||||
}
|
||||
@@ -210,6 +224,21 @@ func (s *tStep) makeRequestCookies(entry *Entry) error {
|
||||
|
||||
func (s *tStep) makeRequestHeaders(entry *Entry) error {
|
||||
s.Request.Headers = make(map[string]string)
|
||||
headers, ok := s.profile["headers"]
|
||||
if ok {
|
||||
// use headers from profile
|
||||
cookies, ok := headers.(map[string]interface{})
|
||||
if ok {
|
||||
for k, v := range cookies {
|
||||
s.Request.Headers[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
log.Warn().Interface("headers", headers).
|
||||
Msg("headers from profile is not a map, ignore!")
|
||||
}
|
||||
|
||||
// use headers from har
|
||||
for _, header := range entry.Request.Headers {
|
||||
if strings.EqualFold(header.Name, "cookie") {
|
||||
continue
|
||||
@@ -230,10 +259,14 @@ func (s *tStep) makeRequestBody(entry *Entry) error {
|
||||
if strings.HasPrefix(mimeType, "application/json") {
|
||||
// post json
|
||||
var body interface{}
|
||||
err := json.Unmarshal([]byte(entry.Request.PostData.Text), &body)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("make request body failed")
|
||||
return err
|
||||
if entry.Request.PostData.Text == "" {
|
||||
body = nil
|
||||
} else {
|
||||
err := json.Unmarshal([]byte(entry.Request.PostData.Text), &body)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("make request body failed")
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.Request.Body = body
|
||||
} else if strings.HasPrefix(mimeType, "application/x-www-form-urlencoded") {
|
||||
|
||||
@@ -9,8 +9,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
harPath = "../../../examples/hrp/har/demo.har"
|
||||
harPath2 = "../../../examples/hrp/har/postman-echo.har"
|
||||
harPath = "../../../examples/data/har/demo.har"
|
||||
harPath2 = "../../../examples/data/har/postman-echo.har"
|
||||
profilePath = "../../../examples/data/har/profile.yml"
|
||||
)
|
||||
|
||||
func TestGenJSON(t *testing.T) {
|
||||
@@ -47,6 +48,26 @@ func TestLoadHAR(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadHARWithProfile(t *testing.T) {
|
||||
har := NewHAR(harPath)
|
||||
har.SetProfile(profilePath)
|
||||
_, err := har.load()
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !assert.Equal(t,
|
||||
map[string]interface{}{"Content-Type": "application/x-www-form-urlencoded"},
|
||||
har.profile["headers"]) {
|
||||
t.Fail()
|
||||
}
|
||||
if !assert.Equal(t,
|
||||
map[string]interface{}{"UserName": "debugtalk"},
|
||||
har.profile["cookies"]) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeTestCase(t *testing.T) {
|
||||
har := NewHAR(harPath)
|
||||
tCase, err := har.makeTestCase()
|
||||
@@ -115,8 +136,228 @@ func TestMakeTestCase(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetFilenameWithoutExtension(t *testing.T) {
|
||||
filename := getFilenameWithoutExtension("../../../examples/hrp/har/postman-echo.har")
|
||||
filename := getFilenameWithoutExtension(harPath2)
|
||||
if !assert.Equal(t, "postman-echo", filename) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeRequestHeaders(t *testing.T) {
|
||||
har := NewHAR("")
|
||||
entry := &Entry{
|
||||
Request: Request{
|
||||
Method: "POST",
|
||||
Headers: []NVP{
|
||||
{Name: "Content-Type", Value: "application/json; charset=utf-8"},
|
||||
},
|
||||
},
|
||||
}
|
||||
step, err := har.prepareTestStep(entry)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !assert.Equal(t, map[string]string{
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
}, step.Request.Headers) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeRequestHeadersWithProfile(t *testing.T) {
|
||||
har := NewHAR("")
|
||||
har.SetProfile(profilePath)
|
||||
entry := &Entry{
|
||||
Request: Request{
|
||||
Method: "POST",
|
||||
Headers: []NVP{
|
||||
{Name: "Content-Type", Value: "application/json; charset=utf-8"},
|
||||
},
|
||||
},
|
||||
}
|
||||
step, err := har.prepareTestStep(entry)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !assert.Equal(t, map[string]string{
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
}, step.Request.Headers) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeRequestCookies(t *testing.T) {
|
||||
har := NewHAR("")
|
||||
entry := &Entry{
|
||||
Request: Request{
|
||||
Method: "POST",
|
||||
Cookies: []Cookie{
|
||||
{Name: "abc", Value: "123"},
|
||||
{Name: "UserName", Value: "leolee"},
|
||||
},
|
||||
},
|
||||
}
|
||||
step, err := har.prepareTestStep(entry)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !assert.Equal(t, map[string]string{
|
||||
"abc": "123",
|
||||
"UserName": "leolee",
|
||||
}, step.Request.Cookies) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeRequestCookiesWithProfile(t *testing.T) {
|
||||
har := NewHAR("")
|
||||
har.SetProfile(profilePath)
|
||||
entry := &Entry{
|
||||
Request: Request{
|
||||
Method: "POST",
|
||||
Cookies: []Cookie{
|
||||
{Name: "abc", Value: "123"},
|
||||
{Name: "UserName", Value: "leolee"},
|
||||
},
|
||||
},
|
||||
}
|
||||
step, err := har.prepareTestStep(entry)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !assert.Equal(t, map[string]string{
|
||||
"UserName": "debugtalk",
|
||||
}, step.Request.Cookies) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeRequestDataParams(t *testing.T) {
|
||||
har := NewHAR("")
|
||||
entry := &Entry{
|
||||
Request: Request{
|
||||
Method: "POST",
|
||||
PostData: PostData{
|
||||
MimeType: "application/x-www-form-urlencoded; charset=utf-8",
|
||||
Params: []PostParam{
|
||||
{Name: "a", Value: "1"},
|
||||
{Name: "b", Value: "2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
step, err := har.prepareTestStep(entry)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !assert.Equal(t, "a=1&b=2", step.Request.Body) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeRequestDataJSON(t *testing.T) {
|
||||
har := NewHAR("")
|
||||
entry := &Entry{
|
||||
Request: Request{
|
||||
Method: "POST",
|
||||
PostData: PostData{
|
||||
MimeType: "application/json; charset=utf-8",
|
||||
Text: "{\"a\":\"1\",\"b\":\"2\"}",
|
||||
},
|
||||
},
|
||||
}
|
||||
step, err := har.prepareTestStep(entry)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !assert.Equal(t, map[string]interface{}{"a": "1", "b": "2"}, step.Request.Body) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeRequestDataTextEmpty(t *testing.T) {
|
||||
har := NewHAR("")
|
||||
entry := &Entry{
|
||||
Request: Request{
|
||||
Method: "POST",
|
||||
PostData: PostData{
|
||||
MimeType: "application/json; charset=utf-8",
|
||||
Text: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
step, err := har.prepareTestStep(entry)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
if !assert.Equal(t, nil, step.Request.Body) { // TODO
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeValidate(t *testing.T) {
|
||||
har := NewHAR("")
|
||||
entry := &Entry{
|
||||
Response: Response{
|
||||
Status: 200,
|
||||
Headers: []NVP{
|
||||
{Name: "Content-Type", Value: "application/json; charset=utf-8"},
|
||||
},
|
||||
Content: Content{
|
||||
Size: 71,
|
||||
MimeType: "application/json; charset=utf-8",
|
||||
// map[Code:200 IsSuccess:true Message:<nil> Value:map[BlnResult:true]]
|
||||
Text: "eyJJc1N1Y2Nlc3MiOnRydWUsIkNvZGUiOjIwMCwiTWVzc2FnZSI6bnVsbCwiVmFsdWUiOnsiQmxuUmVzdWx0Ijp0cnVlfX0=",
|
||||
Encoding: "base64",
|
||||
},
|
||||
},
|
||||
}
|
||||
step, err := har.prepareTestStep(entry)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
validator, ok := step.Validators[0].(hrp.Validator)
|
||||
if !ok {
|
||||
t.Fail()
|
||||
}
|
||||
if !assert.Equal(t, validator,
|
||||
hrp.Validator{
|
||||
Check: "status_code",
|
||||
Expect: 200,
|
||||
Assert: "equals",
|
||||
Message: "assert response status code"}) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
validator, ok = step.Validators[1].(hrp.Validator)
|
||||
if !ok {
|
||||
t.Fail()
|
||||
}
|
||||
if !assert.Equal(t, validator,
|
||||
hrp.Validator{
|
||||
Check: "headers.\"Content-Type\"",
|
||||
Expect: "application/json; charset=utf-8",
|
||||
Assert: "equals",
|
||||
Message: "assert response header Content-Type"}) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
validator, ok = step.Validators[2].(hrp.Validator)
|
||||
if !ok {
|
||||
t.Fail()
|
||||
}
|
||||
if !assert.Equal(t, validator,
|
||||
hrp.Validator{
|
||||
Check: "body.Code",
|
||||
Expect: float64(200), // TODO
|
||||
Assert: "equals",
|
||||
Message: "assert response body Code"}) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
package scaffold
|
||||
|
||||
import "github.com/httprunner/httprunner/hrp"
|
||||
|
||||
var demoTestCase = &hrp.TestCase{
|
||||
Config: hrp.NewConfig("demo with complex mechanisms").
|
||||
SetBaseURL("https://postman-echo.com").
|
||||
WithVariables(map[string]interface{}{ // global level variables
|
||||
"n": "${sum_ints(1, 2, 2)}",
|
||||
"a": "${sum(10, 2.3)}",
|
||||
"b": 3.45,
|
||||
"varFoo1": "${gen_random_string($n)}",
|
||||
"varFoo2": "${max($a, $b)}", // 12.3; eval with built-in function
|
||||
}),
|
||||
TestSteps: []hrp.IStep{
|
||||
hrp.NewStep("transaction 1 start").StartTransaction("tran1"), // start transaction
|
||||
hrp.NewStep("get with params").
|
||||
WithVariables(map[string]interface{}{ // step level variables
|
||||
"n": 3, // inherit config level variables if not set in step level, a/varFoo1
|
||||
"b": 34.5, // override config level variable if existed, n/b/varFoo2
|
||||
"varFoo2": "${max($a, $b)}", // 34.5; override variable b and eval again
|
||||
"name": "get with params",
|
||||
}).
|
||||
SetupHook("${setup_hook_example($name)}").
|
||||
GET("/get").
|
||||
TeardownHook("${teardown_hook_example($name)}").
|
||||
WithParams(map[string]interface{}{"foo1": "$varFoo1", "foo2": "$varFoo2"}). // request with params
|
||||
WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). // request with headers
|
||||
Extract().
|
||||
WithJmesPath("body.args.foo1", "varFoo1"). // extract variable with jmespath
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check response status code"). // validate response status code
|
||||
AssertStartsWith("headers.\"Content-Type\"", "application/json", ""). // validate response header
|
||||
AssertLengthEqual("body.args.foo1", 5, "check args foo1"). // validate response body with jmespath
|
||||
AssertLengthEqual("$varFoo1", 5, "check args foo1"). // assert with extracted variable from current step
|
||||
AssertEqual("body.args.foo2", "34.5", "check args foo2"), // notice: request params value will be converted to string
|
||||
hrp.NewStep("transaction 1 end").EndTransaction("tran1"), // end transaction
|
||||
hrp.NewStep("post json data").
|
||||
POST("/post").
|
||||
WithBody(map[string]interface{}{
|
||||
"foo1": "$varFoo1", // reference former extracted variable
|
||||
"foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here
|
||||
}).
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check status code").
|
||||
AssertLengthEqual("body.json.foo1", 5, "check args foo1").
|
||||
AssertEqual("body.json.foo2", 12.3, "check args foo2"),
|
||||
hrp.NewStep("post form data").
|
||||
POST("/post").
|
||||
WithHeaders(map[string]string{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}).
|
||||
WithBody(map[string]interface{}{
|
||||
"foo1": "$varFoo1", // reference former extracted variable
|
||||
"foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here
|
||||
"time": "${get_timestamp()}",
|
||||
}).
|
||||
Extract().
|
||||
WithJmesPath("body.form.time", "varTime").
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check status code").
|
||||
AssertLengthEqual("body.form.foo1", 5, "check args foo1").
|
||||
AssertEqual("body.form.foo2", "12.3", "check args foo2"), // form data will be converted to string
|
||||
hrp.NewStep("get with timestamp").
|
||||
GET("/get").WithParams(map[string]interface{}{"time": "$varTime"}).
|
||||
Validate().
|
||||
AssertLengthEqual("body.args.time", 13, "check extracted var timestamp"),
|
||||
},
|
||||
}
|
||||
|
||||
var demoTestCaseWithoutPlugin = &hrp.TestCase{
|
||||
Config: hrp.NewConfig("demo without custom function plugin").
|
||||
SetBaseURL("https://postman-echo.com").
|
||||
WithVariables(map[string]interface{}{ // global level variables
|
||||
"n": 5,
|
||||
"a": 12.3,
|
||||
"b": 3.45,
|
||||
"varFoo1": "${gen_random_string($n)}",
|
||||
"varFoo2": "${max($a, $b)}", // 12.3; eval with built-in function
|
||||
}),
|
||||
TestSteps: []hrp.IStep{
|
||||
hrp.NewStep("transaction 1 start").StartTransaction("tran1"), // start transaction
|
||||
hrp.NewStep("get with params").
|
||||
WithVariables(map[string]interface{}{ // step level variables
|
||||
"n": 3, // inherit config level variables if not set in step level, a/varFoo1
|
||||
"b": 34.5, // override config level variable if existed, n/b/varFoo2
|
||||
"varFoo2": "${max($a, $b)}", // 34.5; override variable b and eval again
|
||||
"name": "get with params",
|
||||
}).
|
||||
GET("/get").
|
||||
WithParams(map[string]interface{}{"foo1": "$varFoo1", "foo2": "$varFoo2"}). // request with params
|
||||
WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). // request with headers
|
||||
Extract().
|
||||
WithJmesPath("body.args.foo1", "varFoo1"). // extract variable with jmespath
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check response status code"). // validate response status code
|
||||
AssertStartsWith("headers.\"Content-Type\"", "application/json", ""). // validate response header
|
||||
AssertLengthEqual("body.args.foo1", 5, "check args foo1"). // validate response body with jmespath
|
||||
AssertLengthEqual("$varFoo1", 5, "check args foo1"). // assert with extracted variable from current step
|
||||
AssertEqual("body.args.foo2", "34.5", "check args foo2"), // notice: request params value will be converted to string
|
||||
hrp.NewStep("transaction 1 end").EndTransaction("tran1"), // end transaction
|
||||
hrp.NewStep("post json data").
|
||||
POST("/post").
|
||||
WithBody(map[string]interface{}{
|
||||
"foo1": "$varFoo1", // reference former extracted variable
|
||||
"foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here
|
||||
}).
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check status code").
|
||||
AssertLengthEqual("body.json.foo1", 5, "check args foo1").
|
||||
AssertEqual("body.json.foo2", 12.3, "check args foo2"),
|
||||
hrp.NewStep("post form data").
|
||||
POST("/post").
|
||||
WithHeaders(map[string]string{"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}).
|
||||
WithBody(map[string]interface{}{
|
||||
"foo1": "$varFoo1", // reference former extracted variable
|
||||
"foo2": "${max($a, $b)}", // 12.3; step level variables are independent, variable b is 3.45 here
|
||||
"time": "${get_timestamp()}",
|
||||
}).
|
||||
Extract().
|
||||
WithJmesPath("body.form.time", "varTime").
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check status code").
|
||||
AssertLengthEqual("body.form.foo1", 5, "check args foo1").
|
||||
AssertEqual("body.form.foo2", "12.3", "check args foo2"), // form data will be converted to string
|
||||
hrp.NewStep("get with timestamp").
|
||||
GET("/get").WithParams(map[string]interface{}{"time": "$varTime"}).
|
||||
Validate().
|
||||
AssertLengthEqual("body.args.time", 13, "check extracted var timestamp"),
|
||||
},
|
||||
}
|
||||
|
||||
// debugtalk.go
|
||||
var demoGoPlugin = `package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/httprunner/funplugin/fungo"
|
||||
)
|
||||
|
||||
func SumTwoInt(a, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
func SumInts(args ...int) int {
|
||||
var sum int
|
||||
for _, arg := range args {
|
||||
sum += arg
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func Sum(args ...interface{}) (interface{}, error) {
|
||||
var sum float64
|
||||
for _, arg := range args {
|
||||
switch v := arg.(type) {
|
||||
case int:
|
||||
sum += float64(v)
|
||||
case float64:
|
||||
sum += v
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected type: %T", arg)
|
||||
}
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
func SetupHookExample(args string) string {
|
||||
return fmt.Sprintf("step name: %v, setup...", args)
|
||||
}
|
||||
|
||||
func TeardownHookExample(args string) string {
|
||||
return fmt.Sprintf("step name: %v, teardown...", args)
|
||||
}
|
||||
|
||||
func main() {
|
||||
fungo.Register("sum_ints", SumInts)
|
||||
fungo.Register("sum_two_int", SumTwoInt)
|
||||
fungo.Register("sum", Sum)
|
||||
fungo.Register("setup_hook_example", SetupHookExample)
|
||||
fungo.Register("teardown_hook_example", TeardownHookExample)
|
||||
fungo.Serve()
|
||||
}
|
||||
`
|
||||
|
||||
// debugtalk.py
|
||||
var demoPyPlugin = `import logging
|
||||
from typing import List
|
||||
|
||||
import funppy
|
||||
|
||||
|
||||
def sum(*args):
|
||||
result = 0
|
||||
for arg in args:
|
||||
result += arg
|
||||
return result
|
||||
|
||||
def sum_ints(*args: List[int]) -> int:
|
||||
result = 0
|
||||
for arg in args:
|
||||
result += arg
|
||||
return result
|
||||
|
||||
def sum_two_int(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
def sum_two_string(a: str, b: str) -> str:
|
||||
return a + b
|
||||
|
||||
def sum_strings(*args: List[str]) -> str:
|
||||
result = ""
|
||||
for arg in args:
|
||||
result += arg
|
||||
return result
|
||||
|
||||
def concatenate(*args: List[str]) -> str:
|
||||
result = ""
|
||||
for arg in args:
|
||||
result += str(arg)
|
||||
return result
|
||||
|
||||
def setup_hook_example(name):
|
||||
logging.warning("setup_hook_example")
|
||||
return f"setup_hook_example: {name}"
|
||||
|
||||
def teardown_hook_example(name):
|
||||
logging.warning("teardown_hook_example")
|
||||
return f"teardown_hook_example: {name}"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
funppy.register("sum", sum)
|
||||
funppy.register("sum_ints", sum_ints)
|
||||
funppy.register("concatenate", concatenate)
|
||||
funppy.register("sum_two_int", sum_two_int)
|
||||
funppy.register("sum_two_string", sum_two_string)
|
||||
funppy.register("sum_strings", sum_strings)
|
||||
funppy.register("setup_hook_example", setup_hook_example)
|
||||
funppy.register("teardown_hook_example", teardown_hook_example)
|
||||
funppy.serve()
|
||||
`
|
||||
|
||||
// .gitignore
|
||||
var demoIgnoreContent = `.env
|
||||
reports/
|
||||
*.so
|
||||
.vscode/
|
||||
.idea/
|
||||
.DS_Store
|
||||
output/
|
||||
|
||||
# plugin
|
||||
debugtalk.bin
|
||||
debugtalk.so
|
||||
`
|
||||
|
||||
// .env
|
||||
var demoEnvContent = `USERNAME=debugtalk
|
||||
PASSWORD=123456
|
||||
`
|
||||
@@ -1,75 +0,0 @@
|
||||
package scaffold
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/hrp"
|
||||
"github.com/httprunner/httprunner/hrp/internal/builtin"
|
||||
)
|
||||
|
||||
var (
|
||||
demoTestCaseJSONPath hrp.TestCasePath = "../../../examples/hrp/demo.json"
|
||||
demoTestCaseYAMLPath hrp.TestCasePath = "../../../examples/hrp/demo.yaml"
|
||||
)
|
||||
|
||||
func buildHashicorpPlugin() {
|
||||
log.Info().Msg("[init] build hashicorp go plugin")
|
||||
cmd := exec.Command("go", "build",
|
||||
"-o", "../../../examples/hrp/debugtalk.bin",
|
||||
"../../../examples/hrp/plugin/hashicorp.go", "../../../examples/hrp/plugin/debugtalk.go")
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func removeHashicorpPlugin() {
|
||||
log.Info().Msg("[teardown] remove hashicorp plugin")
|
||||
os.Remove("../../../examples/hrp/debugtalk.bin")
|
||||
}
|
||||
|
||||
func TestGenDemoTestCase(t *testing.T) {
|
||||
tCase, _ := demoTestCase.ToTCase()
|
||||
err := builtin.Dump2JSON(tCase, demoTestCaseJSONPath.ToString())
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
err = builtin.Dump2YAML(tCase, demoTestCaseYAMLPath.ToString())
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestExampleDemo(t *testing.T) {
|
||||
buildHashicorpPlugin()
|
||||
defer removeHashicorpPlugin()
|
||||
|
||||
demoTestCase.Config.Path = "../../../examples/hrp/debugtalk.bin"
|
||||
err := hrp.NewRunner(nil).Run(demoTestCase) // hrp.Run(demoTestCase)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonDemo(t *testing.T) {
|
||||
buildHashicorpPlugin()
|
||||
defer removeHashicorpPlugin()
|
||||
|
||||
err := hrp.NewRunner(nil).Run(&demoTestCaseJSONPath) // hrp.Run(testCase)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestYamlDemo(t *testing.T) {
|
||||
buildHashicorpPlugin()
|
||||
defer removeHashicorpPlugin()
|
||||
|
||||
err := hrp.NewRunner(nil).Run(&demoTestCaseYAMLPath) // hrp.Run(testCase)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
29
hrp/internal/scaffold/examples_test.go
Normal file
29
hrp/internal/scaffold/examples_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package scaffold
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenDemoExamples(t *testing.T) {
|
||||
dir := "../../../examples/demo-with-go-plugin"
|
||||
os.RemoveAll(dir)
|
||||
err := CreateScaffold(dir, Go)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
dir = "../../../examples/demo-with-py-plugin"
|
||||
os.RemoveAll(dir)
|
||||
err = CreateScaffold(dir, Py)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
dir = "../../../examples/demo-without-plugin"
|
||||
os.RemoveAll(dir)
|
||||
err = CreateScaffold(dir, Ignore)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
package scaffold
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/funplugin/shared"
|
||||
"github.com/httprunner/httprunner/hrp"
|
||||
"github.com/httprunner/httprunner/hrp/internal/builtin"
|
||||
"github.com/httprunner/httprunner/hrp/internal/sdk"
|
||||
)
|
||||
@@ -23,6 +23,25 @@ const (
|
||||
Go PluginType = "go"
|
||||
)
|
||||
|
||||
//go:embed templates/*
|
||||
var templatesDir embed.FS
|
||||
|
||||
// CopyFile copies a file from templates dir to scaffold project
|
||||
func CopyFile(templateFile, targetFile string) error {
|
||||
log.Info().Str("path", targetFile).Msg("create file")
|
||||
content, err := templatesDir.ReadFile(templateFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "template file not found")
|
||||
}
|
||||
|
||||
err = os.WriteFile(targetFile, content, 0o644)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("create file failed")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateScaffold(projectName string, pluginType PluginType) error {
|
||||
// report event
|
||||
sdk.SendEvent(sdk.EventTracking{
|
||||
@@ -46,48 +65,62 @@ func CreateScaffold(projectName string, pluginType PluginType) error {
|
||||
if err := builtin.CreateFolder(projectName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := builtin.CreateFolder(path.Join(projectName, "har")); err != nil {
|
||||
if err := builtin.CreateFolder(filepath.Join(projectName, "har")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := builtin.CreateFolder(path.Join(projectName, "testcases")); err != nil {
|
||||
if err := builtin.CreateFile(filepath.Join(projectName, "har", ".keep"), ""); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := builtin.CreateFolder(path.Join(projectName, "reports")); err != nil {
|
||||
if err := builtin.CreateFolder(filepath.Join(projectName, "testcases")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create demo testcases
|
||||
var tCase *hrp.TCase
|
||||
if pluginType == Ignore {
|
||||
tCase, _ = demoTestCaseWithoutPlugin.ToTCase()
|
||||
} else {
|
||||
tCase, _ = demoTestCase.ToTCase()
|
||||
}
|
||||
err := builtin.Dump2JSON(tCase, path.Join(projectName, "testcases", "demo.json"))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("create demo.json testcase failed")
|
||||
if err := builtin.CreateFolder(filepath.Join(projectName, "reports")); err != nil {
|
||||
return err
|
||||
}
|
||||
err = builtin.Dump2YAML(tCase, path.Join(projectName, "testcases", "demo.yaml"))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("create demo.yml testcase failed")
|
||||
if err := builtin.CreateFile(filepath.Join(projectName, "reports", ".keep"), ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create .gitignore
|
||||
if err := builtin.CreateFile(path.Join(projectName, ".gitignore"), demoIgnoreContent); err != nil {
|
||||
err := CopyFile("templates/gitignore", filepath.Join(projectName, ".gitignore"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// create .env
|
||||
if err := builtin.CreateFile(path.Join(projectName, ".env"), demoEnvContent); err != nil {
|
||||
err = CopyFile("templates/env", filepath.Join(projectName, ".env"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create demo testcases
|
||||
if pluginType == Ignore {
|
||||
err := CopyFile("templates/testcases/demo_without_funplugin.json",
|
||||
filepath.Join(projectName, "testcases", "demo_without_funplugin.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Msg("skip creating function plugin")
|
||||
return nil
|
||||
}
|
||||
|
||||
err = CopyFile("templates/testcases/demo_with_funplugin.json",
|
||||
filepath.Join(projectName, "testcases", "demo_with_funplugin.json"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = CopyFile("templates/testcases/demo_requests.yml",
|
||||
filepath.Join(projectName, "testcases", "demo_requests.yml"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = CopyFile("templates/testcases/demo_ref_testcase.yml",
|
||||
filepath.Join(projectName, "testcases", "demo_ref_testcase.yml"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create debugtalk function plugin
|
||||
switch pluginType {
|
||||
case Ignore:
|
||||
log.Info().Msg("skip creating function plugin")
|
||||
return nil
|
||||
case Py:
|
||||
return createPythonPlugin(projectName)
|
||||
case Go:
|
||||
@@ -105,12 +138,13 @@ func createGoPlugin(projectName string) error {
|
||||
}
|
||||
|
||||
// create debugtalk.go
|
||||
pluginDir := path.Join(projectName, "plugin")
|
||||
pluginDir := filepath.Join(projectName, "plugin")
|
||||
if err := builtin.CreateFolder(pluginDir); err != nil {
|
||||
return err
|
||||
}
|
||||
pluginFile := path.Join(pluginDir, "debugtalk.go")
|
||||
if err := builtin.CreateFile(pluginFile, demoGoPlugin); err != nil {
|
||||
err := CopyFile("templates/plugin/debugtalk.go",
|
||||
filepath.Join(projectName, "plugin", "debugtalk.go"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -120,12 +154,14 @@ func createGoPlugin(projectName string) error {
|
||||
}
|
||||
|
||||
// download plugin dependency
|
||||
if err := builtin.ExecCommand(exec.Command("go", "get", "github.com/httprunner/funplugin"), pluginDir); err != nil {
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
// build plugin debugtalk.bin
|
||||
if err := builtin.ExecCommand(exec.Command("go", "build", "-o", path.Join("..", "debugtalk.bin"), "debugtalk.go"), pluginDir); err != nil {
|
||||
if err := builtin.ExecCommand(exec.Command("go", "build", "-o", filepath.Join("..", "debugtalk.bin"), "debugtalk.go"), pluginDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -136,14 +172,21 @@ func createPythonPlugin(projectName string) error {
|
||||
log.Info().Msg("start to create hashicorp python plugin")
|
||||
|
||||
// create debugtalk.py
|
||||
pluginFile := path.Join(projectName, "debugtalk.py")
|
||||
if err := builtin.CreateFile(pluginFile, demoPyPlugin); err != nil {
|
||||
return err
|
||||
pluginFile := filepath.Join(projectName, "debugtalk.py")
|
||||
err := CopyFile("templates/plugin/debugtalk.py", pluginFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "copy file failed")
|
||||
}
|
||||
|
||||
// create python venv
|
||||
if _, err := shared.PreparePython3Venv(pluginFile); err != nil {
|
||||
return err
|
||||
home, err := os.UserHomeDir()
|
||||
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 nil
|
||||
|
||||
2
hrp/internal/scaffold/templates/env
Normal file
2
hrp/internal/scaffold/templates/env
Normal file
@@ -0,0 +1,2 @@
|
||||
USERNAME=debugtalk
|
||||
PASSWORD=123456
|
||||
15
hrp/internal/scaffold/templates/gitignore
Normal file
15
hrp/internal/scaffold/templates/gitignore
Normal file
@@ -0,0 +1,15 @@
|
||||
.env
|
||||
reports/
|
||||
*.so
|
||||
.vscode/
|
||||
.idea/
|
||||
.DS_Store
|
||||
output/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.python-version
|
||||
logs/
|
||||
|
||||
# plugin
|
||||
debugtalk.bin
|
||||
debugtalk.so
|
||||
57
hrp/internal/scaffold/templates/plugin/debugtalk.go
Normal file
57
hrp/internal/scaffold/templates/plugin/debugtalk.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/httprunner/funplugin/fungo"
|
||||
)
|
||||
|
||||
func SumTwoInt(a, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
func SumInts(args ...int) int {
|
||||
var sum int
|
||||
for _, arg := range args {
|
||||
sum += arg
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func Sum(args ...interface{}) (interface{}, error) {
|
||||
var sum float64
|
||||
for _, arg := range args {
|
||||
switch v := arg.(type) {
|
||||
case int:
|
||||
sum += float64(v)
|
||||
case float64:
|
||||
sum += v
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected type: %T", arg)
|
||||
}
|
||||
}
|
||||
return sum, nil
|
||||
}
|
||||
|
||||
func SetupHookExample(args string) string {
|
||||
return fmt.Sprintf("step name: %v, setup...", args)
|
||||
}
|
||||
|
||||
func TeardownHookExample(args string) string {
|
||||
return fmt.Sprintf("step name: %v, teardown...", args)
|
||||
}
|
||||
|
||||
func GetVersion() string {
|
||||
return "v4.0.0-alpha"
|
||||
}
|
||||
|
||||
func main() {
|
||||
fungo.Register("get_httprunner_version", GetVersion)
|
||||
fungo.Register("sum_ints", SumInts)
|
||||
fungo.Register("sum_two_int", SumTwoInt)
|
||||
fungo.Register("sum_two", SumTwoInt)
|
||||
fungo.Register("sum", Sum)
|
||||
fungo.Register("setup_hook_example", SetupHookExample)
|
||||
fungo.Register("teardown_hook_example", TeardownHookExample)
|
||||
fungo.Serve()
|
||||
}
|
||||
73
hrp/internal/scaffold/templates/plugin/debugtalk.py
Normal file
73
hrp/internal/scaffold/templates/plugin/debugtalk.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import logging
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
import funppy
|
||||
|
||||
|
||||
def get_httprunner_version():
|
||||
return "v4.0.0-alpha"
|
||||
|
||||
|
||||
def sleep(n_secs):
|
||||
time.sleep(n_secs)
|
||||
|
||||
|
||||
def sum(*args):
|
||||
result = 0
|
||||
for arg in args:
|
||||
result += arg
|
||||
return result
|
||||
|
||||
|
||||
def sum_ints(*args: List[int]) -> int:
|
||||
result = 0
|
||||
for arg in args:
|
||||
result += arg
|
||||
return result
|
||||
|
||||
|
||||
def sum_two_int(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
|
||||
def sum_two_string(a: str, b: str) -> str:
|
||||
return a + b
|
||||
|
||||
|
||||
def sum_strings(*args: List[str]) -> str:
|
||||
result = ""
|
||||
for arg in args:
|
||||
result += arg
|
||||
return result
|
||||
|
||||
|
||||
def concatenate(*args: List[str]) -> str:
|
||||
result = ""
|
||||
for arg in args:
|
||||
result += str(arg)
|
||||
return result
|
||||
|
||||
|
||||
def setup_hook_example(name):
|
||||
logging.warning("setup_hook_example")
|
||||
return f"setup_hook_example: {name}"
|
||||
|
||||
|
||||
def teardown_hook_example(name):
|
||||
logging.warning("teardown_hook_example")
|
||||
return f"teardown_hook_example: {name}"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
funppy.register("get_httprunner_version", get_httprunner_version)
|
||||
funppy.register("sum", sum)
|
||||
funppy.register("sum_ints", sum_ints)
|
||||
funppy.register("concatenate", concatenate)
|
||||
funppy.register("sum_two_int", sum_two_int)
|
||||
funppy.register("sum_two", sum_two_int)
|
||||
funppy.register("sum_two_string", sum_two_string)
|
||||
funppy.register("sum_strings", sum_strings)
|
||||
funppy.register("setup_hook_example", setup_hook_example)
|
||||
funppy.register("teardown_hook_example", teardown_hook_example)
|
||||
funppy.serve()
|
||||
1
hrp/internal/scaffold/templates/testcases/__init__.py
Normal file
1
hrp/internal/scaffold/templates/testcases/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# NOTICE: Generated By HttpRunner. DO NOT EDIT!
|
||||
@@ -0,0 +1,33 @@
|
||||
config:
|
||||
name: "request methods testcase: reference testcase"
|
||||
variables:
|
||||
foo1: testsuite_config_bar1
|
||||
expect_foo1: testsuite_config_bar1
|
||||
expect_foo2: config_bar2
|
||||
base_url: "https://postman-echo.com"
|
||||
verify: False
|
||||
|
||||
teststeps:
|
||||
-
|
||||
name: request with functions
|
||||
variables:
|
||||
foo1: testcase_ref_bar1
|
||||
expect_foo1: testcase_ref_bar1
|
||||
testcase: testcases/demo_requests.yml
|
||||
export:
|
||||
- foo3
|
||||
-
|
||||
name: post form data
|
||||
variables:
|
||||
foo1: bar1
|
||||
request:
|
||||
method: POST
|
||||
url: /post
|
||||
headers:
|
||||
User-Agent: HttpRunner/${get_httprunner_version()}
|
||||
Content-Type: "application/x-www-form-urlencoded"
|
||||
data: "foo1=$foo1&foo2=$foo3"
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["body.form.foo1", "bar1"]
|
||||
- eq: ["body.form.foo2", "bar21"]
|
||||
@@ -0,0 +1,60 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# FROM: testcases/demo_ref_testcase.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 testcases.demo_requests_test import TestCaseDemoRequests as DemoRequests
|
||||
|
||||
|
||||
class TestCaseDemoRefTestcase(HttpRunner):
|
||||
|
||||
config = (
|
||||
Config("request methods testcase: reference testcase")
|
||||
.variables(
|
||||
**{
|
||||
"foo1": "testsuite_config_bar1",
|
||||
"expect_foo1": "testsuite_config_bar1",
|
||||
"expect_foo2": "config_bar2",
|
||||
}
|
||||
)
|
||||
.base_url("https://postman-echo.com")
|
||||
.verify(False)
|
||||
)
|
||||
|
||||
teststeps = [
|
||||
Step(
|
||||
RunTestCase("request with functions")
|
||||
.with_variables(
|
||||
**{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"}
|
||||
)
|
||||
.call(DemoRequests)
|
||||
.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__":
|
||||
TestCaseDemoRefTestcase().test_start()
|
||||
65
hrp/internal/scaffold/templates/testcases/demo_requests.yml
Normal file
65
hrp/internal/scaffold/templates/testcases/demo_requests.yml
Normal file
@@ -0,0 +1,65 @@
|
||||
config:
|
||||
name: "request methods testcase with functions"
|
||||
variables:
|
||||
foo1: config_bar1
|
||||
foo2: config_bar2
|
||||
expect_foo1: config_bar1
|
||||
expect_foo2: config_bar2
|
||||
base_url: "https://postman-echo.com"
|
||||
verify: False
|
||||
export: ["foo3"]
|
||||
|
||||
teststeps:
|
||||
-
|
||||
name: get with params
|
||||
variables:
|
||||
foo1: bar11
|
||||
foo2: bar21
|
||||
sum_v: "${sum_two(1, 2)}"
|
||||
request:
|
||||
method: GET
|
||||
url: /get
|
||||
params:
|
||||
foo1: $foo1
|
||||
foo2: $foo2
|
||||
sum_v: $sum_v
|
||||
headers:
|
||||
User-Agent: HttpRunner/${get_httprunner_version()}
|
||||
extract:
|
||||
foo3: "body.args.foo2"
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["body.args.foo1", "bar11"]
|
||||
- eq: ["body.args.sum_v", "3"]
|
||||
- eq: ["body.args.foo2", "bar21"]
|
||||
-
|
||||
name: post raw text
|
||||
variables:
|
||||
foo1: "bar12"
|
||||
foo3: "bar32"
|
||||
request:
|
||||
method: POST
|
||||
url: /post
|
||||
headers:
|
||||
User-Agent: HttpRunner/${get_httprunner_version()}
|
||||
Content-Type: "text/plain"
|
||||
data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3."
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."]
|
||||
-
|
||||
name: post form data
|
||||
variables:
|
||||
foo2: bar23
|
||||
request:
|
||||
method: POST
|
||||
url: /post
|
||||
headers:
|
||||
User-Agent: HttpRunner/${get_httprunner_version()}
|
||||
Content-Type: "application/x-www-form-urlencoded"
|
||||
data: "foo1=$foo1&foo2=$foo2&foo3=$foo3"
|
||||
validate:
|
||||
- eq: ["status_code", 200]
|
||||
- eq: ["body.form.foo1", "$expect_foo1"]
|
||||
- eq: ["body.form.foo2", "bar23"]
|
||||
- eq: ["body.form.foo3", "bar21"]
|
||||
@@ -0,0 +1,83 @@
|
||||
# NOTE: Generated By HttpRunner v4.0.0-alpha
|
||||
# FROM: testcases/demo_requests.yml
|
||||
|
||||
|
||||
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
|
||||
|
||||
class TestCaseDemoRequests(HttpRunner):
|
||||
|
||||
config = (
|
||||
Config("request methods testcase with functions")
|
||||
.variables(
|
||||
**{
|
||||
"foo1": "config_bar1",
|
||||
"foo2": "config_bar2",
|
||||
"expect_foo1": "config_bar1",
|
||||
"expect_foo2": "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.",
|
||||
)
|
||||
),
|
||||
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)
|
||||
.assert_equal("body.form.foo1", "$expect_foo1")
|
||||
.assert_equal("body.form.foo2", "bar23")
|
||||
.assert_equal("body.form.foo3", "bar21")
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
TestCaseDemoRequests().test_start()
|
||||
@@ -0,0 +1,176 @@
|
||||
{
|
||||
"config": {
|
||||
"name": "demo with complex mechanisms",
|
||||
"base_url": "https://postman-echo.com",
|
||||
"variables": {
|
||||
"a": "${sum(10, 2.3)}",
|
||||
"b": 3.45,
|
||||
"n": "${sum_ints(1, 2, 2)}",
|
||||
"varFoo1": "${gen_random_string($n)}",
|
||||
"varFoo2": "${max($a, $b)}"
|
||||
}
|
||||
},
|
||||
"teststeps": [
|
||||
{
|
||||
"name": "transaction 1 start",
|
||||
"transaction": {
|
||||
"name": "tran1",
|
||||
"type": "start"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get with params",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/get",
|
||||
"params": {
|
||||
"foo1": "$varFoo1",
|
||||
"foo2": "$varFoo2"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "HttpRunnerPlus"
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"b": 34.5,
|
||||
"n": 3,
|
||||
"name": "get with params",
|
||||
"varFoo2": "${max($a, $b)}"
|
||||
},
|
||||
"setup_hooks": [
|
||||
"${setup_hook_example($name)}"
|
||||
],
|
||||
"teardown_hooks": [
|
||||
"${teardown_hook_example($name)}"
|
||||
],
|
||||
"extract": {
|
||||
"varFoo1": "body.args.foo1"
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "status_code",
|
||||
"assert": "equals",
|
||||
"expect": 200,
|
||||
"msg": "check response status code"
|
||||
},
|
||||
{
|
||||
"check": "headers.\"Content-Type\"",
|
||||
"assert": "startswith",
|
||||
"expect": "application/json"
|
||||
},
|
||||
{
|
||||
"check": "body.args.foo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "$varFoo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "body.args.foo2",
|
||||
"assert": "equals",
|
||||
"expect": "34.5",
|
||||
"msg": "check args foo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "transaction 1 end",
|
||||
"transaction": {
|
||||
"name": "tran1",
|
||||
"type": "end"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "post json data",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "/post",
|
||||
"body": {
|
||||
"foo1": "$varFoo1",
|
||||
"foo2": "${max($a, $b)}"
|
||||
}
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "status_code",
|
||||
"assert": "equals",
|
||||
"expect": 200,
|
||||
"msg": "check status code"
|
||||
},
|
||||
{
|
||||
"check": "body.json.foo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "body.json.foo2",
|
||||
"assert": "equals",
|
||||
"expect": 12.3,
|
||||
"msg": "check args foo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "post form data",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "/post",
|
||||
"headers": {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
|
||||
},
|
||||
"body": {
|
||||
"foo1": "$varFoo1",
|
||||
"foo2": "${max($a, $b)}",
|
||||
"time": "${get_timestamp()}"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"varTime": "body.form.time"
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "status_code",
|
||||
"assert": "equals",
|
||||
"expect": 200,
|
||||
"msg": "check status code"
|
||||
},
|
||||
{
|
||||
"check": "body.form.foo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "body.form.foo2",
|
||||
"assert": "equals",
|
||||
"expect": "12.3",
|
||||
"msg": "check args foo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "get with timestamp",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/get",
|
||||
"params": {
|
||||
"time": "$varTime"
|
||||
}
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "body.args.time",
|
||||
"assert": "length_equals",
|
||||
"expect": 13,
|
||||
"msg": "check extracted var timestamp"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"config": {
|
||||
"name": "demo without custom function plugin",
|
||||
"base_url": "https://postman-echo.com",
|
||||
"variables": {
|
||||
"a": 12.3,
|
||||
"b": 3.45,
|
||||
"n": 5,
|
||||
"varFoo1": "${gen_random_string($n)}",
|
||||
"varFoo2": "${max($a, $b)}"
|
||||
}
|
||||
},
|
||||
"teststeps": [
|
||||
{
|
||||
"name": "transaction 1 start",
|
||||
"transaction": {
|
||||
"name": "tran1",
|
||||
"type": "start"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get with params",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/get",
|
||||
"params": {
|
||||
"foo1": "$varFoo1",
|
||||
"foo2": "$varFoo2"
|
||||
},
|
||||
"headers": {
|
||||
"User-Agent": "HttpRunnerPlus"
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"b": 34.5,
|
||||
"n": 3,
|
||||
"name": "get with params",
|
||||
"varFoo2": "${max($a, $b)}"
|
||||
},
|
||||
"extract": {
|
||||
"varFoo1": "body.args.foo1"
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "status_code",
|
||||
"assert": "equals",
|
||||
"expect": 200,
|
||||
"msg": "check response status code"
|
||||
},
|
||||
{
|
||||
"check": "headers.\"Content-Type\"",
|
||||
"assert": "startswith",
|
||||
"expect": "application/json"
|
||||
},
|
||||
{
|
||||
"check": "body.args.foo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "$varFoo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "body.args.foo2",
|
||||
"assert": "equals",
|
||||
"expect": "34.5",
|
||||
"msg": "check args foo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "transaction 1 end",
|
||||
"transaction": {
|
||||
"name": "tran1",
|
||||
"type": "end"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "post json data",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "/post",
|
||||
"body": {
|
||||
"foo1": "$varFoo1",
|
||||
"foo2": "${max($a, $b)}"
|
||||
}
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "status_code",
|
||||
"assert": "equals",
|
||||
"expect": 200,
|
||||
"msg": "check status code"
|
||||
},
|
||||
{
|
||||
"check": "body.json.foo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "body.json.foo2",
|
||||
"assert": "equals",
|
||||
"expect": 12.3,
|
||||
"msg": "check args foo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "post form data",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "/post",
|
||||
"headers": {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
|
||||
},
|
||||
"body": {
|
||||
"foo1": "$varFoo1",
|
||||
"foo2": "${max($a, $b)}",
|
||||
"time": "${get_timestamp()}"
|
||||
}
|
||||
},
|
||||
"extract": {
|
||||
"varTime": "body.form.time"
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "status_code",
|
||||
"assert": "equals",
|
||||
"expect": 200,
|
||||
"msg": "check status code"
|
||||
},
|
||||
{
|
||||
"check": "body.form.foo1",
|
||||
"assert": "length_equals",
|
||||
"expect": 5,
|
||||
"msg": "check args foo1"
|
||||
},
|
||||
{
|
||||
"check": "body.form.foo2",
|
||||
"assert": "equals",
|
||||
"expect": "12.3",
|
||||
"msg": "check args foo2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "get with timestamp",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/get",
|
||||
"params": {
|
||||
"time": "$varTime"
|
||||
}
|
||||
},
|
||||
"validate": [
|
||||
{
|
||||
"check": "body.args.time",
|
||||
"assert": "length_equals",
|
||||
"expect": 13,
|
||||
"msg": "check extracted var timestamp"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
config:
|
||||
name: demo without custom function plugin
|
||||
base_url: https://postman-echo.com
|
||||
variables:
|
||||
a: 12.3
|
||||
b: 3.45
|
||||
"n": 5
|
||||
varFoo1: ${gen_random_string($n)}
|
||||
varFoo2: ${max($a, $b)}
|
||||
teststeps:
|
||||
- name: transaction 1 start
|
||||
transaction:
|
||||
name: tran1
|
||||
type: start
|
||||
- name: get with params
|
||||
request:
|
||||
method: GET
|
||||
url: /get
|
||||
params:
|
||||
foo1: $varFoo1
|
||||
foo2: $varFoo2
|
||||
headers:
|
||||
User-Agent: HttpRunnerPlus
|
||||
variables:
|
||||
b: 34.5
|
||||
"n": 3
|
||||
name: get with params
|
||||
varFoo2: ${max($a, $b)}
|
||||
extract:
|
||||
varFoo1: body.args.foo1
|
||||
validate:
|
||||
- check: status_code
|
||||
assert: equals
|
||||
expect: 200
|
||||
msg: check response status code
|
||||
- check: headers."Content-Type"
|
||||
assert: startswith
|
||||
expect: application/json
|
||||
- check: body.args.foo1
|
||||
assert: length_equals
|
||||
expect: 5
|
||||
msg: check args foo1
|
||||
- check: $varFoo1
|
||||
assert: length_equals
|
||||
expect: 5
|
||||
msg: check args foo1
|
||||
- check: body.args.foo2
|
||||
assert: equals
|
||||
expect: "34.5"
|
||||
msg: check args foo2
|
||||
- name: transaction 1 end
|
||||
transaction:
|
||||
name: tran1
|
||||
type: end
|
||||
- name: post json data
|
||||
request:
|
||||
method: POST
|
||||
url: /post
|
||||
body:
|
||||
foo1: $varFoo1
|
||||
foo2: ${max($a, $b)}
|
||||
validate:
|
||||
- check: status_code
|
||||
assert: equals
|
||||
expect: 200
|
||||
msg: check status code
|
||||
- check: body.json.foo1
|
||||
assert: length_equals
|
||||
expect: 5
|
||||
msg: check args foo1
|
||||
- check: body.json.foo2
|
||||
assert: equals
|
||||
expect: 12.3
|
||||
msg: check args foo2
|
||||
- name: post form data
|
||||
request:
|
||||
method: POST
|
||||
url: /post
|
||||
headers:
|
||||
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
|
||||
body:
|
||||
foo1: $varFoo1
|
||||
foo2: ${max($a, $b)}
|
||||
time: ${get_timestamp()}
|
||||
extract:
|
||||
varTime: body.form.time
|
||||
validate:
|
||||
- check: status_code
|
||||
assert: equals
|
||||
expect: 200
|
||||
msg: check status code
|
||||
- check: body.form.foo1
|
||||
assert: length_equals
|
||||
expect: 5
|
||||
msg: check args foo1
|
||||
- check: body.form.foo2
|
||||
assert: equals
|
||||
expect: "12.3"
|
||||
msg: check args foo2
|
||||
- name: get with timestamp
|
||||
request:
|
||||
method: GET
|
||||
url: /get
|
||||
params:
|
||||
time: $varTime
|
||||
validate:
|
||||
- check: body.args.time
|
||||
assert: length_equals
|
||||
expect: 13
|
||||
msg: check extracted var timestamp
|
||||
@@ -193,6 +193,11 @@ type API struct {
|
||||
Extract map[string]string `json:"extract,omitempty" yaml:"extract,omitempty"`
|
||||
Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"`
|
||||
Export []string `json:"export,omitempty" yaml:"export,omitempty"`
|
||||
Path string
|
||||
}
|
||||
|
||||
func (api *API) GetPath() string {
|
||||
return api.Path
|
||||
}
|
||||
|
||||
func (api *API) ToAPI() (*API, error) {
|
||||
@@ -210,6 +215,7 @@ type Validator struct {
|
||||
// IAPI represents interface for api,
|
||||
// includes API and APIPath.
|
||||
type IAPI interface {
|
||||
GetPath() string
|
||||
ToAPI() (*API, error)
|
||||
}
|
||||
|
||||
@@ -219,8 +225,8 @@ type TStep struct {
|
||||
Name string `json:"name" yaml:"name"` // required
|
||||
Request *Request `json:"request,omitempty" yaml:"request,omitempty"`
|
||||
APIPath string `json:"api,omitempty" yaml:"api,omitempty"`
|
||||
TestCasePath string `json:"testcase,omitempty" yaml:"testcase,omitempty"`
|
||||
APIContent IAPI `json:"api_content,omitempty" yaml:"api_content,omitempty"`
|
||||
TestCasePath string `json:"testcase,omitempty" yaml:"testcase,omitempty"`
|
||||
TestCaseContent ITestCase `json:"testcase_content,omitempty" yaml:"testcase_content,omitempty"`
|
||||
Transaction *Transaction `json:"transaction,omitempty" yaml:"transaction,omitempty"`
|
||||
Rendezvous *Rendezvous `json:"rendezvous,omitempty" yaml:"rendezvous,omitempty"`
|
||||
@@ -287,6 +293,10 @@ type TCase struct {
|
||||
TestSteps []*TStep `json:"teststeps" yaml:"teststeps"`
|
||||
}
|
||||
|
||||
func (tc *TCase) Path() string {
|
||||
return tc.Config.Path
|
||||
}
|
||||
|
||||
// IStep represents interface for all types for teststeps, includes:
|
||||
// StepRequest, StepRequestWithOptionalArgs, StepRequestValidation, StepRequestExtraction,
|
||||
// StepTestCaseWithOptionalArgs,
|
||||
@@ -300,6 +310,7 @@ type IStep interface {
|
||||
// ITestCase represents interface for testcases,
|
||||
// includes TestCase and TestCasePath.
|
||||
type ITestCase interface {
|
||||
GetPath() string
|
||||
ToTestCase() (*TestCase, error)
|
||||
ToTCase() (*TCase, error)
|
||||
}
|
||||
@@ -311,6 +322,10 @@ type TestCase struct {
|
||||
TestSteps []IStep
|
||||
}
|
||||
|
||||
func (tc *TestCase) GetPath() string {
|
||||
return tc.Config.Path
|
||||
}
|
||||
|
||||
func (tc *TestCase) ToTestCase() (*TestCase, error) {
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package hrp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -742,7 +743,7 @@ func TestParseParameters(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
map[string]interface{}{
|
||||
"username-password": "${parameterize(../examples/hrp/account.csv)}",
|
||||
"username-password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir),
|
||||
"user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}},
|
||||
6,
|
||||
},
|
||||
@@ -782,17 +783,17 @@ func TestParseParametersError(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
map[string]interface{}{
|
||||
"username_password": "${parameterize(../examples/hrp/account.csv)}",
|
||||
"username_password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir),
|
||||
"user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}},
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"username-password": "${parameterize(../examples/hrp/account.csv)}",
|
||||
"username-password": fmt.Sprintf("${parameterize(%s/account.csv)}", hrpExamplesDir),
|
||||
"user-agent": []interface{}{"IOS/10.1", "IOS/10.2"}},
|
||||
},
|
||||
{
|
||||
map[string]interface{}{
|
||||
"username-password": "${param(../examples/hrp/account.csv)}",
|
||||
"username-password": fmt.Sprintf("${param(%s/account.csv)}", hrpExamplesDir),
|
||||
"user_agent": []interface{}{"IOS/10.1", "IOS/10.2"}},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,32 +8,24 @@ import (
|
||||
|
||||
func TestLocateFile(t *testing.T) {
|
||||
// specify target file path
|
||||
_, err := locateFile("../examples/hrp/plugin/debugtalk.go", "debugtalk.go")
|
||||
_, err := locateFile(templatesDir+"plugin/debugtalk.go", "debugtalk.go")
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// specify path with the same dir
|
||||
_, err = locateFile("../examples/hrp/plugin/hashicorp.go", "debugtalk.go")
|
||||
_, err = locateFile(templatesDir+"plugin/debugtalk.py", "debugtalk.go")
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// specify target file path dir
|
||||
_, err = locateFile("../examples/hrp/plugin/", "debugtalk.go")
|
||||
_, err = locateFile(templatesDir+"plugin/", "debugtalk.go")
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// specify wrong path
|
||||
_, err = locateFile("../examples/hrp", "debugtalk.go")
|
||||
if !assert.Error(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
_, err = locateFile("../examples/hrp/demo.json", "debugtalk.go")
|
||||
if !assert.Error(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
_, err = locateFile(".", "debugtalk.go")
|
||||
if !assert.Error(t, err) {
|
||||
t.Fail()
|
||||
@@ -45,17 +37,17 @@ func TestLocateFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLocatePythonPlugin(t *testing.T) {
|
||||
_, err := locatePlugin("../examples/hrp/debugtalk.py")
|
||||
_, err := locatePlugin(templatesDir + "plugin/debugtalk.py")
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocateGoPlugin(t *testing.T) {
|
||||
buildHashicorpPlugin()
|
||||
defer removeHashicorpPlugin()
|
||||
buildHashicorpGoPlugin()
|
||||
defer removeHashicorpGoPlugin()
|
||||
|
||||
_, err := locatePlugin("../examples/hrp/debugtalk.bin")
|
||||
_, err := locatePlugin(templatesDir + "debugtalk.bin")
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
@@ -238,6 +238,7 @@ func (r *caseRunner) reset() *caseRunner {
|
||||
|
||||
func (r *caseRunner) run() error {
|
||||
config := r.TestCase.Config
|
||||
log.Info().Str("testcase", config.Name).Msg("run testcase start")
|
||||
// init plugin
|
||||
var err error
|
||||
if r.parser.plugin, err = initPlugin(config.Path, r.hrpRunner.pluginLogOn); err != nil {
|
||||
@@ -251,7 +252,6 @@ func (r *caseRunner) run() error {
|
||||
if err := r.parseConfig(config); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info().Str("testcase", config.Name).Msg("run testcase start")
|
||||
|
||||
r.startTime = time.Now()
|
||||
for index := range r.TestCase.TestSteps {
|
||||
@@ -1048,7 +1048,7 @@ func setBodyBytes(req *http.Request, data []byte) {
|
||||
req.ContentLength = int64(len(data))
|
||||
}
|
||||
|
||||
//go:embed internal/report/template.html
|
||||
//go:embed internal/scaffold/templates/report/template.html
|
||||
var reportTemplate string
|
||||
|
||||
func (s *Summary) genHTMLReport() error {
|
||||
|
||||
@@ -8,31 +8,49 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/httprunner/httprunner/hrp/internal/scaffold"
|
||||
)
|
||||
|
||||
func buildHashicorpPlugin() {
|
||||
func buildHashicorpGoPlugin() {
|
||||
log.Info().Msg("[init] build hashicorp go plugin")
|
||||
cmd := exec.Command("go", "build",
|
||||
"-o", "../examples/hrp/debugtalk.bin",
|
||||
"../examples/hrp/plugin/hashicorp.go", "../examples/hrp/plugin/debugtalk.go")
|
||||
"-o", templatesDir+"debugtalk.bin", templatesDir+"plugin/debugtalk.go")
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func removeHashicorpPlugin() {
|
||||
log.Info().Msg("[teardown] remove hashicorp plugin")
|
||||
os.Remove("../examples/hrp/debugtalk.bin")
|
||||
func removeHashicorpGoPlugin() {
|
||||
log.Info().Msg("[teardown] remove hashicorp go plugin")
|
||||
os.Remove(templatesDir + "debugtalk.bin")
|
||||
}
|
||||
|
||||
func TestHttpRunnerWithGoPlugin(t *testing.T) {
|
||||
buildHashicorpPlugin()
|
||||
defer removeHashicorpPlugin()
|
||||
func buildHashicorpPyPlugin() {
|
||||
log.Info().Msg("[init] prepare hashicorp python plugin")
|
||||
pluginFile := templatesDir + "debugtalk.py"
|
||||
err := scaffold.CopyFile("templates/plugin/debugtalk.py", pluginFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func removeHashicorpPyPlugin() {
|
||||
log.Info().Msg("[teardown] remove hashicorp python plugin")
|
||||
os.Remove(templatesDir + "debugtalk.py")
|
||||
}
|
||||
|
||||
func TestRunCaseWithGoPlugin(t *testing.T) {
|
||||
buildHashicorpGoPlugin()
|
||||
defer removeHashicorpGoPlugin()
|
||||
|
||||
assertRunTestCases(t)
|
||||
}
|
||||
|
||||
func TestHttpRunnerWithPythonPlugin(t *testing.T) {
|
||||
func TestRunCaseWithPythonPlugin(t *testing.T) {
|
||||
buildHashicorpPyPlugin()
|
||||
defer removeHashicorpPyPlugin()
|
||||
|
||||
assertRunTestCases(t)
|
||||
}
|
||||
|
||||
@@ -41,19 +59,19 @@ func assertRunTestCases(t *testing.T) {
|
||||
Config: NewConfig("TestCase1").
|
||||
SetBaseURL("http://httpbin.org"),
|
||||
TestSteps: []IStep{
|
||||
NewStep("headers").
|
||||
NewStep("testcase1-step1").
|
||||
GET("/headers").
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check status code").
|
||||
AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"),
|
||||
NewStep("user-agent").
|
||||
NewStep("testcase1-step2").
|
||||
GET("/user-agent").
|
||||
Validate().
|
||||
AssertEqual("status_code", 200, "check status code").
|
||||
AssertEqual("headers.\"Content-Type\"", "application/json", "check http response Content-Type"),
|
||||
NewStep("TestCase3").CallRefCase(
|
||||
NewStep("testcase1-step3").CallRefCase(
|
||||
&TestCase{
|
||||
Config: NewConfig("TestCase3").SetBaseURL("http://httpbin.org"),
|
||||
Config: NewConfig("testcase1-step3-ref-case").SetBaseURL("http://httpbin.org"),
|
||||
TestSteps: []IStep{
|
||||
NewStep("ip").
|
||||
GET("/ip").
|
||||
@@ -63,32 +81,23 @@ func assertRunTestCases(t *testing.T) {
|
||||
},
|
||||
},
|
||||
),
|
||||
NewStep("TestCase4").CallRefCase(&demoRefAPIYAMLPath),
|
||||
NewStep("TestCase5").CallRefCase(&demoTestCaseJSONPath),
|
||||
NewStep("testcase1-step4").CallRefCase(&demoTestCaseWithPluginJSONPath),
|
||||
},
|
||||
}
|
||||
testcase2 := &TestCase{
|
||||
Config: NewConfig("TestCase2").SetWeight(3),
|
||||
}
|
||||
testcase3 := &TestCase{
|
||||
Config: NewConfig("TestCase1").
|
||||
SetBaseURL("https://postman-echo.com"),
|
||||
TestSteps: []IStep{
|
||||
NewStep("TestCase5").CallRefAPI(&demoAPIYAMLPath),
|
||||
},
|
||||
}
|
||||
testcase4 := &demoRefTestCaseJSONPath
|
||||
|
||||
r := NewRunner(t)
|
||||
r.SetPluginLogOn()
|
||||
err := r.Run(testcase1, testcase2, testcase3, testcase4)
|
||||
err := r.Run(testcase1, testcase2)
|
||||
if err != nil {
|
||||
t.Fatalf("run testcase error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitRendezvous(t *testing.T) {
|
||||
rendezvousBonudaryTestcase := &TestCase{
|
||||
func TestRunCaseWithRendezvous(t *testing.T) {
|
||||
rendezvousBoundaryTestcase := &TestCase{
|
||||
Config: NewConfig("run request with functions").
|
||||
SetBaseURL("https://postman-echo.com").
|
||||
WithVariables(map[string]interface{}{
|
||||
@@ -137,7 +146,7 @@ func TestInitRendezvous(t *testing.T) {
|
||||
{number: 100, percent: 1, timeout: 5000},
|
||||
}
|
||||
|
||||
rendezvousList := initRendezvous(rendezvousBonudaryTestcase, 100)
|
||||
rendezvousList := initRendezvous(rendezvousBoundaryTestcase, 100)
|
||||
|
||||
for i, r := range rendezvousList {
|
||||
if r.Number != expectedRendezvousParams[i].number {
|
||||
@@ -152,9 +161,9 @@ func TestInitRendezvous(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestThinkTime(t *testing.T) {
|
||||
buildHashicorpPlugin()
|
||||
defer removeHashicorpPlugin()
|
||||
func TestRunCaseWithThinkTime(t *testing.T) {
|
||||
buildHashicorpGoPlugin()
|
||||
defer removeHashicorpGoPlugin()
|
||||
|
||||
testcases := []*TestCase{
|
||||
{
|
||||
@@ -187,7 +196,8 @@ func TestThinkTime(t *testing.T) {
|
||||
{
|
||||
Config: NewConfig("TestCase5"),
|
||||
TestSteps: []IStep{
|
||||
NewStep("thinkTime").CallRefCase(&demoThinkTimeJsonPath), // think time: 3s, random pct: {"min_percentage":1, "max_percentage":1.5}, limit: 4s
|
||||
// think time: 3s, random pct: {"min_percentage":1, "max_percentage":1.5}, limit: 4s
|
||||
NewStep("thinkTime").CallRefCase(&demoTestCaseWithThinkTimePath),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -229,3 +239,47 @@ func TestGenHTMLReport(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCaseWithPluginJSON(t *testing.T) {
|
||||
buildHashicorpGoPlugin()
|
||||
defer removeHashicorpGoPlugin()
|
||||
|
||||
err := NewRunner(nil).Run(&demoTestCaseWithPluginJSONPath) // hrp.Run(testCase)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCaseWithPluginYAML(t *testing.T) {
|
||||
buildHashicorpGoPlugin()
|
||||
defer removeHashicorpGoPlugin()
|
||||
|
||||
err := NewRunner(nil).Run(&demoTestCaseWithPluginYAMLPath) // hrp.Run(testCase)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCaseWithRefAPI(t *testing.T) {
|
||||
buildHashicorpGoPlugin()
|
||||
defer removeHashicorpGoPlugin()
|
||||
|
||||
err := NewRunner(nil).Run(&demoTestCaseWithRefAPIPath)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
testcase := &TestCase{
|
||||
Config: NewConfig("TestCase").
|
||||
SetBaseURL("https://postman-echo.com"),
|
||||
TestSteps: []IStep{
|
||||
NewStep("run referenced api").CallRefAPI(&demoAPIGETPath),
|
||||
},
|
||||
}
|
||||
|
||||
r := NewRunner(t)
|
||||
err = r.Run(testcase)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
21
hrp/step.go
21
hrp/step.go
@@ -1,6 +1,11 @@
|
||||
package hrp
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// NewConfig returns a new constructed testcase config with specified testcase name.
|
||||
func NewConfig(name string) *TConfig {
|
||||
@@ -163,7 +168,12 @@ func (s *StepRequest) PATCH(url string) *StepRequestWithOptionalArgs {
|
||||
|
||||
// CallRefCase calls a referenced testcase.
|
||||
func (s *StepRequest) CallRefCase(tc ITestCase) *StepTestCaseWithOptionalArgs {
|
||||
s.step.TestCaseContent, _ = tc.ToTestCase()
|
||||
var err error
|
||||
s.step.TestCaseContent, err = tc.ToTestCase()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to load testcase")
|
||||
os.Exit(1)
|
||||
}
|
||||
return &StepTestCaseWithOptionalArgs{
|
||||
step: s.step,
|
||||
}
|
||||
@@ -171,7 +181,12 @@ func (s *StepRequest) CallRefCase(tc ITestCase) *StepTestCaseWithOptionalArgs {
|
||||
|
||||
// CallRefAPI calls a referenced api.
|
||||
func (s *StepRequest) CallRefAPI(api IAPI) *StepAPIWithOptionalArgs {
|
||||
s.step.APIContent, _ = api.ToAPI()
|
||||
var err error
|
||||
s.step.APIContent, err = api.ToAPI()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to load api")
|
||||
os.Exit(1)
|
||||
}
|
||||
return &StepAPIWithOptionalArgs{
|
||||
step: s.step,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package examples
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
package examples
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
package examples
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
package examples
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
package examples
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
package examples
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,50 +0,0 @@
|
||||
import unittest
|
||||
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
from httprunner.app.main import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
class TestDebug(unittest.TestCase):
|
||||
def test_debug_single_testcase(self):
|
||||
json_data = {
|
||||
"project_meta": {
|
||||
"debugtalk_py": "\ndef hello(name):\n print(f'hello, {name}')\n",
|
||||
"variables": {},
|
||||
"env": {},
|
||||
},
|
||||
"testcase": {
|
||||
"config": {
|
||||
"name": "test demo for debug service",
|
||||
"verify": False,
|
||||
"base_url": "",
|
||||
"variables": {},
|
||||
"setup_hooks": [],
|
||||
"teardown_hooks": [],
|
||||
"export": [],
|
||||
},
|
||||
"teststeps": [
|
||||
{
|
||||
"name": "get index page",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "https://httpbin.org/",
|
||||
"params": {},
|
||||
"headers": {},
|
||||
"json": {},
|
||||
"cookies": {},
|
||||
"timeout": 30,
|
||||
"allow_redirects": True,
|
||||
"verify": False,
|
||||
},
|
||||
"extract": {},
|
||||
"validate": [],
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
response = client.post("/hrun/debug/testcase", json=json_data)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["code"] == 0
|
||||
@@ -1,16 +0,0 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
from httprunner import __version__
|
||||
from .routers import deps, debugtalk, debug
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/hrun/version")
|
||||
async def get_hrun_version():
|
||||
return {"code": 0, "message": "success", "result": {"HttpRunner": __version__}}
|
||||
|
||||
|
||||
app.include_router(deps.router)
|
||||
app.include_router(debugtalk.router)
|
||||
app.include_router(debug.router)
|
||||
@@ -1,54 +0,0 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from httprunner.runner import HttpRunner
|
||||
from httprunner.models import ProjectMeta, TestCase
|
||||
|
||||
router = APIRouter()
|
||||
runner = HttpRunner()
|
||||
|
||||
|
||||
@router.post("/hrun/debug/testcase", tags=["debug"])
|
||||
async def debug_single_testcase(project_meta: ProjectMeta, testcase: TestCase):
|
||||
resp = {"code": 0, "message": "success", "result": {}}
|
||||
|
||||
if project_meta.debugtalk_py:
|
||||
origin_local_keys = list(locals().keys()).copy()
|
||||
exec(project_meta.debugtalk_py, {}, locals())
|
||||
new_local_keys = list(locals().keys()).copy()
|
||||
new_added_keys = set(new_local_keys) - set(origin_local_keys)
|
||||
new_added_keys.remove("origin_local_keys")
|
||||
for func_name in new_added_keys:
|
||||
project_meta.functions[func_name] = locals()[func_name]
|
||||
|
||||
runner.with_project_meta(project_meta).run_testcase(testcase)
|
||||
summary = runner.get_summary()
|
||||
|
||||
if not summary.success:
|
||||
resp["code"] = 1
|
||||
resp["message"] = "fail"
|
||||
|
||||
resp["result"] = summary.dict()
|
||||
return resp
|
||||
|
||||
|
||||
# @router.post("/hrun/debug/api", tags=["debug"])
|
||||
# async def debug_single_api():
|
||||
# resp = {
|
||||
# "code": 0,
|
||||
# "message": "success",
|
||||
# "result": {}
|
||||
# }
|
||||
#
|
||||
# # tests_mapping
|
||||
#
|
||||
# # summary = runner.run_tests(tests_mapping)
|
||||
#
|
||||
# return resp
|
||||
#
|
||||
#
|
||||
# @router.post("/hrun/debug/testcases", tags=["debug"])
|
||||
# async def debug_multiple_testcases(project_meta: ProjectMeta, testcases: TestCases):
|
||||
# tests_mapping = {
|
||||
# "project_meta": project_meta,
|
||||
# "testcases": testcases
|
||||
# }
|
||||
@@ -1,42 +0,0 @@
|
||||
import contextlib
|
||||
import sys
|
||||
from io import StringIO
|
||||
|
||||
from fastapi import APIRouter
|
||||
from loguru import logger
|
||||
from starlette.requests import Request
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def stdout_io(stdout=None):
|
||||
old = sys.stdout
|
||||
if stdout is None:
|
||||
stdout = StringIO()
|
||||
sys.stdout = stdout
|
||||
yield stdout
|
||||
sys.stdout = old
|
||||
|
||||
|
||||
@router.post("/hrun/debug/debugtalk_py", tags=["debugtalk"])
|
||||
async def debug_python(request: Request):
|
||||
body = await request.body()
|
||||
|
||||
if request.headers.get("content-transfer-encoding") == "base64":
|
||||
# TODO: decode base64
|
||||
pass
|
||||
|
||||
resp = {"code": 0, "message": "success", "result": ""}
|
||||
try:
|
||||
with stdout_io() as s:
|
||||
exec(body, globals())
|
||||
output = s.getvalue()
|
||||
resp["result"] = output
|
||||
except Exception as ex:
|
||||
resp["code"] = 1
|
||||
resp["message"] = "fail"
|
||||
resp["result"] = str(ex)
|
||||
logger.error(resp)
|
||||
|
||||
return resp
|
||||
@@ -1,34 +0,0 @@
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
import pkg_resources
|
||||
from fastapi import APIRouter
|
||||
from loguru import logger
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/hrun/deps", tags=["deps"])
|
||||
async def get_installed_dependenies():
|
||||
resp = {"code": 0, "message": "success", "result": {}}
|
||||
for p in pkg_resources.working_set:
|
||||
resp["result"][p.project_name] = p.version
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
@router.post("/hrun/deps", tags=["deps"])
|
||||
async def install_dependenies(deps: List[str]):
|
||||
resp = {"code": 0, "message": "success", "result": {}}
|
||||
for dep in deps:
|
||||
try:
|
||||
p = subprocess.run(["pip", "install", dep])
|
||||
assert p.returncode == 0
|
||||
resp["result"][dep] = True
|
||||
except (AssertionError, subprocess.SubprocessError):
|
||||
resp["result"][dep] = False
|
||||
resp["code"] = 1
|
||||
resp["message"] = "fail"
|
||||
logger.error(f"failed to install dependency: {dep}")
|
||||
|
||||
return resp
|
||||
@@ -8,10 +8,8 @@ from loguru import logger
|
||||
|
||||
from httprunner import __description__, __version__
|
||||
from httprunner.compat import ensure_cli_args
|
||||
from httprunner.ext.har2case import init_har2case_parser, main_har2case
|
||||
from httprunner.make import init_make_parser, main_make
|
||||
from httprunner.scaffold import init_parser_scaffold, main_scaffold
|
||||
from httprunner.utils import init_sentry_sdk, ga_client
|
||||
from httprunner.utils import ga_client, init_sentry_sdk
|
||||
|
||||
init_sentry_sdk()
|
||||
|
||||
@@ -66,8 +64,6 @@ def main():
|
||||
|
||||
subparsers = parser.add_subparsers(help="sub-command help")
|
||||
sub_parser_run = init_parser_run(subparsers)
|
||||
sub_parser_scaffold = init_parser_scaffold(subparsers)
|
||||
sub_parser_har2case = init_har2case_parser(subparsers)
|
||||
sub_parser_make = init_make_parser(subparsers)
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
@@ -82,12 +78,6 @@ def main():
|
||||
elif sys.argv[1] in ["-h", "--help"]:
|
||||
# httprunner -h
|
||||
parser.print_help()
|
||||
elif sys.argv[1] == "startproject":
|
||||
# httprunner startproject
|
||||
sub_parser_scaffold.print_help()
|
||||
elif sys.argv[1] == "har2case":
|
||||
# httprunner har2case
|
||||
sub_parser_har2case.print_help()
|
||||
elif sys.argv[1] == "run":
|
||||
# httprunner run
|
||||
pytest.main(["-h"])
|
||||
@@ -114,10 +104,6 @@ def main():
|
||||
|
||||
if sys.argv[1] == "run":
|
||||
sys.exit(main_run(extra_args))
|
||||
elif sys.argv[1] == "startproject":
|
||||
main_scaffold(args)
|
||||
elif sys.argv[1] == "har2case":
|
||||
main_har2case(args)
|
||||
elif sys.argv[1] == "make":
|
||||
main_make(args.testcase_path)
|
||||
|
||||
@@ -150,13 +136,5 @@ def main_make_alias():
|
||||
main()
|
||||
|
||||
|
||||
def main_har2case_alias():
|
||||
""" command alias
|
||||
har2case = httprunner har2case
|
||||
"""
|
||||
sys.argv.insert(1, "har2case")
|
||||
main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
""" Convert HAR (HTTP Archive) to YAML/JSON testcase for HttpRunner.
|
||||
|
||||
Usage:
|
||||
# convert to JSON format testcase
|
||||
$ hrun har2case demo.har
|
||||
|
||||
# convert to YAML format testcase
|
||||
$ hrun har2case demo.har -2y
|
||||
|
||||
"""
|
||||
|
||||
from httprunner.ext.har2case.core import HarParser
|
||||
from httprunner.utils import ga_client
|
||||
|
||||
|
||||
def init_har2case_parser(subparsers):
|
||||
""" HAR converter: parse command line options and run commands.
|
||||
"""
|
||||
parser = subparsers.add_parser(
|
||||
"har2case",
|
||||
help="Convert HAR(HTTP Archive) to YAML/JSON testcases for HttpRunner.",
|
||||
)
|
||||
parser.add_argument("har_source_file", nargs="?", help="Specify HAR source file")
|
||||
parser.add_argument(
|
||||
"-2y",
|
||||
"--to-yml",
|
||||
"--to-yaml",
|
||||
dest="to_yaml",
|
||||
action="store_true",
|
||||
help="Convert to YAML format, if not specified, convert to pytest format by default.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-2j",
|
||||
"--to-json",
|
||||
dest="to_json",
|
||||
action="store_true",
|
||||
help="Convert to JSON format, if not specified, convert to pytest format by default.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--filter",
|
||||
help="Specify filter keyword, only url include filter string will be converted.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--exclude",
|
||||
help="Specify exclude keyword, url that includes exclude string will be ignored, "
|
||||
"multiple keywords can be joined with '|'",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--profile",
|
||||
dest="profile",
|
||||
help="Specify yaml file to overwrite headers and cookies in HAR.",
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main_har2case(args):
|
||||
har_source_file = args.har_source_file
|
||||
|
||||
if args.to_yaml:
|
||||
output_file_type = "YAML"
|
||||
elif args.to_json:
|
||||
output_file_type = "JSON"
|
||||
else:
|
||||
output_file_type = "pytest"
|
||||
|
||||
ga_client.track_event("ConvertTests", f"har2case {output_file_type}")
|
||||
HarParser(har_source_file, args.filter, args.exclude, args.profile).gen_testcase(output_file_type)
|
||||
|
||||
return 0
|
||||
@@ -1,385 +0,0 @@
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.parse as urlparse
|
||||
from typing import Text
|
||||
|
||||
from httprunner.compat import ensure_path_sep
|
||||
from loguru import logger
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
from httprunner.ext.har2case import utils
|
||||
from httprunner.make import make_testcase, format_pytest_with_black
|
||||
from httprunner.loader import load_test_file
|
||||
|
||||
try:
|
||||
from json.decoder import JSONDecodeError
|
||||
except ImportError:
|
||||
JSONDecodeError = ValueError
|
||||
|
||||
|
||||
def ensure_file_path(path: Text) -> Text:
|
||||
|
||||
if not path or not path.endswith(".har"):
|
||||
logger.error("HAR file not specified.")
|
||||
sys.exit(1)
|
||||
|
||||
path = ensure_path_sep(path)
|
||||
if not os.path.isfile(path):
|
||||
logger.error(f"HAR file not exists: {path}")
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(os.getcwd(), path)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
class HarParser(object):
|
||||
def __init__(self, har_file_path, filter_str=None, exclude_str=None, profile=None):
|
||||
self.har_file_path = ensure_file_path(har_file_path)
|
||||
self.filter_str = filter_str
|
||||
self.exclude_str = exclude_str or ""
|
||||
self.profile = profile and load_test_file(profile)
|
||||
|
||||
def __make_request_url(self, teststep_dict, entry_json):
|
||||
""" parse HAR entry request url and queryString, and make teststep url and params
|
||||
|
||||
Args:
|
||||
entry_json (dict):
|
||||
{
|
||||
"request": {
|
||||
"url": "https://httprunner.top/home?v=1&w=2",
|
||||
"queryString": [
|
||||
{"name": "v", "value": "1"},
|
||||
{"name": "w", "value": "2"}
|
||||
],
|
||||
},
|
||||
"response": {}
|
||||
}
|
||||
|
||||
Returns:
|
||||
{
|
||||
"name: "/home",
|
||||
"request": {
|
||||
url: "https://httprunner.top/home",
|
||||
params: {"v": "1", "w": "2"}
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
request_params = utils.convert_list_to_dict(
|
||||
entry_json["request"].get("queryString", [])
|
||||
)
|
||||
|
||||
url = entry_json["request"].get("url")
|
||||
if not url:
|
||||
logger.exception("url missed in request.")
|
||||
sys.exit(1)
|
||||
|
||||
parsed_object = urlparse.urlparse(url)
|
||||
if request_params:
|
||||
parsed_object = parsed_object._replace(query="")
|
||||
teststep_dict["request"]["url"] = parsed_object.geturl()
|
||||
teststep_dict["request"]["params"] = request_params
|
||||
else:
|
||||
teststep_dict["request"]["url"] = url
|
||||
|
||||
teststep_dict["name"] = parsed_object.path
|
||||
|
||||
def __make_request_method(self, teststep_dict, entry_json):
|
||||
""" parse HAR entry request method, and make teststep method.
|
||||
"""
|
||||
method = entry_json["request"].get("method")
|
||||
if not method:
|
||||
logger.exception("method missed in request.")
|
||||
sys.exit(1)
|
||||
|
||||
teststep_dict["request"]["method"] = method
|
||||
|
||||
def __make_request_cookies(self, teststep_dict, entry_json):
|
||||
if self.profile and self.profile.get("cookies"):
|
||||
teststep_dict["request"]["cookies"] = self.profile.get("cookies")
|
||||
else:
|
||||
cookies = {}
|
||||
for cookie in entry_json["request"].get("cookies", []):
|
||||
cookies[cookie["name"]] = cookie["value"]
|
||||
|
||||
if cookies:
|
||||
teststep_dict["request"]["cookies"] = cookies
|
||||
|
||||
def __make_request_headers(self, teststep_dict, entry_json):
|
||||
""" parse HAR entry request headers, and make teststep headers.
|
||||
header in IGNORE_REQUEST_HEADERS will be ignored.
|
||||
|
||||
Args:
|
||||
entry_json (dict):
|
||||
{
|
||||
"request": {
|
||||
"headers": [
|
||||
{"name": "Host", "value": "httprunner.top"},
|
||||
{"name": "Content-Type", "value": "application/json"},
|
||||
{"name": "User-Agent", "value": "iOS/10.3"}
|
||||
],
|
||||
},
|
||||
"response": {}
|
||||
}
|
||||
|
||||
Returns:
|
||||
{
|
||||
"request": {
|
||||
headers: {"Content-Type": "application/json"}
|
||||
}
|
||||
|
||||
"""
|
||||
if self.profile and self.profile.get("headers"):
|
||||
teststep_dict["request"]["headers"] = self.profile.get("headers")
|
||||
else:
|
||||
teststep_headers = {}
|
||||
for header in entry_json["request"].get("headers", []):
|
||||
if header["name"] == "cookie" or header["name"].startswith(":"):
|
||||
continue
|
||||
|
||||
teststep_headers[header["name"]] = header["value"]
|
||||
|
||||
if teststep_headers:
|
||||
teststep_dict["request"]["headers"] = teststep_headers
|
||||
|
||||
def _make_request_data(self, teststep_dict, entry_json):
|
||||
""" parse HAR entry request data, and make teststep request data
|
||||
|
||||
Args:
|
||||
entry_json (dict):
|
||||
{
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"postData": {
|
||||
"mimeType": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"params": [
|
||||
{"name": "a", "value": 1},
|
||||
{"name": "b", "value": "2"}
|
||||
}
|
||||
},
|
||||
},
|
||||
"response": {...}
|
||||
}
|
||||
|
||||
|
||||
Returns:
|
||||
{
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"data": {"v": "1", "w": "2"}
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
method = entry_json["request"].get("method")
|
||||
if method in ["POST", "PUT", "PATCH"]:
|
||||
postData = entry_json["request"].get("postData", {})
|
||||
mimeType = postData.get("mimeType")
|
||||
|
||||
# Note that text and params fields are mutually exclusive.
|
||||
if "text" in postData:
|
||||
post_data = postData.get("text")
|
||||
else:
|
||||
params = postData.get("params", [])
|
||||
post_data = utils.convert_list_to_dict(params)
|
||||
|
||||
request_data_key = "data"
|
||||
if not mimeType:
|
||||
pass
|
||||
elif mimeType.startswith("application/json"):
|
||||
try:
|
||||
post_data = json.loads(post_data)
|
||||
request_data_key = "json"
|
||||
except JSONDecodeError:
|
||||
pass
|
||||
elif mimeType.startswith("application/x-www-form-urlencoded"):
|
||||
post_data = utils.convert_x_www_form_urlencoded_to_dict(post_data)
|
||||
else:
|
||||
# TODO: make compatible with more mimeType
|
||||
pass
|
||||
|
||||
teststep_dict["request"][request_data_key] = post_data
|
||||
|
||||
def _make_validate(self, teststep_dict, entry_json):
|
||||
""" parse HAR entry response and make teststep validate.
|
||||
|
||||
Args:
|
||||
entry_json (dict):
|
||||
{
|
||||
"request": {},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json; charset=utf-8"
|
||||
},
|
||||
],
|
||||
"content": {
|
||||
"size": 71,
|
||||
"mimeType": "application/json; charset=utf-8",
|
||||
"text": "eyJJc1N1Y2Nlc3MiOnRydWUsIkNvZGUiOjIwMCwiTWVzc2FnZSI6bnVsbCwiVmFsdWUiOnsiQmxuUmVzdWx0Ijp0cnVlfX0=",
|
||||
"encoding": "base64"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Returns:
|
||||
{
|
||||
"validate": [
|
||||
{"eq": ["status_code", 200]}
|
||||
]
|
||||
}
|
||||
|
||||
"""
|
||||
teststep_dict["validate"].append(
|
||||
{"eq": ["status_code", entry_json["response"].get("status")]}
|
||||
)
|
||||
|
||||
resp_content_dict = entry_json["response"].get("content")
|
||||
|
||||
headers_mapping = utils.convert_list_to_dict(
|
||||
entry_json["response"].get("headers", [])
|
||||
)
|
||||
if "Content-Type" in headers_mapping:
|
||||
teststep_dict["validate"].append(
|
||||
{"eq": ["headers.Content-Type", headers_mapping["Content-Type"]]}
|
||||
)
|
||||
|
||||
text = resp_content_dict.get("text")
|
||||
if not text:
|
||||
return
|
||||
|
||||
mime_type = resp_content_dict.get("mimeType")
|
||||
if mime_type and mime_type.startswith("application/json"):
|
||||
|
||||
encoding = resp_content_dict.get("encoding")
|
||||
if encoding and encoding == "base64":
|
||||
content = base64.b64decode(text)
|
||||
try:
|
||||
content = content.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
logger.warning(f"failed to decode base64 content with utf-8 !")
|
||||
return
|
||||
else:
|
||||
content = text
|
||||
|
||||
try:
|
||||
resp_content_json = json.loads(content)
|
||||
except JSONDecodeError:
|
||||
logger.warning(f"response content can not be loaded as json: {content}")
|
||||
return
|
||||
|
||||
if not isinstance(resp_content_json, dict):
|
||||
# e.g. ['a', 'b']
|
||||
return
|
||||
|
||||
for key, value in resp_content_json.items():
|
||||
if isinstance(value, (dict, list)):
|
||||
continue
|
||||
|
||||
teststep_dict["validate"].append({"eq": ["body.{}".format(key), value]})
|
||||
|
||||
def _prepare_teststep(self, entry_json):
|
||||
""" extract info from entry dict and make teststep
|
||||
|
||||
Args:
|
||||
entry_json (dict):
|
||||
{
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://httprunner.top/api/v1/Account/Login",
|
||||
"headers": [],
|
||||
"queryString": [],
|
||||
"postData": {},
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": [],
|
||||
"content": {}
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
teststep_dict = {"name": "", "request": {}, "validate": []}
|
||||
|
||||
self.__make_request_url(teststep_dict, entry_json)
|
||||
self.__make_request_method(teststep_dict, entry_json)
|
||||
self.__make_request_cookies(teststep_dict, entry_json)
|
||||
self.__make_request_headers(teststep_dict, entry_json)
|
||||
self._make_request_data(teststep_dict, entry_json)
|
||||
self._make_validate(teststep_dict, entry_json)
|
||||
|
||||
return teststep_dict
|
||||
|
||||
def _prepare_config(self):
|
||||
""" prepare config block.
|
||||
"""
|
||||
return {"name": "testcase description", "variables": {}, "verify": False}
|
||||
|
||||
def _prepare_teststeps(self):
|
||||
""" make teststep list.
|
||||
teststeps list are parsed from HAR log entries list.
|
||||
|
||||
"""
|
||||
|
||||
def is_exclude(url, exclude_str):
|
||||
exclude_str_list = exclude_str.split("|")
|
||||
for exclude_str in exclude_str_list:
|
||||
if exclude_str and exclude_str in url:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
teststeps = []
|
||||
log_entries = utils.load_har_log_entries(self.har_file_path)
|
||||
for entry_json in log_entries:
|
||||
url = entry_json["request"].get("url")
|
||||
if self.filter_str and self.filter_str not in url:
|
||||
continue
|
||||
|
||||
if is_exclude(url, self.exclude_str):
|
||||
continue
|
||||
|
||||
teststeps.append(self._prepare_teststep(entry_json))
|
||||
|
||||
return teststeps
|
||||
|
||||
def _make_testcase(self):
|
||||
""" Extract info from HAR file and prepare for testcase
|
||||
"""
|
||||
logger.info("Extract info from HAR file and prepare for testcase.")
|
||||
|
||||
config = self._prepare_config()
|
||||
teststeps = self._prepare_teststeps()
|
||||
|
||||
testcase = {"config": config, "teststeps": teststeps}
|
||||
return testcase
|
||||
|
||||
def gen_testcase(self, file_type="pytest"):
|
||||
logger.info(f"Start to generate testcase from {self.har_file_path}")
|
||||
harfile = os.path.splitext(self.har_file_path)[0]
|
||||
|
||||
try:
|
||||
testcase = self._make_testcase()
|
||||
except Exception as ex:
|
||||
capture_exception(ex)
|
||||
raise
|
||||
|
||||
if file_type == "JSON":
|
||||
output_testcase_file = f"{harfile}.json"
|
||||
utils.dump_json(testcase, output_testcase_file)
|
||||
elif file_type == "YAML":
|
||||
output_testcase_file = f"{harfile}.yml"
|
||||
utils.dump_yaml(testcase, output_testcase_file)
|
||||
else:
|
||||
# default to generate pytest file
|
||||
testcase["config"]["path"] = self.har_file_path
|
||||
output_testcase_file = make_testcase(testcase)
|
||||
format_pytest_with_black(output_testcase_file)
|
||||
|
||||
logger.info(f"generated testcase: {output_testcase_file}")
|
||||
@@ -1,180 +0,0 @@
|
||||
import os
|
||||
|
||||
from httprunner.ext.har2case.core import HarParser
|
||||
from httprunner.ext.har2case.utils import load_har_log_entries
|
||||
from httprunner.ext.har2case.utils_test import TestHar2CaseUtils
|
||||
|
||||
|
||||
class TestHar(TestHar2CaseUtils):
|
||||
def setUp(self):
|
||||
self.data_dir = os.path.join(os.getcwd(), "examples", "data", "har2case")
|
||||
self.har_path = os.path.join(self.data_dir, "demo.har")
|
||||
self.har_parser = HarParser(self.har_path)
|
||||
self.profile_path = os.path.join(self.data_dir, "profile.yml")
|
||||
|
||||
def test_prepare_teststep(self):
|
||||
log_entries = load_har_log_entries(self.har_path)
|
||||
teststep_dict = self.har_parser._prepare_teststep(log_entries[0])
|
||||
self.assertIn("name", teststep_dict)
|
||||
self.assertIn("request", teststep_dict)
|
||||
self.assertIn("validate", teststep_dict)
|
||||
|
||||
validators_mapping = {
|
||||
validator["eq"][0]: validator["eq"][1]
|
||||
for validator in teststep_dict["validate"]
|
||||
}
|
||||
self.assertEqual(validators_mapping["status_code"], 200)
|
||||
self.assertEqual(validators_mapping["body.IsSuccess"], True)
|
||||
self.assertEqual(validators_mapping["body.Code"], 200)
|
||||
self.assertEqual(validators_mapping["body.Message"], None)
|
||||
|
||||
def test_prepare_teststeps(self):
|
||||
teststeps = self.har_parser._prepare_teststeps()
|
||||
self.assertIsInstance(teststeps, list)
|
||||
self.assertIn("name", teststeps[0])
|
||||
self.assertIn("request", teststeps[0])
|
||||
self.assertIn("validate", teststeps[0])
|
||||
|
||||
def test_gen_testcase_yaml(self):
|
||||
yaml_file = os.path.join(self.data_dir, "demo.yml")
|
||||
|
||||
self.har_parser.gen_testcase(file_type="YAML")
|
||||
self.assertTrue(os.path.isfile(yaml_file))
|
||||
os.remove(yaml_file)
|
||||
|
||||
def test_gen_testcase_json(self):
|
||||
json_file = os.path.join(self.data_dir, "demo.json")
|
||||
|
||||
self.har_parser.gen_testcase(file_type="JSON")
|
||||
self.assertTrue(os.path.isfile(json_file))
|
||||
os.remove(json_file)
|
||||
|
||||
def test_profile(self):
|
||||
har_parser = HarParser(self.har_path, profile=self.profile_path)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(
|
||||
teststeps[0]["request"]["headers"],
|
||||
{"Content-Type": "application/x-www-form-urlencoded"},
|
||||
)
|
||||
self.assertEqual(
|
||||
teststeps[0]["request"]["cookies"],
|
||||
{"CASTGC": "TGT"},
|
||||
)
|
||||
|
||||
def test_filter(self):
|
||||
filter_str = "httprunner"
|
||||
har_parser = HarParser(self.har_path, filter_str)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(
|
||||
teststeps[0]["request"]["url"],
|
||||
"https://httprunner.top/api/v1/Account/Login",
|
||||
)
|
||||
|
||||
filter_str = "debugtalk"
|
||||
har_parser = HarParser(self.har_path, filter_str)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(teststeps, [])
|
||||
|
||||
def test_exclude(self):
|
||||
exclude_str = "debugtalk"
|
||||
har_parser = HarParser(self.har_path, exclude_str=exclude_str)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(
|
||||
teststeps[0]["request"]["url"],
|
||||
"https://httprunner.top/api/v1/Account/Login",
|
||||
)
|
||||
|
||||
exclude_str = "httprunner"
|
||||
har_parser = HarParser(self.har_path, exclude_str=exclude_str)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(teststeps, [])
|
||||
|
||||
def test_exclude_multiple(self):
|
||||
exclude_str = "httprunner|v2"
|
||||
har_parser = HarParser(self.har_path, exclude_str=exclude_str)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(teststeps, [])
|
||||
|
||||
exclude_str = "http2|v1"
|
||||
har_parser = HarParser(self.har_path, exclude_str=exclude_str)
|
||||
teststeps = har_parser._prepare_teststeps()
|
||||
self.assertEqual(teststeps, [])
|
||||
|
||||
def test_make_request_data_params(self):
|
||||
testcase_dict = {"name": "", "request": {}, "validate": []}
|
||||
entry_json = {
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"postData": {
|
||||
"mimeType": "application/x-www-form-urlencoded; charset=utf-8",
|
||||
"params": [{"name": "a", "value": 1}, {"name": "b", "value": "2"}],
|
||||
},
|
||||
}
|
||||
}
|
||||
self.har_parser._make_request_data(testcase_dict, entry_json)
|
||||
self.assertEqual(testcase_dict["request"]["data"]["a"], 1)
|
||||
self.assertEqual(testcase_dict["request"]["data"]["b"], "2")
|
||||
|
||||
def test_make_request_data_json(self):
|
||||
testcase_dict = {"name": "", "request": {}, "validate": []}
|
||||
entry_json = {
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"postData": {
|
||||
"mimeType": "application/json; charset=utf-8",
|
||||
"text": '{"a":"1","b":"2"}',
|
||||
},
|
||||
}
|
||||
}
|
||||
self.har_parser._make_request_data(testcase_dict, entry_json)
|
||||
self.assertEqual(testcase_dict["request"]["json"], {"a": "1", "b": "2"})
|
||||
|
||||
def test_make_request_data_text_empty(self):
|
||||
testcase_dict = {"name": "", "request": {}, "validate": []}
|
||||
entry_json = {
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"postData": {"mimeType": "application/json; charset=utf-8", "text": ""},
|
||||
}
|
||||
}
|
||||
self.har_parser._make_request_data(testcase_dict, entry_json)
|
||||
self.assertEqual(testcase_dict["request"]["data"], "")
|
||||
|
||||
def test_make_validate(self):
|
||||
testcase_dict = {"name": "", "request": {}, "validate": []}
|
||||
entry_json = {
|
||||
"request": {},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json; charset=utf-8",
|
||||
},
|
||||
],
|
||||
"content": {
|
||||
"size": 71,
|
||||
"mimeType": "application/json; charset=utf-8",
|
||||
# raw response content text is application/jose type
|
||||
"text": "ZXlKaGJHY2lPaUpTVTBFeFh6VWlMQ0psYm1NaU9pSkJNVEk0UTBKRExV",
|
||||
"encoding": "base64",
|
||||
},
|
||||
},
|
||||
}
|
||||
self.har_parser._make_validate(testcase_dict, entry_json)
|
||||
self.assertEqual(testcase_dict["validate"][0], {"eq": ["status_code", 200]})
|
||||
self.assertEqual(
|
||||
testcase_dict["validate"][1],
|
||||
{"eq": ["headers.Content-Type", "application/json; charset=utf-8"]},
|
||||
)
|
||||
|
||||
def test_make_testcase(self):
|
||||
har_path = os.path.join(
|
||||
self.data_dir, "demo-quickstart.har"
|
||||
)
|
||||
har_parser = HarParser(har_path)
|
||||
testcase = har_parser._make_testcase()
|
||||
self.assertIsInstance(testcase, dict)
|
||||
self.assertIn("config", testcase)
|
||||
self.assertIn("teststeps", testcase)
|
||||
self.assertEqual(len(testcase["teststeps"]), 2)
|
||||
@@ -1,130 +0,0 @@
|
||||
import json
|
||||
import sys
|
||||
from json.decoder import JSONDecodeError
|
||||
from urllib.parse import unquote
|
||||
|
||||
import yaml
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def load_har_log_entries(file_path):
|
||||
""" load HAR file and return log entries list
|
||||
|
||||
Args:
|
||||
file_path (str)
|
||||
|
||||
Returns:
|
||||
list: entries
|
||||
[
|
||||
{
|
||||
"request": {},
|
||||
"response": {}
|
||||
},
|
||||
{
|
||||
"request": {},
|
||||
"response": {}
|
||||
}
|
||||
]
|
||||
|
||||
"""
|
||||
with open(file_path, mode="rb") as f:
|
||||
try:
|
||||
content_json = json.load(f)
|
||||
return content_json["log"]["entries"]
|
||||
except (TypeError, JSONDecodeError) as ex:
|
||||
logger.error(f"failed to load HAR file {file_path}: {ex}")
|
||||
sys.exit(1)
|
||||
except KeyError:
|
||||
logger.error(f"log entries not found in HAR file: {content_json}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def x_www_form_urlencoded(post_data):
|
||||
""" convert origin dict to x-www-form-urlencoded
|
||||
|
||||
Args:
|
||||
post_data (dict):
|
||||
{"a": 1, "b":2}
|
||||
|
||||
Returns:
|
||||
str:
|
||||
a=1&b=2
|
||||
|
||||
"""
|
||||
if isinstance(post_data, dict):
|
||||
return "&".join(
|
||||
["{}={}".format(key, value) for key, value in post_data.items()]
|
||||
)
|
||||
else:
|
||||
return post_data
|
||||
|
||||
|
||||
def convert_x_www_form_urlencoded_to_dict(post_data):
|
||||
""" convert x_www_form_urlencoded data to dict
|
||||
|
||||
Args:
|
||||
post_data (str): a=1&b=2
|
||||
|
||||
Returns:
|
||||
dict: {"a":1, "b":2}
|
||||
|
||||
"""
|
||||
if isinstance(post_data, str):
|
||||
converted_dict = {}
|
||||
for k_v in post_data.split("&"):
|
||||
try:
|
||||
key, value = k_v.split("=")
|
||||
except ValueError:
|
||||
raise Exception(
|
||||
"Invalid x_www_form_urlencoded data format: {}".format(post_data)
|
||||
)
|
||||
converted_dict[key] = unquote(value)
|
||||
return converted_dict
|
||||
else:
|
||||
return post_data
|
||||
|
||||
|
||||
def convert_list_to_dict(origin_list):
|
||||
""" convert HAR data list to mapping
|
||||
|
||||
Args:
|
||||
origin_list (list)
|
||||
[
|
||||
{"name": "v", "value": "1"},
|
||||
{"name": "w", "value": "2"}
|
||||
]
|
||||
|
||||
Returns:
|
||||
dict:
|
||||
{"v": "1", "w": "2"}
|
||||
|
||||
"""
|
||||
return {item["name"]: item.get("value") for item in origin_list}
|
||||
|
||||
|
||||
def dump_yaml(testcase, yaml_file):
|
||||
""" dump HAR entries to yaml testcase
|
||||
"""
|
||||
logger.info("dump testcase to YAML format.")
|
||||
|
||||
with open(yaml_file, "w", encoding="utf-8") as outfile:
|
||||
yaml.dump(
|
||||
testcase, outfile, allow_unicode=True, default_flow_style=False, indent=4
|
||||
)
|
||||
|
||||
logger.info("Generate YAML testcase successfully: {}".format(yaml_file))
|
||||
|
||||
|
||||
def dump_json(testcase, json_file):
|
||||
""" dump HAR entries to json testcase
|
||||
"""
|
||||
logger.info("dump testcase to JSON format.")
|
||||
|
||||
with open(json_file, "w", encoding="utf-8") as outfile:
|
||||
my_json_str = json.dumps(testcase, ensure_ascii=False, indent=4)
|
||||
if isinstance(my_json_str, bytes):
|
||||
my_json_str = my_json_str.decode("utf-8")
|
||||
|
||||
outfile.write(my_json_str)
|
||||
|
||||
logger.info("Generate JSON testcase successfully: {}".format(json_file))
|
||||
@@ -1,59 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from httprunner.ext.har2case import utils
|
||||
|
||||
|
||||
class TestHar2CaseUtils(unittest.TestCase):
|
||||
|
||||
data_dir = os.path.join(os.getcwd(), "examples", "data", "har2case")
|
||||
|
||||
@staticmethod
|
||||
def create_har_file(file_name, content):
|
||||
file_path = os.path.join(
|
||||
TestHar2CaseUtils.data_dir, "{}.har".format(file_name)
|
||||
)
|
||||
with open(file_path, "w") as f:
|
||||
f.write(json.dumps(content))
|
||||
|
||||
return file_path
|
||||
|
||||
def test_load_har_log_entries(self):
|
||||
har_path = os.path.join(TestHar2CaseUtils.data_dir, "demo.har")
|
||||
log_entries = utils.load_har_log_entries(har_path)
|
||||
self.assertIsInstance(log_entries, list)
|
||||
self.assertIn("request", log_entries[0])
|
||||
self.assertIn("response", log_entries[0])
|
||||
|
||||
def test_load_har_log_key_error(self):
|
||||
empty_json_file_path = TestHar2CaseUtils.create_har_file(
|
||||
file_name="empty_json", content={}
|
||||
)
|
||||
with self.assertRaises(SystemExit):
|
||||
utils.load_har_log_entries(empty_json_file_path)
|
||||
os.remove(empty_json_file_path)
|
||||
|
||||
def test_load_har_log_empty_error(self):
|
||||
empty_file_path = TestHar2CaseUtils.create_har_file(
|
||||
file_name="empty", content=""
|
||||
)
|
||||
with self.assertRaises(SystemExit):
|
||||
utils.load_har_log_entries(empty_file_path)
|
||||
os.remove(empty_file_path)
|
||||
|
||||
# def test_x_www_form_urlencoded(self):
|
||||
# origin_dict = {"a":1, "b": "2"}
|
||||
# self.assertIn("a=1", utils.x_www_form_urlencoded(origin_dict))
|
||||
# self.assertIn("b=2", utils.x_www_form_urlencoded(origin_dict))
|
||||
|
||||
def test_convert_list_to_dict(self):
|
||||
origin_list = [{"name": "v", "value": "1"}, {"name": "w", "value": "2"}]
|
||||
self.assertEqual(utils.convert_list_to_dict(origin_list), {"v": "1", "w": "2"})
|
||||
|
||||
def test_convert_x_www_form_urlencoded_to_dict(self):
|
||||
origin_str = "a=1&b=2"
|
||||
converted_dict = utils.convert_x_www_form_urlencoded_to_dict(origin_str)
|
||||
self.assertIsInstance(converted_dict, dict)
|
||||
self.assertEqual(converted_dict["a"], "1")
|
||||
self.assertEqual(converted_dict["b"], "2")
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user