mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-05 15:37:35 +08:00
Merge branch 'wings' into 'v5'
wings合入 See merge request iesqa/httprunner!65
This commit is contained in:
@@ -10,11 +10,11 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/httprunner/funplugin/fungo"
|
|
||||||
"github.com/httprunner/funplugin/myexec"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/httprunner/funplugin/fungo"
|
||||||
|
"github.com/httprunner/funplugin/myexec"
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v5/internal/config"
|
"github.com/httprunner/httprunner/v5/internal/config"
|
||||||
@@ -107,7 +107,7 @@ func (pt *pluginTemplate) generate(tmpl, output string) error {
|
|||||||
func (pt *pluginTemplate) generatePy(output string) error {
|
func (pt *pluginTemplate) generatePy(output string) error {
|
||||||
// specify output file path
|
// specify output file path
|
||||||
if output == "" {
|
if output == "" {
|
||||||
output = filepath.Join(config.RootDir, PluginPySourceGenFile)
|
output = filepath.Join(config.GetConfig().RootDir, PluginPySourceGenFile)
|
||||||
} else if builtin.IsFolderPathExists(output) {
|
} else if builtin.IsFolderPathExists(output) {
|
||||||
output = filepath.Join(output, PluginPySourceGenFile)
|
output = filepath.Join(output, PluginPySourceGenFile)
|
||||||
}
|
}
|
||||||
@@ -155,7 +155,7 @@ func (pt *pluginTemplate) generateGo(output string) error {
|
|||||||
|
|
||||||
// specify output file path
|
// specify output file path
|
||||||
if output == "" {
|
if output == "" {
|
||||||
output = filepath.Join(config.RootDir, PluginHashicorpGoBuiltFile)
|
output = filepath.Join(config.GetConfig().RootDir, PluginHashicorpGoBuiltFile)
|
||||||
} else if builtin.IsFolderPathExists(output) {
|
} else if builtin.IsFolderPathExists(output) {
|
||||||
output = filepath.Join(output, PluginHashicorpGoBuiltFile)
|
output = filepath.Join(output, PluginHashicorpGoBuiltFile)
|
||||||
}
|
}
|
||||||
|
|||||||
-141
@@ -1,141 +0,0 @@
|
|||||||
package hrp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRun(t *testing.T) {
|
|
||||||
err := BuildPlugin(tmpl("plugin/debugtalk.go"), "./debugtalk.bin")
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
genDebugTalkPyPath := filepath.Join(tmpl("plugin/"), PluginPySourceGenFile)
|
|
||||||
err = BuildPlugin(tmpl("plugin/debugtalk.py"), genDebugTalkPyPath)
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
contentBytes, err := readFile(genDebugTalkPyPath)
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
content := string(contentBytes)
|
|
||||||
if !assert.Contains(t, content, "import funppy") {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !assert.Contains(t, content, "funppy.register") {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
reg, _ := regexp.Compile(`funppy\.register`)
|
|
||||||
matchedSlice := reg.FindAllStringSubmatch(content, -1)
|
|
||||||
if !assert.Len(t, matchedSlice, 10) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindAllPythonFunctionNames(t *testing.T) {
|
|
||||||
content := `
|
|
||||||
def test_1(): # exported function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _test_2(): # exported function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __test_3(): # private function
|
|
||||||
pass
|
|
||||||
|
|
||||||
# def test_4(): # commented out function
|
|
||||||
# pass
|
|
||||||
|
|
||||||
def Test5(): # exported function
|
|
||||||
pass
|
|
||||||
`
|
|
||||||
names, err := regexPyFunctionName.findAllFunctionNames(content)
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !assert.Contains(t, names, "test_1") {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !assert.Contains(t, names, "Test5") {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !assert.Contains(t, names, "_test_2") {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !assert.NotContains(t, names, "__test_3") {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
// commented out function
|
|
||||||
if !assert.NotContains(t, names, "test_4") {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindAllGoFunctionNames(t *testing.T) {
|
|
||||||
content := `
|
|
||||||
func Test1() { // exported function
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFunc2() { // exported function
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { // private function
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Test3() { // exported function
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// func Test4() { // commented out function
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
`
|
|
||||||
names, err := regexGoFunctionName.findAllFunctionNames(content)
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !assert.Contains(t, names, "Test1") {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !assert.Contains(t, names, "testFunc2") {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !assert.NotContains(t, names, "init") {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if !assert.Contains(t, names, "_Test3") {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
// commented out function
|
|
||||||
if !assert.NotContains(t, names, "Test4") {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFindAllGoFunctionNamesAbnormal(t *testing.T) {
|
|
||||||
content := `
|
|
||||||
func init() { // private function
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() { // should not define main() function
|
|
||||||
return
|
|
||||||
}
|
|
||||||
`
|
|
||||||
_, err := regexGoFunctionName.findAllFunctionNames(content)
|
|
||||||
if !assert.NotNil(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -3,8 +3,8 @@ package adb
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
var serial string
|
var serial string
|
||||||
|
|||||||
+2
-2
@@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/internal/sdk"
|
"github.com/httprunner/httprunner/v5/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
+2
-2
@@ -4,14 +4,14 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/httprunner/funplugin/myexec"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/httprunner/funplugin/myexec"
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
|
"github.com/httprunner/httprunner/v5/convert"
|
||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/convert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var convertCmd = &cobra.Command{
|
var convertCmd = &cobra.Command{
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/internal/sdk"
|
"github.com/httprunner/httprunner/v5/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
"github.com/httprunner/httprunner/v5/internal/sdk"
|
"github.com/httprunner/httprunner/v5/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
|
|||||||
+2
-2
@@ -3,8 +3,8 @@ package ios
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
var iosRootCmd = &cobra.Command{
|
var iosRootCmd = &cobra.Command{
|
||||||
|
|||||||
+2
-2
@@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/internal/sdk"
|
"github.com/httprunner/httprunner/v5/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
var installCmd = &cobra.Command{
|
var installCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package ios
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/danielpaulus/go-ios/ios"
|
||||||
|
"github.com/httprunner/httprunner/v5/internal/sdk"
|
||||||
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tunnelCmd = &cobra.Command{
|
||||||
|
Use: "tunnel",
|
||||||
|
Short: "tunnel start",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
defer func() {
|
||||||
|
sdk.SendGA4Event("hrp_ios_tunnel", map[string]interface{}{
|
||||||
|
"args": strings.Join(args, "-"),
|
||||||
|
"success": err == nil,
|
||||||
|
"engagement_time_msec": time.Since(startTime).Milliseconds(),
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
ctx := context.TODO()
|
||||||
|
err = uixt.StartTunnel(ctx, os.TempDir(), ios.HttpApiPort(), true)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("failed to start tunnel")
|
||||||
|
}
|
||||||
|
<-ctx.Done()
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
iosRootCmd.AddCommand(tunnelCmd)
|
||||||
|
}
|
||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/internal/sdk"
|
"github.com/httprunner/httprunner/v5/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
var uninstallCmd = &cobra.Command{
|
var uninstallCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ var serverCmd = &cobra.Command{
|
|||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return server_ext.NewExtRouter().Run(port)
|
return server_ext.NewExtRouter().Run(port)
|
||||||
|
// return server.NewRouter().Run(port)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConvertCaseCompatibility converts TestCase compatible with Golang engine style
|
// ConvertCaseCompatibility converts TestCase compatible with Golang engine style
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IConfig interface {
|
type IConfig interface {
|
||||||
@@ -80,7 +80,7 @@ func (c *TConfig) WithParameters(parameters map[string]interface{}) *TConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetThinkTime sets think time config for current testcase.
|
// SetThinkTime sets think time config for current testcase.
|
||||||
func (c *TConfig) SetThinkTime(strategy thinkTimeStrategy, cfg interface{}, limit float64) *TConfig {
|
func (c *TConfig) SetThinkTime(strategy ThinkTimeStrategy, cfg interface{}, limit float64) *TConfig {
|
||||||
c.ThinkTimeSetting = &ThinkTimeConfig{strategy, cfg, limit}
|
c.ThinkTimeSetting = &ThinkTimeConfig{strategy, cfg, limit}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
@@ -194,7 +194,7 @@ func (c *TConfig) DisableAutoPopupHandler() *TConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ThinkTimeConfig struct {
|
type ThinkTimeConfig struct {
|
||||||
Strategy thinkTimeStrategy `json:"strategy,omitempty" yaml:"strategy,omitempty"` // default、random、multiply、ignore
|
Strategy ThinkTimeStrategy `json:"strategy,omitempty" yaml:"strategy,omitempty"` // default、random、multiply、ignore
|
||||||
Setting interface{} `json:"setting,omitempty" yaml:"setting,omitempty"` // random(map): {"min_percentage": 0.5, "max_percentage": 1.5}; 10、multiply(float64): 1.5
|
Setting interface{} `json:"setting,omitempty" yaml:"setting,omitempty"` // random(map): {"min_percentage": 0.5, "max_percentage": 1.5}; 10、multiply(float64): 1.5
|
||||||
Limit float64 `json:"limit,omitempty" yaml:"limit,omitempty"` // limit think time no more than specific time, ignore if value <= 0
|
Limit float64 `json:"limit,omitempty" yaml:"limit,omitempty"` // limit think time no more than specific time, ignore if value <= 0
|
||||||
}
|
}
|
||||||
@@ -205,10 +205,10 @@ func (ttc *ThinkTimeConfig) checkThinkTime() {
|
|||||||
}
|
}
|
||||||
// unset strategy, set default strategy
|
// unset strategy, set default strategy
|
||||||
if ttc.Strategy == "" {
|
if ttc.Strategy == "" {
|
||||||
ttc.Strategy = thinkTimeDefault
|
ttc.Strategy = ThinkTimeDefault
|
||||||
}
|
}
|
||||||
// check think time
|
// check think time
|
||||||
if ttc.Strategy == thinkTimeRandomPercentage {
|
if ttc.Strategy == ThinkTimeRandomPercentage {
|
||||||
if ttc.Setting == nil || reflect.TypeOf(ttc.Setting).Kind() != reflect.Map {
|
if ttc.Setting == nil || reflect.TypeOf(ttc.Setting).Kind() != reflect.Map {
|
||||||
ttc.Setting = thinkTimeDefaultRandom
|
ttc.Setting = thinkTimeDefaultRandom
|
||||||
return
|
return
|
||||||
@@ -237,7 +237,7 @@ func (ttc *ThinkTimeConfig) checkThinkTime() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ttc.Setting = map[string]float64{"min_percentage": left, "max_percentage": right}
|
ttc.Setting = map[string]float64{"min_percentage": left, "max_percentage": right}
|
||||||
} else if ttc.Strategy == thinkTimeMultiply {
|
} else if ttc.Strategy == ThinkTimeMultiply {
|
||||||
if ttc.Setting == nil {
|
if ttc.Setting == nil {
|
||||||
ttc.Setting = float64(0) // default
|
ttc.Setting = float64(0) // default
|
||||||
return
|
return
|
||||||
@@ -248,19 +248,19 @@ func (ttc *ThinkTimeConfig) checkThinkTime() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ttc.Setting = value
|
ttc.Setting = value
|
||||||
} else if ttc.Strategy != thinkTimeIgnore {
|
} else if ttc.Strategy != ThinkTimeIgnore {
|
||||||
// unrecognized strategy, set default strategy
|
// unrecognized strategy, set default strategy
|
||||||
ttc.Strategy = thinkTimeDefault
|
ttc.Strategy = ThinkTimeDefault
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type thinkTimeStrategy string
|
type ThinkTimeStrategy string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
thinkTimeDefault thinkTimeStrategy = "default" // as recorded
|
ThinkTimeDefault ThinkTimeStrategy = "default" // as recorded
|
||||||
thinkTimeRandomPercentage thinkTimeStrategy = "random_percentage" // use random percentage of recorded think time
|
ThinkTimeRandomPercentage ThinkTimeStrategy = "random_percentage" // use random percentage of recorded think time
|
||||||
thinkTimeMultiply thinkTimeStrategy = "multiply" // multiply recorded think time
|
ThinkTimeMultiply ThinkTimeStrategy = "multiply" // multiply recorded think time
|
||||||
thinkTimeIgnore thinkTimeStrategy = "ignore" // ignore recorded think time
|
ThinkTimeIgnore ThinkTimeStrategy = "ignore" // ignore recorded think time
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
@@ -4,7 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
hrp "github.com/httprunner/httprunner/v5"
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAndroidDouyinE2E(t *testing.T) {
|
func TestAndroidDouyinE2E(t *testing.T) {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/ai"
|
"github.com/httprunner/httprunner/v5/uixt/ai"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
hrp "github.com/httprunner/httprunner/v5"
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAndroidDouyinFeedTest(t *testing.T) {
|
func TestAndroidDouyinFeedTest(t *testing.T) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
hrp "github.com/httprunner/httprunner/v5"
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAndroidLiveSwipeTest(t *testing.T) {
|
func TestAndroidLiveSwipeTest(t *testing.T) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
hrp "github.com/httprunner/httprunner/v5"
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIOSDouyinFollowLive(t *testing.T) {
|
func TestIOSDouyinFollowLive(t *testing.T) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
hrp "github.com/httprunner/httprunner/v5"
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHamonyDouyinFeedTest(t *testing.T) {
|
func TestHamonyDouyinFeedTest(t *testing.T) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
hrp "github.com/httprunner/httprunner/v5"
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIOSDouyinLive(t *testing.T) {
|
func TestIOSDouyinLive(t *testing.T) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
hrp "github.com/httprunner/httprunner/v5"
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWDALog(t *testing.T) {
|
func TestWDALog(t *testing.T) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
hrp "github.com/httprunner/httprunner/v5"
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAndroidExpertTest(t *testing.T) {
|
func TestAndroidExpertTest(t *testing.T) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
hrp "github.com/httprunner/httprunner/v5"
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHarmonyDouyinE2E(t *testing.T) {
|
func TestHarmonyDouyinE2E(t *testing.T) {
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/ai"
|
"github.com/httprunner/httprunner/v5/uixt/ai"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func convertTimeToSeconds(timeStr string) (int, error) {
|
func convertTimeToSeconds(timeStr string) (int, error) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
hrp "github.com/httprunner/httprunner/v5"
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConvertTimeToSeconds(t *testing.T) {
|
func TestConvertTimeToSeconds(t *testing.T) {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ go 1.23.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
code.byted.org/iesqa/ghdc v0.0.0-20241009025217-ecb76cf5bd27
|
code.byted.org/iesqa/ghdc v0.0.0-20241009025217-ecb76cf5bd27
|
||||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69
|
|
||||||
github.com/Masterminds/semver v1.5.0
|
github.com/Masterminds/semver v1.5.0
|
||||||
github.com/andybalholm/brotli v1.0.4
|
github.com/andybalholm/brotli v1.0.4
|
||||||
github.com/danielpaulus/go-ios v1.0.161
|
github.com/danielpaulus/go-ios v1.0.161
|
||||||
@@ -54,7 +53,7 @@ require (
|
|||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/grandcat/zeroconf v1.0.0 // indirect
|
github.com/grandcat/zeroconf v1.0.0 // indirect
|
||||||
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
||||||
github.com/hashicorp/go-plugin v1.4.10 // indirect
|
github.com/hashicorp/go-plugin v1.4.10 // indirect
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
code.byted.org/iesqa/ghdc v0.0.0-20241009025217-ecb76cf5bd27 h1:+wNJiEXXIUP6luKJRA4tfwDqfnWUON6LIopKD9tvUns=
|
code.byted.org/iesqa/ghdc v0.0.0-20241009025217-ecb76cf5bd27 h1:+wNJiEXXIUP6luKJRA4tfwDqfnWUON6LIopKD9tvUns=
|
||||||
code.byted.org/iesqa/ghdc v0.0.0-20241009025217-ecb76cf5bd27/go.mod h1:C2kq6TTE+JAOnqDorSwae1MQzRuex03RshuSUC2U/FY=
|
code.byted.org/iesqa/ghdc v0.0.0-20241009025217-ecb76cf5bd27/go.mod h1:C2kq6TTE+JAOnqDorSwae1MQzRuex03RshuSUC2U/FY=
|
||||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 h1:+tu3HOoMXB7RXEINRVIpxJCT+KdYiI7LAEAUrOw3dIU=
|
|
||||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZdiDllfyYH5l5OkAaZtk7VkWe89bPJFmnDBNHxg=
|
|
||||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||||
@@ -86,8 +84,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE
|
|||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
|
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
|
||||||
|
|||||||
@@ -47,10 +47,6 @@ func escapeQuotes(s string) string {
|
|||||||
return quoteEscaper.Replace(s)
|
return quoteEscaper.Replace(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
func random_range(a, b float64) float64 {
|
func random_range(a, b float64) float64 {
|
||||||
return a + rand.Float64()*(b-a)
|
return a + rand.Float64()*(b-a)
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-74
@@ -5,16 +5,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
builtinJSON "encoding/json"
|
builtinJSON "encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -23,7 +20,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BurntSushi/locker"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
@@ -229,6 +225,22 @@ func InterfaceType(raw interface{}) string {
|
|||||||
return reflect.TypeOf(raw).String()
|
return reflect.TypeOf(raw).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LoadFile(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, errors.Wrap(code.LoadFileError, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("read file failed")
|
||||||
|
return nil, errors.Wrap(code.LoadFileError, err.Error())
|
||||||
|
}
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
func loadFromCSV(path string) []map[string]interface{} {
|
func loadFromCSV(path string) []map[string]interface{} {
|
||||||
log.Info().Str("path", path).Msg("load csv file")
|
log.Info().Str("path", path).Msg("load csv file")
|
||||||
file, err := os.ReadFile(path)
|
file, err := os.ReadFile(path)
|
||||||
@@ -358,21 +370,18 @@ func ConvertToStringSlice(val interface{}) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetFreePort() (int, error) {
|
func GetFreePort() (int, error) {
|
||||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
minPort := 20000
|
||||||
if err != nil {
|
maxPort := 50000
|
||||||
return 0, errors.Wrap(err, "resolve tcp addr failed")
|
for i := 0; i < 10; i++ {
|
||||||
}
|
port := rand.Intn(maxPort-minPort+1) + minPort
|
||||||
|
addr := fmt.Sprintf("0.0.0.0:%d", port)
|
||||||
l, err := net.ListenTCP("tcp", addr)
|
l, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
return 0, errors.Wrap(err, "listen tcp addr failed")
|
defer l.Close() // 端口成功绑定后立即释放,返回该端口号
|
||||||
}
|
return port, nil
|
||||||
defer func() {
|
|
||||||
if err = l.Close(); err != nil {
|
|
||||||
log.Error().Err(err).Msg(fmt.Sprintf("close addr %s error", l.Addr().String()))
|
|
||||||
}
|
}
|
||||||
}()
|
}
|
||||||
return l.Addr().(*net.TCPAddr).Port, nil
|
return 0, errors.New("failed to get available port")
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCurrentDay() string {
|
func GetCurrentDay() string {
|
||||||
@@ -382,7 +391,7 @@ func GetCurrentDay() string {
|
|||||||
return formattedDate
|
return formattedDate
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileExists(filepath string) bool {
|
func FileExists(filepath string) bool {
|
||||||
_, err := os.Stat(filepath)
|
_, err := os.Stat(filepath)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return false // 文件不存在
|
return false // 文件不存在
|
||||||
@@ -390,61 +399,6 @@ func fileExists(filepath string) bool {
|
|||||||
return err == nil // 文件存在,且没有其他错误
|
return err == nil // 文件存在,且没有其他错误
|
||||||
}
|
}
|
||||||
|
|
||||||
func DownloadFileByUrl(fileUrl string) (filePath string, err error) {
|
|
||||||
// 使用 UUID 生成唯一文件名
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
hash := md5.Sum([]byte(fileUrl))
|
|
||||||
fileName := fmt.Sprintf("%x", hash)
|
|
||||||
filePath = filepath.Join(cwd, fileName)
|
|
||||||
locker.Lock(filePath)
|
|
||||||
defer locker.Unlock(filePath)
|
|
||||||
if fileExists(filePath) {
|
|
||||||
return filePath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Downloading file to %s from URL %s\n", filePath, fileUrl)
|
|
||||||
|
|
||||||
// Create an HTTP client with default settings.
|
|
||||||
client := &http.Client{}
|
|
||||||
|
|
||||||
// Build the HTTP GET request.
|
|
||||||
req, err := http.NewRequest("GET", fileUrl, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform the request.
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// Check the HTTP status code.
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return "", fmt.Errorf("failed to download file: %s", resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the output file.
|
|
||||||
outFile, err := os.Create(fileName)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer outFile.Close()
|
|
||||||
|
|
||||||
// Copy the response body to the file.
|
|
||||||
_, err = io.Copy(outFile, resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("File downloaded successfully: %s\n", fileName)
|
|
||||||
return filePath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunCommand(cmdName string, args ...string) error {
|
func RunCommand(cmdName string, args ...string) error {
|
||||||
cmd := exec.Command(cmdName, args...)
|
cmd := exec.Command(cmdName, args...)
|
||||||
log.Info().Str("command", cmd.String()).Msg("exec command")
|
log.Info().Str("command", cmd.String()).Msg("exec command")
|
||||||
|
|||||||
+43
-21
@@ -3,6 +3,7 @@ package config
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@@ -12,39 +13,60 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
ResultsDirName = "results"
|
ResultsDirName = "results"
|
||||||
|
DownloadsDirName = "downloads"
|
||||||
ScreenshotsDirName = "screenshots"
|
ScreenshotsDirName = "screenshots"
|
||||||
ActionLogDirName = "action_log"
|
ActionLogDirName = "action_log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type Config struct {
|
||||||
RootDir string
|
RootDir string
|
||||||
ResultsDir string
|
ResultsDir string
|
||||||
ResultsPath string
|
ResultsPath string
|
||||||
|
DownloadsPath string
|
||||||
ScreenShotsPath string
|
ScreenShotsPath string
|
||||||
StartTime = time.Now()
|
StartTime time.Time
|
||||||
StartTimeStr = StartTime.Format("20060102150405")
|
|
||||||
ActionLogFilePath string
|
ActionLogFilePath string
|
||||||
DeviceActionLogFilePath string
|
DeviceActionLogFilePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalConfig *Config
|
||||||
|
once sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func GetConfig() *Config {
|
||||||
var err error
|
once.Do(func() {
|
||||||
RootDir, err = os.Getwd()
|
cfg := &Config{
|
||||||
if err != nil {
|
StartTime: time.Now(),
|
||||||
panic(err)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ResultsDir = filepath.Join(ResultsDirName, StartTimeStr)
|
var err error
|
||||||
ResultsPath = filepath.Join(RootDir, ResultsDir)
|
cfg.RootDir, err = os.Getwd()
|
||||||
ScreenShotsPath = filepath.Join(ResultsPath, ScreenshotsDirName)
|
if err != nil {
|
||||||
ActionLogFilePath = filepath.Join(ResultsDir, ActionLogDirName)
|
panic(err)
|
||||||
DeviceActionLogFilePath = "/sdcard/Android/data/io.appium.uiautomator2.server/files/hodor"
|
}
|
||||||
|
|
||||||
// create results directory
|
startTimeStr := cfg.StartTime.Format("20060102150405")
|
||||||
if err := builtin.EnsureFolderExists(ResultsPath); err != nil {
|
cfg.ResultsDir = filepath.Join(ResultsDirName, startTimeStr)
|
||||||
log.Fatal().Err(err).Msg("create results directory failed")
|
cfg.ResultsPath = filepath.Join(cfg.RootDir, cfg.ResultsDir)
|
||||||
}
|
cfg.DownloadsPath = filepath.Join(cfg.RootDir, filepath.Join(DownloadsDirName, startTimeStr))
|
||||||
if err := builtin.EnsureFolderExists(ScreenShotsPath); err != nil {
|
cfg.ScreenShotsPath = filepath.Join(cfg.ResultsPath, ScreenshotsDirName)
|
||||||
log.Fatal().Err(err).Msg("create screenshots directory failed")
|
cfg.ActionLogFilePath = filepath.Join(cfg.ResultsDir, ActionLogDirName)
|
||||||
}
|
cfg.DeviceActionLogFilePath = "/sdcard/Android/data/io.appium.uiautomator2.server/files/hodor"
|
||||||
|
|
||||||
|
// create results directory
|
||||||
|
if err := builtin.EnsureFolderExists(cfg.ResultsPath); err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("create results directory failed")
|
||||||
|
}
|
||||||
|
if err := builtin.EnsureFolderExists(cfg.DownloadsPath); err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("create downloads directory failed")
|
||||||
|
}
|
||||||
|
if err := builtin.EnsureFolderExists(cfg.ScreenShotsPath); err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("create screenshots directory failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
globalConfig = cfg
|
||||||
|
})
|
||||||
|
|
||||||
|
return globalConfig
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
package demo
|
package httpstat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/httpstat"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(t *testing.T) {
|
func TestMain(t *testing.T) {
|
||||||
var httpStat httpstat.Stat
|
var httpStat Stat
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", "https://httprunner.com", nil)
|
req, _ := http.NewRequest("GET", "https://httprunner.com", nil)
|
||||||
ctx := httpstat.WithHTTPStat(req, &httpStat)
|
ctx := WithHTTPStat(req, &httpStat)
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: time.Second * 10,
|
Timeout: time.Second * 10,
|
||||||
@@ -1 +1 @@
|
|||||||
v5.0.0+2502192152
|
v5.0.0-beta-2503051939
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v5/internal/json"
|
"github.com/httprunner/httprunner/v5/internal/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -85,7 +86,7 @@ func LoadTestCases(tests ...ITestCase) ([]*TestCase, error) {
|
|||||||
// LoadFileObject loads file content with file extension and assigns to structObj
|
// LoadFileObject loads file content with file extension and assigns to structObj
|
||||||
func LoadFileObject(path string, structObj interface{}) (err error) {
|
func LoadFileObject(path string, structObj interface{}) (err error) {
|
||||||
log.Info().Str("path", path).Msg("load file")
|
log.Info().Str("path", path).Msg("load file")
|
||||||
file, err := readFile(path)
|
file, err := builtin.LoadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "read file failed")
|
return errors.Wrap(err, "read file failed")
|
||||||
}
|
}
|
||||||
@@ -145,19 +146,3 @@ func parseEnvContent(file []byte, obj interface{}) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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, errors.Wrap(code.LoadFileError, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("read file failed")
|
|
||||||
return nil, errors.Wrap(code.LoadFileError, err.Error())
|
|
||||||
}
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
package hrp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLoadTestCases(t *testing.T) {
|
|
||||||
// load test cases from folder path
|
|
||||||
tc := TestCasePath("../examples/demo-with-py-plugin/testcases/")
|
|
||||||
testCases, err := LoadTestCases(&tc)
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, 4, len(testCases)) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// load test cases from folder path, including sub folders
|
|
||||||
tc = TestCasePath("../examples/demo-with-py-plugin/")
|
|
||||||
testCases, err = LoadTestCases(&tc)
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, 4, len(testCases)) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// load test cases from single file path
|
|
||||||
tc = TestCasePath(demoTestCaseWithPluginJSONPath)
|
|
||||||
testCases, err = LoadTestCases(&tc)
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, 1, len(testCases)) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// load test cases from TestCase instance
|
|
||||||
testcase := &TestCase{
|
|
||||||
Config: NewConfig("TestCase").SetWeight(3),
|
|
||||||
}
|
|
||||||
testCases, err = LoadTestCases(testcase)
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, len(testCases), 1) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// load test cases from TestCaseJSON
|
|
||||||
testcaseJSON := TestCaseJSON(`
|
|
||||||
{
|
|
||||||
"config":{"name":"TestCaseJSON"},
|
|
||||||
"teststeps":[
|
|
||||||
{"name": "step1", "request":{"url": "https://httpbin.org/get"}},
|
|
||||||
{"name": "step2", "shell":{"string": "ls -l"}}
|
|
||||||
]
|
|
||||||
}`)
|
|
||||||
testCases, err = LoadTestCases(&testcaseJSON)
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, len(testCases), 1) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadCase(t *testing.T) {
|
|
||||||
tcJSON := &TestCaseDef{}
|
|
||||||
tcYAML := &TestCaseDef{}
|
|
||||||
err := LoadFileObject(demoTestCaseWithPluginJSONPath, tcJSON)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
err = LoadFileObject(demoTestCaseWithPluginYAMLPath, tcYAML)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !assert.Equal(t, tcJSON.Config.Name, tcYAML.Config.Name) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, tcJSON.Config.BaseURL, tcYAML.Config.BaseURL) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, tcJSON.Steps[1].StepName, tcYAML.Steps[1].StepName) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
if !assert.Equal(t, tcJSON.Steps[1].Request, tcJSON.Steps[1].Request) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+29
-29
@@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
type TParamsConfig struct {
|
type TParamsConfig struct {
|
||||||
PickOrder iteratorPickOrder `json:"pick_order,omitempty" yaml:"pick_order,omitempty"` // overall pick-order strategy
|
PickOrder iteratorPickOrder `json:"pick_order,omitempty" yaml:"pick_order,omitempty"` // overall pick-order strategy
|
||||||
Strategies map[string]iteratorStrategy `json:"strategies,omitempty" yaml:"strategies,omitempty"` // individual strategies for each parameters
|
Strategies map[string]IteratorStrategy `json:"strategies,omitempty" yaml:"strategies,omitempty"` // individual strategies for each parameters
|
||||||
Limit int `json:"limit,omitempty" yaml:"limit,omitempty"`
|
Limit int `json:"limit,omitempty" yaml:"limit,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,13 +35,13 @@ const (
|
|||||||
*/
|
*/
|
||||||
type Parameters []map[string]interface{}
|
type Parameters []map[string]interface{}
|
||||||
|
|
||||||
type iteratorStrategy struct {
|
type IteratorStrategy struct {
|
||||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
PickOrder iteratorPickOrder `json:"pick_order,omitempty" yaml:"pick_order,omitempty"`
|
PickOrder iteratorPickOrder `json:"pick_order,omitempty" yaml:"pick_order,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Parser) initParametersIterator(cfg *TConfig) (*ParametersIterator, error) {
|
func (p *Parser) InitParametersIterator(cfg *TConfig) (*ParametersIterator, error) {
|
||||||
parameters, err := p.loadParameters(cfg.Parameters, cfg.Variables)
|
parameters, err := p.LoadParameters(cfg.Parameters, cfg.Variables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -57,13 +57,13 @@ func newParametersIterator(parameters map[string]Parameters, config *TParamsConf
|
|||||||
hasNext: true,
|
hasNext: true,
|
||||||
sequentialParameters: nil,
|
sequentialParameters: nil,
|
||||||
randomParameterNames: nil,
|
randomParameterNames: nil,
|
||||||
limit: config.Limit,
|
Limit: config.Limit,
|
||||||
index: 0,
|
Index: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(parameters) == 0 {
|
if len(parameters) == 0 {
|
||||||
iterator.data = map[string]Parameters{}
|
iterator.data = map[string]Parameters{}
|
||||||
iterator.limit = 1
|
iterator.Limit = 1
|
||||||
return iterator
|
return iterator
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,24 +85,24 @@ func newParametersIterator(parameters map[string]Parameters, config *TParamsConf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generate cartesian product for sequential parameters
|
// generate cartesian product for sequential parameters
|
||||||
iterator.sequentialParameters = genCartesianProduct(parametersList)
|
iterator.sequentialParameters = GenCartesianProduct(parametersList)
|
||||||
|
|
||||||
if iterator.limit < 0 {
|
if iterator.Limit < 0 {
|
||||||
log.Warn().Msg("parameters unlimited mode is only supported for load testing")
|
log.Warn().Msg("parameters unlimited mode is only supported for load testing")
|
||||||
iterator.limit = 0
|
iterator.Limit = 0
|
||||||
}
|
}
|
||||||
if iterator.limit == 0 {
|
if iterator.Limit == 0 {
|
||||||
// limit not set
|
// limit not set
|
||||||
if len(iterator.sequentialParameters) > 0 {
|
if len(iterator.sequentialParameters) > 0 {
|
||||||
// use cartesian product of sequential parameters size as limit
|
// use cartesian product of sequential parameters size as limit
|
||||||
iterator.limit = len(iterator.sequentialParameters)
|
iterator.Limit = len(iterator.sequentialParameters)
|
||||||
} else {
|
} else {
|
||||||
// all parameters are selected by random
|
// all parameters are selected by random
|
||||||
// only run once
|
// only run once
|
||||||
iterator.limit = 1
|
iterator.Limit = 1
|
||||||
}
|
}
|
||||||
} else { // limit > 0
|
} else { // limit > 0
|
||||||
log.Info().Int("limit", iterator.limit).Msg("set limit for parameters")
|
log.Info().Int("limit", iterator.Limit).Msg("set limit for parameters")
|
||||||
}
|
}
|
||||||
|
|
||||||
return iterator
|
return iterator
|
||||||
@@ -114,14 +114,14 @@ type ParametersIterator struct {
|
|||||||
hasNext bool // cache query result
|
hasNext bool // cache query result
|
||||||
sequentialParameters Parameters // cartesian product for sequential parameters
|
sequentialParameters Parameters // cartesian product for sequential parameters
|
||||||
randomParameterNames []string // value is parameter names
|
randomParameterNames []string // value is parameter names
|
||||||
limit int // limit count for iteration
|
Limit int // limit count for iteration
|
||||||
index int // current iteration index
|
Index int // current iteration index
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetUnlimitedMode is used for load testing
|
// SetUnlimitedMode is used for load testing
|
||||||
func (iter *ParametersIterator) SetUnlimitedMode() {
|
func (iter *ParametersIterator) SetUnlimitedMode() {
|
||||||
log.Info().Msg("set parameters unlimited mode")
|
log.Info().Msg("set parameters unlimited mode")
|
||||||
iter.limit = -1
|
iter.Limit = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iter *ParametersIterator) HasNext() bool {
|
func (iter *ParametersIterator) HasNext() bool {
|
||||||
@@ -130,12 +130,12 @@ func (iter *ParametersIterator) HasNext() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unlimited mode
|
// unlimited mode
|
||||||
if iter.limit == -1 {
|
if iter.Limit == -1 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// reached limit
|
// reached limit
|
||||||
if iter.index >= iter.limit {
|
if iter.Index >= iter.Limit {
|
||||||
// cache query result
|
// cache query result
|
||||||
iter.hasNext = false
|
iter.hasNext = false
|
||||||
return false
|
return false
|
||||||
@@ -155,11 +155,11 @@ func (iter *ParametersIterator) Next() map[string]interface{} {
|
|||||||
var selectedParameters map[string]interface{}
|
var selectedParameters map[string]interface{}
|
||||||
if len(iter.sequentialParameters) == 0 {
|
if len(iter.sequentialParameters) == 0 {
|
||||||
selectedParameters = make(map[string]interface{})
|
selectedParameters = make(map[string]interface{})
|
||||||
} else if iter.index < len(iter.sequentialParameters) {
|
} else if iter.Index < len(iter.sequentialParameters) {
|
||||||
selectedParameters = iter.sequentialParameters[iter.index]
|
selectedParameters = iter.sequentialParameters[iter.Index]
|
||||||
} else {
|
} else {
|
||||||
// loop back to the first sequential parameter
|
// loop back to the first sequential parameter
|
||||||
index := iter.index % len(iter.sequentialParameters)
|
index := iter.Index % len(iter.sequentialParameters)
|
||||||
selectedParameters = iter.sequentialParameters[index]
|
selectedParameters = iter.sequentialParameters[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,8 +172,8 @@ func (iter *ParametersIterator) Next() map[string]interface{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iter.index++
|
iter.Index++
|
||||||
if iter.limit > 0 && iter.index >= iter.limit {
|
if iter.Limit > 0 && iter.Index >= iter.Limit {
|
||||||
iter.hasNext = false
|
iter.hasNext = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ func (iter *ParametersIterator) Data() map[string]interface{} {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func genCartesianProduct(multiParameters []Parameters) Parameters {
|
func GenCartesianProduct(multiParameters []Parameters) Parameters {
|
||||||
if len(multiParameters) == 0 {
|
if len(multiParameters) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,7 @@ func genCartesianProduct(multiParameters []Parameters) Parameters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
loadParameters loads parameters from multiple sources.
|
LoadParameters loads parameters from multiple sources.
|
||||||
|
|
||||||
parameter value may be in three types:
|
parameter value may be in three types:
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ parameter value may be in three types:
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
func (p *Parser) loadParameters(configParameters map[string]interface{}, variablesMapping map[string]interface{}) (
|
func (p *Parser) LoadParameters(configParameters map[string]interface{}, variablesMapping map[string]interface{}) (
|
||||||
map[string]Parameters, error) {
|
map[string]Parameters, error) {
|
||||||
|
|
||||||
if len(configParameters) == 0 {
|
if len(configParameters) == 0 {
|
||||||
@@ -291,7 +291,7 @@ func (p *Parser) loadParameters(configParameters map[string]interface{}, variabl
|
|||||||
return nil, errors.New("config parameters raw value format error")
|
return nil, errors.New("config parameters raw value format error")
|
||||||
}
|
}
|
||||||
|
|
||||||
parameterSlice, err := convertParameters(k, parametersRawList)
|
parameterSlice, err := ConvertParameters(k, parametersRawList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -320,7 +320,7 @@ case 3:
|
|||||||
key = "username-password"
|
key = "username-password"
|
||||||
parametersRawList = [["test1", "111111"], ["test2", "222222"]]
|
parametersRawList = [["test1", "111111"], ["test2", "222222"]]
|
||||||
*/
|
*/
|
||||||
func convertParameters(key string, parametersRawList interface{}) (parameterSlice []map[string]interface{}, err error) {
|
func ConvertParameters(key string, parametersRawList interface{}) (parameterSlice []map[string]interface{}, err error) {
|
||||||
parametersRawSlice := reflect.ValueOf(parametersRawList)
|
parametersRawSlice := reflect.ValueOf(parametersRawList)
|
||||||
if parametersRawSlice.Kind() != reflect.Slice {
|
if parametersRawSlice.Kind() != reflect.Slice {
|
||||||
return nil, errors.New("parameters raw value is not list")
|
return nil, errors.New("parameters raw value is not list")
|
||||||
|
|||||||
@@ -10,17 +10,17 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/httprunner/funplugin"
|
|
||||||
"github.com/httprunner/funplugin/fungo"
|
|
||||||
"github.com/maja42/goval"
|
"github.com/maja42/goval"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/httprunner/funplugin"
|
||||||
|
"github.com/httprunner/funplugin/fungo"
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newParser() *Parser {
|
func NewParser() *Parser {
|
||||||
return &Parser{}
|
return &Parser{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+202
-9
@@ -1,8 +1,11 @@
|
|||||||
package hrp
|
package hrp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -208,7 +211,7 @@ func TestParseDataStringWithVariables(t *testing.T) {
|
|||||||
{"abc$var_5", "abctrue"}, // "abcTrue"
|
{"abc$var_5", "abctrue"}, // "abcTrue"
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := newParser()
|
parser := NewParser()
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
parsedData, err := parser.Parse(data.expr, variablesMapping)
|
parsedData, err := parser.Parse(data.expr, variablesMapping)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
@@ -233,7 +236,7 @@ func TestParseDataStringWithUndefinedVariables(t *testing.T) {
|
|||||||
{"/api/$SECRET_KEY", "/api/$SECRET_KEY"}, // raise error
|
{"/api/$SECRET_KEY", "/api/$SECRET_KEY"}, // raise error
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := newParser()
|
parser := NewParser()
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
parsedData, err := parser.Parse(data.expr, variablesMapping)
|
parsedData, err := parser.Parse(data.expr, variablesMapping)
|
||||||
if !assert.Error(t, err) {
|
if !assert.Error(t, err) {
|
||||||
@@ -278,7 +281,7 @@ func TestParseDataStringWithVariablesAbnormal(t *testing.T) {
|
|||||||
{"ABC$var_1{}a", "ABCabc{}a"}, // {}
|
{"ABC$var_1{}a", "ABCabc{}a"}, // {}
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := newParser()
|
parser := NewParser()
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
parsedData, err := parser.Parse(data.expr, variablesMapping)
|
parsedData, err := parser.Parse(data.expr, variablesMapping)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
@@ -309,7 +312,7 @@ func TestParseDataMapWithVariables(t *testing.T) {
|
|||||||
{map[string]interface{}{"$var2": "$val1"}, map[string]interface{}{"123": 200}},
|
{map[string]interface{}{"$var2": "$val1"}, map[string]interface{}{"123": 200}},
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := newParser()
|
parser := NewParser()
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
parsedData, err := parser.Parse(data.expr, variablesMapping)
|
parsedData, err := parser.Parse(data.expr, variablesMapping)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
@@ -343,7 +346,7 @@ func TestParseHeaders(t *testing.T) {
|
|||||||
{map[string]string{"$var2": "$val2"}, map[string]string{"123": "<nil>"}},
|
{map[string]string{"$var2": "$val2"}, map[string]string{"123": "<nil>"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := newParser()
|
parser := NewParser()
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
parsedHeaders, err := parser.ParseHeaders(data.rawHeaders, variablesMapping)
|
parsedHeaders, err := parser.ParseHeaders(data.rawHeaders, variablesMapping)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
@@ -488,7 +491,7 @@ func TestMergeValidators(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCallBuiltinFunction(t *testing.T) {
|
func TestCallBuiltinFunction(t *testing.T) {
|
||||||
parser := newParser()
|
parser := NewParser()
|
||||||
|
|
||||||
// call function without arguments
|
// call function without arguments
|
||||||
_, err := parser.callFunc("get_timestamp")
|
_, err := parser.callFunc("get_timestamp")
|
||||||
@@ -601,7 +604,7 @@ func TestParseDataStringWithFunctions(t *testing.T) {
|
|||||||
{"123${gen_random_string($n)}abc", 11},
|
{"123${gen_random_string($n)}abc", 11},
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := newParser()
|
parser := NewParser()
|
||||||
for _, data := range testData1 {
|
for _, data := range testData1 {
|
||||||
value, err := parser.Parse(data.expr, variablesMapping)
|
value, err := parser.Parse(data.expr, variablesMapping)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
@@ -670,7 +673,7 @@ func TestParseVariables(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := newParser()
|
parser := NewParser()
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
value, err := parser.ParseVariables(data.rawVars)
|
value, err := parser.ParseVariables(data.rawVars)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
@@ -701,7 +704,7 @@ func TestParseVariablesAbnormal(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
parser := newParser()
|
parser := NewParser()
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
value, err := parser.ParseVariables(data.rawVars)
|
value, err := parser.ParseVariables(data.rawVars)
|
||||||
if !assert.Error(t, err) {
|
if !assert.Error(t, err) {
|
||||||
@@ -784,3 +787,193 @@ func TestFindallVariables(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchJmespath(t *testing.T) {
|
||||||
|
testText := `{"a": {"b": "foo"}, "c": "bar", "d": {"e": [{"f": "foo"}, {"f": "bar"}]}}`
|
||||||
|
testData := []struct {
|
||||||
|
raw string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"body.a.b", "foo"},
|
||||||
|
{"body.c", "bar"},
|
||||||
|
{"body.d.e[0].f", "foo"},
|
||||||
|
{"body.d.e[1].f", "bar"},
|
||||||
|
}
|
||||||
|
resp := http.Response{}
|
||||||
|
resp.Body = io.NopCloser(strings.NewReader(testText))
|
||||||
|
respObj, err := newHttpResponseObject(t, NewParser(), &resp)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
for _, data := range testData {
|
||||||
|
if !assert.Equal(t, data.expected, respObj.searchJmespath(data.raw)) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchRegexp(t *testing.T) {
|
||||||
|
testText := `
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li><a href="/order/addToCart" style="color: white"><i class="fa fa-shopping-cart fa-2x"></i><span class="badge">0</span></a></li>
|
||||||
|
<li class="dropdown">
|
||||||
|
<a class="dropdown-toggle" data-toggle="dropdown" href="#" style="color: white">
|
||||||
|
Leo <i class="fa fa-cog fa-2x"></i><span class="caret"></span></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="/user/changePassword">Change Password</a></li>
|
||||||
|
<li><a href="/user/addAddress">Shipping</a></li>
|
||||||
|
<li><a href="/user/addCard">Payment</a></li>
|
||||||
|
<li><a href="/order/orderHistory">Order History</a></li>
|
||||||
|
<li><a href="/user/signOut">Sign Out</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li> </li>
|
||||||
|
<li><a href="/user/signOut" style="color: white"><i class="fa fa-sign-out fa-2x"></i>
|
||||||
|
Sign Out</a></li>
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
testData := []struct {
|
||||||
|
raw string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"/user/signOut\">(.*)</a></li>", "Sign Out"},
|
||||||
|
{"<li><a href=\"/user/(.*)\" style", "signOut"},
|
||||||
|
{" (.*) <i class=\"fa fa-cog fa-2x\"></i>", "Leo"},
|
||||||
|
}
|
||||||
|
// new response object
|
||||||
|
resp := http.Response{}
|
||||||
|
resp.Body = io.NopCloser(strings.NewReader(testText))
|
||||||
|
respObj, err := newHttpResponseObject(t, NewParser(), &resp)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
for _, data := range testData {
|
||||||
|
if !assert.Equal(t, data.expected, respObj.searchRegexp(data.raw)) {
|
||||||
|
t.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertCheckExpr(t *testing.T) {
|
||||||
|
exprs := []struct {
|
||||||
|
before string
|
||||||
|
after string
|
||||||
|
}{
|
||||||
|
// normal check expression
|
||||||
|
{"a.b.c", "a.b.c"},
|
||||||
|
{"a.\"b-c\".d", "a.\"b-c\".d"},
|
||||||
|
{"a.b-c.d", "a.b-c.d"},
|
||||||
|
{"body.args.a[-1]", "body.args.a[-1]"},
|
||||||
|
// check expression using regex
|
||||||
|
{"covering (.*) testing,", "covering (.*) testing,"},
|
||||||
|
{" (.*) a-b-c", " (.*) a-b-c"},
|
||||||
|
// abnormal check expression
|
||||||
|
{"headers.Content-Type", "headers.\"Content-Type\""},
|
||||||
|
{"headers.\"Content-Type", "headers.\"Content-Type\""},
|
||||||
|
{"headers.Content-Type\"", "headers.\"Content-Type\""},
|
||||||
|
{"headers.User-Agent", "headers.\"User-Agent\""},
|
||||||
|
}
|
||||||
|
for _, expr := range exprs {
|
||||||
|
assert.Equal(t, expr.after, convertJmespathExpr(expr.before))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindAllPythonFunctionNames(t *testing.T) {
|
||||||
|
content := `
|
||||||
|
def test_1(): # exported function
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _test_2(): # exported function
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __test_3(): # private function
|
||||||
|
pass
|
||||||
|
|
||||||
|
# def test_4(): # commented out function
|
||||||
|
# pass
|
||||||
|
|
||||||
|
def Test5(): # exported function
|
||||||
|
pass
|
||||||
|
`
|
||||||
|
names, err := regexPyFunctionName.findAllFunctionNames(content)
|
||||||
|
if !assert.Nil(t, err) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.Contains(t, names, "test_1") {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.Contains(t, names, "Test5") {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.Contains(t, names, "_test_2") {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.NotContains(t, names, "__test_3") {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
// commented out function
|
||||||
|
if !assert.NotContains(t, names, "test_4") {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindAllGoFunctionNames(t *testing.T) {
|
||||||
|
content := `
|
||||||
|
func Test1() { // exported function
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFunc2() { // exported function
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { // private function
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func _Test3() { // exported function
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func Test4() { // commented out function
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
`
|
||||||
|
names, err := regexGoFunctionName.findAllFunctionNames(content)
|
||||||
|
if !assert.Nil(t, err) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.Contains(t, names, "Test1") {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.Contains(t, names, "testFunc2") {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.NotContains(t, names, "init") {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if !assert.Contains(t, names, "_Test3") {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
// commented out function
|
||||||
|
if !assert.NotContains(t, names, "Test4") {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindAllGoFunctionNamesAbnormal(t *testing.T) {
|
||||||
|
content := `
|
||||||
|
func init() { // private function
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() { // should not define main() function
|
||||||
|
return
|
||||||
|
}
|
||||||
|
`
|
||||||
|
_, err := regexGoFunctionName.findAllFunctionNames(content)
|
||||||
|
if !assert.NotNil(t, err) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -640,6 +640,7 @@ func (d *Device) InstallAPK(apkPath string, args ...string) (string, error) {
|
|||||||
haserr := func(ret string) bool {
|
haserr := func(ret string) bool {
|
||||||
return strings.Contains(ret, "Failure")
|
return strings.Contains(ret, "Failure")
|
||||||
}
|
}
|
||||||
|
// 该方法掉线不会返回error。导致误认为安装成功
|
||||||
if d.HasFeature(FeatAbbExec) {
|
if d.HasFeature(FeatAbbExec) {
|
||||||
raw, err := d.installViaABBExec(apkFile)
|
raw, err := d.installViaABBExec(apkFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -734,6 +735,11 @@ func (d *Device) ScreenCap() ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove temp file
|
||||||
|
defer func() {
|
||||||
|
go d.RunShellCommand("rm", tempPath)
|
||||||
|
}()
|
||||||
|
|
||||||
buffer := bytes.NewBuffer(nil)
|
buffer := bytes.NewBuffer(nil)
|
||||||
err = d.Pull(tempPath, buffer)
|
err = d.Pull(tempPath, buffer)
|
||||||
return buffer.Bytes(), err
|
return buffer.Bytes(), err
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
package driver_ext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/ai"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IStubDriver interface {
|
|
||||||
uixt.IDriver
|
|
||||||
|
|
||||||
LoginNoneUI(packageName, phoneNumber, captcha, password string) (info AppLoginInfo, err error)
|
|
||||||
LogoutNoneUI(packageName string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewXTDriver(driver uixt.IDriver, opts ...ai.AIServiceOption) *XTDriver {
|
|
||||||
services := ai.NewAIService(opts...)
|
|
||||||
driverExt := &XTDriver{
|
|
||||||
XTDriver: &uixt.XTDriver{
|
|
||||||
IDriver: driver,
|
|
||||||
CVService: services.ICVService,
|
|
||||||
LLMService: services.ILLMService,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return driverExt
|
|
||||||
}
|
|
||||||
|
|
||||||
type XTDriver struct {
|
|
||||||
*uixt.XTDriver
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dExt *XTDriver) InstallByUrl(url string, opts ...option.InstallOption) error {
|
|
||||||
appPath, err := builtin.DownloadFileByUrl(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = dExt.Install(appPath, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dExt *XTDriver) Install(filePath string, opts ...option.InstallOption) error {
|
|
||||||
if _, ok := dExt.GetDevice().(*uixt.AndroidDevice); ok {
|
|
||||||
stopChan := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
ticker := time.NewTicker(8 * time.Second)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
_ = dExt.TapByOCR("^(.*无视风险安装|正在扫描.*|我知道了|稍后继续|稍后提醒|继续安装|知道了|确定|继续|完成|点击继续安装|继续安装旧版本|替换|.*正在安装|安装|授权本次安装|重新安装|仍要安装|更多详情|我知道了|已了解此应用未经检测.)$", option.WithRegex(true), option.WithIgnoreNotFoundError(true))
|
|
||||||
case <-stopChan:
|
|
||||||
fmt.Println("Ticker stopped")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
defer func() {
|
|
||||||
close(stopChan)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
return dExt.GetDevice().Install(filePath, opts...)
|
|
||||||
}
|
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
package driver_ext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
|
||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StubIOSDriver struct {
|
|
||||||
*uixt.WDADriver
|
|
||||||
|
|
||||||
timeout time.Duration
|
|
||||||
douyinUrlPrefix string
|
|
||||||
douyinLiteUrlPrefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
IOSDouyinPort = 32921
|
|
||||||
IOSDouyinLitePort = 33461
|
|
||||||
defaultBightInsightPort = 8000
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewStubIOSDriver(dev *uixt.IOSDevice) (*StubIOSDriver, error) {
|
|
||||||
// lazy setup WDA
|
|
||||||
dev.Options.LazySetup = true
|
|
||||||
|
|
||||||
wdaDriver, err := uixt.NewWDADriver(dev)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
driver := &StubIOSDriver{
|
|
||||||
WDADriver: wdaDriver,
|
|
||||||
timeout: 10 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup driver
|
|
||||||
if err := driver.Setup(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// register driver session reset handler
|
|
||||||
driver.Session.RegisterResetHandler(driver.Setup)
|
|
||||||
|
|
||||||
return driver, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StubIOSDriver) Setup() error {
|
|
||||||
localPort, err := s.getLocalPort()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.Session.SetupPortForward(localPort)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.Session.SetBaseURL(fmt.Sprintf("http://127.0.0.1:%d", localPort))
|
|
||||||
|
|
||||||
localDouyinPort, err := builtin.GetFreePort()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(code.DeviceHTTPDriverError,
|
|
||||||
fmt.Sprintf("get free port failed: %v", err))
|
|
||||||
}
|
|
||||||
if err = s.Device.Forward(localDouyinPort, IOSDouyinPort); err != nil {
|
|
||||||
return errors.Wrap(code.DeviceHTTPDriverError,
|
|
||||||
fmt.Sprintf("forward tcp port failed: %v", err))
|
|
||||||
}
|
|
||||||
s.douyinUrlPrefix = fmt.Sprintf("http://127.0.0.1:%d", localDouyinPort)
|
|
||||||
|
|
||||||
localDouyinLitePort, err := builtin.GetFreePort()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(code.DeviceHTTPDriverError,
|
|
||||||
fmt.Sprintf("get free port failed: %v", err))
|
|
||||||
}
|
|
||||||
if err = s.Device.Forward(localDouyinLitePort, IOSDouyinLitePort); err != nil {
|
|
||||||
return errors.Wrap(code.DeviceHTTPDriverError,
|
|
||||||
fmt.Sprintf("forward tcp port failed: %v", err))
|
|
||||||
}
|
|
||||||
s.douyinLiteUrlPrefix = fmt.Sprintf("http://127.0.0.1:%d", localDouyinLitePort)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StubIOSDriver) getLocalPort() (int, error) {
|
|
||||||
localStubPort, err := builtin.GetFreePort()
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.Wrap(code.DeviceHTTPDriverError,
|
|
||||||
fmt.Sprintf("get free port failed: %v", err))
|
|
||||||
}
|
|
||||||
if err = s.Device.Forward(localStubPort, defaultBightInsightPort); err != nil {
|
|
||||||
return 0, errors.Wrap(code.DeviceHTTPDriverError,
|
|
||||||
fmt.Sprintf("forward tcp port failed: %v", err))
|
|
||||||
}
|
|
||||||
return localStubPort, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StubIOSDriver) Source(srcOpt ...option.SourceOption) (string, error) {
|
|
||||||
resp, err := s.Session.GET("/source?format=json&onlyWeb=false")
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("get source err")
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
return string(resp), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StubIOSDriver) OpenUrl(urlStr string, options ...option.ActionOption) (err error) {
|
|
||||||
targetUrl := fmt.Sprintf("/openURL?url=%s", url.QueryEscape(urlStr))
|
|
||||||
_, err = s.Session.GET(targetUrl)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("get source err")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StubIOSDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
|
|
||||||
appInfo, err := s.ForegroundInfo()
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
if appInfo.BundleId == "com.ss.iphone.ugc.AwemeInhouse" || appInfo.BundleId == "com.ss.iphone.ugc.awemeinhouse.lite" {
|
|
||||||
return s.LoginDouyin(appInfo.BundleId, phoneNumber, captcha, password)
|
|
||||||
} else if appInfo.BundleId == "com.ss.iphone.InHouse.article.Video" {
|
|
||||||
return s.LoginXigua(appInfo.BundleId, phoneNumber, captcha, password)
|
|
||||||
} else {
|
|
||||||
return info, fmt.Errorf("not support app")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StubIOSDriver) LoginXigua(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
|
|
||||||
loginSchema := ""
|
|
||||||
if captcha != "" {
|
|
||||||
loginSchema = fmt.Sprintf("snssdk32://local_channel_autologin?login_type=1&account=%s&smscode=%s", phoneNumber, captcha)
|
|
||||||
} else if password != "" {
|
|
||||||
loginSchema = fmt.Sprintf("snssdk32://local_channel_autologin?login_type=2&account=%s&password=%s", phoneNumber, password)
|
|
||||||
} else {
|
|
||||||
return info, fmt.Errorf("password and capcha is empty")
|
|
||||||
}
|
|
||||||
info.IsLogin = true
|
|
||||||
return info, s.OpenUrl(loginSchema)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StubIOSDriver) LoginDouyin(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"phone": phoneNumber,
|
|
||||||
}
|
|
||||||
if captcha != "" {
|
|
||||||
params["captcha"] = captcha
|
|
||||||
} else if password != "" {
|
|
||||||
params["password"] = password
|
|
||||||
} else {
|
|
||||||
return info, fmt.Errorf("password and capcha is empty")
|
|
||||||
}
|
|
||||||
bsJSON, err := json.Marshal(params)
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
|
|
||||||
urlPrefix, err := s.getUrlPrefix(packageName)
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
fullUrl := urlPrefix + "/host/login/account/" + urlPrefix
|
|
||||||
resp, err := s.Session.POST(bsJSON, fullUrl)
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
res, err := resp.ValueConvertToJsonObject()
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
log.Info().Msgf("%v", res)
|
|
||||||
// {'isSuccess': True, 'data': '登录成功', 'code': 0}
|
|
||||||
if res["isSuccess"] != true {
|
|
||||||
err = fmt.Errorf("falied to logout %s", res["data"])
|
|
||||||
log.Err(err).Msgf("%v", res)
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
time.Sleep(20 * time.Second)
|
|
||||||
info, err = s.getLoginAppInfo(packageName)
|
|
||||||
if err != nil || !info.IsLogin {
|
|
||||||
return info, fmt.Errorf("falied to login %v", info)
|
|
||||||
}
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StubIOSDriver) LogoutNoneUI(packageName string) error {
|
|
||||||
urlPrefix, err := s.getUrlPrefix(packageName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fullUrl := urlPrefix + "/host/loginout/"
|
|
||||||
resp, err := s.Session.GET(fullUrl)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res, err := resp.ValueConvertToJsonObject()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Info().Msgf("%v", res)
|
|
||||||
if res["isSuccess"] != true {
|
|
||||||
err = fmt.Errorf("falied to logout %s", res["data"])
|
|
||||||
log.Err(err).Msgf("%v", res)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Second)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StubIOSDriver) EnableDevtool(packageName string, enable bool) (err error) {
|
|
||||||
urlPrefix, err := s.getUrlPrefix(packageName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fullUrl := urlPrefix + "/host/devtool/enable"
|
|
||||||
|
|
||||||
params := map[string]interface{}{
|
|
||||||
"enable": enable,
|
|
||||||
}
|
|
||||||
bsJSON, err := json.Marshal(params)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp, err := s.Session.POST(bsJSON, fullUrl)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res, err := resp.ValueConvertToJsonObject()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Info().Msgf("%v", res)
|
|
||||||
if res["isSuccess"] != true {
|
|
||||||
err = fmt.Errorf("falied to enable devtool %s", res["data"])
|
|
||||||
log.Err(err).Msgf("%v", res)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StubIOSDriver) getLoginAppInfo(packageName string) (info AppLoginInfo, err error) {
|
|
||||||
urlPrefix, err := s.getUrlPrefix(packageName)
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
fullUrl := urlPrefix + "/host/app/info/"
|
|
||||||
|
|
||||||
resp, err := s.Session.GET(fullUrl)
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
res, err := resp.ValueConvertToJsonObject()
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
log.Info().Msgf("%v", res)
|
|
||||||
if res["isSuccess"] != true {
|
|
||||||
err = fmt.Errorf("falied to get is login %s", res["data"])
|
|
||||||
log.Err(err).Msgf("%v", res)
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal([]byte(res["data"].(string)), &info)
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StubIOSDriver) getUrlPrefix(packageName string) (urlPrefix string, err error) {
|
|
||||||
if packageName == "com.ss.iphone.ugc.AwemeInhouse" {
|
|
||||||
urlPrefix = s.douyinUrlPrefix
|
|
||||||
} else if packageName == "com.ss.iphone.ugc.awemeinhouse.lite" {
|
|
||||||
urlPrefix = s.douyinLiteUrlPrefix
|
|
||||||
} else {
|
|
||||||
return "", fmt.Errorf("not support app %s", packageName)
|
|
||||||
}
|
|
||||||
return urlPrefix, nil
|
|
||||||
}
|
|
||||||
@@ -6,11 +6,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/httprunner/funplugin"
|
|
||||||
"github.com/httprunner/funplugin/myexec"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/httprunner/funplugin"
|
||||||
|
"github.com/httprunner/funplugin/myexec"
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
"github.com/httprunner/httprunner/v5/internal/config"
|
"github.com/httprunner/httprunner/v5/internal/config"
|
||||||
"github.com/httprunner/httprunner/v5/internal/sdk"
|
"github.com/httprunner/httprunner/v5/internal/sdk"
|
||||||
@@ -37,12 +37,12 @@ func initPlugin(path, venv string, logOn bool) (plugin funplugin.IPlugin, err er
|
|||||||
Bool("logOn", logOn).Msg("init plugin")
|
Bool("logOn", logOn).Msg("init plugin")
|
||||||
// plugin file not found
|
// plugin file not found
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return nil, nil
|
return nil, errors.New("testcase path not specified")
|
||||||
}
|
}
|
||||||
pluginPath, err := locatePlugin(path)
|
pluginPath, err := LocatePlugin(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Str("path", path).Msg("locate plugin failed")
|
log.Warn().Str("path", path).Msg("locate plugin failed")
|
||||||
return nil, nil
|
return nil, errors.Wrap(err, "locate plugin failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginMutex.Lock()
|
pluginMutex.Lock()
|
||||||
@@ -100,21 +100,21 @@ func initPlugin(path, venv string, logOn bool) (plugin funplugin.IPlugin, err er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func locatePlugin(path string) (pluginPath string, err error) {
|
func LocatePlugin(path string) (pluginPath string, err error) {
|
||||||
log.Info().Str("path", path).Msg("locate plugin")
|
log.Info().Str("path", path).Msg("locate plugin")
|
||||||
// priority: hashicorp plugin (debugtalk.bin > debugtalk.py) > go plugin (debugtalk.so)
|
// priority: hashicorp plugin (debugtalk.bin > debugtalk.py) > go plugin (debugtalk.so)
|
||||||
|
|
||||||
pluginPath, err = locateFile(path, PluginHashicorpGoBuiltFile)
|
pluginPath, err = LocateFile(path, PluginHashicorpGoBuiltFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginPath, err = locateFile(path, PluginPySourceFile)
|
pluginPath, err = LocateFile(path, PluginPySourceFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginPath, err = locateFile(path, PluginGoBuiltFile)
|
pluginPath, err = LocateFile(path, PluginGoBuiltFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -122,9 +122,9 @@ func locatePlugin(path string) (pluginPath string, err error) {
|
|||||||
return "", errors.New("plugin file not found")
|
return "", errors.New("plugin file not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// locateFile searches destFile upward recursively until system root dir
|
// LocateFile searches destFile upward recursively until system root dir
|
||||||
// if not found, then searches in hrp executable dir
|
// if not found, then searches in hrp executable dir
|
||||||
func locateFile(startPath string, destFile string) (pluginPath string, err error) {
|
func LocateFile(startPath string, destFile string) (pluginPath string, err error) {
|
||||||
stat, err := os.Stat(startPath)
|
stat, err := os.Stat(startPath)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return "", errors.Wrap(err, "start path not exists")
|
return "", errors.Wrap(err, "start path not exists")
|
||||||
@@ -153,7 +153,7 @@ func locateFile(startPath string, destFile string) (pluginPath string, err error
|
|||||||
return "", errors.New("searched to system root dir, plugin file not found")
|
return "", errors.New("searched to system root dir, plugin file not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return locateFile(parentDir, destFile)
|
return LocateFile(parentDir, destFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// locateExecutable finds destFile in hrp executable dir
|
// locateExecutable finds destFile in hrp executable dir
|
||||||
@@ -173,13 +173,13 @@ func locateExecutable(destFile string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetProjectRootDirPath(path string) (rootDir string, err error) {
|
func GetProjectRootDirPath(path string) (rootDir string, err error) {
|
||||||
pluginPath, err := locatePlugin(path)
|
pluginPath, err := LocatePlugin(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
rootDir = filepath.Dir(pluginPath)
|
rootDir = filepath.Dir(pluginPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// fix: no debugtalk file in project but having proj.json created by startproject
|
// fix: no debugtalk file in project but having proj.json created by startproject
|
||||||
projPath, err := locateFile(path, projectInfoFile)
|
projPath, err := LocateFile(path, projectInfoFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
rootDir = filepath.Dir(projPath)
|
rootDir = filepath.Dir(projPath)
|
||||||
return
|
return
|
||||||
@@ -188,5 +188,5 @@ func GetProjectRootDirPath(path string) (rootDir string, err error) {
|
|||||||
// failed to locate project root dir
|
// failed to locate project root dir
|
||||||
// maybe project plugin debugtalk.xx and proj.json are not exist
|
// maybe project plugin debugtalk.xx and proj.json are not exist
|
||||||
// use current dir instead
|
// use current dir instead
|
||||||
return config.RootDir, nil
|
return config.GetConfig().RootDir, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package hrp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLocateFile(t *testing.T) {
|
|
||||||
// specify target file path
|
|
||||||
_, err := locateFile(tmpl("plugin/debugtalk.go"), PluginGoSourceFile)
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// specify path with the same dir
|
|
||||||
_, err = locateFile(tmpl("plugin/debugtalk.py"), PluginGoSourceFile)
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// specify target file path dir
|
|
||||||
_, err = locateFile(tmpl("plugin/"), PluginGoSourceFile)
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// specify wrong path
|
|
||||||
_, err = locateFile(".", PluginGoSourceFile)
|
|
||||||
if !assert.Error(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
_, err = locateFile("/abc", PluginGoSourceFile)
|
|
||||||
if !assert.Error(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLocatePythonPlugin(t *testing.T) {
|
|
||||||
_, err := locatePlugin(tmpl("plugin/debugtalk.py"))
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLocateGoPlugin(t *testing.T) {
|
|
||||||
buildHashicorpGoPlugin()
|
|
||||||
defer removeHashicorpGoPlugin()
|
|
||||||
|
|
||||||
_, err := locatePlugin(tmpl("debugtalk.bin"))
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,17 +17,18 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/httprunner/funplugin"
|
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
|
|
||||||
|
"github.com/httprunner/funplugin"
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v5/internal/sdk"
|
"github.com/httprunner/httprunner/v5/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v5/internal/version"
|
"github.com/httprunner/httprunner/v5/internal/version"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/ai"
|
"github.com/httprunner/httprunner/v5/uixt/ai"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Run starts to run testcase with default configs.
|
// Run starts to run testcase with default configs.
|
||||||
@@ -281,7 +282,7 @@ func (r *HRPRunner) NewCaseRunner(testcase TestCase) (*CaseRunner, error) {
|
|||||||
caseRunner := &CaseRunner{
|
caseRunner := &CaseRunner{
|
||||||
TestCase: testcase,
|
TestCase: testcase,
|
||||||
hrpRunner: r,
|
hrpRunner: r,
|
||||||
parser: newParser(),
|
parser: NewParser(),
|
||||||
uixtDrivers: make(map[string]*uixt.XTDriver),
|
uixtDrivers: make(map[string]*uixt.XTDriver),
|
||||||
}
|
}
|
||||||
config := testcase.Config.Get()
|
config := testcase.Config.Get()
|
||||||
@@ -296,7 +297,7 @@ func (r *HRPRunner) NewCaseRunner(testcase TestCase) (*CaseRunner, error) {
|
|||||||
|
|
||||||
// load plugin info to testcase config
|
// load plugin info to testcase config
|
||||||
pluginPath := plugin.Path()
|
pluginPath := plugin.Path()
|
||||||
pluginContent, err := readFile(pluginPath)
|
pluginContent, err := builtin.LoadFile(pluginPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -407,7 +408,7 @@ func (r *CaseRunner) parseConfig() (parsedConfig *TConfig, err error) {
|
|||||||
parsedConfig.WebSocketSetting.checkWebSocket()
|
parsedConfig.WebSocketSetting.checkWebSocket()
|
||||||
|
|
||||||
// parse testcase config parameters
|
// parse testcase config parameters
|
||||||
parametersIterator, err := r.parser.initParametersIterator(parsedConfig)
|
parametersIterator, err := r.parser.InitParametersIterator(parsedConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).
|
log.Error().Err(err).
|
||||||
Interface("parameters", parsedConfig.Parameters).
|
Interface("parameters", parsedConfig.Parameters).
|
||||||
|
|||||||
+17
-16
@@ -2,11 +2,13 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func foregroundAppHandler(c *gin.Context) {
|
func (r *Router) foregroundAppHandler(c *gin.Context) {
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -18,13 +20,13 @@ func foregroundAppHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, appInfo)
|
RenderSuccess(c, appInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func appInfoHandler(c *gin.Context) {
|
func (r *Router) appInfoHandler(c *gin.Context) {
|
||||||
var appInfoReq AppInfoRequest
|
var appInfoReq AppInfoRequest
|
||||||
if err := c.ShouldBindQuery(&appInfoReq); err != nil {
|
if err := c.ShouldBindQuery(&appInfoReq); err != nil {
|
||||||
RenderErrorValidateRequest(c, err)
|
RenderErrorValidateRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
device, err := GetDevice(c)
|
device, err := r.GetDevice(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -47,18 +49,18 @@ func appInfoHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearAppHandler(c *gin.Context) {
|
func (r *Router) clearAppHandler(c *gin.Context) {
|
||||||
var appClearReq AppClearRequest
|
var appClearReq AppClearRequest
|
||||||
if err := c.ShouldBindJSON(&appClearReq); err != nil {
|
if err := c.ShouldBindJSON(&appClearReq); err != nil {
|
||||||
RenderErrorValidateRequest(c, err)
|
RenderErrorValidateRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = driver.IDriver.(*uixt.ADBDriver).AppClear(appClearReq.PackageName)
|
err = driver.AppClear(appClearReq.PackageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RenderError(c, err)
|
RenderError(c, err)
|
||||||
return
|
return
|
||||||
@@ -66,13 +68,13 @@ func clearAppHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, true)
|
RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func launchAppHandler(c *gin.Context) {
|
func (r *Router) launchAppHandler(c *gin.Context) {
|
||||||
var appLaunchReq AppLaunchRequest
|
var appLaunchReq AppLaunchRequest
|
||||||
if err := c.ShouldBindJSON(&appLaunchReq); err != nil {
|
if err := c.ShouldBindJSON(&appLaunchReq); err != nil {
|
||||||
RenderErrorValidateRequest(c, err)
|
RenderErrorValidateRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -84,13 +86,13 @@ func launchAppHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, true)
|
RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func terminalAppHandler(c *gin.Context) {
|
func (r *Router) terminalAppHandler(c *gin.Context) {
|
||||||
var appTerminalReq AppTerminalRequest
|
var appTerminalReq AppTerminalRequest
|
||||||
if err := c.ShouldBindJSON(&appTerminalReq); err != nil {
|
if err := c.ShouldBindJSON(&appTerminalReq); err != nil {
|
||||||
RenderErrorValidateRequest(c, err)
|
RenderErrorValidateRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -102,20 +104,19 @@ func terminalAppHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, true)
|
RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func uninstallAppHandler(c *gin.Context) {
|
func (r *Router) uninstallAppHandler(c *gin.Context) {
|
||||||
var appUninstallReq AppUninstallRequest
|
var appUninstallReq AppUninstallRequest
|
||||||
if err := c.ShouldBindJSON(&appUninstallReq); err != nil {
|
if err := c.ShouldBindJSON(&appUninstallReq); err != nil {
|
||||||
RenderErrorValidateRequest(c, err)
|
RenderErrorValidateRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = driver.GetDevice().Uninstall(appUninstallReq.PackageName)
|
err = driver.GetDevice().Uninstall(appUninstallReq.PackageName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RenderError(c, err)
|
log.Err(err).Msg("failed to uninstall app")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
RenderSuccess(c, true)
|
RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-12
@@ -9,17 +9,17 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/ai"
|
"github.com/httprunner/httprunner/v5/uixt/ai"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetDriver(c *gin.Context) (driverExt *uixt.XTDriver, err error) {
|
func (r *Router) GetDriver(c *gin.Context) (driverExt *uixt.XTDriver, err error) {
|
||||||
deviceObj, exists := c.Get("device")
|
|
||||||
var device uixt.IDevice
|
var device uixt.IDevice
|
||||||
var driver uixt.IDriver
|
var driver uixt.IDriver
|
||||||
|
deviceObj, exists := c.Get("device")
|
||||||
if !exists {
|
if !exists {
|
||||||
device, err = GetDevice(c)
|
device, err = r.GetDevice(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -32,14 +32,14 @@ func GetDriver(c *gin.Context) (driverExt *uixt.XTDriver, err error) {
|
|||||||
RenderErrorInitDriver(c, err)
|
RenderErrorInitDriver(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Set("driver", driver)
|
|
||||||
|
|
||||||
driverExt = uixt.NewXTDriver(driver,
|
driverExt = uixt.NewXTDriver(driver,
|
||||||
ai.WithCVService(ai.CVServiceTypeVEDEM))
|
ai.WithCVService(ai.CVServiceTypeVEDEM))
|
||||||
|
c.Set("driver", driverExt)
|
||||||
return driverExt, nil
|
return driverExt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDevice(c *gin.Context) (device uixt.IDevice, err error) {
|
func (r *Router) GetDevice(c *gin.Context) (device uixt.IDevice, err error) {
|
||||||
platform := c.Param("platform")
|
platform := c.Param("platform")
|
||||||
serial := c.Param("serial")
|
serial := c.Param("serial")
|
||||||
if serial == "" {
|
if serial == "" {
|
||||||
@@ -54,7 +54,6 @@ func GetDevice(c *gin.Context) (device uixt.IDevice, err error) {
|
|||||||
RenderErrorInitDriver(c, err)
|
RenderErrorInitDriver(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = device.Setup()
|
|
||||||
case "ios":
|
case "ios":
|
||||||
device, err = uixt.NewIOSDevice(
|
device, err = uixt.NewIOSDevice(
|
||||||
option.WithUDID(serial),
|
option.WithUDID(serial),
|
||||||
@@ -66,10 +65,20 @@ func GetDevice(c *gin.Context) (device uixt.IDevice, err error) {
|
|||||||
RenderErrorInitDriver(c, err)
|
RenderErrorInitDriver(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case "browser":
|
||||||
|
device, err = uixt.NewBrowserDevice(option.WithBrowserID(serial))
|
||||||
|
if err != nil {
|
||||||
|
RenderErrorInitDriver(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("[%s]: invalid platform", c.HandlerName())
|
err = fmt.Errorf("[%s]: invalid platform", c.HandlerName())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
err = device.Setup()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("setup device failed")
|
||||||
|
}
|
||||||
c.Set("device", device)
|
c.Set("device", device)
|
||||||
return device, nil
|
return device, nil
|
||||||
}
|
}
|
||||||
@@ -87,7 +96,7 @@ func RenderError(c *gin.Context, err error) {
|
|||||||
c.JSON(http.StatusInternalServerError,
|
c.JSON(http.StatusInternalServerError,
|
||||||
HttpResponse{
|
HttpResponse{
|
||||||
Code: code.GetErrorCode(err),
|
Code: code.GetErrorCode(err),
|
||||||
Message: err.Error(),
|
Message: "grey " + err.Error(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
@@ -102,7 +111,7 @@ func RenderErrorInitDriver(c *gin.Context, err error) {
|
|||||||
c.JSON(http.StatusInternalServerError,
|
c.JSON(http.StatusInternalServerError,
|
||||||
HttpResponse{
|
HttpResponse{
|
||||||
Code: errCode,
|
Code: errCode,
|
||||||
Message: "init driver failed",
|
Message: "grey init driver failed",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
@@ -112,7 +121,7 @@ func RenderErrorValidateRequest(c *gin.Context, err error) {
|
|||||||
log.Error().Err(err).Msg("validate request failed")
|
log.Error().Err(err).Msg("validate request failed")
|
||||||
c.JSON(http.StatusBadRequest, HttpResponse{
|
c.JSON(http.StatusBadRequest, HttpResponse{
|
||||||
Code: code.GetErrorCode(code.InvalidParamError),
|
Code: code.GetErrorCode(code.InvalidParamError),
|
||||||
Message: fmt.Sprintf("validate request param failed: %s", err.Error()),
|
Message: fmt.Sprintf("grey validate request param failed: %s", err.Error()),
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-11
@@ -7,14 +7,14 @@ import (
|
|||||||
"github.com/Masterminds/semver"
|
"github.com/Masterminds/semver"
|
||||||
"github.com/danielpaulus/go-ios/ios"
|
"github.com/danielpaulus/go-ios/ios"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/gadb"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v5/pkg/gadb"
|
||||||
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func listDeviceHandler(c *gin.Context) {
|
func (r *Router) listDeviceHandler(c *gin.Context) {
|
||||||
var deviceList []interface{}
|
var deviceList []interface{}
|
||||||
client, err := gadb.NewClient()
|
client, err := gadb.NewClient()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -88,17 +88,47 @@ func listDeviceHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, deviceList)
|
RenderSuccess(c, deviceList)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pushImageHandler(c *gin.Context) {
|
func createBrowserHandler(c *gin.Context) {
|
||||||
|
var createBrowserReq CreateBrowserRequest
|
||||||
|
if err := c.ShouldBindJSON(&createBrowserReq); err != nil {
|
||||||
|
RenderErrorValidateRequest(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
browserInfo, err := uixt.CreateBrowser(createBrowserReq.Timeout)
|
||||||
|
if err != nil {
|
||||||
|
RenderError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
RenderSuccess(c, browserInfo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) deleteBrowserHandler(c *gin.Context) {
|
||||||
|
driver, err := r.GetDriver(c)
|
||||||
|
if err != nil {
|
||||||
|
RenderError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = driver.DeleteSession()
|
||||||
|
if err != nil {
|
||||||
|
RenderError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
RenderSuccess(c, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) pushImageHandler(c *gin.Context) {
|
||||||
var pushMediaReq PushMediaRequest
|
var pushMediaReq PushMediaRequest
|
||||||
if err := c.ShouldBindJSON(&pushMediaReq); err != nil {
|
if err := c.ShouldBindJSON(&pushMediaReq); err != nil {
|
||||||
RenderErrorValidateRequest(c, err)
|
RenderErrorValidateRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
imagePath, err := builtin.DownloadFileByUrl(pushMediaReq.ImageUrl)
|
imagePath, err := uixt.DownloadFileByUrl(pushMediaReq.ImageUrl)
|
||||||
if path.Ext(imagePath) == "" {
|
if path.Ext(imagePath) == "" {
|
||||||
err = os.Rename(imagePath, imagePath+".png")
|
err = os.Rename(imagePath, imagePath+".png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -122,8 +152,8 @@ func pushImageHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, true)
|
RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearImageHandler(c *gin.Context) {
|
func (r *Router) clearImageHandler(c *gin.Context) {
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -135,6 +165,6 @@ func clearImageHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, true)
|
RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func videoHandler(c *gin.Context) {
|
func (r *Router) videoHandler(c *gin.Context) {
|
||||||
RenderSuccess(c, "")
|
RenderSuccess(c, "")
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-6
@@ -6,18 +6,17 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
|
||||||
"github.com/httprunner/httprunner/v5/server"
|
"github.com/httprunner/httprunner/v5/server"
|
||||||
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func installAppHandler(c *gin.Context) {
|
func (r *RouterExt) installAppHandler(c *gin.Context) {
|
||||||
var appInstallReq AppInstallRequest
|
var appInstallReq AppInstallRequest
|
||||||
if err := c.ShouldBindJSON(&appInstallReq); err != nil {
|
if err := c.ShouldBindJSON(&appInstallReq); err != nil {
|
||||||
server.RenderErrorValidateRequest(c, err)
|
server.RenderErrorValidateRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -32,7 +31,7 @@ func installAppHandler(c *gin.Context) {
|
|||||||
server.RenderSuccess(c, true)
|
server.RenderSuccess(c, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
localMappingPath, err := builtin.DownloadFileByUrl(appInstallReq.MappingUrl)
|
localMappingPath, err := uixt.DownloadFileByUrl(appInstallReq.MappingUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.RenderError(c, err)
|
server.RenderError(c, err)
|
||||||
}
|
}
|
||||||
@@ -45,7 +44,7 @@ func installAppHandler(c *gin.Context) {
|
|||||||
server.RenderError(c, err)
|
server.RenderError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
localResourceMappingPath, err := builtin.DownloadFileByUrl(
|
localResourceMappingPath, err := uixt.DownloadFileByUrl(
|
||||||
appInstallReq.ResourceMappingUrl)
|
appInstallReq.ResourceMappingUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.RenderError(c, err)
|
server.RenderError(c, err)
|
||||||
|
|||||||
+13
-10
@@ -5,37 +5,40 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/ai"
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/driver_ext"
|
|
||||||
"github.com/httprunner/httprunner/v5/server"
|
"github.com/httprunner/httprunner/v5/server"
|
||||||
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
|
"github.com/httprunner/httprunner/v5/uixt/ai"
|
||||||
|
"github.com/httprunner/httprunner/v5/uixt/driver_ext"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetDriver(c *gin.Context) (driverExt *driver_ext.XTDriver, err error) {
|
func (r *RouterExt) GetDriver(c *gin.Context) (driverExt *driver_ext.StubXTDriver, err error) {
|
||||||
platform := c.Param("platform")
|
|
||||||
deviceObj, exists := c.Get("device")
|
|
||||||
var device uixt.IDevice
|
var device uixt.IDevice
|
||||||
var driver uixt.IDriver
|
var driver driver_ext.IStubDriver
|
||||||
|
deviceObj, exists := c.Get("device")
|
||||||
if !exists {
|
if !exists {
|
||||||
device, err = server.GetDevice(c)
|
device, err = r.GetDevice(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
device = deviceObj.(uixt.IDevice)
|
device = deviceObj.(uixt.IDevice)
|
||||||
}
|
}
|
||||||
|
platform := c.Param("platform")
|
||||||
switch strings.ToLower(platform) {
|
switch strings.ToLower(platform) {
|
||||||
case "android":
|
case "android":
|
||||||
driver, err = driver_ext.NewStubAndroidDriver(device.(*uixt.AndroidDevice))
|
driver, err = driver_ext.NewStubAndroidDriver(device.(*uixt.AndroidDevice))
|
||||||
case "ios":
|
case "ios":
|
||||||
driver, err = driver_ext.NewStubIOSDriver(device.(*uixt.IOSDevice))
|
driver, err = driver_ext.NewStubIOSDriver(device.(*uixt.IOSDevice))
|
||||||
|
case "browser":
|
||||||
|
driver, err = driver_ext.NewStubBrowserDriver(device.(*uixt.BrowserDevice))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.RenderErrorInitDriver(c, err)
|
server.RenderErrorInitDriver(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Set("driver", driver)
|
|
||||||
driverExt = driver_ext.NewXTDriver(driver,
|
driverExt = driver_ext.NewStubXTDriver(driver,
|
||||||
ai.WithCVService(ai.CVServiceTypeVEDEM))
|
ai.WithCVService(ai.CVServiceTypeVEDEM))
|
||||||
|
c.Set("driver", driverExt)
|
||||||
return driverExt, nil
|
return driverExt, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-12
@@ -1,27 +1,28 @@
|
|||||||
package server_ext
|
package server_ext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/driver_ext"
|
|
||||||
"github.com/httprunner/httprunner/v5/server"
|
"github.com/httprunner/httprunner/v5/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
func loginHandler(c *gin.Context) {
|
func (r *RouterExt) loginHandler(c *gin.Context) {
|
||||||
var loginReq LoginRequest
|
var loginReq LoginRequest
|
||||||
if err := c.ShouldBindJSON(&loginReq); err != nil {
|
if err := c.ShouldBindJSON(&loginReq); err != nil {
|
||||||
server.RenderErrorValidateRequest(c, err)
|
server.RenderErrorValidateRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
info, err := driver.IDriver.(driver_ext.IStubDriver).
|
info, err := driver.LoginNoneUI(
|
||||||
LoginNoneUI(loginReq.PackageName, loginReq.PhoneNumber,
|
loginReq.PackageName, loginReq.PhoneNumber,
|
||||||
loginReq.Captcha, loginReq.Password)
|
loginReq.Captcha, loginReq.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.RenderError(c, err)
|
server.RenderError(c, err)
|
||||||
return
|
return
|
||||||
@@ -29,19 +30,18 @@ func loginHandler(c *gin.Context) {
|
|||||||
server.RenderSuccess(c, info)
|
server.RenderSuccess(c, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
func logoutHandler(c *gin.Context) {
|
func (r *RouterExt) logoutHandler(c *gin.Context) {
|
||||||
var logoutReq LogoutRequest
|
var logoutReq LogoutRequest
|
||||||
if err := c.ShouldBindJSON(&logoutReq); err != nil {
|
if err := c.ShouldBindJSON(&logoutReq); err != nil {
|
||||||
server.RenderErrorValidateRequest(c, err)
|
server.RenderErrorValidateRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = driver.IDriver.(driver_ext.IStubDriver).
|
err = driver.LogoutNoneUI(logoutReq.PackageName)
|
||||||
LogoutNoneUI(logoutReq.PackageName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.RenderError(c, err)
|
server.RenderError(c, err)
|
||||||
return
|
return
|
||||||
@@ -49,8 +49,8 @@ func logoutHandler(c *gin.Context) {
|
|||||||
server.RenderSuccess(c, true)
|
server.RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sourceHandler(c *gin.Context) {
|
func (r *RouterExt) sourceHandler(c *gin.Context) {
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -58,5 +58,9 @@ func sourceHandler(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Msg("get source failed")
|
log.Warn().Err(err).Msg("get source failed")
|
||||||
}
|
}
|
||||||
|
if source == "{}" || source == "" {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
source, err = driver.Source()
|
||||||
|
}
|
||||||
server.RenderSuccess(c, source)
|
server.RenderSuccess(c, source)
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-7
@@ -4,13 +4,23 @@ import (
|
|||||||
"github.com/httprunner/httprunner/v5/server"
|
"github.com/httprunner/httprunner/v5/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewExtRouter() *server.Router {
|
type RouterExt struct {
|
||||||
router := server.NewRouter()
|
*server.Router
|
||||||
apiV1PlatformSerial := router.Group("/api/v1").Group("/:platform").Group("/:serial")
|
}
|
||||||
|
|
||||||
apiV1PlatformSerial.GET("/stub/source", sourceHandler)
|
func NewExtRouter() *RouterExt {
|
||||||
apiV1PlatformSerial.POST("/stub/login", loginHandler)
|
router := &RouterExt{
|
||||||
apiV1PlatformSerial.POST("/stub/logout", logoutHandler)
|
Router: server.NewRouter(),
|
||||||
apiV1PlatformSerial.POST("/app/install", installAppHandler)
|
}
|
||||||
|
router.Init()
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RouterExt) Init() {
|
||||||
|
apiV1PlatformSerial := r.Group("/api/v1").Group("/:platform").Group("/:serial")
|
||||||
|
|
||||||
|
apiV1PlatformSerial.GET("/stub/source", r.sourceHandler)
|
||||||
|
apiV1PlatformSerial.POST("/stub/login", r.loginHandler)
|
||||||
|
apiV1PlatformSerial.POST("/stub/logout", r.logoutHandler)
|
||||||
|
apiV1PlatformSerial.POST("/app/install", r.installAppHandler)
|
||||||
|
}
|
||||||
|
|||||||
+3
-3
@@ -9,7 +9,7 @@ type AppInstallRequest struct {
|
|||||||
|
|
||||||
type LoginRequest struct {
|
type LoginRequest struct {
|
||||||
PackageName string `json:"packageName"`
|
PackageName string `json:"packageName"`
|
||||||
PhoneNumber string `json:"phoneNumber" binding:"required"`
|
PhoneNumber string `json:"phoneNumber"`
|
||||||
Captcha string `json:"captcha" binding:"required_without=Password"`
|
Captcha string `json:"captcha" binding:"required_without=Password"`
|
||||||
Password string `json:"password" binding:"required_without=Captcha"`
|
Password string `json:"password" binding:"required_without=Captcha"`
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ type LogoutRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HttpResponse struct {
|
type HttpResponse struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"errorCode"`
|
||||||
Message string `json:"msg"`
|
Message string `json:"errorMsg"`
|
||||||
Result interface{} `json:"result,omitempty"`
|
Result interface{} `json:"result,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-9
@@ -3,11 +3,11 @@ package server
|
|||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func unlockHandler(c *gin.Context) {
|
func (r *Router) unlockHandler(c *gin.Context) {
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -19,8 +19,8 @@ func unlockHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, true)
|
RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func homeHandler(c *gin.Context) {
|
func (r *Router) homeHandler(c *gin.Context) {
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ func homeHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, true)
|
RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func backspaceHandler(c *gin.Context) {
|
func (r *Router) backspaceHandler(c *gin.Context) {
|
||||||
var deleteReq DeleteRequest
|
var deleteReq DeleteRequest
|
||||||
if err := c.ShouldBindJSON(&deleteReq); err != nil {
|
if err := c.ShouldBindJSON(&deleteReq); err != nil {
|
||||||
RenderErrorValidateRequest(c, err)
|
RenderErrorValidateRequest(c, err)
|
||||||
@@ -41,7 +41,7 @@ func backspaceHandler(c *gin.Context) {
|
|||||||
if deleteReq.Count == 0 {
|
if deleteReq.Count == 0 {
|
||||||
deleteReq.Count = 20
|
deleteReq.Count = 20
|
||||||
}
|
}
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -53,13 +53,13 @@ func backspaceHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, true)
|
RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func keycodeHandler(c *gin.Context) {
|
func (r *Router) keycodeHandler(c *gin.Context) {
|
||||||
var keycodeReq KeycodeRequest
|
var keycodeReq KeycodeRequest
|
||||||
if err := c.ShouldBindJSON(&keycodeReq); err != nil {
|
if err := c.ShouldBindJSON(&keycodeReq); err != nil {
|
||||||
RenderErrorValidateRequest(c, err)
|
RenderErrorValidateRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-33
@@ -4,14 +4,16 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRouter() *Router {
|
func NewRouter() *Router {
|
||||||
router := &Router{}
|
router := &Router{
|
||||||
|
Engine: gin.Default(),
|
||||||
|
}
|
||||||
router.Init()
|
router.Init()
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
@@ -21,46 +23,51 @@ type Router struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Init() {
|
func (r *Router) Init() {
|
||||||
r.Engine = gin.Default()
|
r.Engine.Use(r.teardown())
|
||||||
r.Engine.Use(teardown())
|
r.Engine.GET("/ping", r.pingHandler)
|
||||||
r.Engine.GET("/ping", pingHandler)
|
r.Engine.GET("/", r.pingHandler)
|
||||||
r.Engine.GET("/", pingHandler)
|
r.Engine.POST("/", r.pingHandler)
|
||||||
r.Engine.POST("/", pingHandler)
|
r.Engine.GET("/api/v1/devices", r.listDeviceHandler)
|
||||||
r.Engine.GET("/api/v1/devices", listDeviceHandler)
|
r.Engine.POST("/api/v1/browser/create_browser", createBrowserHandler)
|
||||||
|
|
||||||
apiV1PlatformSerial := r.Group("/api/v1").Group("/:platform").Group("/:serial")
|
apiV1PlatformSerial := r.Group("/api/v1").Group("/:platform").Group("/:serial")
|
||||||
|
|
||||||
// UI operations
|
// UI operations
|
||||||
apiV1PlatformSerial.POST("/ui/tap", tapHandler)
|
apiV1PlatformSerial.POST("/ui/tap", r.tapHandler)
|
||||||
apiV1PlatformSerial.POST("/ui/double_tap", doubleTapHandler)
|
apiV1PlatformSerial.POST("/ui/right_click", r.rightClickHandler)
|
||||||
apiV1PlatformSerial.POST("/ui/drag", dragHandler)
|
apiV1PlatformSerial.POST("/ui/double_tap", r.doubleTapHandler)
|
||||||
apiV1PlatformSerial.POST("/ui/input", inputHandler)
|
apiV1PlatformSerial.POST("/ui/drag", r.dragHandler)
|
||||||
apiV1PlatformSerial.POST("/ui/home", homeHandler)
|
apiV1PlatformSerial.POST("/ui/input", r.inputHandler)
|
||||||
|
apiV1PlatformSerial.POST("/ui/home", r.homeHandler)
|
||||||
|
apiV1PlatformSerial.POST("/ui/upload", r.uploadHandler)
|
||||||
|
apiV1PlatformSerial.POST("/ui/hover", r.hoverHandler)
|
||||||
|
apiV1PlatformSerial.POST("/ui/scroll", r.scrollHandler)
|
||||||
|
|
||||||
// Key operations
|
// Key operations
|
||||||
apiV1PlatformSerial.POST("/key/unlock", unlockHandler)
|
apiV1PlatformSerial.POST("/key/unlock", r.unlockHandler)
|
||||||
apiV1PlatformSerial.POST("/key/home", homeHandler)
|
apiV1PlatformSerial.POST("/key/home", r.homeHandler)
|
||||||
apiV1PlatformSerial.POST("/key/backspace", backspaceHandler)
|
apiV1PlatformSerial.POST("/key/backspace", r.backspaceHandler)
|
||||||
apiV1PlatformSerial.POST("/key", keycodeHandler)
|
apiV1PlatformSerial.POST("/key", r.keycodeHandler)
|
||||||
|
|
||||||
// APP operations
|
// APP operations
|
||||||
apiV1PlatformSerial.GET("/app/foreground", foregroundAppHandler)
|
apiV1PlatformSerial.GET("/app/foreground", r.foregroundAppHandler)
|
||||||
apiV1PlatformSerial.GET("/app/appInfo", appInfoHandler)
|
apiV1PlatformSerial.GET("/app/appInfo", r.appInfoHandler)
|
||||||
apiV1PlatformSerial.POST("/app/clear", clearAppHandler)
|
apiV1PlatformSerial.POST("/app/clear", r.clearAppHandler)
|
||||||
apiV1PlatformSerial.POST("/app/launch", launchAppHandler)
|
apiV1PlatformSerial.POST("/app/launch", r.launchAppHandler)
|
||||||
apiV1PlatformSerial.POST("/app/terminal", terminalAppHandler)
|
apiV1PlatformSerial.POST("/app/terminal", r.terminalAppHandler)
|
||||||
apiV1PlatformSerial.POST("/app/uninstall", uninstallAppHandler)
|
apiV1PlatformSerial.POST("/app/uninstall", r.uninstallAppHandler)
|
||||||
|
|
||||||
// Device operations
|
// Device operations
|
||||||
apiV1PlatformSerial.GET("/screenshot", screenshotHandler)
|
apiV1PlatformSerial.GET("/screenshot", r.screenshotHandler)
|
||||||
apiV1PlatformSerial.GET("/video", videoHandler)
|
apiV1PlatformSerial.DELETE("/close_browser", r.deleteBrowserHandler)
|
||||||
apiV1PlatformSerial.POST("/device/push_image", pushImageHandler)
|
apiV1PlatformSerial.GET("/video", r.videoHandler)
|
||||||
apiV1PlatformSerial.POST("/device/clear_image", clearImageHandler)
|
apiV1PlatformSerial.POST("/device/push_image", r.pushImageHandler)
|
||||||
apiV1PlatformSerial.GET("/adb/source", adbSourceHandler)
|
apiV1PlatformSerial.POST("/device/clear_image", r.clearImageHandler)
|
||||||
|
apiV1PlatformSerial.GET("/adb/source", r.adbSourceHandler)
|
||||||
|
|
||||||
// uixt operations
|
// uixt operations
|
||||||
apiV1PlatformSerial.POST("/uixt/action", uixtActionHandler)
|
apiV1PlatformSerial.POST("/uixt/action", r.uixtActionHandler)
|
||||||
apiV1PlatformSerial.POST("/uixt/actions", uixtActionsHandler)
|
apiV1PlatformSerial.POST("/uixt/actions", r.uixtActionsHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Run(port int) error {
|
func (r *Router) Run(port int) error {
|
||||||
@@ -72,11 +79,11 @@ func (r *Router) Run(port int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func pingHandler(c *gin.Context) {
|
func (r *Router) pingHandler(c *gin.Context) {
|
||||||
RenderSuccess(c, true)
|
RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func teardown() gin.HandlerFunc {
|
func (r *Router) teardown() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
logID := c.Request.Header.Get("x-tt-logid")
|
logID := c.Request.Header.Get("x-tt-logid")
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
@@ -99,7 +106,7 @@ func teardown() gin.HandlerFunc {
|
|||||||
|
|
||||||
deviceObj, exists := c.Get("device")
|
deviceObj, exists := c.Get("device")
|
||||||
if exists {
|
if exists {
|
||||||
if device, ok := deviceObj.(*uixt.IOSDevice); ok {
|
if device, ok := deviceObj.(uixt.IDevice); ok {
|
||||||
err := device.Teardown()
|
err := device.Teardown()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err)
|
log.Error().Err(err)
|
||||||
|
|||||||
+10
-3
@@ -1,7 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TapRequest struct {
|
type TapRequest struct {
|
||||||
@@ -11,6 +11,13 @@ type TapRequest struct {
|
|||||||
Options *option.ActionOptions `json:"options,omitempty"`
|
Options *option.ActionOptions `json:"options,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type uploadRequest struct {
|
||||||
|
X float64 `json:"x"`
|
||||||
|
Y float64 `json:"y"`
|
||||||
|
FileUrl string `json:"file_url"`
|
||||||
|
FileFormat string `json:"file_format"`
|
||||||
|
}
|
||||||
|
|
||||||
type DragRequest struct {
|
type DragRequest struct {
|
||||||
FromX float64 `json:"from_x" binding:"required"`
|
FromX float64 `json:"from_x" binding:"required"`
|
||||||
FromY float64 `json:"from_y" binding:"required"`
|
FromY float64 `json:"from_y" binding:"required"`
|
||||||
@@ -71,8 +78,8 @@ type OperateRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HttpResponse struct {
|
type HttpResponse struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"errorCode"`
|
||||||
Message string `json:"msg"`
|
Message string `json:"errorMsg"`
|
||||||
Result interface{} `json:"result,omitempty"`
|
Result interface{} `json:"result,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -6,11 +6,11 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func screenshotHandler(c *gin.Context) {
|
func (r *Router) screenshotHandler(c *gin.Context) {
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -23,8 +23,8 @@ func screenshotHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, base64.StdEncoding.EncodeToString(raw.Bytes()))
|
RenderSuccess(c, base64.StdEncoding.EncodeToString(raw.Bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func screenResultHandler(c *gin.Context) {
|
func (r *Router) screenResultHandler(c *gin.Context) {
|
||||||
dExt, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ func screenResultHandler(c *gin.Context) {
|
|||||||
actionOptions = screenReq.Options.Options()
|
actionOptions = screenReq.Options.Options()
|
||||||
}
|
}
|
||||||
|
|
||||||
screenResult, err := dExt.GetScreenResult(actionOptions...)
|
screenResult, err := driver.GetScreenResult(actionOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("get screen result failed")
|
log.Err(err).Msg("get screen result failed")
|
||||||
RenderError(c, err)
|
RenderError(c, err)
|
||||||
@@ -49,8 +49,8 @@ func screenResultHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, screenResult)
|
RenderSuccess(c, screenResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
func adbSourceHandler(c *gin.Context) {
|
func (r *Router) adbSourceHandler(c *gin.Context) {
|
||||||
dExt, err := GetDriver(c)
|
dExt, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+102
-21
@@ -2,25 +2,25 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func tapHandler(c *gin.Context) {
|
func (r *Router) tapHandler(c *gin.Context) {
|
||||||
var tapReq TapRequest
|
var tapReq TapRequest
|
||||||
if err := c.ShouldBindJSON(&tapReq); err != nil {
|
if err := c.ShouldBindJSON(&tapReq); err != nil {
|
||||||
RenderErrorValidateRequest(c, err)
|
RenderErrorValidateRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if tapReq.Duration > 0 {
|
if tapReq.Duration > 0 {
|
||||||
err = driver.Drag(tapReq.X, tapReq.Y, tapReq.X, tapReq.Y,
|
err = driver.Drag(tapReq.X, tapReq.Y, tapReq.X, tapReq.Y,
|
||||||
option.WithDuration(tapReq.Duration),
|
option.WithDuration(tapReq.Duration))
|
||||||
option.WithAbsoluteCoordinate(true))
|
|
||||||
} else {
|
} else {
|
||||||
err = driver.TapAbsXY(tapReq.X, tapReq.Y)
|
err = driver.TapXY(tapReq.X, tapReq.Y)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RenderError(c, err)
|
RenderError(c, err)
|
||||||
@@ -29,25 +29,106 @@ func tapHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, true)
|
RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func doubleTapHandler(c *gin.Context) {
|
func (r *Router) rightClickHandler(c *gin.Context) {
|
||||||
|
var rightClickReq TapRequest
|
||||||
|
if err := c.ShouldBindJSON(&rightClickReq); err != nil {
|
||||||
|
RenderErrorValidateRequest(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
driver, err := r.GetDriver(c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = driver.IDriver.(*uixt.BrowserDriver).
|
||||||
|
RightClick(rightClickReq.X, rightClickReq.Y)
|
||||||
|
if err != nil {
|
||||||
|
RenderError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
RenderSuccess(c, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) uploadHandler(c *gin.Context) {
|
||||||
|
var uploadRequest uploadRequest
|
||||||
|
if err := c.ShouldBindJSON(&uploadRequest); err != nil {
|
||||||
|
RenderErrorValidateRequest(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
driver, err := r.GetDriver(c)
|
||||||
|
if err != nil {
|
||||||
|
RenderError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = driver.IDriver.(*uixt.BrowserDriver).
|
||||||
|
UploadFile(uploadRequest.X, uploadRequest.Y,
|
||||||
|
uploadRequest.FileUrl, uploadRequest.FileFormat)
|
||||||
|
if err != nil {
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
RenderSuccess(c, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) hoverHandler(c *gin.Context) {
|
||||||
|
var hoverReq HoverRequest
|
||||||
|
if err := c.ShouldBindJSON(&hoverReq); err != nil {
|
||||||
|
RenderErrorValidateRequest(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
driver, err := r.GetDriver(c)
|
||||||
|
if err != nil {
|
||||||
|
RenderError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = driver.IDriver.(*uixt.BrowserDriver).
|
||||||
|
Hover(hoverReq.X, hoverReq.Y)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
RenderError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
RenderSuccess(c, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) scrollHandler(c *gin.Context) {
|
||||||
|
var scrollReq ScrollRequest
|
||||||
|
if err := c.ShouldBindJSON(&scrollReq); err != nil {
|
||||||
|
RenderErrorValidateRequest(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
driver, err := r.GetDriver(c)
|
||||||
|
if err != nil {
|
||||||
|
RenderError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = driver.IDriver.(*uixt.BrowserDriver).
|
||||||
|
Scroll(scrollReq.Delta)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
RenderError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
RenderSuccess(c, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) doubleTapHandler(c *gin.Context) {
|
||||||
var tapReq TapRequest
|
var tapReq TapRequest
|
||||||
if err := c.ShouldBindJSON(&tapReq); err != nil {
|
if err := c.ShouldBindJSON(&tapReq); err != nil {
|
||||||
RenderErrorValidateRequest(c, err)
|
RenderErrorValidateRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if tapReq.X < 1 && tapReq.Y < 1 {
|
err = driver.DoubleTap(tapReq.X, tapReq.Y)
|
||||||
err = driver.DoubleTapXY(tapReq.X, tapReq.Y)
|
|
||||||
} else {
|
|
||||||
err = driver.DoubleTapXY(tapReq.X, tapReq.Y,
|
|
||||||
option.WithAbsoluteCoordinate(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RenderError(c, err)
|
RenderError(c, err)
|
||||||
return
|
return
|
||||||
@@ -55,7 +136,7 @@ func doubleTapHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, true)
|
RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dragHandler(c *gin.Context) {
|
func (r *Router) dragHandler(c *gin.Context) {
|
||||||
var dragReq DragRequest
|
var dragReq DragRequest
|
||||||
if err := c.ShouldBindJSON(&dragReq); err != nil {
|
if err := c.ShouldBindJSON(&dragReq); err != nil {
|
||||||
RenderErrorValidateRequest(c, err)
|
RenderErrorValidateRequest(c, err)
|
||||||
@@ -64,14 +145,14 @@ func dragHandler(c *gin.Context) {
|
|||||||
if dragReq.Duration == 0 {
|
if dragReq.Duration == 0 {
|
||||||
dragReq.Duration = 1
|
dragReq.Duration = 1
|
||||||
}
|
}
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = driver.Drag(dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY,
|
err = driver.Drag(dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY,
|
||||||
option.WithDuration(dragReq.Duration), option.WithPressDuration(dragReq.PressDuration),
|
option.WithDuration(dragReq.Duration),
|
||||||
option.WithAbsoluteCoordinate(true))
|
option.WithPressDuration(dragReq.PressDuration))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RenderError(c, err)
|
RenderError(c, err)
|
||||||
return
|
return
|
||||||
@@ -79,13 +160,13 @@ func dragHandler(c *gin.Context) {
|
|||||||
RenderSuccess(c, true)
|
RenderSuccess(c, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func inputHandler(c *gin.Context) {
|
func (r *Router) inputHandler(c *gin.Context) {
|
||||||
var inputReq InputRequest
|
var inputReq InputRequest
|
||||||
if err := c.ShouldBindJSON(&inputReq); err != nil {
|
if err := c.ShouldBindJSON(&inputReq); err != nil {
|
||||||
RenderErrorValidateRequest(c, err)
|
RenderErrorValidateRequest(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
driver, err := GetDriver(c)
|
driver, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTapHandler(t *testing.T) {
|
||||||
|
router := NewRouter()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
tapReq TapRequest
|
||||||
|
wantStatus int
|
||||||
|
wantResp HttpResponse
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "tap abs xy",
|
||||||
|
path: fmt.Sprintf("/api/v1/android/%s/ui/tap", "4622ca24"),
|
||||||
|
tapReq: TapRequest{
|
||||||
|
X: 500,
|
||||||
|
Y: 800,
|
||||||
|
Duration: 0,
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantResp: HttpResponse{
|
||||||
|
Code: 0,
|
||||||
|
Message: "success",
|
||||||
|
Result: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tap relative xy",
|
||||||
|
path: fmt.Sprintf("/api/v1/android/%s/ui/tap", "4622ca24"),
|
||||||
|
tapReq: TapRequest{
|
||||||
|
X: 0.5,
|
||||||
|
Y: 0.6,
|
||||||
|
Duration: 0,
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantResp: HttpResponse{
|
||||||
|
Code: 0,
|
||||||
|
Message: "success",
|
||||||
|
Result: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
reqBody, _ := json.Marshal(tt.tapReq)
|
||||||
|
req := httptest.NewRequest(http.MethodPost, tt.path, bytes.NewBuffer(reqBody))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.wantStatus, w.Code)
|
||||||
|
|
||||||
|
var got HttpResponse
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &got)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.wantResp, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-5
@@ -2,13 +2,13 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// exec a single uixt action
|
// exec a single uixt action
|
||||||
func uixtActionHandler(c *gin.Context) {
|
func (r *Router) uixtActionHandler(c *gin.Context) {
|
||||||
dExt, err := GetDriver(c)
|
dExt, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -29,8 +29,8 @@ func uixtActionHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// exec multiple uixt actions
|
// exec multiple uixt actions
|
||||||
func uixtActionsHandler(c *gin.Context) {
|
func (r *Router) uixtActionsHandler(c *gin.Context) {
|
||||||
dExt, err := GetDriver(c)
|
dExt, err := r.GetDriver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package hrp
|
package hrp
|
||||||
|
|
||||||
import "github.com/httprunner/httprunner/v5/pkg/uixt"
|
import "github.com/httprunner/httprunner/v5/uixt"
|
||||||
|
|
||||||
type StepType string
|
type StepType string
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -5,9 +5,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StepFunction implements IStep interface.
|
// StepFunction implements IStep interface.
|
||||||
|
|||||||
+23
-23
@@ -22,21 +22,21 @@ import (
|
|||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
|
"github.com/httprunner/httprunner/v5/internal/httpstat"
|
||||||
"github.com/httprunner/httprunner/v5/internal/json"
|
"github.com/httprunner/httprunner/v5/internal/json"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/httpstat"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPMethod string
|
type HTTPMethod string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
httpGET HTTPMethod = "GET"
|
HTTP_GET HTTPMethod = "GET"
|
||||||
httpHEAD HTTPMethod = "HEAD"
|
HTTP_HEAD HTTPMethod = "HEAD"
|
||||||
httpPOST HTTPMethod = "POST"
|
HTTP_POST HTTPMethod = "POST"
|
||||||
httpPUT HTTPMethod = "PUT"
|
HTTP_PUT HTTPMethod = "PUT"
|
||||||
httpDELETE HTTPMethod = "DELETE"
|
HTTP_DELETE HTTPMethod = "DELETE"
|
||||||
httpOPTIONS HTTPMethod = "OPTIONS"
|
HTTP_OPTIONS HTTPMethod = "OPTIONS"
|
||||||
httpPATCH HTTPMethod = "PATCH"
|
HTTP_PATCH HTTPMethod = "PATCH"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Request represents HTTP request data structure.
|
// Request represents HTTP request data structure.
|
||||||
@@ -566,11 +566,11 @@ func (s *StepRequest) Loop(times int) *StepRequest {
|
|||||||
// GET makes a HTTP GET request.
|
// GET makes a HTTP GET request.
|
||||||
func (s *StepRequest) GET(url string) *StepRequestWithOptionalArgs {
|
func (s *StepRequest) GET(url string) *StepRequestWithOptionalArgs {
|
||||||
if s.Request != nil {
|
if s.Request != nil {
|
||||||
s.Request.Method = httpGET
|
s.Request.Method = HTTP_GET
|
||||||
s.Request.URL = url
|
s.Request.URL = url
|
||||||
} else {
|
} else {
|
||||||
s.Request = &Request{
|
s.Request = &Request{
|
||||||
Method: httpGET,
|
Method: HTTP_GET,
|
||||||
URL: url,
|
URL: url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -582,11 +582,11 @@ func (s *StepRequest) GET(url string) *StepRequestWithOptionalArgs {
|
|||||||
// HEAD makes a HTTP HEAD request.
|
// HEAD makes a HTTP HEAD request.
|
||||||
func (s *StepRequest) HEAD(url string) *StepRequestWithOptionalArgs {
|
func (s *StepRequest) HEAD(url string) *StepRequestWithOptionalArgs {
|
||||||
if s.Request != nil {
|
if s.Request != nil {
|
||||||
s.Request.Method = httpHEAD
|
s.Request.Method = HTTP_HEAD
|
||||||
s.Request.URL = url
|
s.Request.URL = url
|
||||||
} else {
|
} else {
|
||||||
s.Request = &Request{
|
s.Request = &Request{
|
||||||
Method: httpHEAD,
|
Method: HTTP_HEAD,
|
||||||
URL: url,
|
URL: url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -598,11 +598,11 @@ func (s *StepRequest) HEAD(url string) *StepRequestWithOptionalArgs {
|
|||||||
// POST makes a HTTP POST request.
|
// POST makes a HTTP POST request.
|
||||||
func (s *StepRequest) POST(url string) *StepRequestWithOptionalArgs {
|
func (s *StepRequest) POST(url string) *StepRequestWithOptionalArgs {
|
||||||
if s.Request != nil {
|
if s.Request != nil {
|
||||||
s.Request.Method = httpPOST
|
s.Request.Method = HTTP_POST
|
||||||
s.Request.URL = url
|
s.Request.URL = url
|
||||||
} else {
|
} else {
|
||||||
s.Request = &Request{
|
s.Request = &Request{
|
||||||
Method: httpPOST,
|
Method: HTTP_POST,
|
||||||
URL: url,
|
URL: url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -614,11 +614,11 @@ func (s *StepRequest) POST(url string) *StepRequestWithOptionalArgs {
|
|||||||
// PUT makes a HTTP PUT request.
|
// PUT makes a HTTP PUT request.
|
||||||
func (s *StepRequest) PUT(url string) *StepRequestWithOptionalArgs {
|
func (s *StepRequest) PUT(url string) *StepRequestWithOptionalArgs {
|
||||||
if s.Request != nil {
|
if s.Request != nil {
|
||||||
s.Request.Method = httpPUT
|
s.Request.Method = HTTP_PUT
|
||||||
s.Request.URL = url
|
s.Request.URL = url
|
||||||
} else {
|
} else {
|
||||||
s.Request = &Request{
|
s.Request = &Request{
|
||||||
Method: httpPUT,
|
Method: HTTP_PUT,
|
||||||
URL: url,
|
URL: url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -630,11 +630,11 @@ func (s *StepRequest) PUT(url string) *StepRequestWithOptionalArgs {
|
|||||||
// DELETE makes a HTTP DELETE request.
|
// DELETE makes a HTTP DELETE request.
|
||||||
func (s *StepRequest) DELETE(url string) *StepRequestWithOptionalArgs {
|
func (s *StepRequest) DELETE(url string) *StepRequestWithOptionalArgs {
|
||||||
if s.Request != nil {
|
if s.Request != nil {
|
||||||
s.Request.Method = httpDELETE
|
s.Request.Method = HTTP_DELETE
|
||||||
s.Request.URL = url
|
s.Request.URL = url
|
||||||
} else {
|
} else {
|
||||||
s.Request = &Request{
|
s.Request = &Request{
|
||||||
Method: httpDELETE,
|
Method: HTTP_DELETE,
|
||||||
URL: url,
|
URL: url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -646,11 +646,11 @@ func (s *StepRequest) DELETE(url string) *StepRequestWithOptionalArgs {
|
|||||||
// OPTIONS makes a HTTP OPTIONS request.
|
// OPTIONS makes a HTTP OPTIONS request.
|
||||||
func (s *StepRequest) OPTIONS(url string) *StepRequestWithOptionalArgs {
|
func (s *StepRequest) OPTIONS(url string) *StepRequestWithOptionalArgs {
|
||||||
if s.Request != nil {
|
if s.Request != nil {
|
||||||
s.Request.Method = httpOPTIONS
|
s.Request.Method = HTTP_OPTIONS
|
||||||
s.Request.URL = url
|
s.Request.URL = url
|
||||||
} else {
|
} else {
|
||||||
s.Request = &Request{
|
s.Request = &Request{
|
||||||
Method: httpOPTIONS,
|
Method: HTTP_OPTIONS,
|
||||||
URL: url,
|
URL: url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -662,11 +662,11 @@ func (s *StepRequest) OPTIONS(url string) *StepRequestWithOptionalArgs {
|
|||||||
// PATCH makes a HTTP PATCH request.
|
// PATCH makes a HTTP PATCH request.
|
||||||
func (s *StepRequest) PATCH(url string) *StepRequestWithOptionalArgs {
|
func (s *StepRequest) PATCH(url string) *StepRequestWithOptionalArgs {
|
||||||
if s.Request != nil {
|
if s.Request != nil {
|
||||||
s.Request.Method = httpPATCH
|
s.Request.Method = HTTP_PATCH
|
||||||
s.Request.URL = url
|
s.Request.URL = url
|
||||||
} else {
|
} else {
|
||||||
s.Request = &Request{
|
s.Request = &Request{
|
||||||
Method: httpPATCH,
|
Method: HTTP_PATCH,
|
||||||
URL: url,
|
URL: url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
package hrp
|
|
||||||
+5
-5
@@ -42,14 +42,14 @@ func (s *StepThinkTime) Run(r *SessionRunner) (*StepResult, error) {
|
|||||||
|
|
||||||
cfg := r.caseRunner.Config.Get().ThinkTimeSetting
|
cfg := r.caseRunner.Config.Get().ThinkTimeSetting
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
cfg = &ThinkTimeConfig{thinkTimeDefault, nil, 0}
|
cfg = &ThinkTimeConfig{ThinkTimeDefault, nil, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
var tt time.Duration
|
var tt time.Duration
|
||||||
switch cfg.Strategy {
|
switch cfg.Strategy {
|
||||||
case thinkTimeDefault:
|
case ThinkTimeDefault:
|
||||||
tt = time.Duration(thinkTime.Time*1000) * time.Millisecond
|
tt = time.Duration(thinkTime.Time*1000) * time.Millisecond
|
||||||
case thinkTimeRandomPercentage:
|
case ThinkTimeRandomPercentage:
|
||||||
// e.g. {"min_percentage": 0.5, "max_percentage": 1.5}
|
// e.g. {"min_percentage": 0.5, "max_percentage": 1.5}
|
||||||
m, ok := cfg.Setting.(map[string]float64)
|
m, ok := cfg.Setting.(map[string]float64)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -58,13 +58,13 @@ func (s *StepThinkTime) Run(r *SessionRunner) (*StepResult, error) {
|
|||||||
}
|
}
|
||||||
res := builtin.GetRandomNumber(int(thinkTime.Time*m["min_percentage"]*1000), int(thinkTime.Time*m["max_percentage"]*1000))
|
res := builtin.GetRandomNumber(int(thinkTime.Time*m["min_percentage"]*1000), int(thinkTime.Time*m["max_percentage"]*1000))
|
||||||
tt = time.Duration(res) * time.Millisecond
|
tt = time.Duration(res) * time.Millisecond
|
||||||
case thinkTimeMultiply:
|
case ThinkTimeMultiply:
|
||||||
value, ok := cfg.Setting.(float64) // e.g. 0.5
|
value, ok := cfg.Setting.(float64) // e.g. 0.5
|
||||||
if !ok || value <= 0 {
|
if !ok || value <= 0 {
|
||||||
value = thinkTimeDefaultMultiply
|
value = thinkTimeDefaultMultiply
|
||||||
}
|
}
|
||||||
tt = time.Duration(thinkTime.Time*value*1000) * time.Millisecond
|
tt = time.Duration(thinkTime.Time*value*1000) * time.Millisecond
|
||||||
case thinkTimeIgnore:
|
case ThinkTimeIgnore:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -10,8 +10,8 @@ import (
|
|||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
"github.com/httprunner/httprunner/v5/internal/sdk"
|
"github.com/httprunner/httprunner/v5/internal/sdk"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MobileUI struct {
|
type MobileUI struct {
|
||||||
@@ -103,7 +103,9 @@ func (s *StepMobile) Home() *StepMobile {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// TapXY taps the point {X,Y}, X & Y is percentage of coordinates
|
// TapXY taps the point {X,Y}
|
||||||
|
// if X<1 & Y<1, {X,Y} will be considered as percentage
|
||||||
|
// else, X & Y will be considered as absolute coordinates
|
||||||
func (s *StepMobile) TapXY(x, y float64, opts ...option.ActionOption) *StepMobile {
|
func (s *StepMobile) TapXY(x, y float64, opts ...option.ActionOption) *StepMobile {
|
||||||
action := uixt.MobileAction{
|
action := uixt.MobileAction{
|
||||||
Method: uixt.ACTION_TapXY,
|
Method: uixt.ACTION_TapXY,
|
||||||
|
|||||||
+116
-14
@@ -220,28 +220,130 @@ func (s *StepWebSocket) WithCloseStatus(closeStatus int64) *StepWebSocket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate switches to step validation.
|
// Validate switches to step validation.
|
||||||
func (s *StepWebSocket) Validate() *StepRequestValidation {
|
func (s *StepWebSocket) Validate() *StepWebSocketValidation {
|
||||||
return &StepRequestValidation{
|
return &StepWebSocketValidation{
|
||||||
StepRequestWithOptionalArgs: &StepRequestWithOptionalArgs{
|
StepWebSocket: s,
|
||||||
StepRequest: &StepRequest{
|
|
||||||
StepConfig: s.StepConfig,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract switches to step extraction.
|
// Extract switches to step extraction.
|
||||||
func (s *StepWebSocket) Extract() *StepRequestExtraction {
|
func (s *StepWebSocket) Extract() *StepWebSocketExtraction {
|
||||||
s.StepConfig.Extract = make(map[string]string)
|
s.StepConfig.Extract = make(map[string]string)
|
||||||
return &StepRequestExtraction{
|
return &StepWebSocketExtraction{
|
||||||
StepRequestWithOptionalArgs: &StepRequestWithOptionalArgs{
|
StepWebSocket: s,
|
||||||
StepRequest: &StepRequest{
|
|
||||||
StepConfig: s.StepConfig,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StepWebSocketExtraction implements IStep interface.
|
||||||
|
type StepWebSocketExtraction struct {
|
||||||
|
*StepWebSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithJmesPath sets the JMESPath expression to extract from the response.
|
||||||
|
func (s *StepWebSocketExtraction) WithJmesPath(jmesPath string, varName string) *StepWebSocketExtraction {
|
||||||
|
s.StepConfig.Extract[varName] = jmesPath
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate switches to step validation.
|
||||||
|
func (s *StepWebSocketExtraction) Validate() *StepWebSocketValidation {
|
||||||
|
return &StepWebSocketValidation{
|
||||||
|
StepWebSocket: s.StepWebSocket,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWebSocketExtraction) Name() string {
|
||||||
|
return s.StepName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWebSocketExtraction) Type() StepType {
|
||||||
|
stepType := StepType(fmt.Sprintf("websocket-%v", s.WebSocket.Type))
|
||||||
|
return stepType + stepTypeSuffixExtraction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWebSocketExtraction) Struct() *StepConfig {
|
||||||
|
return &s.StepConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWebSocketExtraction) Run(r *SessionRunner) (*StepResult, error) {
|
||||||
|
if s.WebSocket != nil {
|
||||||
|
return runStepWebSocket(r, s.StepWebSocket)
|
||||||
|
}
|
||||||
|
return nil, errors.New("unexpected protocol type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StepWebSocketValidation implements IStep interface.
|
||||||
|
type StepWebSocketValidation struct {
|
||||||
|
*StepWebSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWebSocketValidation) Name() string {
|
||||||
|
if s.StepName != "" {
|
||||||
|
return s.StepName
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s %s", s.WebSocket.Type, s.WebSocket.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWebSocketValidation) Type() StepType {
|
||||||
|
stepType := StepType(fmt.Sprintf("websocket-%v", s.WebSocket.Type))
|
||||||
|
return stepType + stepTypeSuffixValidation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWebSocketValidation) Config() *StepConfig {
|
||||||
|
return &s.StepConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWebSocketValidation) Run(r *SessionRunner) (*StepResult, error) {
|
||||||
|
if s.WebSocket != nil {
|
||||||
|
return runStepWebSocket(r, s.StepWebSocket)
|
||||||
|
}
|
||||||
|
return nil, errors.New("unexpected protocol type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWebSocketValidation) AssertEqual(jmesPath string, expected interface{}, msg string) *StepWebSocketValidation {
|
||||||
|
v := Validator{
|
||||||
|
Check: jmesPath,
|
||||||
|
Assert: "equals",
|
||||||
|
Expect: expected,
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
|
s.Validators = append(s.Validators, v)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWebSocketValidation) AssertEqualFold(jmesPath string, expected interface{}, msg string) *StepWebSocketValidation {
|
||||||
|
v := Validator{
|
||||||
|
Check: jmesPath,
|
||||||
|
Assert: "equal_fold",
|
||||||
|
Expect: expected,
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
|
s.Validators = append(s.Validators, v)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWebSocketValidation) AssertLengthEqual(jmesPath string, expected interface{}, msg string) *StepWebSocketValidation {
|
||||||
|
v := Validator{
|
||||||
|
Check: jmesPath,
|
||||||
|
Assert: "length_equals",
|
||||||
|
Expect: expected,
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
|
s.Validators = append(s.Validators, v)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StepWebSocketValidation) AssertContains(jmesPath string, expected interface{}, msg string) *StepWebSocketValidation {
|
||||||
|
v := Validator{
|
||||||
|
Check: jmesPath,
|
||||||
|
Assert: "contains",
|
||||||
|
Expect: expected,
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
|
s.Validators = append(s.Validators, v)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
type WebSocketAction struct {
|
type WebSocketAction struct {
|
||||||
Type WSActionType `json:"type" yaml:"type"`
|
Type WSActionType `json:"type" yaml:"type"`
|
||||||
URL string `json:"url" yaml:"url"`
|
URL string `json:"url" yaml:"url"`
|
||||||
|
|||||||
+4
-4
@@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v5/internal/config"
|
"github.com/httprunner/httprunner/v5/internal/config"
|
||||||
"github.com/httprunner/httprunner/v5/internal/version"
|
"github.com/httprunner/httprunner/v5/internal/version"
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewSummary() *Summary {
|
func NewSummary() *Summary {
|
||||||
@@ -32,7 +32,7 @@ func NewSummary() *Summary {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Time: &TestCaseTime{
|
Time: &TestCaseTime{
|
||||||
StartAt: config.StartTime,
|
StartAt: config.GetConfig().StartTime,
|
||||||
},
|
},
|
||||||
Platform: platForm,
|
Platform: platForm,
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ func (s *Summary) AddCaseSummary(caseSummary *TestCaseSummary) {
|
|||||||
s.rootDir = caseSummary.RootDir
|
s.rootDir = caseSummary.RootDir
|
||||||
} else if s.rootDir != caseSummary.RootDir {
|
} else if s.rootDir != caseSummary.RootDir {
|
||||||
// if multiple testcases have different root path, use current working dir
|
// if multiple testcases have different root path, use current working dir
|
||||||
s.rootDir = config.RootDir
|
s.rootDir = config.GetConfig().RootDir
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge action stats
|
// merge action stats
|
||||||
@@ -80,7 +80,7 @@ func (s *Summary) AddCaseSummary(caseSummary *TestCaseSummary) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Summary) SetupDirPath() (path string, err error) {
|
func (s *Summary) SetupDirPath() (path string, err error) {
|
||||||
dirPath := filepath.Join(s.rootDir, config.ResultsDir)
|
dirPath := filepath.Join(s.rootDir, config.GetConfig().ResultsDir)
|
||||||
err = builtin.EnsureFolderExists(dirPath)
|
err = builtin.EnsureFolderExists(dirPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRun(t *testing.T) {
|
||||||
|
err := hrp.BuildPlugin(tmpl("plugin/debugtalk.go"), "./debugtalk.bin")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
genDebugTalkPyPath := filepath.Join(tmpl("plugin/"), hrp.PluginPySourceGenFile)
|
||||||
|
err = hrp.BuildPlugin(tmpl("plugin/debugtalk.py"), genDebugTalkPyPath)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
contentBytes, err := builtin.LoadFile(genDebugTalkPyPath)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
content := string(contentBytes)
|
||||||
|
assert.Contains(t, content, "import funppy")
|
||||||
|
assert.Contains(t, content, "funppy.register")
|
||||||
|
|
||||||
|
reg, _ := regexp.Compile(`funppy\.register`)
|
||||||
|
matchedSlice := reg.FindAllStringSubmatch(content, -1)
|
||||||
|
assert.Len(t, matchedSlice, 10)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadTestCases(t *testing.T) {
|
||||||
|
// load test cases from folder path
|
||||||
|
tc := hrp.TestCasePath("../examples/demo-with-py-plugin/testcases/")
|
||||||
|
testCases, err := hrp.LoadTestCases(&tc)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 3, len(testCases))
|
||||||
|
|
||||||
|
// load test cases from folder path, including sub folders
|
||||||
|
tc = hrp.TestCasePath("../examples/demo-with-py-plugin/")
|
||||||
|
testCases, err = hrp.LoadTestCases(&tc)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 3, len(testCases))
|
||||||
|
|
||||||
|
// load test cases from single file path
|
||||||
|
tc = hrp.TestCasePath(demoTestCaseWithPluginJSONPath)
|
||||||
|
testCases, err = hrp.LoadTestCases(&tc)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, 1, len(testCases))
|
||||||
|
|
||||||
|
// load test cases from TestCase instance
|
||||||
|
testcase := &hrp.TestCase{
|
||||||
|
Config: hrp.NewConfig("TestCase").SetWeight(3),
|
||||||
|
}
|
||||||
|
testCases, err = hrp.LoadTestCases(testcase)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, len(testCases), 1)
|
||||||
|
|
||||||
|
// load test cases from TestCaseJSON
|
||||||
|
testcaseJSON := hrp.TestCaseJSON(`
|
||||||
|
{
|
||||||
|
"config":{"name":"TestCaseJSON"},
|
||||||
|
"teststeps":[
|
||||||
|
{"name": "step1", "request":{"url": "https://httpbin.org/get"}},
|
||||||
|
{"name": "step2", "shell":{"string": "ls -l"}}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
testCases, err = hrp.LoadTestCases(&testcaseJSON)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, len(testCases), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadCase(t *testing.T) {
|
||||||
|
tcJSON := &hrp.TestCaseDef{}
|
||||||
|
tcYAML := &hrp.TestCaseDef{}
|
||||||
|
err := hrp.LoadFileObject(demoTestCaseWithPluginJSONPath, tcJSON)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
err = hrp.LoadFileObject(demoTestCaseWithPluginYAMLPath, tcYAML)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tcJSON.Config.Name, tcYAML.Config.Name)
|
||||||
|
assert.Equal(t, tcJSON.Config.BaseURL, tcYAML.Config.BaseURL)
|
||||||
|
assert.Equal(t, tcJSON.Steps[1].StepName, tcYAML.Steps[1].StepName)
|
||||||
|
assert.Equal(t, tcJSON.Steps[1].Request, tcJSON.Steps[1].Request)
|
||||||
|
}
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
package hrp
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadParameters(t *testing.T) {
|
func TestLoadParameters(t *testing.T) {
|
||||||
testData := []struct {
|
testData := []struct {
|
||||||
configParameters map[string]interface{}
|
configParameters map[string]interface{}
|
||||||
loadedParameters map[string]Parameters
|
loadedParameters map[string]hrp.Parameters
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"username-password": fmt.Sprintf("${parameterize(%s/$file)}", hrpExamplesDir),
|
"username-password": fmt.Sprintf("${parameterize(%s/$file)}", hrpExamplesDir),
|
||||||
},
|
},
|
||||||
map[string]Parameters{
|
map[string]hrp.Parameters{
|
||||||
"username-password": {
|
"username-password": {
|
||||||
{"username": "test1", "password": "111111"},
|
{"username": "test1", "password": "111111"},
|
||||||
{"username": "test2", "password": "222222"},
|
{"username": "test2", "password": "222222"},
|
||||||
@@ -33,7 +35,7 @@ func TestLoadParameters(t *testing.T) {
|
|||||||
"user_agent": []interface{}{"iOS/10.1", "iOS/10.2"},
|
"user_agent": []interface{}{"iOS/10.1", "iOS/10.2"},
|
||||||
"app_version": []interface{}{4.0},
|
"app_version": []interface{}{4.0},
|
||||||
},
|
},
|
||||||
map[string]Parameters{
|
map[string]hrp.Parameters{
|
||||||
"username-password": {
|
"username-password": {
|
||||||
{"username": "test1", "password": "111111"},
|
{"username": "test1", "password": "111111"},
|
||||||
{"username": "test2", "password": "222222"},
|
{"username": "test2", "password": "222222"},
|
||||||
@@ -54,7 +56,7 @@ func TestLoadParameters(t *testing.T) {
|
|||||||
[]interface{}{"test2", "222222"},
|
[]interface{}{"test2", "222222"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
map[string]Parameters{
|
map[string]hrp.Parameters{
|
||||||
"username-password": {
|
"username-password": {
|
||||||
{"username": "test1", "password": "111111"},
|
{"username": "test1", "password": "111111"},
|
||||||
{"username": "test2", "password": "222222"},
|
{"username": "test2", "password": "222222"},
|
||||||
@@ -74,15 +76,11 @@ func TestLoadParameters(t *testing.T) {
|
|||||||
variablesMapping := map[string]interface{}{
|
variablesMapping := map[string]interface{}{
|
||||||
"file": "account.csv",
|
"file": "account.csv",
|
||||||
}
|
}
|
||||||
parser := newParser()
|
parser := hrp.NewParser()
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
value, err := parser.loadParameters(data.configParameters, variablesMapping)
|
value, err := parser.LoadParameters(data.configParameters, variablesMapping)
|
||||||
if !assert.Nil(t, err) {
|
assert.Nil(t, err)
|
||||||
t.Fatal()
|
assert.Equal(t, data.loadedParameters, value)
|
||||||
}
|
|
||||||
if !assert.Equal(t, data.loadedParameters, value) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,12 +107,10 @@ func TestLoadParametersError(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
parser := newParser()
|
parser := hrp.NewParser()
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
_, err := parser.loadParameters(data.configParameters, map[string]interface{}{})
|
_, err := parser.LoadParameters(data.configParameters, map[string]interface{}{})
|
||||||
if !assert.Error(t, err) {
|
assert.Error(t, err)
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,28 +121,28 @@ func TestInitParametersIteratorCount(t *testing.T) {
|
|||||||
"app_version": []interface{}{4.0}, // 1
|
"app_version": []interface{}{4.0}, // 1
|
||||||
}
|
}
|
||||||
testData := []struct {
|
testData := []struct {
|
||||||
cfg *TConfig
|
cfg *hrp.TConfig
|
||||||
expectLimit int
|
expectLimit int
|
||||||
}{
|
}{
|
||||||
// default, no parameters setting
|
// default, no parameters setting
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: configParameters,
|
Parameters: configParameters,
|
||||||
ParametersSetting: &TParamsConfig{},
|
ParametersSetting: &hrp.TParamsConfig{},
|
||||||
},
|
},
|
||||||
6, // 3 * 2 * 1
|
6, // 3 * 2 * 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: configParameters,
|
Parameters: configParameters,
|
||||||
},
|
},
|
||||||
6, // 3 * 2 * 1
|
6, // 3 * 2 * 1
|
||||||
},
|
},
|
||||||
// default equals to set overall parameters pick-order to "sequential"
|
// default equals to set overall parameters pick-order to "sequential"
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: configParameters,
|
Parameters: configParameters,
|
||||||
ParametersSetting: &TParamsConfig{
|
ParametersSetting: &hrp.TParamsConfig{
|
||||||
PickOrder: "sequential",
|
PickOrder: "sequential",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -154,10 +150,10 @@ func TestInitParametersIteratorCount(t *testing.T) {
|
|||||||
},
|
},
|
||||||
// default equals to set each individual parameters pick-order to "sequential"
|
// default equals to set each individual parameters pick-order to "sequential"
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: configParameters,
|
Parameters: configParameters,
|
||||||
ParametersSetting: &TParamsConfig{
|
ParametersSetting: &hrp.TParamsConfig{
|
||||||
Strategies: map[string]iteratorStrategy{
|
Strategies: map[string]hrp.IteratorStrategy{
|
||||||
"username-password": {Name: "user-info", PickOrder: "sequential"},
|
"username-password": {Name: "user-info", PickOrder: "sequential"},
|
||||||
"user_agent": {Name: "user-identity", PickOrder: "sequential"},
|
"user_agent": {Name: "user-identity", PickOrder: "sequential"},
|
||||||
"app_version": {Name: "app-version", PickOrder: "sequential"},
|
"app_version": {Name: "app-version", PickOrder: "sequential"},
|
||||||
@@ -167,10 +163,10 @@ func TestInitParametersIteratorCount(t *testing.T) {
|
|||||||
6, // 3 * 2 * 1
|
6, // 3 * 2 * 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: configParameters,
|
Parameters: configParameters,
|
||||||
ParametersSetting: &TParamsConfig{
|
ParametersSetting: &hrp.TParamsConfig{
|
||||||
Strategies: map[string]iteratorStrategy{
|
Strategies: map[string]hrp.IteratorStrategy{
|
||||||
"user_agent": {Name: "user-identity", PickOrder: "sequential"},
|
"user_agent": {Name: "user-identity", PickOrder: "sequential"},
|
||||||
"app_version": {Name: "app-version", PickOrder: "sequential"},
|
"app_version": {Name: "app-version", PickOrder: "sequential"},
|
||||||
},
|
},
|
||||||
@@ -182,9 +178,9 @@ func TestInitParametersIteratorCount(t *testing.T) {
|
|||||||
// set overall parameters overall pick-order to "random"
|
// set overall parameters overall pick-order to "random"
|
||||||
// each random parameters only select one item
|
// each random parameters only select one item
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: configParameters,
|
Parameters: configParameters,
|
||||||
ParametersSetting: &TParamsConfig{
|
ParametersSetting: &hrp.TParamsConfig{
|
||||||
PickOrder: "random",
|
PickOrder: "random",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -193,10 +189,10 @@ func TestInitParametersIteratorCount(t *testing.T) {
|
|||||||
// set some individual parameters pick-order to "random"
|
// set some individual parameters pick-order to "random"
|
||||||
// this will override overall strategy
|
// this will override overall strategy
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: configParameters,
|
Parameters: configParameters,
|
||||||
ParametersSetting: &TParamsConfig{
|
ParametersSetting: &hrp.TParamsConfig{
|
||||||
Strategies: map[string]iteratorStrategy{
|
Strategies: map[string]hrp.IteratorStrategy{
|
||||||
"user_agent": {Name: "user-identity", PickOrder: "random"},
|
"user_agent": {Name: "user-identity", PickOrder: "random"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -204,10 +200,10 @@ func TestInitParametersIteratorCount(t *testing.T) {
|
|||||||
3, // 3 * 1 * 1
|
3, // 3 * 1 * 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: configParameters,
|
Parameters: configParameters,
|
||||||
ParametersSetting: &TParamsConfig{
|
ParametersSetting: &hrp.TParamsConfig{
|
||||||
Strategies: map[string]iteratorStrategy{
|
Strategies: map[string]hrp.IteratorStrategy{
|
||||||
"username-password": {Name: "user-info", PickOrder: "random"},
|
"username-password": {Name: "user-info", PickOrder: "random"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -217,18 +213,18 @@ func TestInitParametersIteratorCount(t *testing.T) {
|
|||||||
|
|
||||||
// set limit for parameters
|
// set limit for parameters
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: configParameters, // total: 6 = 3 * 2 * 1
|
Parameters: configParameters, // total: 6 = 3 * 2 * 1
|
||||||
ParametersSetting: &TParamsConfig{
|
ParametersSetting: &hrp.TParamsConfig{
|
||||||
Limit: 4, // limit could be less than total
|
Limit: 4, // limit could be less than total
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
4,
|
4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: configParameters, // total: 6 = 3 * 2 * 1
|
Parameters: configParameters, // total: 6 = 3 * 2 * 1
|
||||||
ParametersSetting: &TParamsConfig{
|
ParametersSetting: &hrp.TParamsConfig{
|
||||||
Limit: 9, // limit could also be greater than total
|
Limit: 9, // limit could also be greater than total
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -238,33 +234,25 @@ func TestInitParametersIteratorCount(t *testing.T) {
|
|||||||
// no parameters
|
// no parameters
|
||||||
// also will generate one empty item
|
// also will generate one empty item
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: nil,
|
Parameters: nil,
|
||||||
ParametersSetting: nil,
|
ParametersSetting: nil,
|
||||||
},
|
},
|
||||||
1,
|
1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
parser := newParser()
|
parser := hrp.NewParser()
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
iterator, err := parser.initParametersIterator(data.cfg)
|
iterator, err := parser.InitParametersIterator(data.cfg)
|
||||||
if !assert.Nil(t, err) {
|
assert.Nil(t, err)
|
||||||
t.Fatal()
|
assert.Equal(t, data.expectLimit, iterator.Limit)
|
||||||
}
|
|
||||||
if !assert.Equal(t, data.expectLimit, iterator.limit) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < data.expectLimit; i++ {
|
for i := 0; i < data.expectLimit; i++ {
|
||||||
if !assert.True(t, iterator.HasNext()) {
|
assert.True(t, iterator.HasNext())
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
iterator.Next() // consume next parameters
|
iterator.Next() // consume next parameters
|
||||||
}
|
}
|
||||||
// should not have next
|
// should not have next
|
||||||
if !assert.False(t, iterator.HasNext()) {
|
assert.False(t, iterator.HasNext())
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,50 +263,40 @@ func TestInitParametersIteratorUnlimitedCount(t *testing.T) {
|
|||||||
"app_version": []interface{}{4.0}, // 1
|
"app_version": []interface{}{4.0}, // 1
|
||||||
}
|
}
|
||||||
testData := []struct {
|
testData := []struct {
|
||||||
cfg *TConfig
|
cfg *hrp.TConfig
|
||||||
}{
|
}{
|
||||||
// default, no parameters setting
|
// default, no parameters setting
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: configParameters,
|
Parameters: configParameters,
|
||||||
ParametersSetting: &TParamsConfig{},
|
ParametersSetting: &hrp.TParamsConfig{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// no parameters
|
// no parameters
|
||||||
// also will generate one empty item
|
// also will generate one empty item
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: nil,
|
Parameters: nil,
|
||||||
ParametersSetting: nil,
|
ParametersSetting: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
parser := newParser()
|
parser := hrp.NewParser()
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
iterator, err := parser.initParametersIterator(data.cfg)
|
iterator, err := parser.InitParametersIterator(data.cfg)
|
||||||
if !assert.Nil(t, err) {
|
assert.Nil(t, err)
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
// set unlimited mode
|
// set unlimited mode
|
||||||
iterator.SetUnlimitedMode()
|
iterator.SetUnlimitedMode()
|
||||||
if !assert.Equal(t, -1, iterator.limit) {
|
assert.Equal(t, -1, iterator.Limit)
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
if !assert.True(t, iterator.HasNext()) {
|
assert.True(t, iterator.HasNext())
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
iterator.Next() // consume next parameters
|
iterator.Next() // consume next parameters
|
||||||
}
|
}
|
||||||
if !assert.Equal(t, 100, iterator.index) {
|
assert.Equal(t, 100, iterator.Index)
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
// should also have next
|
// should also have next
|
||||||
if !assert.True(t, iterator.HasNext()) {
|
assert.True(t, iterator.HasNext())
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,13 +307,13 @@ func TestInitParametersIteratorContent(t *testing.T) {
|
|||||||
"app_version": []interface{}{4.0}, // 1
|
"app_version": []interface{}{4.0}, // 1
|
||||||
}
|
}
|
||||||
testData := []struct {
|
testData := []struct {
|
||||||
cfg *TConfig
|
cfg *hrp.TConfig
|
||||||
checkIndex int
|
checkIndex int
|
||||||
expectParameters map[string]interface{}
|
expectParameters map[string]interface{}
|
||||||
}{
|
}{
|
||||||
// default, no parameters setting
|
// default, no parameters setting
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: configParameters,
|
Parameters: configParameters,
|
||||||
},
|
},
|
||||||
0, // check first item
|
0, // check first item
|
||||||
@@ -346,16 +324,16 @@ func TestInitParametersIteratorContent(t *testing.T) {
|
|||||||
|
|
||||||
// set limit for parameters
|
// set limit for parameters
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: map[string]interface{}{
|
Parameters: map[string]interface{}{
|
||||||
"username-password": []map[string]interface{}{ // 1
|
"username-password": []map[string]interface{}{ // 1
|
||||||
{"username": "test1", "password": 111111, "other": "111"},
|
{"username": "test1", "password": 111111, "other": "111"},
|
||||||
},
|
},
|
||||||
"user_agent": []string{"iOS/10.1", "iOS/10.2"}, // 2
|
"user_agent": []string{"iOS/10.1", "iOS/10.2"}, // 2
|
||||||
},
|
},
|
||||||
ParametersSetting: &TParamsConfig{
|
ParametersSetting: &hrp.TParamsConfig{
|
||||||
Limit: 5, // limit could also be greater than total
|
Limit: 5, // limit could also be greater than total
|
||||||
Strategies: map[string]iteratorStrategy{
|
Strategies: map[string]hrp.IteratorStrategy{
|
||||||
"username-password": {Name: "user-info", PickOrder: "random"},
|
"username-password": {Name: "user-info", PickOrder: "random"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -369,7 +347,7 @@ func TestInitParametersIteratorContent(t *testing.T) {
|
|||||||
// no parameters
|
// no parameters
|
||||||
// also will generate one empty item
|
// also will generate one empty item
|
||||||
{
|
{
|
||||||
&TConfig{
|
&hrp.TConfig{
|
||||||
Parameters: nil,
|
Parameters: nil,
|
||||||
ParametersSetting: nil,
|
ParametersSetting: nil,
|
||||||
},
|
},
|
||||||
@@ -377,35 +355,29 @@ func TestInitParametersIteratorContent(t *testing.T) {
|
|||||||
map[string]interface{}{},
|
map[string]interface{}{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
parser := newParser()
|
parser := hrp.NewParser()
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
iterator, err := parser.initParametersIterator(data.cfg)
|
iterator, err := parser.InitParametersIterator(data.cfg)
|
||||||
if !assert.Nil(t, err) {
|
assert.Nil(t, err)
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// get expected parameters item
|
// get expected parameters item
|
||||||
for i := 0; i < data.checkIndex; i++ {
|
for i := 0; i < data.checkIndex; i++ {
|
||||||
if !assert.True(t, iterator.HasNext()) {
|
assert.True(t, iterator.HasNext())
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
iterator.Next() // consume next parameters
|
iterator.Next() // consume next parameters
|
||||||
}
|
}
|
||||||
parametersItem := iterator.Next()
|
parametersItem := iterator.Next()
|
||||||
|
|
||||||
if !assert.Equal(t, data.expectParameters, parametersItem) {
|
assert.Equal(t, data.expectParameters, parametersItem)
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenCartesianProduct(t *testing.T) {
|
func TestGenCartesianProduct(t *testing.T) {
|
||||||
testData := []struct {
|
testData := []struct {
|
||||||
multiParameters []Parameters
|
multiParameters []hrp.Parameters
|
||||||
expect Parameters
|
expect hrp.Parameters
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
[]Parameters{
|
[]hrp.Parameters{
|
||||||
{
|
{
|
||||||
{"app_version": 4.0},
|
{"app_version": 4.0},
|
||||||
},
|
},
|
||||||
@@ -418,7 +390,7 @@ func TestGenCartesianProduct(t *testing.T) {
|
|||||||
{"user_agent": "iOS/10.2"},
|
{"user_agent": "iOS/10.2"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Parameters{
|
hrp.Parameters{
|
||||||
{"app_version": 4.0, "password": "111111", "user_agent": "iOS/10.1", "username": "test1"},
|
{"app_version": 4.0, "password": "111111", "user_agent": "iOS/10.1", "username": "test1"},
|
||||||
{"app_version": 4.0, "password": "111111", "user_agent": "iOS/10.2", "username": "test1"},
|
{"app_version": 4.0, "password": "111111", "user_agent": "iOS/10.2", "username": "test1"},
|
||||||
{"app_version": 4.0, "password": "222222", "user_agent": "iOS/10.1", "username": "test2"},
|
{"app_version": 4.0, "password": "222222", "user_agent": "iOS/10.1", "username": "test2"},
|
||||||
@@ -430,16 +402,14 @@ func TestGenCartesianProduct(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
[]Parameters{},
|
[]hrp.Parameters{},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
parameters := genCartesianProduct(data.multiParameters)
|
parameters := hrp.GenCartesianProduct(data.multiParameters)
|
||||||
if !assert.Equal(t, data.expect, parameters) {
|
assert.Equal(t, data.expect, parameters)
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,13 +460,9 @@ func TestConvertParameters(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
value, err := convertParameters(data.key, data.parametersRawList)
|
value, err := hrp.ConvertParameters(data.key, data.parametersRawList)
|
||||||
if !assert.Nil(t, err) {
|
assert.Nil(t, err)
|
||||||
t.Fatal()
|
assert.Equal(t, data.expect, value)
|
||||||
}
|
|
||||||
if !assert.Equal(t, data.expect, value) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,9 +496,7 @@ func TestConvertParametersError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, data := range testData {
|
for _, data := range testData {
|
||||||
_, err := convertParameters(data.key, data.parametersRawList)
|
_, err := hrp.ConvertParameters(data.key, data.parametersRawList)
|
||||||
if !assert.Error(t, err) {
|
assert.Error(t, err)
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLocateFile(t *testing.T) {
|
||||||
|
// specify target file path
|
||||||
|
_, err := hrp.LocateFile(tmpl("plugin/debugtalk.go"), hrp.PluginGoSourceFile)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// specify path with the same dir
|
||||||
|
_, err = hrp.LocateFile(tmpl("plugin/debugtalk.py"), hrp.PluginGoSourceFile)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// specify target file path dir
|
||||||
|
_, err = hrp.LocateFile(tmpl("plugin/"), hrp.PluginGoSourceFile)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// specify wrong path
|
||||||
|
_, err = hrp.LocateFile(".", hrp.PluginGoSourceFile)
|
||||||
|
assert.Error(t, err)
|
||||||
|
_, err = hrp.LocateFile("/abc", hrp.PluginGoSourceFile)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocatePythonPlugin(t *testing.T) {
|
||||||
|
_, err := hrp.LocatePlugin(tmpl("plugin/debugtalk.py"))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocateGoPlugin(t *testing.T) {
|
||||||
|
buildHashicorpGoPlugin()
|
||||||
|
defer removeHashicorpGoPlugin()
|
||||||
|
|
||||||
|
_, err := hrp.LocatePlugin(tmpl("debugtalk.bin"))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
}
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
package hrp
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
"github.com/httprunner/httprunner/v5/code"
|
"github.com/httprunner/httprunner/v5/code"
|
||||||
)
|
)
|
||||||
|
|
||||||
func buildHashicorpGoPlugin() {
|
func buildHashicorpGoPlugin() {
|
||||||
log.Info().Msg("[init] build hashicorp go plugin")
|
log.Info().Msg("[init] build hashicorp go plugin")
|
||||||
err := BuildPlugin(tmpl("plugin/debugtalk.go"), tmpl("debugtalk.bin"))
|
err := hrp.BuildPlugin(tmpl("plugin/debugtalk.go"), tmpl("debugtalk.bin"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("build hashicorp go plugin failed")
|
log.Error().Err(err).Msg("build hashicorp go plugin failed")
|
||||||
os.Exit(code.GetErrorCode(err))
|
os.Exit(code.GetErrorCode(err))
|
||||||
@@ -26,8 +26,6 @@ func buildHashicorpGoPlugin() {
|
|||||||
func removeHashicorpGoPlugin() {
|
func removeHashicorpGoPlugin() {
|
||||||
log.Info().Msg("[teardown] remove hashicorp go plugin")
|
log.Info().Msg("[teardown] remove hashicorp go plugin")
|
||||||
os.Remove(tmpl("debugtalk.bin"))
|
os.Remove(tmpl("debugtalk.bin"))
|
||||||
pluginPath, _ := filepath.Abs(tmpl("debugtalk.bin"))
|
|
||||||
pluginMap.Delete(pluginPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildHashicorpPyPlugin() {
|
func buildHashicorpPyPlugin() {
|
||||||
@@ -43,8 +41,8 @@ func buildHashicorpPyPlugin() {
|
|||||||
func removeHashicorpPyPlugin() {
|
func removeHashicorpPyPlugin() {
|
||||||
log.Info().Msg("[teardown] remove hashicorp python plugin")
|
log.Info().Msg("[teardown] remove hashicorp python plugin")
|
||||||
// on v4.1^, running case will generate .debugtalk_gen.py used by python plugin
|
// on v4.1^, running case will generate .debugtalk_gen.py used by python plugin
|
||||||
os.Remove(tmpl(PluginPySourceFile))
|
os.Remove(tmpl(hrp.PluginPySourceFile))
|
||||||
os.Remove(tmpl(PluginPySourceGenFile))
|
os.Remove(tmpl(hrp.PluginPySourceGenFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunCaseWithGoPlugin(t *testing.T) {
|
func TestRunCaseWithGoPlugin(t *testing.T) {
|
||||||
@@ -62,21 +60,22 @@ func TestRunCaseWithPythonPlugin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func assertRunTestCases(t *testing.T) {
|
func assertRunTestCases(t *testing.T) {
|
||||||
refCase := TestCasePath(demoTestCaseWithPluginJSONPath)
|
refCase := hrp.TestCasePath(demoTestCaseWithPluginJSONPath)
|
||||||
testcase1 := &TestCase{
|
testcase1 := &hrp.TestCase{
|
||||||
Config: NewConfig("TestCase1").
|
Config: hrp.NewConfig("TestCase1").
|
||||||
SetBaseURL("https://postman-echo.com"),
|
SetBaseURL("https://postman-echo.com").
|
||||||
TestSteps: []IStep{
|
EnablePlugin(), // TODO: FIXME
|
||||||
NewStep("testcase1-step1").
|
TestSteps: []hrp.IStep{
|
||||||
|
hrp.NewStep("testcase1-step1").
|
||||||
GET("/headers").
|
GET("/headers").
|
||||||
Validate().
|
Validate().
|
||||||
AssertEqual("status_code", 200, "check status code").
|
AssertEqual("status_code", 200, "check status code").
|
||||||
AssertEqual("headers.\"Content-Type\"", "application/json; charset=utf-8", "check http response Content-Type"),
|
AssertEqual("headers.\"Content-Type\"", "application/json; charset=utf-8", "check http response Content-Type"),
|
||||||
NewStep("testcase1-step2").CallRefCase(
|
hrp.NewStep("testcase1-step2").CallRefCase(
|
||||||
&TestCase{
|
&hrp.TestCase{
|
||||||
Config: NewConfig("testcase1-step3-ref-case").SetBaseURL("https://postman-echo.com"),
|
Config: hrp.NewConfig("testcase1-step3-ref-case").SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("ip").
|
hrp.NewStep("ip").
|
||||||
GET("/ip").
|
GET("/ip").
|
||||||
Validate().
|
Validate().
|
||||||
AssertEqual("status_code", 200, "check status code").
|
AssertEqual("status_code", 200, "check status code").
|
||||||
@@ -84,14 +83,14 @@ func assertRunTestCases(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
NewStep("testcase1-step3").CallRefCase(&refCase),
|
hrp.NewStep("testcase1-step3").CallRefCase(&refCase),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
testcase2 := &TestCase{
|
testcase2 := &hrp.TestCase{
|
||||||
Config: NewConfig("TestCase2").SetWeight(3),
|
Config: hrp.NewConfig("TestCase2").SetWeight(3),
|
||||||
}
|
}
|
||||||
|
|
||||||
r := NewRunner(t)
|
r := hrp.NewRunner(t)
|
||||||
r.SetPluginLogOn()
|
r.SetPluginLogOn()
|
||||||
err := r.Run(testcase1, testcase2)
|
err := r.Run(testcase1, testcase2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -103,46 +102,46 @@ func TestRunCaseWithThinkTime(t *testing.T) {
|
|||||||
buildHashicorpGoPlugin()
|
buildHashicorpGoPlugin()
|
||||||
defer removeHashicorpGoPlugin()
|
defer removeHashicorpGoPlugin()
|
||||||
|
|
||||||
testcases := []*TestCase{
|
testcases := []*hrp.TestCase{
|
||||||
{
|
{
|
||||||
Config: NewConfig("TestCase1"),
|
Config: hrp.NewConfig("TestCase1"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("thinkTime").SetThinkTime(2),
|
hrp.NewStep("thinkTime").SetThinkTime(2),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Config: NewConfig("TestCase2").
|
Config: hrp.NewConfig("TestCase2").
|
||||||
SetThinkTime(thinkTimeIgnore, nil, 0),
|
SetThinkTime(hrp.ThinkTimeIgnore, nil, 0),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("thinkTime").SetThinkTime(0.5),
|
hrp.NewStep("thinkTime").SetThinkTime(0.5),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Config: NewConfig("TestCase3").
|
Config: hrp.NewConfig("TestCase3").
|
||||||
SetThinkTime(thinkTimeRandomPercentage, nil, 0),
|
SetThinkTime(hrp.ThinkTimeRandomPercentage, nil, 0),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("thinkTime").SetThinkTime(1),
|
hrp.NewStep("thinkTime").SetThinkTime(1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Config: NewConfig("TestCase4").
|
Config: hrp.NewConfig("TestCase4").
|
||||||
SetThinkTime(thinkTimeRandomPercentage, map[string]interface{}{"min_percentage": 2, "max_percentage": 3}, 2.5),
|
SetThinkTime(hrp.ThinkTimeRandomPercentage, map[string]interface{}{"min_percentage": 2, "max_percentage": 3}, 2.5),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("thinkTime").SetThinkTime(1),
|
hrp.NewStep("thinkTime").SetThinkTime(1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Config: NewConfig("TestCase5"),
|
Config: hrp.NewConfig("TestCase5"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
// 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),
|
hrp.NewStep("thinkTime").CallRefCase(&demoTestCaseWithThinkTimePath),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expectedMinValue := []float64{2, 0, 0.5, 2, 3}
|
expectedMinValue := []float64{2, 0, 0.5, 2, 3}
|
||||||
expectedMaxValue := []float64{2.5, 0.5, 2, 3, 10}
|
expectedMaxValue := []float64{2.5, 0.5, 2, 3, 10}
|
||||||
for idx, testcase := range testcases {
|
for idx, testcase := range testcases {
|
||||||
r := NewRunner(t)
|
r := hrp.NewRunner(t)
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
err := r.Run(testcase)
|
err := r.Run(testcase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -158,20 +157,20 @@ func TestRunCaseWithThinkTime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRunCaseWithShell(t *testing.T) {
|
func TestRunCaseWithShell(t *testing.T) {
|
||||||
testcase1 := &TestCase{
|
testcase1 := &hrp.TestCase{
|
||||||
Config: NewConfig("complex shell with env variables").
|
Config: hrp.NewConfig("complex shell with env variables").
|
||||||
WithVariables(map[string]interface{}{
|
WithVariables(map[string]interface{}{
|
||||||
"SS": "12345",
|
"SS": "12345",
|
||||||
"ABC": "$SS",
|
"ABC": "$SS",
|
||||||
}),
|
}),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("shell21").Shell("echo hello world"),
|
hrp.NewStep("shell21").Shell("echo hello world"),
|
||||||
// NewStep("shell21").Shell("echo $ABC"),
|
// NewStep("shell21").Shell("echo $ABC"),
|
||||||
// NewStep("shell21").Shell("which hrp"),
|
// NewStep("shell21").Shell("which hrp"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
r := NewRunner(t)
|
r := hrp.NewRunner(t)
|
||||||
err := r.Run(testcase1)
|
err := r.Run(testcase1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
@@ -193,16 +192,16 @@ func TestRunCaseWithFunction(t *testing.T) {
|
|||||||
err3 = errors.New("func3 error")
|
err3 = errors.New("func3 error")
|
||||||
fmt.Println("call function3 with return value and error")
|
fmt.Println("call function3 with return value and error")
|
||||||
}
|
}
|
||||||
testcase1 := &TestCase{
|
testcase1 := &hrp.TestCase{
|
||||||
Config: NewConfig("call function"),
|
Config: hrp.NewConfig("call function"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("fn1").Function(fn1),
|
hrp.NewStep("fn1").Function(fn1),
|
||||||
NewStep("fn2").Function(fn2),
|
hrp.NewStep("fn2").Function(fn2),
|
||||||
NewStep("fn3").Function(fn3),
|
hrp.NewStep("fn3").Function(fn3),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
r := NewRunner(t)
|
r := hrp.NewRunner(t)
|
||||||
err := r.Run(testcase1)
|
err := r.Run(testcase1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
@@ -220,8 +219,9 @@ func TestRunCaseWithPluginJSON(t *testing.T) {
|
|||||||
buildHashicorpGoPlugin()
|
buildHashicorpGoPlugin()
|
||||||
defer removeHashicorpGoPlugin()
|
defer removeHashicorpGoPlugin()
|
||||||
|
|
||||||
testCase := TestCasePath(demoTestCaseWithPluginJSONPath)
|
testCase := hrp.TestCasePath(demoTestCaseWithPluginJSONPath)
|
||||||
err := NewRunner(nil).Run(&testCase) // hrp.Run(testCase)
|
// TODO: FIXME, enable plugin
|
||||||
|
err := hrp.NewRunner(nil).Run(&testCase) // hrp.Run(testCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
@@ -243,22 +243,22 @@ func TestRunCaseWithRefAPI(t *testing.T) {
|
|||||||
buildHashicorpGoPlugin()
|
buildHashicorpGoPlugin()
|
||||||
defer removeHashicorpGoPlugin()
|
defer removeHashicorpGoPlugin()
|
||||||
|
|
||||||
testCase := TestCasePath(demoTestCaseWithRefAPIPath)
|
testCase := hrp.TestCasePath(demoTestCaseWithRefAPIPath)
|
||||||
err := NewRunner(nil).Run(&testCase)
|
err := hrp.NewRunner(nil).Run(&testCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
|
|
||||||
refAPI := APIPath(demoAPIGETPath)
|
refAPI := hrp.APIPath(demoAPIGETPath)
|
||||||
testcase := &TestCase{
|
testcase := &hrp.TestCase{
|
||||||
Config: NewConfig("TestCase").
|
Config: hrp.NewConfig("TestCase").
|
||||||
SetBaseURL("https://postman-echo.com"),
|
SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("run referenced api").CallRefAPI(&refAPI),
|
hrp.NewStep("run referenced api").CallRefAPI(&refAPI),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
r := NewRunner(t)
|
r := hrp.NewRunner(t)
|
||||||
err = r.Run(testcase)
|
err = r.Run(testcase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
@@ -266,15 +266,15 @@ func TestRunCaseWithRefAPI(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionRunner(t *testing.T) {
|
func TestSessionRunner(t *testing.T) {
|
||||||
testcase := TestCase{
|
testcase := hrp.TestCase{
|
||||||
Config: NewConfig("TestCase").
|
Config: hrp.NewConfig("TestCase").
|
||||||
WithVariables(map[string]interface{}{
|
WithVariables(map[string]interface{}{
|
||||||
"a": 12.3,
|
"a": 12.3,
|
||||||
"b": 3.45,
|
"b": 3.45,
|
||||||
"varFoo": "${max($a, $b)}",
|
"varFoo": "${max($a, $b)}",
|
||||||
}),
|
}),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("check variables").
|
hrp.NewStep("check variables").
|
||||||
WithVariables(map[string]interface{}{
|
WithVariables(map[string]interface{}{
|
||||||
"a": 12.3,
|
"a": 12.3,
|
||||||
"b": 34.5,
|
"b": 34.5,
|
||||||
@@ -287,7 +287,7 @@ func TestSessionRunner(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
caseRunner, _ := NewRunner(t).NewCaseRunner(testcase)
|
caseRunner, _ := hrp.NewRunner(t).NewCaseRunner(testcase)
|
||||||
sessionRunner := caseRunner.NewSession()
|
sessionRunner := caseRunner.NewSession()
|
||||||
step := testcase.TestSteps[0]
|
step := testcase.TestSteps[0]
|
||||||
if !assert.Equal(t, step.Config().Variables["varFoo"], "${max($a, $b)}") {
|
if !assert.Equal(t, step.Config().Variables["varFoo"], "${max($a, $b)}") {
|
||||||
@@ -1,40 +1,42 @@
|
|||||||
package hrp
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRunCaseWithRendezvous(t *testing.T) {
|
func TestRunCaseWithRendezvous(t *testing.T) {
|
||||||
rendezvousBoundaryTestcase := &TestCase{
|
rendezvousBoundaryTestcase := &hrp.TestCase{
|
||||||
Config: NewConfig("run request with functions").
|
Config: hrp.NewConfig("run request with functions").
|
||||||
SetBaseURL("https://postman-echo.com").
|
SetBaseURL("https://postman-echo.com").
|
||||||
WithVariables(map[string]interface{}{
|
WithVariables(map[string]interface{}{
|
||||||
"n": 5,
|
"n": 5,
|
||||||
"a": 12.3,
|
"a": 12.3,
|
||||||
"b": 3.45,
|
"b": 3.45,
|
||||||
}),
|
}),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("test negative number").
|
hrp.NewStep("test negative number").
|
||||||
SetRendezvous("test negative number").
|
SetRendezvous("test negative number").
|
||||||
WithUserNumber(-1),
|
WithUserNumber(-1),
|
||||||
NewStep("test overflow number").
|
hrp.NewStep("test overflow number").
|
||||||
SetRendezvous("test overflow number").
|
SetRendezvous("test overflow number").
|
||||||
WithUserNumber(1000000),
|
WithUserNumber(1000000),
|
||||||
NewStep("test negative percent").
|
hrp.NewStep("test negative percent").
|
||||||
SetRendezvous("test very low percent").
|
SetRendezvous("test very low percent").
|
||||||
WithUserPercent(-0.5),
|
WithUserPercent(-0.5),
|
||||||
NewStep("test very low percent").
|
hrp.NewStep("test very low percent").
|
||||||
SetRendezvous("test very low percent").
|
SetRendezvous("test very low percent").
|
||||||
WithUserPercent(0.00001),
|
WithUserPercent(0.00001),
|
||||||
NewStep("test overflow percent").
|
hrp.NewStep("test overflow percent").
|
||||||
SetRendezvous("test overflow percent").
|
SetRendezvous("test overflow percent").
|
||||||
WithUserPercent(1.5),
|
WithUserPercent(1.5),
|
||||||
NewStep("test conflict params").
|
hrp.NewStep("test conflict params").
|
||||||
SetRendezvous("test conflict params").
|
SetRendezvous("test conflict params").
|
||||||
WithUserNumber(1).
|
WithUserNumber(1).
|
||||||
WithUserPercent(0.123),
|
WithUserPercent(0.123),
|
||||||
NewStep("test negative timeout").
|
hrp.NewStep("test negative timeout").
|
||||||
SetRendezvous("test negative timeout").
|
SetRendezvous("test negative timeout").
|
||||||
WithTimeout(-1000),
|
WithTimeout(-1000),
|
||||||
},
|
},
|
||||||
@@ -55,7 +57,7 @@ func TestRunCaseWithRendezvous(t *testing.T) {
|
|||||||
{number: 100, percent: 1, timeout: 5000},
|
{number: 100, percent: 1, timeout: 5000},
|
||||||
}
|
}
|
||||||
|
|
||||||
rendezvousList := InitRendezvous(rendezvousBoundaryTestcase, 100)
|
rendezvousList := hrp.InitRendezvous(rendezvousBoundaryTestcase, 100)
|
||||||
|
|
||||||
for i, r := range rendezvousList {
|
for i, r := range rendezvousList {
|
||||||
if r.Number != expectedRendezvousParams[i].number {
|
if r.Number != expectedRendezvousParams[i].number {
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
package hrp
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
stepGET = NewStep("get with params").
|
stepGET = hrp.NewStep("get with params").
|
||||||
GET("/get").
|
GET("/get").
|
||||||
WithParams(map[string]interface{}{"foo1": "bar1", "foo2": "bar2"}).
|
WithParams(map[string]interface{}{"foo1": "bar1", "foo2": "bar2"}).
|
||||||
WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}).
|
WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}).
|
||||||
@@ -21,7 +19,7 @@ var (
|
|||||||
AssertEqual("headers.\"Content-Type\"", "application/json; charset=utf-8", "check header Content-Type").
|
AssertEqual("headers.\"Content-Type\"", "application/json; charset=utf-8", "check header Content-Type").
|
||||||
AssertEqual("body.args.foo1", "bar1", "check param foo1").
|
AssertEqual("body.args.foo1", "bar1", "check param foo1").
|
||||||
AssertEqual("body.args.foo2", "bar2", "check param foo2")
|
AssertEqual("body.args.foo2", "bar2", "check param foo2")
|
||||||
stepPOSTData = NewStep("post form data").
|
stepPOSTData = hrp.NewStep("post form data").
|
||||||
POST("/post").
|
POST("/post").
|
||||||
WithParams(map[string]interface{}{"foo1": "bar1", "foo2": "bar2"}).
|
WithParams(map[string]interface{}{"foo1": "bar1", "foo2": "bar2"}).
|
||||||
WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus", "Content-Type": "application/x-www-form-urlencoded"}).
|
WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus", "Content-Type": "application/x-www-form-urlencoded"}).
|
||||||
@@ -33,7 +31,7 @@ var (
|
|||||||
|
|
||||||
func TestRunRequestGetToStruct(t *testing.T) {
|
func TestRunRequestGetToStruct(t *testing.T) {
|
||||||
tStep := stepGET
|
tStep := stepGET
|
||||||
if tStep.Request.Method != httpGET {
|
if tStep.Request.Method != hrp.HTTP_GET {
|
||||||
t.Fatalf("tStep.Request.Method != GET")
|
t.Fatalf("tStep.Request.Method != GET")
|
||||||
}
|
}
|
||||||
if tStep.Request.URL != "/get" {
|
if tStep.Request.URL != "/get" {
|
||||||
@@ -48,7 +46,7 @@ func TestRunRequestGetToStruct(t *testing.T) {
|
|||||||
if tStep.Request.Cookies["user"] != "debugtalk" {
|
if tStep.Request.Cookies["user"] != "debugtalk" {
|
||||||
t.Fatalf("tStep.Request.Cookies mismatch")
|
t.Fatalf("tStep.Request.Cookies mismatch")
|
||||||
}
|
}
|
||||||
validator, ok := tStep.Validators[0].(Validator)
|
validator, ok := tStep.Validators[0].(hrp.Validator)
|
||||||
if !ok || validator.Check != "status_code" || validator.Expect != 200 {
|
if !ok || validator.Check != "status_code" || validator.Expect != 200 {
|
||||||
t.Fatalf("tStep.Validators mismatch")
|
t.Fatalf("tStep.Validators mismatch")
|
||||||
}
|
}
|
||||||
@@ -56,7 +54,7 @@ func TestRunRequestGetToStruct(t *testing.T) {
|
|||||||
|
|
||||||
func TestRunRequestPostDataToStruct(t *testing.T) {
|
func TestRunRequestPostDataToStruct(t *testing.T) {
|
||||||
tStep := stepPOSTData
|
tStep := stepPOSTData
|
||||||
if tStep.Request.Method != httpPOST {
|
if tStep.Request.Method != hrp.HTTP_POST {
|
||||||
t.Fatalf("tStep.Request.Method != POST")
|
t.Fatalf("tStep.Request.Method != POST")
|
||||||
}
|
}
|
||||||
if tStep.Request.URL != "/post" {
|
if tStep.Request.URL != "/post" {
|
||||||
@@ -74,18 +72,18 @@ func TestRunRequestPostDataToStruct(t *testing.T) {
|
|||||||
if tStep.Request.Body != "a=1&b=2" {
|
if tStep.Request.Body != "a=1&b=2" {
|
||||||
t.Fatalf("tStep.Request.Data mismatch")
|
t.Fatalf("tStep.Request.Data mismatch")
|
||||||
}
|
}
|
||||||
validator, ok := tStep.Validators[0].(Validator)
|
validator, ok := tStep.Validators[0].(hrp.Validator)
|
||||||
if !ok || validator.Check != "status_code" || validator.Expect != 200 {
|
if !ok || validator.Check != "status_code" || validator.Expect != 200 {
|
||||||
t.Fatalf("tStep.Validators mismatch")
|
t.Fatalf("tStep.Validators mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunRequestStatOn(t *testing.T) {
|
func TestRunRequestStatOn(t *testing.T) {
|
||||||
testcase := TestCase{
|
testcase := hrp.TestCase{
|
||||||
Config: NewConfig("test").SetBaseURL("https://postman-echo.com"),
|
Config: hrp.NewConfig("test").SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{stepGET, stepPOSTData},
|
TestSteps: []hrp.IStep{stepGET, stepPOSTData},
|
||||||
}
|
}
|
||||||
caseRunner, _ := NewRunner(t).SetHTTPStatOn().NewCaseRunner(testcase)
|
caseRunner, _ := hrp.NewRunner(t).SetHTTPStatOn().NewCaseRunner(testcase)
|
||||||
sessionRunner := caseRunner.NewSession()
|
sessionRunner := caseRunner.NewSession()
|
||||||
summary, err := sessionRunner.Start(nil)
|
summary, err := sessionRunner.Start(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -162,15 +160,15 @@ func TestRunRequestStatOn(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRunCaseWithTimeout(t *testing.T) {
|
func TestRunCaseWithTimeout(t *testing.T) {
|
||||||
r := NewRunner(t)
|
r := hrp.NewRunner(t)
|
||||||
|
|
||||||
// global timeout
|
// global timeout
|
||||||
testcase1 := &TestCase{
|
testcase1 := &hrp.TestCase{
|
||||||
Config: NewConfig("TestCase1").
|
Config: hrp.NewConfig("TestCase1").
|
||||||
SetRequestTimeout(10). // set global timeout to 10s
|
SetRequestTimeout(10). // set global timeout to 10s
|
||||||
SetBaseURL("https://postman-echo.com"),
|
SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("step1").
|
hrp.NewStep("step1").
|
||||||
GET("/delay/1").
|
GET("/delay/1").
|
||||||
Validate().
|
Validate().
|
||||||
AssertEqual("status_code", 200, "check status code"),
|
AssertEqual("status_code", 200, "check status code"),
|
||||||
@@ -181,12 +179,12 @@ func TestRunCaseWithTimeout(t *testing.T) {
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
testcase2 := &TestCase{
|
testcase2 := &hrp.TestCase{
|
||||||
Config: NewConfig("TestCase2").
|
Config: hrp.NewConfig("TestCase2").
|
||||||
SetRequestTimeout(5). // set global timeout to 10s
|
SetRequestTimeout(5). // set global timeout to 10s
|
||||||
SetBaseURL("https://postman-echo.com"),
|
SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("step1").
|
hrp.NewStep("step1").
|
||||||
GET("/delay/10").
|
GET("/delay/10").
|
||||||
Validate().
|
Validate().
|
||||||
AssertEqual("status_code", 200, "check status code"),
|
AssertEqual("status_code", 200, "check status code"),
|
||||||
@@ -198,12 +196,12 @@ func TestRunCaseWithTimeout(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// step timeout
|
// step timeout
|
||||||
testcase3 := &TestCase{
|
testcase3 := &hrp.TestCase{
|
||||||
Config: NewConfig("TestCase3").
|
Config: hrp.NewConfig("TestCase3").
|
||||||
SetRequestTimeout(10).
|
SetRequestTimeout(10).
|
||||||
SetBaseURL("https://postman-echo.com"),
|
SetBaseURL("https://postman-echo.com"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("step2").
|
hrp.NewStep("step2").
|
||||||
GET("/delay/11").
|
GET("/delay/11").
|
||||||
SetTimeout(15*time.Second). // set step timeout to 4s
|
SetTimeout(15*time.Second). // set step timeout to 4s
|
||||||
Validate().
|
Validate().
|
||||||
@@ -215,70 +213,3 @@ func TestRunCaseWithTimeout(t *testing.T) {
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchJmespath(t *testing.T) {
|
|
||||||
testText := `{"a": {"b": "foo"}, "c": "bar", "d": {"e": [{"f": "foo"}, {"f": "bar"}]}}`
|
|
||||||
testData := []struct {
|
|
||||||
raw string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"body.a.b", "foo"},
|
|
||||||
{"body.c", "bar"},
|
|
||||||
{"body.d.e[0].f", "foo"},
|
|
||||||
{"body.d.e[1].f", "bar"},
|
|
||||||
}
|
|
||||||
resp := http.Response{}
|
|
||||||
resp.Body = io.NopCloser(strings.NewReader(testText))
|
|
||||||
respObj, err := newHttpResponseObject(t, newParser(), &resp)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
for _, data := range testData {
|
|
||||||
if !assert.Equal(t, data.expected, respObj.searchJmespath(data.raw)) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSearchRegexp(t *testing.T) {
|
|
||||||
testText := `
|
|
||||||
<ul class="nav navbar-nav navbar-right">
|
|
||||||
<li><a href="/order/addToCart" style="color: white"><i class="fa fa-shopping-cart fa-2x"></i><span class="badge">0</span></a></li>
|
|
||||||
<li class="dropdown">
|
|
||||||
<a class="dropdown-toggle" data-toggle="dropdown" href="#" style="color: white">
|
|
||||||
Leo <i class="fa fa-cog fa-2x"></i><span class="caret"></span></a>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
<li><a href="/user/changePassword">Change Password</a></li>
|
|
||||||
<li><a href="/user/addAddress">Shipping</a></li>
|
|
||||||
<li><a href="/user/addCard">Payment</a></li>
|
|
||||||
<li><a href="/order/orderHistory">Order History</a></li>
|
|
||||||
<li><a href="/user/signOut">Sign Out</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li> </li>
|
|
||||||
<li><a href="/user/signOut" style="color: white"><i class="fa fa-sign-out fa-2x"></i>
|
|
||||||
Sign Out</a></li>
|
|
||||||
</ul>
|
|
||||||
`
|
|
||||||
testData := []struct {
|
|
||||||
raw string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"/user/signOut\">(.*)</a></li>", "Sign Out"},
|
|
||||||
{"<li><a href=\"/user/(.*)\" style", "signOut"},
|
|
||||||
{" (.*) <i class=\"fa fa-cog fa-2x\"></i>", "Leo"},
|
|
||||||
}
|
|
||||||
// new response object
|
|
||||||
resp := http.Response{}
|
|
||||||
resp.Body = io.NopCloser(strings.NewReader(testText))
|
|
||||||
respObj, err := newHttpResponseObject(t, newParser(), &resp)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
for _, data := range testData {
|
|
||||||
if !assert.Equal(t, data.expected, respObj.searchRegexp(data.raw)) {
|
|
||||||
t.Fatal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +1,88 @@
|
|||||||
//go:build localtest
|
//go:build localtest
|
||||||
|
|
||||||
package hrp
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
hrp "github.com/httprunner/httprunner/v5"
|
||||||
|
"github.com/httprunner/httprunner/v5/uixt/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIOSSettingsAction(t *testing.T) {
|
func TestIOSSettingsAction(t *testing.T) {
|
||||||
testCase := &TestCase{
|
testCase := &hrp.TestCase{
|
||||||
Config: NewConfig("ios ui action on Settings").
|
Config: hrp.NewConfig("ios ui action on Settings").
|
||||||
SetIOS(option.WithWDAPort(8700), option.WithWDAMjpegPort(8800)),
|
SetIOS(option.WithWDAPort(8700), option.WithWDAMjpegPort(8800)),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("launch Settings").
|
hrp.NewStep("launch Settings").
|
||||||
IOS().Home().TapByOCR("设置").
|
IOS().Home().TapByOCR("设置").
|
||||||
Validate().
|
Validate().
|
||||||
AssertNameExists("飞行模式").
|
AssertNameExists("飞行模式").
|
||||||
AssertLabelExists("蓝牙").
|
AssertLabelExists("蓝牙").
|
||||||
AssertOCRExists("个人热点"),
|
AssertOCRExists("个人热点"),
|
||||||
NewStep("swipe up and down").
|
hrp.NewStep("swipe up and down").
|
||||||
IOS().SwipeUp().SwipeUp().SwipeDown(),
|
IOS().SwipeUp().SwipeUp().SwipeDown(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err := NewRunner(t).Run(testCase)
|
err := hrp.NewRunner(t).Run(testCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIOSSearchApp(t *testing.T) {
|
func TestIOSSearchApp(t *testing.T) {
|
||||||
testCase := &TestCase{
|
testCase := &hrp.TestCase{
|
||||||
Config: NewConfig("ios ui action on Search App 资源库"),
|
Config: hrp.NewConfig("ios ui action on Search App 资源库"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("进入 App 资源库 搜索框").
|
hrp.NewStep("进入 App 资源库 搜索框").
|
||||||
IOS().Home().SwipeLeft().SwipeLeft().TapByCV("dewey-search-field").
|
IOS().Home().SwipeLeft().SwipeLeft().TapByCV("dewey-search-field").
|
||||||
Validate().
|
Validate().
|
||||||
AssertLabelExists("取消"),
|
AssertLabelExists("取消"),
|
||||||
NewStep("搜索抖音").
|
hrp.NewStep("搜索抖音").
|
||||||
IOS().Input("抖音\n"),
|
IOS().Input("抖音\n"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err := NewRunner(t).Run(testCase)
|
err := hrp.NewRunner(t).Run(testCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIOSAppLaunch(t *testing.T) {
|
func TestIOSAppLaunch(t *testing.T) {
|
||||||
testCase := &TestCase{
|
testCase := &hrp.TestCase{
|
||||||
Config: NewConfig("启动 & 关闭 App").
|
Config: hrp.NewConfig("启动 & 关闭 App").
|
||||||
SetIOS(option.WithWDAPort(8700), option.WithWDAMjpegPort(8800)),
|
SetIOS(option.WithWDAPort(8700), option.WithWDAMjpegPort(8800)),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("终止今日头条").
|
hrp.NewStep("终止今日头条").
|
||||||
IOS().AppTerminate("com.ss.iphone.article.News"),
|
IOS().AppTerminate("com.ss.iphone.article.News"),
|
||||||
NewStep("启动今日头条").
|
hrp.NewStep("启动今日头条").
|
||||||
IOS().AppLaunch("com.ss.iphone.article.News"),
|
IOS().AppLaunch("com.ss.iphone.article.News"),
|
||||||
NewStep("终止今日头条").
|
hrp.NewStep("终止今日头条").
|
||||||
IOS().AppTerminate("com.ss.iphone.article.News"),
|
IOS().AppTerminate("com.ss.iphone.article.News"),
|
||||||
NewStep("启动今日头条").
|
hrp.NewStep("启动今日头条").
|
||||||
IOS().AppLaunch("com.ss.iphone.article.News"),
|
IOS().AppLaunch("com.ss.iphone.article.News"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err := NewRunner(t).Run(testCase)
|
err := hrp.NewRunner(t).Run(testCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAndroidAction(t *testing.T) {
|
func TestAndroidAction(t *testing.T) {
|
||||||
testCase := &TestCase{
|
testCase := &hrp.TestCase{
|
||||||
Config: NewConfig("android ui action"),
|
Config: hrp.NewConfig("android ui action"),
|
||||||
TestSteps: []IStep{
|
TestSteps: []hrp.IStep{
|
||||||
NewStep("launch douyin").
|
hrp.NewStep("launch douyin").
|
||||||
Android().Serial("xxx").TapByOCR("抖音").
|
Android().Serial("xxx").TapByOCR("抖音").
|
||||||
Validate().
|
Validate().
|
||||||
AssertNameExists("首页", "首页 tab 不存在").
|
AssertNameExists("首页", "首页 tab 不存在").
|
||||||
AssertNameExists("消息", "消息 tab 不存在"),
|
AssertNameExists("消息", "消息 tab 不存在"),
|
||||||
NewStep("swipe up and down").
|
hrp.NewStep("swipe up and down").
|
||||||
Android().Serial("xxx").SwipeUp().SwipeUp().SwipeDown(),
|
Android().Serial("xxx").SwipeUp().SwipeUp().SwipeDown(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err := NewRunner(t).Run(testCase)
|
err := hrp.NewRunner(t).Run(testCase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user