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