Merge branch 'wings' into 'v5'

wings合入

See merge request iesqa/httprunner!65
This commit is contained in:
李隆
2025-03-05 11:45:28 +00:00
156 changed files with 3197 additions and 1733 deletions
+4 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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{
+40
View File
@@ -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)
}
+2 -2
View File
@@ -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{
+1
View File
@@ -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)
}, },
} }
+2 -2
View File
@@ -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
+14 -14
View File
@@ -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

+1 -1
View File
@@ -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) {
+2 -2
View File
@@ -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 (
+3 -3
View File
@@ -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) {
+1 -1
View File
@@ -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) {
+1 -1
View File
@@ -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) {
+1 -1
View File
@@ -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) {
+1 -1
View File
@@ -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) {
+1 -1
View File
@@ -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) {
+3 -3
View File
@@ -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{
+2 -2
View File
@@ -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) {
+1 -1
View File
@@ -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) {
+1 -2
View File
@@ -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
+2 -4
View File
@@ -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=
-4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1 +1 @@
v5.0.0+2502192152 v5.0.0-beta-2503051939
+2 -17
View File
@@ -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
}
-94
View File
@@ -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
View File
@@ -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")
+3 -3
View File
@@ -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
View File
@@ -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>&nbsp;&nbsp;&nbsp;</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()
}
}
+6
View File
@@ -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
-71
View File
@@ -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...)
}
-286
View File
@@ -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
}
+15 -15
View File
@@ -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
} }
-54
View File
@@ -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()
}
}
+7 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
} }
+73
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1 +0,0 @@
package hrp
+5 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+32
View File
@@ -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)
}
+65
View File
@@ -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)
}
+79 -115
View File
@@ -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()
}
} }
} }
+42
View File
@@ -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)
}
+69 -69
View File
@@ -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>&nbsp;&nbsp;&nbsp;</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()
}
}
}
+29 -28
View File
@@ -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