Merge branch 'feat/yuhongzheng/q3_release' into 'video-release'

q3评测发版 [skipped]

See merge request iesqa/httprunner!43
This commit is contained in:
余泓铮
2024-08-26 07:26:34 +00:00
35 changed files with 2035 additions and 149 deletions

30
go.mod
View File

@@ -24,13 +24,13 @@ require (
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/shirou/gopsutil v3.21.11+incompatible github.com/shirou/gopsutil v3.21.11+incompatible
github.com/spf13/cobra v1.5.0 github.com/spf13/cobra v1.5.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.9.0
gocv.io/x/gocv v0.32.1 gocv.io/x/gocv v0.32.1
golang.org/x/net v0.20.0 golang.org/x/net v0.25.0
golang.org/x/oauth2 v0.8.0 golang.org/x/oauth2 v0.8.0
golang.org/x/text v0.14.0 golang.org/x/text v0.15.0
google.golang.org/grpc v1.57.0 google.golang.org/grpc v1.57.0
google.golang.org/protobuf v1.31.0 google.golang.org/protobuf v1.34.1
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.0 howett.net/plist v1.0.0
) )
@@ -39,14 +39,25 @@ require (
cloud.google.com/go/compute v1.20.1 // indirect cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.10.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect github.com/go-errors/errors v1.4.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.22.3 // indirect github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect
@@ -55,16 +66,19 @@ require (
github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/incu6us/goimports-reviser/v2 v2.5.3 // indirect github.com/incu6us/goimports-reviser/v2 v2.5.3 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oklog/run v1.1.0 // indirect github.com/oklog/run v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/common v0.37.0 // indirect
@@ -75,10 +89,14 @@ require (
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.5.0 // indirect github.com/tklauser/numcpus v0.5.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/mod v0.14.0 // indirect
golang.org/x/sync v0.6.0 // indirect golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.16.0 // indirect golang.org/x/sys v0.20.0 // indirect
golang.org/x/tools v0.17.0 // indirect golang.org/x/tools v0.17.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect

60
go.sum
View File

@@ -48,6 +48,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -57,6 +61,10 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
@@ -74,8 +82,14 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/getsentry/sentry-go v0.13.0 h1:20dgTiUSfxRB/EhMPtxcL9ZEbM1ZdR+W/7f7NWD+xWo= github.com/getsentry/sentry-go v0.13.0 h1:20dgTiUSfxRB/EhMPtxcL9ZEbM1ZdR+W/7f7NWD+xWo=
github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0= github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@@ -102,7 +116,15 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -203,6 +225,10 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@@ -213,6 +239,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@@ -231,6 +259,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
@@ -255,6 +285,8 @@ github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DV
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -314,19 +346,31 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A= github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A=
github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo= github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -339,12 +383,17 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
gocv.io/x/gocv v0.32.1 h1:BC9hHs5+47nVgySUFVKntc6RsF3SULFzqk6OV9xz+C0= gocv.io/x/gocv v0.32.1 h1:BC9hHs5+47nVgySUFVKntc6RsF3SULFzqk6OV9xz+C0=
gocv.io/x/gocv v0.32.1/go.mod h1:oc6FvfYqfBp99p+yOEzs9tbYF9gOrAQSeL/dyIPefJU= gocv.io/x/gocv v0.32.1/go.mod h1:oc6FvfYqfBp99p+yOEzs9tbYF9gOrAQSeL/dyIPefJU=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -412,6 +461,8 @@ golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -477,11 +528,14 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -495,6 +549,8 @@ golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -627,6 +683,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -658,6 +716,8 @@ howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo=
mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

62
hrp/cmd/adb/install.go Normal file
View File

@@ -0,0 +1,62 @@
package adb
import (
"fmt"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
)
var installCmd = &cobra.Command{
Use: "install [flags] PACKAGE",
Short: "Push package to the device and install them atomically",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
startTime := time.Now()
defer func() {
sdk.SendGA4Event("hrp_adb_devices", map[string]interface{}{
"args": strings.Join(args, "-"),
"success": err == nil,
"engagement_time_msec": time.Since(startTime).Milliseconds(),
})
}()
_, err = getDevice(serial)
if err != nil {
return err
}
device, err := uixt.NewAndroidDevice(uixt.WithSerialNumber(serial))
if err != nil {
fmt.Println(err)
return err
}
driverExt, err := device.NewDriver()
if err != nil {
fmt.Println(err)
return err
}
replace, _ := cmd.Flags().GetBool("replace")
downgrade, _ := cmd.Flags().GetBool("downgrade")
grant, _ := cmd.Flags().GetBool("grant")
err = driverExt.Install(args[0], uixt.NewInstallOptions(uixt.WithReinstall(replace), uixt.WithDowngrade(downgrade), uixt.WithGrantPermission(grant)))
if err != nil {
fmt.Println(err)
return err
}
fmt.Println("success")
return nil
},
}
func init() {
installCmd.Flags().StringVarP(&serial, "serial", "s", "", "filter by device's serial")
installCmd.Flags().BoolP("replace", "r", false, "replace existing application")
installCmd.Flags().BoolP("downgrade", "d", false, "allow version code downgrade (debuggable packages only)")
installCmd.Flags().BoolP("grant", "g", false, "grant all runtime permissions")
androidRootCmd.AddCommand(installCmd)
}

52
hrp/cmd/ios/install.go Normal file
View File

@@ -0,0 +1,52 @@
package ios
import (
"fmt"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
)
var installCmd = &cobra.Command{
Use: "install [flags] PACKAGE",
Short: "Push package to the device and install them atomically",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
startTime := time.Now()
defer func() {
sdk.SendGA4Event("hrp_adb_devices", map[string]interface{}{
"args": strings.Join(args, "-"),
"success": err == nil,
"engagement_time_msec": time.Since(startTime).Milliseconds(),
})
}()
_, err = getDevice(udid)
if err != nil {
return err
}
device, err := uixt.NewIOSDevice(uixt.WithUDID(udid))
if err != nil {
fmt.Println(err)
return err
}
err = device.Install(args[0], uixt.NewInstallOptions())
if err != nil {
fmt.Println(err)
return err
}
fmt.Println("success")
return nil
},
}
func init() {
installCmd.Flags().StringVarP(&udid, "serial", "s", "", "filter by device's serial")
iosRootCmd.AddCommand(installCmd)
}

25
hrp/cmd/server.go Normal file
View File

@@ -0,0 +1,25 @@
package cmd
import (
"github.com/spf13/cobra"
"github.com/httprunner/httprunner/v4/hrp/pkg/server"
)
// serverCmd represents the server command
var serverCmd = &cobra.Command{
Use: "server start",
Short: "start hrp server",
Long: `start hrp server. exec automation by http`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return server.NewServer(port)
},
}
var port int
func init() {
rootCmd.AddCommand(serverCmd)
serverCmd.Flags().IntVarP(&port, "port", "p", 8082, "Port to run the server on")
}

View File

@@ -1,17 +1,23 @@
package builtin package builtin
import ( import (
"bufio"
"bytes" "bytes"
"context"
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"encoding/csv" "encoding/csv"
builtinJSON "encoding/json" builtinJSON "encoding/json"
"fmt" "fmt"
"io"
"math" "math"
"math/rand" "math/rand"
"net" "net"
"net/http"
"net/url"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strconv" "strconv"
@@ -23,6 +29,7 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"github.com/httprunner/httprunner/v4/hrp/internal/code" "github.com/httprunner/httprunner/v4/hrp/internal/code"
"github.com/httprunner/httprunner/v4/hrp/internal/env"
"github.com/httprunner/httprunner/v4/hrp/internal/json" "github.com/httprunner/httprunner/v4/hrp/internal/json"
) )
@@ -509,3 +516,138 @@ func GetCurrentDay() string {
formattedDate := now.Format("20060102") formattedDate := now.Format("20060102")
return formattedDate return formattedDate
} }
func DownloadFile(filePath string, fileUrl string) error {
log.Info().Str("filePath", filePath).Str("url", fileUrl).Msg("download file")
parsedURL, err := url.Parse(fileUrl)
if err != nil {
return err
}
out, err := os.Create(filePath)
if err != nil {
return err
}
defer out.Close()
// 创建一个新的 HTTP 请求
req, err := http.NewRequest("GET", fileUrl, nil)
if err != nil {
return err
}
if env.EAPI_TOKEN != "" {
if parsedURL.Host != "gtf-eapi-cn.bytedance.com" && parsedURL.Host != "gtf-eapi-cn.bytedance.net" {
return errors.New("invalid domain: must be gtf-eapi-cn.bytedance.com")
}
// 添加自定义头部
req.Header.Add("accessKey", "ies.vedem.video")
req.Header.Add("token", env.EAPI_TOKEN)
}
// 创建一个 HTTP 客户端并发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s, download failed", resp.Status)
}
// 将响应主体写入文件
_, err = io.Copy(out, resp.Body)
if err != nil {
return err
}
return nil
}
func RunCommand(cmdName string, args ...string) error {
cmd := exec.Command(cmdName, args...)
log.Info().Str("command", cmd.String()).Msg("exec command")
// print stderr output
var stderr bytes.Buffer
cmd.Stderr = &stderr
var stdout bytes.Buffer
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
stderrStr := stderr.String()
log.Error().Err(err).Msg("failed to exec command. msg: " + stderrStr)
if stderrStr != "" {
err = errors.Wrap(err, stderrStr)
}
return err
}
stderrStr := stderr.String()
log.Error().Msg("failed to exec command. msg: " + stderrStr)
log.Info().Msg("exec command output: " + stdout.String())
return nil
}
type LineCallback func(line string) bool
// RunCommandWithCallback 运行命令并根据回调判断是否成功
func RunCommandWithCallback(cmdName string, args []string, callback LineCallback) error {
cmd := exec.Command(cmdName, args...)
log.Info().Str("command", cmd.String()).Msg("exec command")
// 使用管道获取标准输出
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
log.Error().Err(err).Msg("failed to get stdout pipe")
return err
}
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Start(); err != nil {
log.Error().Err(err).Msg("failed to start command")
return err
}
// 创建一个用于标识成功的通道
done := make(chan struct{})
defer close(done)
// 逐行读取 stdout
go func() {
stdoutScanner := bufio.NewScanner(stdoutPipe)
for stdoutScanner.Scan() {
line := stdoutScanner.Text()
log.Info().Msg("stdout: " + line)
if callback(line) {
done <- struct{}{}
return
}
}
}()
// 等待命令执行完成
err = cmd.Wait()
if err != nil {
log.Error().Msg("failed to exec command. msg: " + stderr.String())
return err
}
// 设置一个1秒的超时上下文
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
select {
case <-done:
return nil
case <-ctx.Done():
// 超时,判断失败
log.Error().Msg("failed to exec command. msg: " + stderr.String())
err = errors.New("command execution failed: callback failed while exec command")
log.Error().Err(err).Msg("failed to find keyword in time")
return err
}
}

View File

@@ -13,6 +13,7 @@ var (
VEDEM_IMAGE_URL = os.Getenv("VEDEM_IMAGE_URL") VEDEM_IMAGE_URL = os.Getenv("VEDEM_IMAGE_URL")
VEDEM_IMAGE_AK = os.Getenv("VEDEM_IMAGE_AK") VEDEM_IMAGE_AK = os.Getenv("VEDEM_IMAGE_AK")
VEDEM_IMAGE_SK = os.Getenv("VEDEM_IMAGE_SK") VEDEM_IMAGE_SK = os.Getenv("VEDEM_IMAGE_SK")
EAPI_TOKEN = os.Getenv("EAPI_TOKEN")
DISABLE_GA = os.Getenv("DISABLE_GA") DISABLE_GA = os.Getenv("DISABLE_GA")
DISABLE_SENTRY = os.Getenv("DISABLE_SENTRY") DISABLE_SENTRY = os.Getenv("DISABLE_SENTRY")
PYPI_INDEX_URL = os.Getenv("PYPI_INDEX_URL") PYPI_INDEX_URL = os.Getenv("PYPI_INDEX_URL")

View File

@@ -1 +1 @@
v4.5.1 v4.6.5

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@@ -209,8 +210,8 @@ func (c Client) KillServer() (err error) {
return return
} }
func (c Client) createTransport() (tp transport, err error) { func (c Client) createTransport(readTimeout ...time.Duration) (tp transport, err error) {
return newTransport(fmt.Sprintf("%s:%d", c.host, c.port)) return newTransport(fmt.Sprintf("%s:%d", c.host, c.port), readTimeout...)
} }
func (c Client) executeCommand(command string, onlyVerifyResponse ...bool) (resp string, err error) { func (c Client) executeCommand(command string, onlyVerifyResponse ...bool) (resp string, err error) {

View File

@@ -292,6 +292,33 @@ func (d *Device) ReverseForwardKill(remoteInterface interface{}) error {
return err return err
} }
func (d *Device) RunStubCommand(command []byte, processName string) (res string, err error) {
var tp transport
if tp, err = d.createDeviceTransport(); err != nil {
return "", err
}
defer func() { _ = tp.Close() }()
if err = tp.SendWithCheck(fmt.Sprintf("localabstract:%s", processName)); err != nil {
return "", err
}
if err = tp.SendBytes(command); err != nil {
return "", err
}
lenBuf, err := tp.ReadBytesN(4)
if err != nil {
return "", err
}
length := binary.LittleEndian.Uint32(lenBuf)
result, err := tp.ReadBytesN(int(length) - 4)
if err != nil {
return "", err
}
return string(result), nil
}
func (d *Device) ReverseForwardKillAll() error { func (d *Device) ReverseForwardKillAll() error {
_, err := d.executeCommand("reverse:killforward-all") _, err := d.executeCommand("reverse:killforward-all")
return err return err
@@ -419,8 +446,8 @@ func (d *Device) EnableAdbOverTCP(port ...int) (err error) {
return return
} }
func (d *Device) createDeviceTransport() (tp transport, err error) { func (d *Device) createDeviceTransport(readTimeout ...time.Duration) (tp transport, err error) {
if tp, err = newTransport(fmt.Sprintf("%s:%d", d.adbClient.host, d.adbClient.port)); err != nil { if tp, err = newTransport(fmt.Sprintf("%s:%d", d.adbClient.host, d.adbClient.port), readTimeout...); err != nil {
return transport{}, err return transport{}, err
} }
@@ -559,7 +586,7 @@ func (d *Device) installViaABBExec(apk io.ReadSeeker, args ...string) (raw []byt
if err != nil { if err != nil {
return nil, err return nil, err
} }
if tp, err = d.createDeviceTransport(); err != nil { if tp, err = d.createDeviceTransport(5 * time.Minute); err != nil {
return nil, err return nil, err
} }
defer func() { _ = tp.Close() }() defer func() { _ = tp.Close() }()

View File

@@ -17,7 +17,7 @@ import (
var ErrConnBroken = errors.New("socket connection broken") var ErrConnBroken = errors.New("socket connection broken")
var DefaultAdbReadTimeout time.Duration = 60 var DefaultAdbReadTimeout time.Duration = 300
var regexDeviceOffline = regexp.MustCompile("device .* not found") var regexDeviceOffline = regexp.MustCompile("device .* not found")

View File

@@ -0,0 +1,15 @@
package server
// 常见的错误代码和消息
const (
InternalServerErrorCode = 100001
InternalServerErrorMsg = "Internal Server Error"
InvalidParamErrorCode = 100002
InvalidParamErrorMsg = "Invalid %s Param"
)
const (
DeviceNotFoundCode = 110001
DeviceNotFoundMsg = "Device %s Not Found"
)

52
hrp/pkg/server/model.go Normal file
View File

@@ -0,0 +1,52 @@
package server
type HttpResponse struct {
Result interface{} `json:"result,omitempty"`
ErrorCode int `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}
type TapRequest struct {
X float64 `json:"x"`
Y float64 `json:"y"`
Duration float64 `json:"duration"`
}
type DragRequest struct {
FromX float64 `json:"from_x"`
FromY float64 `json:"from_y"`
ToX float64 `json:"to_x"`
ToY float64 `json:"to_y"`
Duration float64 `json:"duration"`
}
type InputRequest struct {
Text string `json:"text"`
Frequency int `json:"frequency"` // only iOS
}
type KeycodeRequest struct {
Keycode int `json:"keycode"`
}
type AppClearRequest struct {
PackageName string `json:"packageName"`
}
type AppLaunchRequest struct {
PackageName string `json:"packageName"`
}
type AppTerminalRequest struct {
PackageName string `json:"packageName"`
}
type LoginRequest struct {
PackageName string `json:"packageName"`
PhoneNumber string `json:"phoneNumber"`
Captcha string `json:"captcha"`
}
type LogoutRequest struct {
PackageName string `json:"packageName"`
}

558
hrp/pkg/server/server.go Normal file
View File

@@ -0,0 +1,558 @@
package server
import (
"encoding/base64"
"fmt"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
)
func NewServer(port int) error {
router := gin.Default()
router.GET("/ping", pingHandler)
router.GET("/api/v1/:platform/devices", listDeviceHandler)
router.POST("/api/v1/:platform/:serial/ui/tap", parseDeviceInfo(), tapHandler)
router.POST("/api/v1/:platform/:serial/ui/drag", parseDeviceInfo(), dragHandler)
router.POST("/api/v1/:platform/:serial/ui/input", parseDeviceInfo(), inputHandler)
router.POST("/api/v1/:platform/:serial/key/unlock", parseDeviceInfo(), unlockHandler)
router.POST("/api/v1/:platform/:serial/key/home", parseDeviceInfo(), homeHandler)
router.POST("/api/v1/:platform/:serial/key", parseDeviceInfo(), keycodeHandler)
router.GET("/api/v1/:platform/:serial/app/foreground", parseDeviceInfo(), foregroundAppHandler)
router.POST("/api/v1/:platform/:serial/app/clear", parseDeviceInfo(), clearAppHandler)
router.POST("/api/v1/:platform/:serial/app/launch", parseDeviceInfo(), launchAppHandler)
router.POST("/api/v1/:platform/:serial/app/terminal", parseDeviceInfo(), terminalAppHandler)
router.GET("/api/v1/:platform/:serial/screenshot", parseDeviceInfo(), screenshotHandler)
router.GET("/api/v1/:platform/:serial/stub/source", parseDeviceInfo(), sourceHandler)
router.GET("/api/v1/:platform/:serial/adb/source", parseDeviceInfo(), adbSourceHandler)
router.POST("/api/v1/:platform/:serial/stub/login", parseDeviceInfo(), loginHandler)
router.POST("/api/v1/:platform/:serial/stub/logout", parseDeviceInfo(), logoutHandler)
err := router.Run(fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
log.Err(err).Msg("failed to start http server")
return err
}
return nil
}
func pingHandler(c *gin.Context) {
c.JSON(http.StatusOK, HttpResponse{Result: true})
}
func listDeviceHandler(c *gin.Context) {
platform := c.Param("platform")
switch strings.ToLower(platform) {
case "android":
{
client, err := gadb.NewClient()
if err != nil {
log.Err(err).Msg("failed to init adb client")
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
devices, err := client.DeviceList()
if err != nil && strings.Contains(err.Error(), "no android device found") {
c.JSON(http.StatusOK, HttpResponse{Result: nil})
return
} else if err != nil {
log.Err(err).Msg("failed to list devices")
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
var deviceList []interface{}
for _, device := range devices {
brand, err := device.Brand()
if err != nil {
log.Err(err).Msg("failed to get device brand")
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
model, err := device.Model()
if err != nil {
log.Err(err).Msg("failed to get device model")
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
deviceInfo := map[string]interface{}{
"serial": device.Serial(),
"brand": brand,
"model": model,
"platform": "android",
}
deviceList = append(deviceList, deviceInfo)
}
c.JSON(http.StatusOK, HttpResponse{Result: deviceList})
return
}
default:
{
c.JSON(http.StatusBadRequest, HttpResponse{
ErrorCode: InvalidParamErrorCode,
ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "platform"),
})
c.Abort()
return
}
}
}
func tapHandler(c *gin.Context) {
var tapReq TapRequest
if err := c.ShouldBindJSON(&tapReq); err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
c.Abort()
return
}
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
if tapReq.X < 1 && tapReq.Y < 1 {
err := dExt.TapXY(tapReq.X, tapReq.Y, uixt.WithPressDuration(tapReq.Duration))
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to tap %f, %f", c.HandlerName(), tapReq.X, tapReq.Y))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
} else {
err := dExt.TapAbsXY(tapReq.X, tapReq.Y, uixt.WithPressDuration(tapReq.Duration))
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to tap %f, %f", c.HandlerName(), tapReq.X, tapReq.Y))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
}
c.JSON(http.StatusOK, HttpResponse{Result: true})
}
func dragHandler(c *gin.Context) {
var dragReq DragRequest
if err := c.ShouldBindJSON(&dragReq); err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
c.Abort()
return
}
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
if dragReq.FromX < 1 && dragReq.FromY < 1 && dragReq.ToX < 1 && dragReq.ToY < 1 {
err := dExt.SwipeRelative(dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY, uixt.WithPressDuration(dragReq.Duration))
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to drag from %f, %f to %f, %f", c.HandlerName(), dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
} else {
err := dExt.Driver.SwipeFloat(dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY, uixt.WithPressDuration(dragReq.Duration))
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to drag from %f, %f to %f, %f", c.HandlerName(), dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
}
c.JSON(http.StatusOK, HttpResponse{Result: true})
}
func inputHandler(c *gin.Context) {
var inputReq InputRequest
if err := c.ShouldBindJSON(&inputReq); err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
c.Abort()
return
}
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
err := dExt.Driver.SendKeys(inputReq.Text, uixt.WithFrequency(inputReq.Frequency))
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to input text %s", c.HandlerName(), inputReq.Text))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Result: true})
}
func unlockHandler(c *gin.Context) {
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
err := dExt.Driver.Unlock()
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to unlick screen", c.HandlerName()))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Result: true})
}
func homeHandler(c *gin.Context) {
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
err := dExt.Driver.Homescreen()
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to enter homescreen", c.HandlerName()))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Result: true})
}
func keycodeHandler(c *gin.Context) {
var keycodeReq KeycodeRequest
if err := c.ShouldBindJSON(&keycodeReq); err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
c.Abort()
return
}
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
err := dExt.Driver.PressKeyCode(uixt.KeyCode(keycodeReq.Keycode))
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to input keycode %d", c.HandlerName(), keycodeReq.Keycode))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Result: true})
}
func foregroundAppHandler(c *gin.Context) {
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
appInfo, err := dExt.Driver.GetForegroundApp()
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to unlick screen", c.HandlerName()))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Result: appInfo})
}
func clearAppHandler(c *gin.Context) {
var appClearReq AppClearRequest
if err := c.ShouldBindJSON(&appClearReq); err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
c.Abort()
return
}
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: false, ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
err := dExt.Driver.Clear(appClearReq.PackageName)
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to unlick screen", c.HandlerName()))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: false, ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Result: true})
}
func launchAppHandler(c *gin.Context) {
var appLaunchReq AppLaunchRequest
if err := c.ShouldBindJSON(&appLaunchReq); err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
c.Abort()
return
}
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
err := dExt.Driver.AppLaunch(appLaunchReq.PackageName)
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to launch app %s", c.HandlerName(), appLaunchReq.PackageName))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Result: true})
}
func terminalAppHandler(c *gin.Context) {
var appTerminalReq AppTerminalRequest
if err := c.ShouldBindJSON(&appTerminalReq); err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
c.Abort()
return
}
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
success, err := dExt.Driver.AppTerminate(appTerminalReq.PackageName)
if !success {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to launch app %s", c.HandlerName(), appTerminalReq.PackageName))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Result: true})
}
func screenshotHandler(c *gin.Context) {
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
raw, err := dExt.Driver.Screenshot()
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get screenshot", c.HandlerName()))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Result: base64.StdEncoding.EncodeToString(raw.Bytes())})
}
func sourceHandler(c *gin.Context) {
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
app, err := dExt.Driver.GetForegroundApp()
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get foreground app", c.HandlerName()))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
source, err := dExt.Driver.Source(uixt.NewSourceOption().WithProcessName(app.PackageName))
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get source %s", c.HandlerName(), app.PackageName))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Result: source})
}
func adbSourceHandler(c *gin.Context) {
deviceObj, exists := c.Get("device")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "device")})
c.Abort()
return
}
device := deviceObj.(*uixt.AndroidDevice)
driver, err := device.NewAdbDriver()
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to new adb driver", c.HandlerName()))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
source, err := driver.Source()
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get adb source", c.HandlerName()))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Result: source})
}
func loginHandler(c *gin.Context) {
var loginReq LoginRequest
if err := c.ShouldBindJSON(&loginReq); err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
c.Abort()
return
}
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
err := dExt.Driver.LoginNoneUI(loginReq.PackageName, loginReq.PhoneNumber, loginReq.Captcha)
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to login", c.HandlerName()))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Result: true})
}
func logoutHandler(c *gin.Context) {
var logoutReq LogoutRequest
if err := c.ShouldBindJSON(&logoutReq); err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "request")})
c.Abort()
return
}
driverObj, exists := c.Get("driver")
if !exists {
log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{Result: "", ErrorCode: InvalidParamErrorCode, ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "driver")})
c.Abort()
return
}
dExt := driverObj.(*uixt.DriverExt)
err := dExt.Driver.LogoutNoneUI(logoutReq.PackageName)
if err != nil {
log.Err(err).Msg(fmt.Sprintf("[%s]: failed to login", c.HandlerName()))
c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg})
c.Abort()
return
}
c.JSON(http.StatusOK, HttpResponse{Result: true})
}
func parseDeviceInfo() gin.HandlerFunc {
return func(c *gin.Context) {
platform := c.Param("platform")
switch strings.ToLower(platform) {
case "android":
serial := c.Param("serial")
if serial == "" {
log.Error().Str("platform", platform).Msg(fmt.Sprintf("[%s]: serial is empty", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{
ErrorCode: InvalidParamErrorCode,
ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "serial"),
})
c.Abort()
return
}
device, err := uixt.NewAndroidDevice(uixt.WithSerialNumber(serial), uixt.WithStub(true))
if err != nil {
log.Error().Err(err).Str("platform", platform).Str("serial", serial).Msg(fmt.Sprintf("[%s]: Device Not Found", c.HandlerName()))
c.JSON(http.StatusBadRequest, HttpResponse{
ErrorCode: DeviceNotFoundCode,
ErrorMsg: fmt.Sprintf(DeviceNotFoundMsg, serial),
})
c.Abort()
return
}
c.Set("device", device)
driver, err := device.NewDriver(uixt.WithDriverImageService(false), uixt.WithDriverResultFolder(false))
if err != nil {
log.Error().Err(err).Str("platform", platform).Str("serial", serial).Msg(fmt.Sprintf("[%s]: Failed New Driver", c.HandlerName()))
c.JSON(http.StatusInternalServerError, HttpResponse{
ErrorCode: InternalServerErrorCode,
ErrorMsg: err.Error(),
})
c.Abort()
return
}
c.Set("driver", driver)
default:
c.JSON(http.StatusBadRequest, HttpResponse{
ErrorCode: InvalidParamErrorCode,
ErrorMsg: fmt.Sprintf(InvalidParamErrorMsg, "platform"),
})
c.Abort()
return
}
c.Next()
}
}

View File

@@ -17,6 +17,7 @@ type ActionMethod string
const ( const (
ACTION_AppInstall ActionMethod = "install" ACTION_AppInstall ActionMethod = "install"
ACTION_AppUninstall ActionMethod = "uninstall" ACTION_AppUninstall ActionMethod = "uninstall"
ACTION_AppClear ActionMethod = "app_clear"
ACTION_AppStart ActionMethod = "app_start" ACTION_AppStart ActionMethod = "app_start"
ACTION_AppLaunch ActionMethod = "app_launch" // 启动 app 并堵塞等待 app 首屏加载完成 ACTION_AppLaunch ActionMethod = "app_launch" // 启动 app 并堵塞等待 app 首屏加载完成
ACTION_AppTerminate ActionMethod = "app_terminate" ACTION_AppTerminate ActionMethod = "app_terminate"
@@ -26,6 +27,10 @@ const (
ACTION_SleepRandom ActionMethod = "sleep_random" ACTION_SleepRandom ActionMethod = "sleep_random"
ACTION_StartCamera ActionMethod = "camera_start" // alias for app_launch camera ACTION_StartCamera ActionMethod = "camera_start" // alias for app_launch camera
ACTION_StopCamera ActionMethod = "camera_stop" // alias for app_terminate camera ACTION_StopCamera ActionMethod = "camera_stop" // alias for app_terminate camera
ACTION_SetClipboard ActionMethod = "set_clipboard"
ACTION_GetClipboard ActionMethod = "get_clipboard"
ACTION_SetIme ActionMethod = "set_ime"
ACTION_GetSource ActionMethod = "get_source"
// UI validation // UI validation
// selectors // selectors
@@ -60,6 +65,9 @@ const (
ACTION_VideoCrawler ActionMethod = "video_crawler" ACTION_VideoCrawler ActionMethod = "video_crawler"
ACTION_ClosePopups ActionMethod = "close_popups" ACTION_ClosePopups ActionMethod = "close_popups"
ACTION_EndToEndDelay ActionMethod = "live_e2e" ACTION_EndToEndDelay ActionMethod = "live_e2e"
ACTION_InstallApp ActionMethod = "install_app"
ACTION_UninstallApp ActionMethod = "uninstall_app"
ACTION_DownloadApp ActionMethod = "download_app"
) )
type MobileAction struct { type MobileAction struct {
@@ -554,8 +562,23 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
switch action.Method { switch action.Method {
case ACTION_AppInstall: case ACTION_AppInstall:
// TODO if appUrl, ok := action.Params.(string); ok {
return errActionNotImplemented if err = dExt.InstallByUrl(appUrl, NewInstallOptions(WithRetryTime(action.MaxRetryTimes))); err != nil {
return errors.Wrap(err, "failed to install app")
}
}
case ACTION_AppUninstall:
if packageName, ok := action.Params.(string); ok {
if err = dExt.Uninstall(packageName, action.GetOptions()...); err != nil {
return errors.Wrap(err, "failed to uninstall app")
}
}
case ACTION_AppClear:
if packageName, ok := action.Params.(string); ok {
if err = dExt.Driver.Clear(packageName); err != nil {
return errors.Wrap(err, "failed to clear app")
}
}
case ACTION_AppLaunch: case ACTION_AppLaunch:
if bundleId, ok := action.Params.(string); ok { if bundleId, ok := action.Params.(string); ok {
return dExt.Driver.AppLaunch(bundleId) return dExt.Driver.AppLaunch(bundleId)
@@ -594,8 +617,34 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
return nil return nil
} }
return fmt.Errorf("app_terminate params should be bundleId(string), got %v", action.Params) return fmt.Errorf("app_terminate params should be bundleId(string), got %v", action.Params)
case ACTION_SetClipboard:
if text, ok := action.Params.(string); ok {
err := dExt.Driver.SetPasteboard(PasteboardTypePlaintext, text)
if err != nil {
return errors.Wrap(err, "failed to set clipboard")
}
return nil
}
return fmt.Errorf("set_clioboard params should be text(string), got %v", action.Params)
case ACTION_Home: case ACTION_Home:
return dExt.Driver.Homescreen() return dExt.Driver.Homescreen()
case ACTION_SetIme:
if ime, ok := action.Params.(string); ok {
err = dExt.Driver.SetIme(ime)
if err != nil {
return errors.Wrap(err, "failed to set ime")
}
return nil
}
case ACTION_GetSource:
if packageName, ok := action.Params.(string); ok {
source := NewSourceOption().WithProcessName(packageName)
_, err = dExt.Driver.Source(source)
if err != nil {
return errors.Wrap(err, "failed to set ime")
}
return nil
}
case ACTION_TapXY: case ACTION_TapXY:
if location, ok := action.Params.([]interface{}); ok { if location, ok := action.Params.([]interface{}); ok {
// relative x,y of window size: [0.5, 0.5] // relative x,y of window size: [0.5, 0.5]

View File

@@ -3,6 +3,7 @@ package uixt
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/json"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io/fs" "io/fs"
@@ -218,10 +219,18 @@ func (ad *adbDriver) Orientation() (orientation Orientation, err error) {
} }
func (ad *adbDriver) Homescreen() (err error) { func (ad *adbDriver) Homescreen() (err error) {
return ad.PressKeyCode(KCHome, KMEmpty) return ad.PressKeyCodes(KCHome, KMEmpty)
} }
func (ad *adbDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta) (err error) { func (ad *adbDriver) Unlock() (err error) {
return ad.PressKeyCodes(KCMenu, KMEmpty)
}
func (ad *adbDriver) PressKeyCode(keyCode KeyCode) (err error) {
return ad.PressKeyCodes(keyCode, KMEmpty)
}
func (ad *adbDriver) PressKeyCodes(keyCode KeyCode, metaState KeyMeta) (err error) {
// adb shell input keyevent <keyCode> // adb shell input keyevent <keyCode>
_, err = ad.adbClient.RunShellCommand( _, err = ad.adbClient.RunShellCommand(
"input", "keyevent", fmt.Sprintf("%d", keyCode)) "input", "keyevent", fmt.Sprintf("%d", keyCode))
@@ -421,6 +430,14 @@ func (ad *adbDriver) IsUnicodeIMEInstalled() bool {
return strings.Contains(output, UnicodeImePackageName) return strings.Contains(output, UnicodeImePackageName)
} }
func (ad *adbDriver) ListIme() []string {
output, err := ad.adbClient.RunShellCommand("ime", "list", "-s")
if err != nil {
return []string{}
}
return strings.Split(output, "\n")
}
func (ad *adbDriver) SendKeysByAdbKeyBoard(text string) (err error) { func (ad *adbDriver) SendKeysByAdbKeyBoard(text string) (err error) {
defer func() { defer func() {
// Reset to default, don't care which keyboard was chosen before switch: // Reset to default, don't care which keyboard was chosen before switch:
@@ -458,6 +475,15 @@ func (ad *adbDriver) Input(text string, options ...ActionOption) (err error) {
return ad.SendKeys(text, options...) return ad.SendKeys(text, options...)
} }
func (ad *adbDriver) Clear(packageName string) error {
if _, err := ad.adbClient.RunShellCommand("pm", "clear", packageName); err != nil {
log.Error().Str("packageName", packageName).Err(err).Msg("failed to clear package cache")
return err
}
return nil
}
func (ad *adbDriver) PressButton(devBtn DeviceButton) (err error) { func (ad *adbDriver) PressButton(devBtn DeviceButton) (err error) {
err = errDriverNotImplemented err = errDriverNotImplemented
return return
@@ -499,6 +525,14 @@ func (ad *adbDriver) Source(srcOpt ...SourceOption) (source string, err error) {
return return
} }
func (ad *adbDriver) LoginNoneUI(packageName, phoneNumber string, captcha string) error {
return errDriverNotImplemented
}
func (ad *adbDriver) LogoutNoneUI(packageName string) error {
return errDriverNotImplemented
}
func (ad *adbDriver) sourceTree(srcOpt ...SourceOption) (sourceTree *Hierarchy, err error) { func (ad *adbDriver) sourceTree(srcOpt ...SourceOption) (sourceTree *Hierarchy, err error) {
source, err := ad.Source() source, err := ad.Source()
if err != nil { if err != nil {
@@ -680,46 +714,57 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) {
return pointRes, nil return pointRes, nil
} }
func (ad *adbDriver) GetDriverResults() []*DriverResult {
return nil
}
func (ad *adbDriver) GetForegroundApp() (app AppInfo, err error) { func (ad *adbDriver) GetForegroundApp() (app AppInfo, err error) {
// adb shell dumpsys activity activities packageInfo, err := ad.adbClient.RunShellCommand("CLASSPATH=/data/local/tmp/evalite", "app_process", "/", "com.bytedance.iesqa.eval_process.PackageService")
output, err := ad.adbClient.RunShellCommand("dumpsys", "activity", "activities")
if err != nil { if err != nil {
log.Error().Err(err).Msg("failed to dumpsys activities") return app, err
return AppInfo{}, errors.Wrap(err, "dumpsys activities failed") }
log.Info().Msg(packageInfo)
err = json.Unmarshal([]byte(strings.TrimSpace(packageInfo)), &app)
return
}
func (ad *adbDriver) SetIme(imeRegx string) error {
imeList := ad.ListIme()
ime := ""
for _, imeName := range imeList {
if regexp.MustCompile(imeRegx).MatchString(imeName) {
ime = imeName
break
}
}
if ime == "" {
return fmt.Errorf("failed to set ime by %s, ime list: %v", imeRegx, imeList)
}
brand, _ := ad.adbClient.Brand()
packageName := strings.Split(ime, "/")[0]
res, err := ad.adbClient.RunShellCommand("ime", "set", ime)
log.Info().Str("funcName", "SetIme").Interface("ime", ime).
Interface("output", res).Msg("set ime")
if err != nil {
return err
} }
lines := strings.Split(string(output), "\n") if strings.ToLower(brand) == "oppo" {
for _, line := range lines { time.Sleep(1 * time.Second)
trimmedLine := strings.TrimSpace(line) pid, _ := ad.adbClient.RunShellCommand("pidof", packageName)
// grep mResumedActivity|ResumedActivity if strings.TrimSpace(pid) == "" {
if strings.HasPrefix(trimmedLine, "mResumedActivity:") || strings.HasPrefix(trimmedLine, "ResumedActivity:") { appInfo, err := ad.GetForegroundApp()
// mResumedActivity: ActivityRecord{9656d74 u0 com.android.settings/.Settings t407} _ = ad.AppLaunch(packageName)
// ResumedActivity: ActivityRecord{8265c25 u0 com.android.settings/.Settings t73} if err == nil && packageName != UnicodeImePackageName {
strs := strings.Split(trimmedLine, " ") time.Sleep(10 * time.Second)
for _, str := range strs { nextAppInfo, err := ad.GetForegroundApp()
if strings.Contains(str, "/") { log.Info().Str("beforeFocusedPackage", appInfo.PackageName).Str("afterFocusedPackage", nextAppInfo.PackageName).Msg("")
// com.android.settings/.Settings if err == nil && nextAppInfo.PackageName != appInfo.PackageName {
s := strings.Split(str, "/") _ = ad.PressKeyCodes(KCBack, KMEmpty)
app := AppInfo{
AppBaseInfo: AppBaseInfo{
PackageName: s[0],
Activity: s[1],
},
}
return app, nil
} }
} }
} }
} }
return AppInfo{}, errors.Wrap(code.MobileUIAssertForegroundAppError, "get foreground app failed")
}
func (ad *adbDriver) SetIme(ime string) error {
_, err := ad.adbClient.RunShellCommand("ime", "set", ime)
if err != nil {
return err
}
// even if the shell command has returned, // even if the shell command has returned,
// as there might be a situation where the input method has not been completely switched yet // as there might be a situation where the input method has not been completely switched yet
// Listen to the following message. // Listen to the following message.

View File

@@ -4,26 +4,43 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"crypto/md5"
"embed"
"encoding/base64"
"encoding/hex"
"fmt" "fmt"
"io"
"os"
"os/exec" "os/exec"
"regexp"
"strconv"
"strings" "strings"
"time"
"github.com/httprunner/funplugin/myexec" "github.com/httprunner/funplugin/myexec"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin"
"github.com/httprunner/httprunner/v4/hrp/internal/code" "github.com/httprunner/httprunner/v4/hrp/internal/code"
"github.com/httprunner/httprunner/v4/hrp/internal/env"
"github.com/httprunner/httprunner/v4/hrp/internal/json" "github.com/httprunner/httprunner/v4/hrp/internal/json"
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb" "github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
) )
var ( var (
AdbServerHost = "localhost" DouyinServerPort = 32316
AdbServerPort = gadb.AdbServerPort // 5037 AdbServerHost = "localhost"
UIA2ServerHost = "localhost" AdbServerPort = gadb.AdbServerPort // 5037
UIA2ServerPort = 6790 UIA2ServerHost = "localhost"
UIA2ServerPort = 6790
EvalInstallerPackageName = "sogou.mobile.explorer"
InstallViaInstallerCommand = "am start -S -n sogou.mobile.explorer/.PackageInstallerActivity -d"
) )
//go:embed evalite
var evalite embed.FS
const forwardToPrefix = "forward-to-" const forwardToPrefix = "forward-to-"
type AndroidDeviceOption func(*AndroidDevice) type AndroidDeviceOption func(*AndroidDevice)
@@ -40,6 +57,12 @@ func WithUIA2(uia2On bool) AndroidDeviceOption {
} }
} }
func WithStub(stubOn bool) AndroidDeviceOption {
return func(device *AndroidDevice) {
device.STUB = stubOn
}
}
func WithUIA2IP(ip string) AndroidDeviceOption { func WithUIA2IP(ip string) AndroidDeviceOption {
return func(device *AndroidDevice) { return func(device *AndroidDevice) {
device.UIA2IP = ip device.UIA2IP = ip
@@ -108,7 +131,14 @@ func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, er
device.d = dev device.d = dev
device.logcat = NewAdbLogcat(device.SerialNumber) device.logcat = NewAdbLogcat(device.SerialNumber)
evalToolRaw, err := evalite.ReadFile("evalite")
if err != nil {
return nil, errors.Wrap(code.LoadFileError, err.Error())
}
err = dev.Push(bytes.NewReader(evalToolRaw), "/data/local/tmp/evalite", time.Now())
if err != nil {
return nil, errors.Wrap(code.AndroidShellExecError, err.Error())
}
log.Info().Str("serial", device.SerialNumber).Msg("init android device") log.Info().Str("serial", device.SerialNumber).Msg("init android device")
return device, nil return device, nil
} }
@@ -151,9 +181,18 @@ type AndroidDevice struct {
logcat *AdbLogcat logcat *AdbLogcat
SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"` SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"`
UIA2 bool `json:"uia2,omitempty" yaml:"uia2,omitempty"` // use uiautomator2 UIA2 bool `json:"uia2,omitempty" yaml:"uia2,omitempty"` // use uiautomator2
STUB bool `json:"stub,omitempty" yaml:"stub,omitempty"` // use uiautomator2
UIA2IP string `json:"uia2_ip,omitempty" yaml:"uia2_ip,omitempty"` // uiautomator2 server ip UIA2IP string `json:"uia2_ip,omitempty" yaml:"uia2_ip,omitempty"` // uiautomator2 server ip
UIA2Port int `json:"uia2_port,omitempty" yaml:"uia2_port,omitempty"` // uiautomator2 server port UIA2Port int `json:"uia2_port,omitempty" yaml:"uia2_port,omitempty"` // uiautomator2 server port
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"` LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
}
func (dev *AndroidDevice) Init() error {
myexec.RunCommand("adb", "-s", dev.SerialNumber, "shell",
"ime", "enable", "io.appium.settings/.UnicodeIME")
myexec.RunCommand("adb", "-s", dev.SerialNumber, "shell", "rm", "-r", env.DeviceActionLogFilePath)
return nil
} }
func (dev *AndroidDevice) UUID() string { func (dev *AndroidDevice) UUID() string {
@@ -165,7 +204,7 @@ func (dev *AndroidDevice) LogEnabled() bool {
} }
func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverExt, err error) { func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverExt, err error) {
driverOptions := &DriverOptions{} driverOptions := NewDriverOptions()
for _, option := range options { for _, option := range options {
option(driverOptions) option(driverOptions)
} }
@@ -173,6 +212,8 @@ func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverE
var driver WebDriver var driver WebDriver
if dev.UIA2 || dev.LogOn { if dev.UIA2 || dev.LogOn {
driver, err = dev.NewUSBDriver(driverOptions.capabilities) driver, err = dev.NewUSBDriver(driverOptions.capabilities)
} else if dev.STUB {
driver, err = dev.NewStubDriver(driverOptions.capabilities)
} else { } else {
driver, err = dev.NewAdbDriver() driver, err = dev.NewAdbDriver()
} }
@@ -180,7 +221,7 @@ func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverE
return nil, errors.Wrap(err, "failed to init UIA driver") return nil, errors.Wrap(err, "failed to init UIA driver")
} }
driverExt, err = newDriverExt(dev, driver, driverOptions.plugin) driverExt, err = newDriverExt(dev, driver, options...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -217,6 +258,36 @@ func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver WebDri
return uiaDriver, nil return uiaDriver, nil
} }
func (dev *AndroidDevice) NewStubDriver(capabilities Capabilities) (driver *stubAndroidDriver, err error) {
socketLocalPort, err := dev.d.Forward(StubSocketName)
if err != nil {
return nil, errors.Wrap(code.AndroidDeviceConnectionError,
fmt.Sprintf("forward port %d->%s failed: %v",
socketLocalPort, StubSocketName, err))
}
serverLocalPort, err := dev.d.Forward(DouyinServerPort)
if err != nil {
return nil, errors.Wrap(code.AndroidDeviceConnectionError,
fmt.Sprintf("forward port %d->%d failed: %v",
serverLocalPort, DouyinServerPort, err))
}
rawURL := fmt.Sprintf("http://%s%d:%d",
forwardToPrefix, serverLocalPort, DouyinServerPort)
stubDriver, err := newStubAndroidDriver(fmt.Sprintf("127.0.0.1:%d", socketLocalPort), rawURL)
if err != nil {
_ = dev.d.ForwardKill(socketLocalPort)
_ = dev.d.ForwardKill(serverLocalPort)
return nil, errors.Wrap(code.AndroidDeviceConnectionError, err.Error())
}
stubDriver.adbClient = dev.d
stubDriver.logcat = dev.logcat
return stubDriver, nil
}
// NewHTTPDriver creates new remote HTTP client, this will also start a new session. // NewHTTPDriver creates new remote HTTP client, this will also start a new session.
func (dev *AndroidDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver, err error) { func (dev *AndroidDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver, err error) {
rawURL := fmt.Sprintf("http://%s:%d/wd/hub", dev.UIA2IP, dev.UIA2Port) rawURL := fmt.Sprintf("http://%s:%d/wd/hub", dev.UIA2IP, dev.UIA2Port)
@@ -257,6 +328,116 @@ func (dev *AndroidDevice) StopPcap() string {
return "" return ""
} }
func (dev *AndroidDevice) Uninstall(packageName string) error {
return myexec.RunCommand("adb", "-s", dev.SerialNumber, "uninstall", packageName)
}
func (dev *AndroidDevice) Install(appPath string, opts *InstallOptions) error {
app, err := os.Open(appPath)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("install %s open file failed", appPath))
}
defer app.Close()
brand, err := dev.d.Brand()
if err != nil {
return err
}
args := []string{}
if opts.Reinstall {
args = append(args, "-r")
}
if opts.GrantPermission {
args = append(args, "-g")
}
if opts.Downgrade {
args = append(args, "-d")
}
switch strings.ToLower(brand) {
case "vivo":
return dev.installVivoSilent(app, args...)
case "oppo", "realme", "oneplus":
if dev.d.IsPackagesInstalled(EvalInstallerPackageName) {
return dev.installViaInstaller(app, args...)
}
log.Warn().Msg("oppo not install eval installer")
return dev.installCommon(app, args...)
default:
return dev.installCommon(app, args...)
}
}
func (dev *AndroidDevice) installVivoSilent(app io.ReadSeeker, args ...string) error {
currentTime := builtin.GetCurrentDay()
md5HashInBytes := md5.Sum([]byte(currentTime))
verifyCode := hex.EncodeToString(md5HashInBytes[:])
verifyCode = base64.StdEncoding.EncodeToString([]byte(verifyCode))
verifyCode = verifyCode[:8]
verifyCode = "-V" + verifyCode
args = append([]string{verifyCode}, args...)
_, err := dev.d.InstallAPK(app, args...)
return err
}
func (dev *AndroidDevice) installViaInstaller(app io.ReadSeeker, args ...string) error {
appRemotePath := "/data/local/tmp/" + strconv.FormatInt(time.Now().UnixMilli(), 10) + ".apk"
err := dev.d.Push(app, appRemotePath, time.Now())
if err != nil {
return err
}
done := make(chan error)
defer func() {
close(done)
}()
logcat := NewAdbLogcatWithCallback(dev.d.Serial(), func(line string) {
re := regexp.MustCompile(`\{.*?}`)
match := re.FindString(line)
if match == "" {
return
}
var result InstallResult
err := json.Unmarshal([]byte(match), &result)
if err != nil {
log.Warn().Msg("parse Install msg line error: " + match)
return
}
if result.Result == 0 {
// 安装成功
done <- nil
} else {
done <- errors.New(match)
}
})
err = logcat.CatchLogcat("PackageInstallerCallback")
if err != nil {
return err
}
defer func() {
_ = logcat.Stop()
}()
// 需要监听是否完成安装
command := strings.Split(InstallViaInstallerCommand, " ")
args = append(command, appRemotePath)
_, err = dev.d.RunShellCommand("am", args[1:]...)
if err != nil {
return err
}
// 等待安装完成或超时
timeout := 3 * time.Minute
select {
case err := <-done:
return err
case <-time.After(timeout):
return fmt.Errorf("installation timed out after %v", timeout)
}
}
func (dev *AndroidDevice) installCommon(app io.ReadSeeker, args ...string) error {
_, err := dev.d.InstallAPK(app, args...)
return err
}
type LineCallback func(string) type LineCallback func(string)
type AdbLogcat struct { type AdbLogcat struct {

View File

@@ -0,0 +1,273 @@
package uixt
import (
"errors"
"fmt"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/internal/json"
)
type stubAndroidDriver struct {
socket net.Conn
seq int
timeout time.Duration
adbDriver
}
const StubSocketName = "com.bytest.device"
// newStubAndroidDriver
// 创建stub Driver address为forward后的端口格式127.0.0.1:${port}
func newStubAndroidDriver(address string, urlPrefix string, readTimeout ...time.Duration) (*stubAndroidDriver, error) {
timeout := 10 * time.Second
if len(readTimeout) > 0 {
timeout = readTimeout[0]
}
conn, err := net.Dial("tcp", address)
if err != nil {
log.Err(err).Msg(fmt.Sprintf("failed to connect %s", address))
return nil, err
}
driver := &stubAndroidDriver{
socket: conn,
timeout: timeout,
}
if driver.urlPrefix, err = url.Parse(urlPrefix); err != nil {
return nil, err
}
return driver, nil
}
func (sad *stubAndroidDriver) httpGET(pathElem ...string) (rawResp rawResponse, err error) {
var localPort int
{
tmpURL, _ := url.Parse(sad.urlPrefix.String())
hostname := tmpURL.Hostname()
if strings.HasPrefix(hostname, forwardToPrefix) {
localPort, _ = strconv.Atoi(strings.TrimPrefix(hostname, forwardToPrefix))
}
}
conn, err := net.Dial("tcp", fmt.Sprintf(":%d", localPort))
if err != nil {
return nil, fmt.Errorf("adb forward: %w", err)
}
sad.client = convertToHTTPClient(conn)
return sad.httpRequest(http.MethodGet, sad.concatURL(nil, pathElem...), nil)
}
func (sad *stubAndroidDriver) httpPOST(data interface{}, pathElem ...string) (rawResp rawResponse, err error) {
var localPort int
{
tmpURL, _ := url.Parse(sad.urlPrefix.String())
hostname := tmpURL.Hostname()
if strings.HasPrefix(hostname, forwardToPrefix) {
localPort, _ = strconv.Atoi(strings.TrimPrefix(hostname, forwardToPrefix))
}
}
conn, err := net.Dial("tcp", fmt.Sprintf(":%d", localPort))
if err != nil {
return nil, fmt.Errorf("adb forward: %w", err)
}
sad.client = convertToHTTPClient(conn)
var bsJSON []byte = nil
if data != nil {
if bsJSON, err = json.Marshal(data); err != nil {
return nil, err
}
}
return sad.httpRequest(http.MethodPost, sad.concatURL(nil, pathElem...), bsJSON)
}
func (sad *stubAndroidDriver) NewSession(capabilities Capabilities) (SessionInfo, error) {
return SessionInfo{}, errDriverNotImplemented
}
func (sad *stubAndroidDriver) sendCommand(packageName string, cmdType string, params map[string]interface{}, readTimeout ...time.Duration) (interface{}, error) {
sad.seq++
packet := map[string]interface{}{
"Seq": sad.seq,
"Cmd": cmdType,
"v": "",
}
for key, value := range params {
if key == "Cmd" || key == "Seq" {
return "", errors.New("params cannot be Cmd or Seq")
}
packet[key] = value
}
data, err := json.Marshal(packet)
if err != nil {
return nil, err
}
res, err := sad.adbClient.RunStubCommand(append(data, '\n'), packageName)
if err != nil {
return nil, err
}
var resultMap map[string]interface{}
if err := json.Unmarshal([]byte(res), &resultMap); err != nil {
return nil, err
}
if resultMap["Error"] != nil {
return nil, fmt.Errorf("failed to call stub command: %s", resultMap["Error"].(string))
}
return resultMap["Result"], nil
}
func (sad *stubAndroidDriver) DeleteSession() error {
return sad.close()
}
func (sad *stubAndroidDriver) close() error {
if sad.socket != nil {
return sad.socket.Close()
}
return nil
}
func (sad *stubAndroidDriver) Status() (DeviceStatus, error) {
app, err := sad.GetForegroundApp()
if err != nil {
return DeviceStatus{}, err
}
res, err := sad.sendCommand(app.PackageName, "Hello", nil)
if err != nil {
return DeviceStatus{}, err
}
log.Info().Msg(fmt.Sprintf("ping stub result :%v", res))
return DeviceStatus{}, nil
}
func (sad *stubAndroidDriver) Source(srcOpt ...SourceOption) (source string, err error) {
app, err := sad.GetForegroundApp()
if err != nil {
return "", err
}
params := map[string]interface{}{
"ClassName": "com.bytedance.byteinsight.MockOperator",
"Method": "getLayout",
"RetType": "",
"Args": []string{},
}
res, err := sad.sendCommand(app.PackageName, "CallStaticMethod", params)
if err != nil {
return "", err
}
return res.(string), nil
}
func (sad *stubAndroidDriver) LoginNoneUIBak(packageName, phoneNumber, captcha string) error {
_, err := sad.adbClient.RunShellCommand("am", "broadcast", "-a", fmt.Sprintf("%s.util.crony.action_login", packageName), "-e", "phone", phoneNumber, "-e", "code", captcha)
time.Sleep(10 * time.Second)
login, err := sad.isLogin(packageName)
if err != nil || !login {
log.Err(err).Msg("failed to login")
return fmt.Errorf("failed to login")
}
return err
}
func (sad *stubAndroidDriver) LoginNoneUI(packageName, phoneNumber, captcha string) error {
params := map[string]interface{}{
"phone": phoneNumber,
"code": captcha,
}
resp, err := sad.httpPOST(params, "/host", "/login", "account")
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)
login, err := sad.isLogin(packageName)
if err != nil {
return err
}
if !login {
return fmt.Errorf("failed to login")
}
return nil
}
func (sad *stubAndroidDriver) LogoutNoneUI(packageName string) error {
resp, err := sad.httpGET("/host", "/logout")
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
}
fmt.Printf("%v", resp)
if err != nil {
return err
}
return nil
}
func (sad *stubAndroidDriver) LoginNoneUIDynamic(packageName, phoneNumber string, captcha string) error {
params := map[string]interface{}{
"ClassName": "qe.python.test.LoginUtil",
"Method": "loginSync",
"RetType": "",
"Args": []string{phoneNumber, captcha},
}
res, err := sad.sendCommand(packageName, "CallStaticMethod", params)
if err != nil {
return err
}
log.Info().Msg(res.(string))
return nil
}
func (sad *stubAndroidDriver) isLogin(packageName string) (login bool, err error) {
resp, err := sad.httpGET("/host", "/login", "/check")
if err != nil {
return false, err
}
res, err := resp.valueConvertToJsonObject()
if err != nil {
return false, 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 false, err
}
fmt.Printf("%v", resp)
if err != nil {
return false, err
}
return true, nil
}

View File

@@ -0,0 +1,56 @@
package uixt
import "testing"
var androidStubDriver *stubAndroidDriver
func setupStubDriver(t *testing.T) {
device, err := NewAndroidDevice()
checkErr(t, err)
device.STUB = true
androidStubDriver, err = device.NewStubDriver(Capabilities{})
checkErr(t, err)
}
func TestHello(t *testing.T) {
setupStubDriver(t)
status, err := androidStubDriver.Status()
if err != nil {
t.Fatal(err)
}
t.Log(status)
}
func TestSource(t *testing.T) {
setupStubDriver(t)
source, err := androidStubDriver.Source()
if err != nil {
t.Fatal(err)
}
t.Log(source)
}
func TestIsLogin(t *testing.T) {
setupStubDriver(t)
res, err := androidStubDriver.isLogin("com.ss.android.ugc.aweme")
if err != nil {
t.Fatal(err)
}
t.Log(res)
}
func TestLogin(t *testing.T) {
setupStubDriver(t)
err := androidStubDriver.LoginNoneUI("com.ss.android.ugc.aweme", "12342316231", "8517")
if err != nil {
t.Fatal(err)
}
}
func TestLogout(t *testing.T) {
setupStubDriver(t)
err := androidStubDriver.LogoutNoneUI("com.ss.android.ugc.aweme")
if err != nil {
t.Fatal(err)
}
}

View File

@@ -3,8 +3,6 @@
package uixt package uixt
import ( import (
"encoding/json"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"strings" "strings"
@@ -22,8 +20,8 @@ var (
func setupAndroid(t *testing.T) { func setupAndroid(t *testing.T) {
device, err := NewAndroidDevice() device, err := NewAndroidDevice()
checkErr(t, err) checkErr(t, err)
device.UIA2 = false device.UIA2 = true
device.LogOn = true device.LogOn = false
driverExt, err = device.NewDriver() driverExt, err = device.NewDriver()
checkErr(t, err) checkErr(t, err)
} }
@@ -125,12 +123,9 @@ func TestDriver_DeviceSize(t *testing.T) {
} }
func TestDriver_Source(t *testing.T) { func TestDriver_Source(t *testing.T) {
driver, err := NewUIADriver(nil, uiaServerURL) setupAndroid(t)
if err != nil {
t.Fatal(err)
}
source, err := driver.Source() source, err := driverExt.Driver.Source()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -200,33 +195,24 @@ func TestDriver_DeviceInfo(t *testing.T) {
func TestDriver_Tap(t *testing.T) { func TestDriver_Tap(t *testing.T) {
setupAndroid(t) setupAndroid(t)
driverExt.Driver.StartCaptureLog("") driverExt.Driver.StartCaptureLog("")
err := driverExt.Driver.Tap(150, 340, WithIdentifier("test")) err := driverExt.TapXY(0.5, 0.5, WithIdentifier("test"), WithPressDuration(4))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
time.Sleep(time.Second) //time.Sleep(time.Second)
//
err = driverExt.Driver.TapFloat(60.5, 125.5, WithIdentifier("test")) //err = driverExt.Driver.TapFloat(60.5, 125.5, WithIdentifier("test"))
if err != nil { //if err != nil {
t.Fatal(err) // t.Fatal(err)
} //}
time.Sleep(time.Second) //time.Sleep(time.Second)
result, _ := driverExt.Driver.StopCaptureLog() //result, _ := driverExt.Driver.StopCaptureLog()
t.Log(result) //t.Log(result)
} }
func TestDriver_Swipe(t *testing.T) { func TestDriver_Swipe(t *testing.T) {
driver, err := NewUIADriver(nil, uiaServerURL) setupAndroid(t)
if err != nil { err := driverExt.Driver.Swipe(400, 1000, 400, 500, WithPressDuration(0.5))
t.Fatal(err)
}
err = driver.Swipe(400, 1000, 400, 500, WithPressDuration(2000))
if err != nil {
t.Fatal(err)
}
err = driver.SwipeFloat(400, 555.5, 400, 1255.5)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -262,7 +248,7 @@ func TestDriver_Drag(t *testing.T) {
func TestDriver_SendKeys(t *testing.T) { func TestDriver_SendKeys(t *testing.T) {
setupAndroid(t) setupAndroid(t)
err := driverExt.Driver.SendKeys("Android\"输入速度测试", WithIdentifier("test")) err := driverExt.Driver.SendKeys("辽宁省沈阳市新民市民族街36-4", WithIdentifier("test"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -307,6 +293,14 @@ func TestDriver_SetRotation(t *testing.T) {
} }
} }
func TestDriver_GetOrientation(t *testing.T) {
setupAndroid(t)
_, _ = driverExt.Driver.AppTerminate("com.quark.browser")
_ = driverExt.Driver.AppLaunch("com.quark.browser")
time.Sleep(2 * time.Second)
_ = driverExt.Driver.Homescreen()
}
func TestUiSelectorHelper_NewUiSelectorHelper(t *testing.T) { func TestUiSelectorHelper_NewUiSelectorHelper(t *testing.T) {
uiSelector := NewUiSelectorHelper().Text("a").String() uiSelector := NewUiSelectorHelper().Text("a").String()
if uiSelector != `new UiSelector().text("a");` { if uiSelector != `new UiSelector().text("a");` {
@@ -449,8 +443,6 @@ func TestConvertPoints(t *testing.T) {
if len(eps) != 3 { if len(eps) != 3 {
t.Fatal() t.Fatal()
} }
jsons, _ := json.Marshal(eps)
println(fmt.Sprintf("%v", string(jsons)))
} }
func TestDriver_ShellInputUnicode(t *testing.T) { func TestDriver_ShellInputUnicode(t *testing.T) {

View File

@@ -29,6 +29,7 @@ func NewUIADriver(capabilities Capabilities, urlPrefix string) (driver *uiaDrive
log.Info().Msg("init uiautomator2 driver") log.Info().Msg("init uiautomator2 driver")
if capabilities == nil { if capabilities == nil {
capabilities = NewCapabilities() capabilities = NewCapabilities()
capabilities.WithWaitForIdleTimeout(0)
} }
driver = new(uiaDriver) driver = new(uiaDriver)
if driver.urlPrefix, err = url.Parse(urlPrefix); err != nil { if driver.urlPrefix, err = url.Parse(urlPrefix); err != nil {
@@ -141,7 +142,12 @@ func (ud *uiaDriver) httpDELETE(pathElem ...string) (rawResp rawResponse, err er
func (ud *uiaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionInfo, err error) { func (ud *uiaDriver) NewSession(capabilities Capabilities) (sessionInfo SessionInfo, err error) {
// register(postHandler, new NewSession("/wd/hub/session")) // register(postHandler, new NewSession("/wd/hub/session"))
var rawResp rawResponse var rawResp rawResponse
data := map[string]interface{}{"capabilities": capabilities} data := make(map[string]interface{})
if len(capabilities) == 0 {
data["capabilities"] = make(map[string]interface{})
} else {
data["capabilities"] = map[string]interface{}{"alwaysMatch": capabilities}
}
if rawResp, err = ud.Driver.httpPOST(data, "/session"); err != nil { if rawResp, err = ud.Driver.httpPOST(data, "/session"); err != nil {
return SessionInfo{SessionId: ""}, err return SessionInfo{SessionId: ""}, err
} }
@@ -244,10 +250,14 @@ func (ud *uiaDriver) PressBack(options ...ActionOption) (err error) {
} }
func (ud *uiaDriver) Homescreen() (err error) { func (ud *uiaDriver) Homescreen() (err error) {
return ud.PressKeyCode(KCHome, KMEmpty) return ud.PressKeyCodes(KCHome, KMEmpty)
} }
func (ud *uiaDriver) PressKeyCode(keyCode KeyCode, metaState KeyMeta, flags ...KeyFlag) (err error) { func (ud *uiaDriver) PressKeyCode(keyCode KeyCode) (err error) {
return ud.PressKeyCodes(keyCode, KMEmpty)
}
func (ud *uiaDriver) PressKeyCodes(keyCode KeyCode, metaState KeyMeta, flags ...KeyFlag) (err error) {
// register(postHandler, new PressKeyCodeAsync("/wd/hub/session/:sessionId/appium/device/press_keycode")) // register(postHandler, new PressKeyCodeAsync("/wd/hub/session/:sessionId/appium/device/press_keycode"))
data := map[string]interface{}{ data := map[string]interface{}{
"keycode": keyCode, "keycode": keyCode,
@@ -293,7 +303,7 @@ func (ud *uiaDriver) TapFloat(x, y float64, options ...ActionOption) (err error)
duration := 100.0 duration := 100.0
if actionOptions.PressDuration > 0 { if actionOptions.PressDuration > 0 {
duration = actionOptions.PressDuration duration = actionOptions.PressDuration * 1000
} }
data := map[string]interface{}{ data := map[string]interface{}{
"actions": []interface{}{ "actions": []interface{}{
@@ -399,7 +409,7 @@ func (ud *uiaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...Actio
duration := 200.0 duration := 200.0
if actionOptions.PressDuration > 0 { if actionOptions.PressDuration > 0 {
duration = actionOptions.PressDuration duration = actionOptions.PressDuration * 1000
} }
data := map[string]interface{}{ data := map[string]interface{}{
"actions": []interface{}{ "actions": []interface{}{
@@ -618,3 +628,10 @@ func (ud *uiaDriver) TapByTexts(actions ...TapTextAction) error {
} }
return nil return nil
} }
func (ud *uiaDriver) GetDriverResults() []*DriverResult {
defer func() {
ud.Driver.driverResults = nil
}()
return ud.Driver.driverResults
}

View File

@@ -17,10 +17,18 @@ import (
) )
type Driver struct { type Driver struct {
urlPrefix *url.URL urlPrefix *url.URL
sessionId string sessionId string
client *http.Client client *http.Client
scale float64 scale float64
driverResults []*DriverResult
}
type DriverResult struct {
RequestUrl string `json:"request_driver_url"`
RequestBody string `json:"request_driver_body,omitempty"`
RequestDuration time.Duration `json:"request_driver_duration"`
RequestTime time.Time `json:"request_driver_time"`
} }
func (wd *Driver) concatURL(u *url.URL, elem ...string) string { func (wd *Driver) concatURL(u *url.URL, elem ...string) string {
@@ -73,7 +81,15 @@ func (wd *Driver) httpRequest(method string, rawURL string, rawBody []byte) (raw
}() }()
rawResp, err = io.ReadAll(resp.Body) rawResp, err = io.ReadAll(resp.Body)
logger := log.Debug().Int("statusCode", resp.StatusCode).Str("duration", time.Since(start).String()) duration := time.Since(start)
driverResult := &DriverResult{
RequestUrl: rawURL,
RequestBody: string(rawBody),
RequestDuration: duration,
RequestTime: time.Now(),
}
wd.driverResults = append(wd.driverResults, driverResult)
logger := log.Debug().Int("statusCode", resp.StatusCode).Str("duration", duration.String())
if !strings.HasSuffix(rawURL, "screenshot") { if !strings.HasSuffix(rawURL, "screenshot") {
// avoid printing screenshot data // avoid printing screenshot data
logger.Str("response", string(rawResp)) logger.Str("response", string(rawResp))

BIN
hrp/pkg/uixt/evalite Normal file

Binary file not shown.

View File

@@ -155,11 +155,16 @@ type DriverExt struct {
plugin funplugin.IPlugin plugin funplugin.IPlugin
} }
func newDriverExt(device Device, driver WebDriver, plugin funplugin.IPlugin) (dExt *DriverExt, err error) { func newDriverExt(device Device, driver WebDriver, options ...DriverOption) (dExt *DriverExt, err error) {
driverOptions := NewDriverOptions()
for _, option := range options {
option(driverOptions)
}
dExt = &DriverExt{ dExt = &DriverExt{
Device: device, Device: device,
Driver: driver, Driver: driver,
plugin: plugin, plugin: driverOptions.plugin,
cacheStepData: cacheStepData{}, cacheStepData: cacheStepData{},
interruptSignal: make(chan os.Signal, 1), interruptSignal: make(chan os.Signal, 1),
} }
@@ -179,21 +184,86 @@ func newDriverExt(device Device, driver WebDriver, plugin funplugin.IPlugin) (dE
if err != nil { if err != nil {
return nil, errors.Wrap(err, "get screen resolution failed") return nil, errors.Wrap(err, "get screen resolution failed")
} }
if driverOptions.withImageService {
if dExt.ImageService, err = newVEDEMImageService(); err != nil { if dExt.ImageService, err = newVEDEMImageService(); err != nil {
return nil, err return nil, err
}
} }
if driverOptions.withResultFolder {
// create results directory // create results directory
if err = builtin.EnsureFolderExists(env.ResultsPath); err != nil { if err = builtin.EnsureFolderExists(env.ResultsPath); err != nil {
return nil, errors.Wrap(err, "create results directory failed") return nil, errors.Wrap(err, "create results directory failed")
} }
if err = builtin.EnsureFolderExists(env.ScreenShotsPath); err != nil { if err = builtin.EnsureFolderExists(env.ScreenShotsPath); err != nil {
return nil, errors.Wrap(err, "create screenshots directory failed") return nil, errors.Wrap(err, "create screenshots directory failed")
}
} }
return dExt, nil return dExt, nil
} }
func (dExt *DriverExt) InstallByUrl(url string, opts *InstallOptions) error {
// 获取当前目录
cwd, err := os.Getwd()
if err != nil {
return err
}
// 将文件保存到当前目录
appPath := filepath.Join(cwd, fmt.Sprint(time.Now().UnixNano())) // 替换为你想保存的文件名
err = builtin.DownloadFile(appPath, url)
if err != nil {
return err
}
err = dExt.Install(appPath, opts)
if err != nil {
return err
}
return nil
}
func (dExt *DriverExt) Uninstall(packageName string, options ...ActionOption) error {
actionOptions := NewActionOptions(options...)
err := dExt.Device.Uninstall(packageName)
if err != nil {
log.Warn().Err(err).Msg("failed to uninstall")
}
if actionOptions.IgnoreNotFoundError {
return nil
}
return err
}
func (dExt *DriverExt) Install(filePath string, opts *InstallOptions) error {
if _, ok := dExt.Device.(*AndroidDevice); ok {
stopChan := make(chan struct{})
go func() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
actions := []TapTextAction{
{Text: "^.*无视风险安装$", Options: []ActionOption{WithTapOffset(100, 0), WithRegex(true), WithIgnoreNotFoundError(true)}},
{Text: "^已了解此应用未经检测.*", Options: []ActionOption{WithTapOffset(-450, 0), WithRegex(true), WithIgnoreNotFoundError(true)}},
}
_ = dExt.Driver.TapByTexts(actions...)
_ = dExt.TapByOCR("^(.*无视风险安装|确定|继续|完成|点击继续安装|继续安装旧版本|替换|安装|授权本次安装|继续安装|重新安装)$", WithRegex(true), WithIgnoreNotFoundError(true))
case <-stopChan:
fmt.Println("Ticker stopped")
return
}
}
}()
defer func() {
close(stopChan)
}()
}
return dExt.Device.Install(filePath, opts)
}
// takeScreenShot takes screenshot and saves image file to $CWD/screenshots/ folder // takeScreenShot takes screenshot and saves image file to $CWD/screenshots/ folder
func (dExt *DriverExt) takeScreenShot(fileName string) (raw *bytes.Buffer, path string, err error) { func (dExt *DriverExt) takeScreenShot(fileName string) (raw *bytes.Buffer, path string, err error) {
// iOS 优先使用 MJPEG 流进行截图,性能最优 // iOS 优先使用 MJPEG 流进行截图,性能最优
@@ -247,7 +317,6 @@ func compressImageBuffer(raw *bytes.Buffer) (compressed *bytes.Buffer, err error
// 返回压缩后的图像数据 // 返回压缩后的图像数据
return &buf, nil return &buf, nil
} }
// saveScreenShot saves image file with file name // saveScreenShot saves image file with file name
@@ -298,7 +367,7 @@ func (dExt *DriverExt) GetStepCacheData() map[string]interface{} {
dExt.cacheStepData.screenResults.updatePopupCloseStatus() dExt.cacheStepData.screenResults.updatePopupCloseStatus()
cacheData["screen_results"] = dExt.cacheStepData.screenResults cacheData["screen_results"] = dExt.cacheStepData.screenResults
cacheData["e2e_results"] = dExt.cacheStepData.e2eDelay cacheData["e2e_results"] = dExt.cacheStepData.e2eDelay
cacheData["driver_request_results"] = dExt.Driver.GetDriverResults()
// clear cache // clear cache
dExt.cacheStepData.reset() dExt.cacheStepData.reset()
return cacheData return cacheData

48
hrp/pkg/uixt/install.go Normal file
View File

@@ -0,0 +1,48 @@
package uixt
type InstallOptions struct {
Reinstall bool
GrantPermission bool
Downgrade bool
RetryTime int
}
type InstallOption func(o *InstallOptions)
func NewInstallOptions(options ...InstallOption) *InstallOptions {
installOptions := &InstallOptions{}
for _, option := range options {
option(installOptions)
}
return installOptions
}
func WithReinstall(reinstall bool) InstallOption {
return func(o *InstallOptions) {
o.Reinstall = reinstall
}
}
func WithGrantPermission(grantPermission bool) InstallOption {
return func(o *InstallOptions) {
o.GrantPermission = grantPermission
}
}
func WithDowngrade(downgrade bool) InstallOption {
return func(o *InstallOptions) {
o.Downgrade = downgrade
}
}
func WithRetryTime(retryTime int) InstallOption {
return func(o *InstallOptions) {
o.RetryTime = retryTime
}
}
type InstallResult struct {
Result int `json:"result"`
ErrorCode int `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}

View File

@@ -252,11 +252,7 @@ type Screen struct {
} }
type AppInfo struct { type AppInfo struct {
ProcessArguments struct { Name string `json:"name,omitempty"`
Env interface{} `json:"env"`
Args []interface{} `json:"args"`
} `json:"processArguments"`
Name string `json:"name"`
AppBaseInfo AppBaseInfo
} }
@@ -266,6 +262,10 @@ type AppBaseInfo struct {
ViewController string `json:"viewController,omitempty"` // ios view controller ViewController string `json:"viewController,omitempty"` // ios view controller
PackageName string `json:"packageName,omitempty"` // android package name PackageName string `json:"packageName,omitempty"` // android package name
Activity string `json:"activity,omitempty"` // android activity Activity string `json:"activity,omitempty"` // android activity
VersionName string `json:"versionName,omitempty"`
VersionCode int `json:"versionCode,omitempty"`
AppName string `json:"appName,omitempty"`
// AppIcon string `json:"appIcon,omitempty"`
} }
type AppState int type AppState int
@@ -376,6 +376,11 @@ func (opt SourceOption) WithFormatAsJson() SourceOption {
return opt return opt
} }
func (opt SourceOption) WithProcessName(processName string) SourceOption {
opt["processName"] = processName
return opt
}
// WithFormatAsXml Application elements tree in form of xml string // WithFormatAsXml Application elements tree in form of xml string
func (opt SourceOption) WithFormatAsXml() SourceOption { func (opt SourceOption) WithFormatAsXml() SourceOption {
opt["format"] = "xml" opt["format"] = "xml"
@@ -448,8 +453,17 @@ type Rect struct {
} }
type DriverOptions struct { type DriverOptions struct {
capabilities Capabilities capabilities Capabilities
plugin funplugin.IPlugin plugin funplugin.IPlugin
withImageService bool
withResultFolder bool
}
func NewDriverOptions() *DriverOptions {
return &DriverOptions{
withImageService: true,
withResultFolder: true,
}
} }
type DriverOption func(*DriverOptions) type DriverOption func(*DriverOptions)
@@ -460,6 +474,18 @@ func WithDriverCapabilities(capabilities Capabilities) DriverOption {
} }
} }
func WithDriverImageService(withImageService bool) DriverOption {
return func(options *DriverOptions) {
options.withImageService = withImageService
}
}
func WithDriverResultFolder(withResultFolder bool) DriverOption {
return func(options *DriverOptions) {
options.withResultFolder = withResultFolder
}
}
func WithDriverPlugin(plugin funplugin.IPlugin) DriverOption { func WithDriverPlugin(plugin funplugin.IPlugin) DriverOption {
return func(options *DriverOptions) { return func(options *DriverOptions) {
options.plugin = plugin options.plugin = plugin
@@ -468,6 +494,7 @@ func WithDriverPlugin(plugin funplugin.IPlugin) DriverOption {
// current implemeted device: IOSDevice, AndroidDevice // current implemeted device: IOSDevice, AndroidDevice
type Device interface { type Device interface {
Init() error // init android device
UUID() string // ios udid or android serial UUID() string // ios udid or android serial
LogEnabled() bool LogEnabled() bool
NewDriver(...DriverOption) (driverExt *DriverExt, err error) NewDriver(...DriverOption) (driverExt *DriverExt, err error)
@@ -477,6 +504,10 @@ type Device interface {
StartPcap() error StartPcap() error
StopPcap() string StopPcap() string
Uninstall(packageName string) error
Install(appPath string, opts *InstallOptions) error
} }
type ForegroundApp struct { type ForegroundApp struct {
@@ -525,6 +556,8 @@ type WebDriver interface {
// Homescreen Forces the device under test to switch to the home screen // Homescreen Forces the device under test to switch to the home screen
Homescreen() error Homescreen() error
Unlock() (err error)
// AppLaunch Launch an application with given bundle identifier in scope of current session. // AppLaunch Launch an application with given bundle identifier in scope of current session.
// !This method is only available since Xcode9 SDK // !This method is only available since Xcode9 SDK
AppLaunch(packageName string) error AppLaunch(packageName string) error
@@ -571,6 +604,8 @@ type WebDriver interface {
// It worked when `WDA` was foreground. https://github.com/appium/WebDriverAgent/issues/330 // It worked when `WDA` was foreground. https://github.com/appium/WebDriverAgent/issues/330
GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error)
SetIme(ime string) error
// SendKeys Types a string into active element. There must be element with keyboard focus, // SendKeys Types a string into active element. There must be element with keyboard focus,
// otherwise an error is raised. // otherwise an error is raised.
// WithFrequency option can be used to set frequency of typing (letters per sec). The default value is 60 // WithFrequency option can be used to set frequency of typing (letters per sec). The default value is 60
@@ -579,17 +614,25 @@ type WebDriver interface {
// Input works like SendKeys // Input works like SendKeys
Input(text string, options ...ActionOption) error Input(text string, options ...ActionOption) error
Clear(packageName string) error
// PressButton Presses the corresponding hardware button on the device // PressButton Presses the corresponding hardware button on the device
PressButton(devBtn DeviceButton) error PressButton(devBtn DeviceButton) error
// PressBack Presses the back button // PressBack Presses the back button
PressBack(options ...ActionOption) error PressBack(options ...ActionOption) error
PressKeyCode(keyCode KeyCode) (err error)
Screenshot() (*bytes.Buffer, error) Screenshot() (*bytes.Buffer, error)
// Source Return application elements tree // Source Return application elements tree
Source(srcOpt ...SourceOption) (string, error) Source(srcOpt ...SourceOption) (string, error)
LoginNoneUI(packageName, phoneNumber string, captcha string) error
LogoutNoneUI(packageName string) error
TapByText(text string, options ...ActionOption) error TapByText(text string, options ...ActionOption) error
TapByTexts(actions ...TapTextAction) error TapByTexts(actions ...TapTextAction) error
@@ -610,4 +653,5 @@ type WebDriver interface {
// triggers the log capture and returns the log entries // triggers the log capture and returns the log entries
StartCaptureLog(identifier ...string) (err error) StartCaptureLog(identifier ...string) (err error)
StopCaptureLog() (result interface{}, err error) StopCaptureLog() (result interface{}, err error)
GetDriverResults() []*DriverResult
} }

View File

@@ -276,6 +276,7 @@ type IOSDevice struct {
MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"` LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
XCTestBundleID string `json:"xctest_bundle_id,omitempty" yaml:"xctest_bundle_id,omitempty"` XCTestBundleID string `json:"xctest_bundle_id,omitempty" yaml:"xctest_bundle_id,omitempty"`
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
// switch to iOS springboard before init WDA session // switch to iOS springboard before init WDA session
ResetHomeOnStartup bool `json:"reset_home_on_startup,omitempty" yaml:"reset_home_on_startup,omitempty"` ResetHomeOnStartup bool `json:"reset_home_on_startup,omitempty" yaml:"reset_home_on_startup,omitempty"`
@@ -294,6 +295,10 @@ type IOSDevice struct {
pcapFile string // saved pcap file path pcapFile string // saved pcap file path
} }
func (dev *IOSDevice) Init() error {
return nil
}
func (dev *IOSDevice) UUID() string { func (dev *IOSDevice) UUID() string {
return dev.UDID return dev.UDID
} }
@@ -303,7 +308,7 @@ func (dev *IOSDevice) LogEnabled() bool {
} }
func (dev *IOSDevice) NewDriver(options ...DriverOption) (driverExt *DriverExt, err error) { func (dev *IOSDevice) NewDriver(options ...DriverOption) (driverExt *DriverExt, err error) {
driverOptions := &DriverOptions{} driverOptions := NewDriverOptions()
for _, option := range options { for _, option := range options {
option(driverOptions) option(driverOptions)
} }
@@ -334,7 +339,7 @@ func (dev *IOSDevice) NewDriver(options ...DriverOption) (driverExt *DriverExt,
} }
} }
driverExt, err = newDriverExt(dev, driver, driverOptions.plugin) driverExt, err = newDriverExt(dev, driver, options...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -467,6 +472,20 @@ func (dev *IOSDevice) StopPcap() string {
return dev.pcapFile return dev.pcapFile
} }
func (dev *IOSDevice) Install(appPath string, opts *InstallOptions) (err error) {
for i := 0; i <= opts.RetryTime; i++ {
err = builtin.RunCommand("go-ios", "install", "--path="+appPath, "--udid="+dev.UDID)
if err == nil {
return nil
}
}
return err
}
func (dev *IOSDevice) Uninstall(bundleId string) error {
return builtin.RunCommand("go-ios", "uninstall", bundleId, "--udid="+dev.UDID)
}
func (dev *IOSDevice) forward(localPort, remotePort int) error { func (dev *IOSDevice) forward(localPort, remotePort int) error {
log.Info().Int("localPort", localPort).Int("remotePort", remotePort). log.Info().Int("localPort", localPort).Int("remotePort", remotePort).
Str("udid", dev.UDID).Msg("forward tcp port") Str("udid", dev.UDID).Msg("forward tcp port")
@@ -631,7 +650,7 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver
wd := new(wdaDriver) wd := new(wdaDriver)
wd.client = http.DefaultClient wd.client = http.DefaultClient
host := "127.0.0.1" host := "localhost"
if wd.urlPrefix, err = url.Parse(fmt.Sprintf("http://%s:%d", host, localPort)); err != nil { if wd.urlPrefix, err = url.Parse(fmt.Sprintf("http://%s:%d", host, localPort)); err != nil {
return nil, errors.Wrap(code.IOSDeviceHTTPDriverError, err.Error()) return nil, errors.Wrap(code.IOSDeviceHTTPDriverError, err.Error())
} }

View File

@@ -591,6 +591,14 @@ func (wd *wdaDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffe
return return
} }
func (wd *wdaDriver) SetIme(ime string) error {
return errDriverNotImplemented
}
func (wd *wdaDriver) PressKeyCode(keyCode KeyCode) (err error) {
return errDriverNotImplemented
}
func (wd *wdaDriver) SendKeys(text string, options ...ActionOption) (err error) { func (wd *wdaDriver) SendKeys(text string, options ...ActionOption) (err error) {
// [[FBRoute POST:@"/wda/keys"] respondWithTarget:self action:@selector(handleKeys:)] // [[FBRoute POST:@"/wda/keys"] respondWithTarget:self action:@selector(handleKeys:)]
actionOptions := NewActionOptions(options...) actionOptions := NewActionOptions(options...)
@@ -607,6 +615,10 @@ func (wd *wdaDriver) Input(text string, options ...ActionOption) (err error) {
return wd.SendKeys(text, options...) return wd.SendKeys(text, options...)
} }
func (wd *wdaDriver) Clear(packageName string) error {
return errDriverNotImplemented
}
// PressBack simulates a short press on the BACK button. // PressBack simulates a short press on the BACK button.
func (wd *wdaDriver) PressBack(options ...ActionOption) (err error) { func (wd *wdaDriver) PressBack(options ...ActionOption) (err error) {
actionOptions := NewActionOptions(options...) actionOptions := NewActionOptions(options...)
@@ -651,6 +663,14 @@ func (wd *wdaDriver) PressButton(devBtn DeviceButton) (err error) {
return return
} }
func (wd *wdaDriver) LoginNoneUI(packageName, phoneNumber string, captcha string) error {
return errDriverNotImplemented
}
func (wd *wdaDriver) LogoutNoneUI(packageName string) error {
return errDriverNotImplemented
}
func (wd *wdaDriver) StartCamera() (err error) { func (wd *wdaDriver) StartCamera() (err error) {
// start camera, alias for app_launch com.apple.camera // start camera, alias for app_launch com.apple.camera
return wd.AppLaunch("com.apple.camera") return wd.AppLaunch("com.apple.camera")
@@ -877,6 +897,13 @@ func (wd *wdaDriver) StopCaptureLog() (result interface{}, err error) {
return reply.Value, nil return reply.Value, nil
} }
func (wd *wdaDriver) GetDriverResults() []*DriverResult {
defer func() {
wd.Driver.driverResults = nil
}()
return wd.Driver.driverResults
}
type rawResponse []byte type rawResponse []byte
func (r rawResponse) checkErr() (err error) { func (r rawResponse) checkErr() (err error) {
@@ -939,6 +966,13 @@ func (r rawResponse) valueConvertToJsonRawMessage() (raw builtinJSON.RawMessage,
return return
} }
func (r rawResponse) valueConvertToJsonObject() (obj map[string]interface{}, err error) {
if err = json.Unmarshal(r, &obj); err != nil {
return nil, err
}
return
}
func (r rawResponse) valueDecodeAsBase64() (raw *bytes.Buffer, err error) { func (r rawResponse) valueDecodeAsBase64() (raw *bytes.Buffer, err error) {
str, err := r.valueConvertToString() str, err := r.valueConvertToString()
if err != nil { if err != nil {

View File

@@ -7,6 +7,8 @@ import (
"fmt" "fmt"
"testing" "testing"
"time" "time"
"github.com/rs/zerolog/log"
) )
var ( var (
@@ -26,7 +28,7 @@ func setup(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
iOSDriverExt, err = newDriverExt(device, driver, nil) iOSDriverExt, err = newDriverExt(device, driver)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -37,6 +39,15 @@ func TestViaUSB(t *testing.T) {
t.Log(driver.Status()) t.Log(driver.Status())
} }
func TestInstall(t *testing.T) {
setup(t)
err := iOSDriverExt.Install("/Users/bytedance/Downloads/com.yueyou.cyreader_1387717110_7.54.20.ipa", NewInstallOptions(WithRetryTime(5)))
log.Error().Err(err)
if err != nil {
t.Fatal(err)
}
}
func TestNewIOSDevice(t *testing.T) { func TestNewIOSDevice(t *testing.T) {
device, _ := NewIOSDevice() device, _ := NewIOSDevice()
if device != nil { if device != nil {

View File

@@ -62,11 +62,11 @@ func (ete *EndToEndDelay) getCurrentLiveTime(utcTime time.Time) error {
// filter ocr texts with time format // filter ocr texts with time format
var liveTimeTexts []string var liveTimeTexts []string
for _, ocrText := range ocrTexts { for _, ocrText := range ocrTexts {
if len(ocrText.Text) < 10 || strings.Contains(ocrText.Text, ":") { if len(ocrText.Text) < 13 || strings.Contains(ocrText.Text, ":") {
continue continue
} }
// exclude digit(s) recognized as letter(s) // exclude digit(s) recognized as letter(s)
_, errParseInt := strconv.ParseInt(ocrText.Text[:10], 10, 64) _, errParseInt := strconv.ParseInt(ocrText.Text[:13], 10, 64)
if errParseInt != nil { if errParseInt != nil {
continue continue
} }
@@ -81,11 +81,6 @@ func (ete *EndToEndDelay) getCurrentLiveTime(utcTime time.Time) error {
return nil return nil
} }
if len(liveTimeText) < 13 {
for (13 - len(liveTimeText)) > 0 {
liveTimeText += "0"
}
}
liveTimeInt, err := strconv.Atoi(liveTimeText) liveTimeInt, err := strconv.Atoi(liveTimeText)
if err != nil { if err != nil {
liveTimeInt = 0 liveTimeInt = 0

View File

@@ -2,6 +2,7 @@ package uixt
import ( import (
"fmt" "fmt"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -110,8 +111,6 @@ func (dExt *DriverExt) DoubleTapXY(x, y float64) error {
x = x * float64(dExt.windowSize.Height) x = x * float64(dExt.windowSize.Height)
y = y * float64(dExt.windowSize.Width) y = y * float64(dExt.windowSize.Width)
} }
x = x * float64(dExt.windowSize.Width)
y = y * float64(dExt.windowSize.Height)
return dExt.Driver.DoubleTapFloat(x, y) return dExt.Driver.DoubleTapFloat(x, y)
} }

11
hrp/pkg/utf7/utf7_test.go Normal file
View File

@@ -0,0 +1,11 @@
package utf7
import "testing"
func Test_Decode(t *testing.T) {
str, err := Encoding.NewDecoder().String("&j71bgXcBbIiWM14CZbBsEV4CbBFlz4hX-36-4")
if err != nil {
t.Fatal(err)
}
t.Log(str)
}

View File

@@ -18,7 +18,6 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/httprunner/funplugin" "github.com/httprunner/funplugin"
"github.com/httprunner/funplugin/myexec"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@@ -26,7 +25,6 @@ import (
"github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/builtin"
"github.com/httprunner/httprunner/v4/hrp/internal/code" "github.com/httprunner/httprunner/v4/hrp/internal/code"
"github.com/httprunner/httprunner/v4/hrp/internal/env"
"github.com/httprunner/httprunner/v4/hrp/internal/sdk" "github.com/httprunner/httprunner/v4/hrp/internal/sdk"
"github.com/httprunner/httprunner/v4/hrp/internal/version" "github.com/httprunner/httprunner/v4/hrp/internal/version"
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt" "github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
@@ -437,11 +435,15 @@ func (r *CaseRunner) parseConfig() error {
if err != nil { if err != nil {
return errors.Wrap(err, "init iOS device failed") return errors.Wrap(err, "init iOS device failed")
} }
if err := device.Init(); err != nil {
return err
}
client, err := device.NewDriver(uixt.WithDriverPlugin(r.parser.plugin)) client, err := device.NewDriver(uixt.WithDriverPlugin(r.parser.plugin))
if err != nil { if err != nil {
return errors.Wrap(err, "init iOS WDA client failed") return errors.Wrap(err, "init iOS WDA client failed")
} }
r.uiClients[device.UDID] = client r.uiClients[device.UDID] = client
} }
for _, androidDeviceConfig := range r.parsedConfig.Android { for _, androidDeviceConfig := range r.parsedConfig.Android {
if androidDeviceConfig.SerialNumber != "" { if androidDeviceConfig.SerialNumber != "" {
@@ -456,11 +458,15 @@ func (r *CaseRunner) parseConfig() error {
if err != nil { if err != nil {
return errors.Wrap(err, "init Android device failed") return errors.Wrap(err, "init Android device failed")
} }
if err := device.Init(); err != nil {
return err
}
client, err := device.NewDriver(uixt.WithDriverPlugin(r.parser.plugin)) client, err := device.NewDriver(uixt.WithDriverPlugin(r.parser.plugin))
if err != nil { if err != nil {
return errors.Wrap(err, "init Android client failed") return errors.Wrap(err, "init Android client failed")
} }
r.uiClients[device.SerialNumber] = client r.uiClients[device.SerialNumber] = client
} }
return nil return nil
@@ -525,11 +531,6 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) error {
config := r.caseRunner.testCase.Config config := r.caseRunner.testCase.Config
log.Info().Str("testcase", config.Name).Msg("run testcase start") log.Info().Str("testcase", config.Name).Msg("run testcase start")
// 安卓系统删除打点日志文件
if r.caseRunner.testCase.Config.Android != nil {
myexec.RunCommand("adb", "-s", r.caseRunner.testCase.Config.Android[0].SerialNumber, "shell", "rm", "-r", env.DeviceActionLogFilePath)
}
// update config variables with given variables // update config variables with given variables
r.InitWithParameters(givenVars) r.InitWithParameters(givenVars)
@@ -711,6 +712,16 @@ func (r *SessionRunner) GetSummary() (*TestCaseSummary, error) {
return caseSummary, nil return caseSummary, nil
} }
func (r *SessionRunner) IgnorePopup() bool {
if r.caseRunner.testCase.Config.Android != nil {
return r.caseRunner.testCase.Config.Android[0].IgnorePopup
}
if r.caseRunner.testCase.Config.IOS != nil {
return r.caseRunner.testCase.Config.IOS[0].IgnorePopup
}
return false
}
// updateSummary updates summary of StepResult. // updateSummary updates summary of StepResult.
func (r *SessionRunner) updateSummary(stepResult *StepResult) { func (r *SessionRunner) updateSummary(stepResult *StepResult) {
switch stepResult.StepType { switch stepResult.StepType {

View File

@@ -48,6 +48,7 @@ type TStep struct {
Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"` Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"`
Export []string `json:"export,omitempty" yaml:"export,omitempty"` Export []string `json:"export,omitempty" yaml:"export,omitempty"`
Loops int `json:"loops,omitempty" yaml:"loops,omitempty"` Loops int `json:"loops,omitempty" yaml:"loops,omitempty"`
IgnorePopup bool `json:"ignore_popup,omitempty" yaml:"ignore_popup,omitempty"`
} }
// IStep represents interface for all types for teststeps, includes: // IStep represents interface for all types for teststeps, includes:

View File

@@ -595,18 +595,18 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
} }
// report GA event // report GA event
sdk.SendGA4Event("hrp_run_ui", map[string]interface{}{ go sdk.SendGA4Event("hrp_run_ui", map[string]interface{}{
"osType": osType, "osType": osType,
}) })
identifer := mobileStep.Identifier identifier := mobileStep.Identifier
if mobileStep.Options != nil && identifer == "" { if mobileStep.Options != nil && identifier == "" {
identifer = mobileStep.Options.Identifier identifier = mobileStep.Options.Identifier
} }
if len(mobileStep.Actions) != 0 && identifer == "" { if len(mobileStep.Actions) != 0 && identifier == "" {
for _, action := range mobileStep.Actions { for _, action := range mobileStep.Actions {
if action.Identifier != "" { if action.Identifier != "" {
identifer = action.Identifier identifier = action.Identifier
break break
} }
} }
@@ -614,7 +614,7 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
stepResult = &StepResult{ stepResult = &StepResult{
Name: step.Name, Name: step.Name,
Identifier: identifer, Identifier: identifier,
StepType: StepType(osType), StepType: StepType(osType),
Success: false, Success: false,
ContentSize: 0, ContentSize: 0,
@@ -647,8 +647,10 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
} }
// automatic handling of pop-up windows on each step finished // automatic handling of pop-up windows on each step finished
if err2 := uiDriver.ClosePopups(); err2 != nil { if !step.IgnorePopup && !s.IgnorePopup() {
log.Error().Err(err2).Str("step", step.Name).Msg("auto handle popup failed") if err2 := uiDriver.ClosePopups(); err2 != nil {
log.Error().Err(err2).Str("step", step.Name).Msg("auto handle popup failed")
}
} }
// save attachments // save attachments