diff --git a/cli/scripts/install.sh b/cli/scripts/install.sh index e987deea..0d35c05a 100644 --- a/cli/scripts/install.sh +++ b/cli/scripts/install.sh @@ -2,7 +2,7 @@ # install hrp with one shell command # bash -c "$(curl -ksSL https://httprunner.oss-cn-beijing.aliyuncs.com/install.sh)" -LATEST_VERSION="v0.6.1" +LATEST_VERSION="v0.6.2" set -e diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d4759487..cc54b050 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,20 +1,26 @@ # Release History -## v0.6.2 (2022-02-21) +## v0.6.3 (2022-02-22) +- feat: support customized setup/teardown hooks (variable assignment not supported) + +## v0.6.2 (2022-02-22) + +- feat: support text/html extraction with regex +- change: json unmarshal to json.Number when parsing data - fix: omit pseudo header names for HTTP/1, e.g. :authority - fix: generate `headers.\"Content-Type\"` in har2case -- change: json unmarshal to json.Number when parsing data - fix: incorrect data type when extracting data using jmespath -- fix: decode response body in br/gzip/deflate formats +- fix: decode response body in brotli/gzip/deflate formats - fix: omit print request/response body for non-text content +- fix: parse data for request cookie value ## v0.6.1 (2022-02-17) +- change: json unmarshal to float64 when parsing data - fix: set request Content-Type for posting json only when not specified - fix: failed to generate API test report when data is null - fix: panic when assertion function not exists -- change: json unmarshal to float64 when parsing data - fix: broadcast to all rendezvous at once when spawn done ## v0.6.0 (2022-02-08) diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 9d12a34e..04adeda2 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -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 15-Feb-2022 +###### Auto generated by spf13/cobra on 22-Feb-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index a0dcf982..ba0861dd 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -39,4 +39,4 @@ hrp boom [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 15-Feb-2022 +###### Auto generated by spf13/cobra on 22-Feb-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index a24d244b..686ed7b1 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -15,12 +15,12 @@ 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 - -j, --to-json convert to JSON format (default) - -y, --to-yaml convert to JSON format + -j, --to-json convert to JSON format (default true) + -y, --to-yaml convert to YAML format ``` ### SEE ALSO * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 15-Feb-2022 +###### Auto generated by spf13/cobra on 22-Feb-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 2c781641..0cd5715c 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -33,4 +33,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 15-Feb-2022 +###### Auto generated by spf13/cobra on 22-Feb-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 7921fc50..b04a4dac 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -16,4 +16,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - One-stop solution for HTTP(S) testing. -###### Auto generated by spf13/cobra on 15-Feb-2022 +###### Auto generated by spf13/cobra on 22-Feb-2022 diff --git a/examples/demo.json b/examples/demo.json index 4a084edc..da332faf 100644 --- a/examples/demo.json +++ b/examples/demo.json @@ -1,169 +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, - "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" - } - ] - } - ] -} + "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" + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/demo.yaml b/examples/demo.yaml index ac157382..387b9345 100644 --- a/examples/demo.yaml +++ b/examples/demo.yaml @@ -24,7 +24,12 @@ teststeps: 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: diff --git a/examples/plugin/debugtalk.go b/examples/plugin/debugtalk.go index ff8d115f..64cdc946 100644 --- a/examples/plugin/debugtalk.go +++ b/examples/plugin/debugtalk.go @@ -55,3 +55,11 @@ func Concatenate(args ...interface{}) (interface{}, error) { } return result, 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) +} diff --git a/examples/plugin/hashicorp.go b/examples/plugin/hashicorp.go index 8bae45a3..1d3f78e2 100644 --- a/examples/plugin/hashicorp.go +++ b/examples/plugin/hashicorp.go @@ -10,5 +10,7 @@ func main() { plugin.Register("sum_two_string", SumTwoString) plugin.Register("sum_strings", SumStrings) plugin.Register("concatenate", Concatenate) + plugin.Register("setup_hook_example", SetupHookExample) + plugin.Register("teardown_hook_example", TeardownHookExample) plugin.Serve() } diff --git a/examples/rendezvous_test.json b/examples/rendezvous_test.json index 278c4b80..e18ab101 100644 --- a/examples/rendezvous_test.json +++ b/examples/rendezvous_test.json @@ -1,106 +1,106 @@ { - "config": { - "name": "run request with functions", - "base_url": "https://postman-echo.com", - "variables": { - "a": 12.3, - "b": 3.45, - "n": 5 - }, - "parameters_setting": { - "strategy": "Sequential", - "parameterIterator": [ - {} - ] - } - }, - "teststeps": [ - { - "name": "waiting for all users in the beginning", - "rendezvous": { - "name": "rendezvous0" - } - }, - { - "name": "rendezvous before get", - "rendezvous": { - "name": "rendezvous1", - "number": 50, - "timeout": 3000 - } - }, - { - "name": "get with params", - "request": { - "method": "GET", - "url": "/get", - "params": { - "foo1": "foo1", - "foo2": "foo2" - }, - "headers": { - "User-Agent": "HttpRunnerPlus" - } - }, - "extract": { - "varFoo1": "body.args.foo1" - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "check status code" - } - ] - }, - { - "name": "rendezvous before post", - "rendezvous": { - "name": "rendezvous2", - "number": 20, - "timeout": 2000 - } - }, - { - "name": "post json data with functions", - "request": { - "method": "POST", - "url": "/post", - "headers": { - "User-Agent": "HttpRunnerPlus" - }, - "body": { - "foo1": "foo1", - "foo2": "foo2" - } - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "check status code" - }, - { - "check": "body.json.foo1", - "assert": "length_equals", - "expect": 4, - "msg": "check args foo1" - }, - { - "check": "body.json.foo2", - "assert": "equals", - "expect": "foo2", - "msg": "check args foo2" - } - ] - }, - { - "name": "waiting for all users in the end", - "rendezvous": { - "name": "rendezvous3" - } - } - ] + "config": { + "name": "run request with functions", + "base_url": "https://postman-echo.com", + "variables": { + "a": 12.3, + "b": 3.45, + "n": 5 + }, + "parameters_setting": { + "strategy": "Sequential", + "parameterIterator": [ + {} + ] + } + }, + "teststeps": [ + { + "name": "waiting for all users in the beginning", + "rendezvous": { + "name": "rendezvous0" + } + }, + { + "name": "rendezvous before get", + "rendezvous": { + "name": "rendezvous1", + "number": 50, + "timeout": 3000 + } + }, + { + "name": "get with params", + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "foo1", + "foo2": "foo2" + }, + "headers": { + "User-Agent": "HttpRunnerPlus" + } + }, + "extract": { + "varFoo1": "body.args.foo1" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + } + ] + }, + { + "name": "rendezvous before post", + "rendezvous": { + "name": "rendezvous2", + "number": 20, + "timeout": 2000 + } + }, + { + "name": "post json data with functions", + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "HttpRunnerPlus" + }, + "body": { + "foo1": "foo1", + "foo2": "foo2" + } + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.json.foo1", + "assert": "length_equals", + "expect": 4, + "msg": "check args foo1" + }, + { + "check": "body.json.foo2", + "assert": "equals", + "expect": "foo2", + "msg": "check args foo2" + } + ] + }, + { + "name": "waiting for all users in the end", + "rendezvous": { + "name": "rendezvous3" + } + } + ] } \ No newline at end of file diff --git a/internal/scaffold/demo.go b/internal/scaffold/demo.go index 19a62fac..de6e8448 100644 --- a/internal/scaffold/demo.go +++ b/internal/scaffold/demo.go @@ -19,8 +19,11 @@ var demoTestCase = &hrp.TestCase{ "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(). @@ -99,10 +102,20 @@ func Sum(args ...interface{}) (interface{}, error) { 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() { plugin.Register("sum_ints", SumInts) plugin.Register("sum_two_int", SumTwoInt) plugin.Register("sum", Sum) + plugin.Register("setup_hook_example", SetupHookExample) + plugin.Register("teardown_hook_example", TeardownHookExample) plugin.Serve() } ` diff --git a/internal/version/init.go b/internal/version/init.go index e6799e52..895d581c 100644 --- a/internal/version/init.go +++ b/internal/version/init.go @@ -1,3 +1,3 @@ package version -const VERSION = "v0.6.1" +const VERSION = "v0.6.2" diff --git a/runner.go b/runner.go index aeb81c95..50ddb2a4 100644 --- a/runner.go +++ b/runner.go @@ -616,6 +616,14 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro } sessionData := newSessionData() + // deal with setup hooks + for _, setupHook := range step.SetupHooks { + _, err = r.parser.parseData(setupHook, step.Variables) + if err != nil { + return stepResult, errors.Wrap(err, "run setup hooks failed") + } + } + // convert request struct to map jsonRequest, _ := json.Marshal(&step.Request) var requestMap map[string]interface{} @@ -659,7 +667,7 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro if len(step.Request.Params) > 0 { params, err := r.parser.parseData(step.Request.Params, step.Variables) if err != nil { - return stepResult, errors.Wrap(err, "parse data failed") + return stepResult, errors.Wrap(err, "parse request params failed") } parsedParams := params.(map[string]interface{}) requestMap["params"] = parsedParams @@ -682,9 +690,13 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro // prepare request cookies for cookieName, cookieValue := range step.Request.Cookies { + value, err := r.parser.parseData(cookieValue, step.Variables) + if err != nil { + return stepResult, errors.Wrap(err, "parse cookie value failed") + } req.AddCookie(&http.Cookie{ Name: cookieName, - Value: cookieValue, + Value: fmt.Sprintf("%v", value), }) } @@ -803,6 +815,14 @@ func (r *caseRunner) runStepRequest(step *TStep) (stepResult *stepData, err erro } stepResult.ContentSize = resp.ContentLength stepResult.Data = sessionData + + // deal with teardown hooks + for _, teardownHook := range step.TeardownHooks { + _, err = r.parser.parseData(teardownHook, step.Variables) + if err != nil { + return stepResult, errors.Wrap(err, "run teardown hooks failed") + } + } return stepResult, err }