mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-26 18:11:34 +08:00
Merge branch 'feat/yuhongzheng/q3_release' into 'video-release'
q3评测发版 [skipped] See merge request iesqa/httprunner!43
This commit is contained in:
30
go.mod
30
go.mod
@@ -24,13 +24,13 @@ require (
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
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
|
||||
golang.org/x/net v0.20.0
|
||||
golang.org/x/net v0.25.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/protobuf v1.31.0
|
||||
google.golang.org/protobuf v1.34.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
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/metadata v0.2.3 // 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/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/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-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // 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/google/go-cmp v0.6.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/incu6us/goimports-reviser/v2 v2.5.3 // 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/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // 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/matttproud/golang_protobuf_extensions v1.0.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/reflect2 v1.0.2 // 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/prometheus/client_model v0.2.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/tklauser/go-sysconf v0.3.10 // 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
|
||||
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/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
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect
|
||||
|
||||
60
go.sum
60
go.sum
@@ -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.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
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/cespare/xxhash/v2 v2.1.1/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/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
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/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=
|
||||
@@ -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.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
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/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/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
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.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
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/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/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=
|
||||
@@ -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.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
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.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=
|
||||
@@ -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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||
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.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.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.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
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/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
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/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.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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
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.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.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/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/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
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/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.27/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=
|
||||
gocv.io/x/gocv v0.32.1 h1:BC9hHs5+47nVgySUFVKntc6RsF3SULFzqk6OV9xz+C0=
|
||||
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-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-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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
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-20190226205417-e64efc72b421/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-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.5.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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
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-20190308202827-9d24e82272b4/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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
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/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=
|
||||
@@ -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=
|
||||
mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo=
|
||||
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/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
62
hrp/cmd/adb/install.go
Normal file
62
hrp/cmd/adb/install.go
Normal 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
52
hrp/cmd/ios/install.go
Normal 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
25
hrp/cmd/server.go
Normal 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")
|
||||
}
|
||||
@@ -1,17 +1,23 @@
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/csv"
|
||||
builtinJSON "encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -23,6 +29,7 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/code"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
||||
)
|
||||
|
||||
@@ -509,3 +516,138 @@ func GetCurrentDay() string {
|
||||
formattedDate := now.Format("20060102")
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
1
hrp/internal/env/env.go
vendored
1
hrp/internal/env/env.go
vendored
@@ -13,6 +13,7 @@ var (
|
||||
VEDEM_IMAGE_URL = os.Getenv("VEDEM_IMAGE_URL")
|
||||
VEDEM_IMAGE_AK = os.Getenv("VEDEM_IMAGE_AK")
|
||||
VEDEM_IMAGE_SK = os.Getenv("VEDEM_IMAGE_SK")
|
||||
EAPI_TOKEN = os.Getenv("EAPI_TOKEN")
|
||||
DISABLE_GA = os.Getenv("DISABLE_GA")
|
||||
DISABLE_SENTRY = os.Getenv("DISABLE_SENTRY")
|
||||
PYPI_INDEX_URL = os.Getenv("PYPI_INDEX_URL")
|
||||
|
||||
@@ -1 +1 @@
|
||||
v4.5.1
|
||||
v4.6.5
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -209,8 +210,8 @@ func (c Client) KillServer() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c Client) createTransport() (tp transport, err error) {
|
||||
return newTransport(fmt.Sprintf("%s:%d", c.host, c.port))
|
||||
func (c Client) createTransport(readTimeout ...time.Duration) (tp transport, err error) {
|
||||
return newTransport(fmt.Sprintf("%s:%d", c.host, c.port), readTimeout...)
|
||||
}
|
||||
|
||||
func (c Client) executeCommand(command string, onlyVerifyResponse ...bool) (resp string, err error) {
|
||||
|
||||
@@ -292,6 +292,33 @@ func (d *Device) ReverseForwardKill(remoteInterface interface{}) error {
|
||||
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 {
|
||||
_, err := d.executeCommand("reverse:killforward-all")
|
||||
return err
|
||||
@@ -419,8 +446,8 @@ func (d *Device) EnableAdbOverTCP(port ...int) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) createDeviceTransport() (tp transport, err error) {
|
||||
if tp, err = newTransport(fmt.Sprintf("%s:%d", d.adbClient.host, d.adbClient.port)); err != nil {
|
||||
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), readTimeout...); err != nil {
|
||||
return transport{}, err
|
||||
}
|
||||
|
||||
@@ -559,7 +586,7 @@ func (d *Device) installViaABBExec(apk io.ReadSeeker, args ...string) (raw []byt
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tp, err = d.createDeviceTransport(); err != nil {
|
||||
if tp, err = d.createDeviceTransport(5 * time.Minute); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tp.Close() }()
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
var ErrConnBroken = errors.New("socket connection broken")
|
||||
|
||||
var DefaultAdbReadTimeout time.Duration = 60
|
||||
var DefaultAdbReadTimeout time.Duration = 300
|
||||
|
||||
var regexDeviceOffline = regexp.MustCompile("device .* not found")
|
||||
|
||||
|
||||
15
hrp/pkg/server/exception.go
Normal file
15
hrp/pkg/server/exception.go
Normal 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
52
hrp/pkg/server/model.go
Normal 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
558
hrp/pkg/server/server.go
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ type ActionMethod string
|
||||
const (
|
||||
ACTION_AppInstall ActionMethod = "install"
|
||||
ACTION_AppUninstall ActionMethod = "uninstall"
|
||||
ACTION_AppClear ActionMethod = "app_clear"
|
||||
ACTION_AppStart ActionMethod = "app_start"
|
||||
ACTION_AppLaunch ActionMethod = "app_launch" // 启动 app 并堵塞等待 app 首屏加载完成
|
||||
ACTION_AppTerminate ActionMethod = "app_terminate"
|
||||
@@ -26,6 +27,10 @@ const (
|
||||
ACTION_SleepRandom ActionMethod = "sleep_random"
|
||||
ACTION_StartCamera ActionMethod = "camera_start" // alias for app_launch 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
|
||||
// selectors
|
||||
@@ -60,6 +65,9 @@ const (
|
||||
ACTION_VideoCrawler ActionMethod = "video_crawler"
|
||||
ACTION_ClosePopups ActionMethod = "close_popups"
|
||||
ACTION_EndToEndDelay ActionMethod = "live_e2e"
|
||||
ACTION_InstallApp ActionMethod = "install_app"
|
||||
ACTION_UninstallApp ActionMethod = "uninstall_app"
|
||||
ACTION_DownloadApp ActionMethod = "download_app"
|
||||
)
|
||||
|
||||
type MobileAction struct {
|
||||
@@ -554,8 +562,23 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
|
||||
|
||||
switch action.Method {
|
||||
case ACTION_AppInstall:
|
||||
// TODO
|
||||
return errActionNotImplemented
|
||||
if appUrl, ok := action.Params.(string); ok {
|
||||
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:
|
||||
if bundleId, ok := action.Params.(string); ok {
|
||||
return dExt.Driver.AppLaunch(bundleId)
|
||||
@@ -594,8 +617,34 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) {
|
||||
return nil
|
||||
}
|
||||
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:
|
||||
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:
|
||||
if location, ok := action.Params.([]interface{}); ok {
|
||||
// relative x,y of window size: [0.5, 0.5]
|
||||
|
||||
@@ -3,6 +3,7 @@ package uixt
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
@@ -218,10 +219,18 @@ func (ad *adbDriver) Orientation() (orientation Orientation, 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>
|
||||
_, err = ad.adbClient.RunShellCommand(
|
||||
"input", "keyevent", fmt.Sprintf("%d", keyCode))
|
||||
@@ -421,6 +430,14 @@ func (ad *adbDriver) IsUnicodeIMEInstalled() bool {
|
||||
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) {
|
||||
defer func() {
|
||||
// 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...)
|
||||
}
|
||||
|
||||
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) {
|
||||
err = errDriverNotImplemented
|
||||
return
|
||||
@@ -499,6 +525,14 @@ func (ad *adbDriver) Source(srcOpt ...SourceOption) (source string, err error) {
|
||||
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) {
|
||||
source, err := ad.Source()
|
||||
if err != nil {
|
||||
@@ -680,46 +714,57 @@ func (ad *adbDriver) StopCaptureLog() (result interface{}, err error) {
|
||||
return pointRes, nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) GetDriverResults() []*DriverResult {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ad *adbDriver) GetForegroundApp() (app AppInfo, err error) {
|
||||
// adb shell dumpsys activity activities
|
||||
output, err := ad.adbClient.RunShellCommand("dumpsys", "activity", "activities")
|
||||
packageInfo, err := ad.adbClient.RunShellCommand("CLASSPATH=/data/local/tmp/evalite", "app_process", "/", "com.bytedance.iesqa.eval_process.PackageService")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to dumpsys activities")
|
||||
return AppInfo{}, errors.Wrap(err, "dumpsys activities failed")
|
||||
return app, err
|
||||
}
|
||||
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")
|
||||
for _, line := range lines {
|
||||
trimmedLine := strings.TrimSpace(line)
|
||||
// grep mResumedActivity|ResumedActivity
|
||||
if strings.HasPrefix(trimmedLine, "mResumedActivity:") || strings.HasPrefix(trimmedLine, "ResumedActivity:") {
|
||||
// mResumedActivity: ActivityRecord{9656d74 u0 com.android.settings/.Settings t407}
|
||||
// ResumedActivity: ActivityRecord{8265c25 u0 com.android.settings/.Settings t73}
|
||||
strs := strings.Split(trimmedLine, " ")
|
||||
for _, str := range strs {
|
||||
if strings.Contains(str, "/") {
|
||||
// com.android.settings/.Settings
|
||||
s := strings.Split(str, "/")
|
||||
app := AppInfo{
|
||||
AppBaseInfo: AppBaseInfo{
|
||||
PackageName: s[0],
|
||||
Activity: s[1],
|
||||
},
|
||||
}
|
||||
return app, nil
|
||||
if strings.ToLower(brand) == "oppo" {
|
||||
time.Sleep(1 * time.Second)
|
||||
pid, _ := ad.adbClient.RunShellCommand("pidof", packageName)
|
||||
if strings.TrimSpace(pid) == "" {
|
||||
appInfo, err := ad.GetForegroundApp()
|
||||
_ = ad.AppLaunch(packageName)
|
||||
if err == nil && packageName != UnicodeImePackageName {
|
||||
time.Sleep(10 * time.Second)
|
||||
nextAppInfo, err := ad.GetForegroundApp()
|
||||
log.Info().Str("beforeFocusedPackage", appInfo.PackageName).Str("afterFocusedPackage", nextAppInfo.PackageName).Msg("")
|
||||
if err == nil && nextAppInfo.PackageName != appInfo.PackageName {
|
||||
_ = ad.PressKeyCodes(KCBack, KMEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
// as there might be a situation where the input method has not been completely switched yet
|
||||
// Listen to the following message.
|
||||
|
||||
@@ -4,26 +4,43 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"embed"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/httprunner/funplugin/myexec"
|
||||
"github.com/pkg/errors"
|
||||
"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/env"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/json"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/gadb"
|
||||
)
|
||||
|
||||
var (
|
||||
AdbServerHost = "localhost"
|
||||
AdbServerPort = gadb.AdbServerPort // 5037
|
||||
UIA2ServerHost = "localhost"
|
||||
UIA2ServerPort = 6790
|
||||
DouyinServerPort = 32316
|
||||
AdbServerHost = "localhost"
|
||||
AdbServerPort = gadb.AdbServerPort // 5037
|
||||
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-"
|
||||
|
||||
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 {
|
||||
return func(device *AndroidDevice) {
|
||||
device.UIA2IP = ip
|
||||
@@ -108,7 +131,14 @@ func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, er
|
||||
|
||||
device.d = dev
|
||||
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")
|
||||
return device, nil
|
||||
}
|
||||
@@ -151,9 +181,18 @@ type AndroidDevice struct {
|
||||
logcat *AdbLogcat
|
||||
SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"`
|
||||
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
|
||||
UIA2Port int `json:"uia2_port,omitempty" yaml:"uia2_port,omitempty"` // uiautomator2 server port
|
||||
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 {
|
||||
@@ -165,7 +204,7 @@ func (dev *AndroidDevice) LogEnabled() bool {
|
||||
}
|
||||
|
||||
func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverExt, err error) {
|
||||
driverOptions := &DriverOptions{}
|
||||
driverOptions := NewDriverOptions()
|
||||
for _, option := range options {
|
||||
option(driverOptions)
|
||||
}
|
||||
@@ -173,6 +212,8 @@ func (dev *AndroidDevice) NewDriver(options ...DriverOption) (driverExt *DriverE
|
||||
var driver WebDriver
|
||||
if dev.UIA2 || dev.LogOn {
|
||||
driver, err = dev.NewUSBDriver(driverOptions.capabilities)
|
||||
} else if dev.STUB {
|
||||
driver, err = dev.NewStubDriver(driverOptions.capabilities)
|
||||
} else {
|
||||
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")
|
||||
}
|
||||
|
||||
driverExt, err = newDriverExt(dev, driver, driverOptions.plugin)
|
||||
driverExt, err = newDriverExt(dev, driver, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -217,6 +258,36 @@ func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver WebDri
|
||||
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.
|
||||
func (dev *AndroidDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver, err error) {
|
||||
rawURL := fmt.Sprintf("http://%s:%d/wd/hub", dev.UIA2IP, dev.UIA2Port)
|
||||
@@ -257,6 +328,116 @@ func (dev *AndroidDevice) StopPcap() string {
|
||||
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 AdbLogcat struct {
|
||||
|
||||
273
hrp/pkg/uixt/android_stub_driver.go
Normal file
273
hrp/pkg/uixt/android_stub_driver.go
Normal 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
|
||||
}
|
||||
56
hrp/pkg/uixt/android_stub_driver_test.go
Normal file
56
hrp/pkg/uixt/android_stub_driver_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@
|
||||
package uixt
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -22,8 +20,8 @@ var (
|
||||
func setupAndroid(t *testing.T) {
|
||||
device, err := NewAndroidDevice()
|
||||
checkErr(t, err)
|
||||
device.UIA2 = false
|
||||
device.LogOn = true
|
||||
device.UIA2 = true
|
||||
device.LogOn = false
|
||||
driverExt, err = device.NewDriver()
|
||||
checkErr(t, err)
|
||||
}
|
||||
@@ -125,12 +123,9 @@ func TestDriver_DeviceSize(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDriver_Source(t *testing.T) {
|
||||
driver, err := NewUIADriver(nil, uiaServerURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
setupAndroid(t)
|
||||
|
||||
source, err := driver.Source()
|
||||
source, err := driverExt.Driver.Source()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -200,33 +195,24 @@ func TestDriver_DeviceInfo(t *testing.T) {
|
||||
func TestDriver_Tap(t *testing.T) {
|
||||
setupAndroid(t)
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
err = driverExt.Driver.TapFloat(60.5, 125.5, WithIdentifier("test"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
result, _ := driverExt.Driver.StopCaptureLog()
|
||||
t.Log(result)
|
||||
//time.Sleep(time.Second)
|
||||
//
|
||||
//err = driverExt.Driver.TapFloat(60.5, 125.5, WithIdentifier("test"))
|
||||
//if err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
//time.Sleep(time.Second)
|
||||
//result, _ := driverExt.Driver.StopCaptureLog()
|
||||
//t.Log(result)
|
||||
}
|
||||
|
||||
func TestDriver_Swipe(t *testing.T) {
|
||||
driver, err := NewUIADriver(nil, uiaServerURL)
|
||||
if err != nil {
|
||||
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)
|
||||
setupAndroid(t)
|
||||
err := driverExt.Driver.Swipe(400, 1000, 400, 500, WithPressDuration(0.5))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -262,7 +248,7 @@ func TestDriver_Drag(t *testing.T) {
|
||||
func TestDriver_SendKeys(t *testing.T) {
|
||||
setupAndroid(t)
|
||||
|
||||
err := driverExt.Driver.SendKeys("Android\"输入速度测试", WithIdentifier("test"))
|
||||
err := driverExt.Driver.SendKeys("辽宁省沈阳市新民市民族街36-4", WithIdentifier("test"))
|
||||
if err != nil {
|
||||
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) {
|
||||
uiSelector := NewUiSelectorHelper().Text("a").String()
|
||||
if uiSelector != `new UiSelector().text("a");` {
|
||||
@@ -449,8 +443,6 @@ func TestConvertPoints(t *testing.T) {
|
||||
if len(eps) != 3 {
|
||||
t.Fatal()
|
||||
}
|
||||
jsons, _ := json.Marshal(eps)
|
||||
println(fmt.Sprintf("%v", string(jsons)))
|
||||
}
|
||||
|
||||
func TestDriver_ShellInputUnicode(t *testing.T) {
|
||||
|
||||
@@ -29,6 +29,7 @@ func NewUIADriver(capabilities Capabilities, urlPrefix string) (driver *uiaDrive
|
||||
log.Info().Msg("init uiautomator2 driver")
|
||||
if capabilities == nil {
|
||||
capabilities = NewCapabilities()
|
||||
capabilities.WithWaitForIdleTimeout(0)
|
||||
}
|
||||
driver = new(uiaDriver)
|
||||
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) {
|
||||
// register(postHandler, new NewSession("/wd/hub/session"))
|
||||
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 {
|
||||
return SessionInfo{SessionId: ""}, err
|
||||
}
|
||||
@@ -244,10 +250,14 @@ func (ud *uiaDriver) PressBack(options ...ActionOption) (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"))
|
||||
data := map[string]interface{}{
|
||||
"keycode": keyCode,
|
||||
@@ -293,7 +303,7 @@ func (ud *uiaDriver) TapFloat(x, y float64, options ...ActionOption) (err error)
|
||||
|
||||
duration := 100.0
|
||||
if actionOptions.PressDuration > 0 {
|
||||
duration = actionOptions.PressDuration
|
||||
duration = actionOptions.PressDuration * 1000
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"actions": []interface{}{
|
||||
@@ -399,7 +409,7 @@ func (ud *uiaDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...Actio
|
||||
|
||||
duration := 200.0
|
||||
if actionOptions.PressDuration > 0 {
|
||||
duration = actionOptions.PressDuration
|
||||
duration = actionOptions.PressDuration * 1000
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"actions": []interface{}{
|
||||
@@ -618,3 +628,10 @@ func (ud *uiaDriver) TapByTexts(actions ...TapTextAction) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ud *uiaDriver) GetDriverResults() []*DriverResult {
|
||||
defer func() {
|
||||
ud.Driver.driverResults = nil
|
||||
}()
|
||||
return ud.Driver.driverResults
|
||||
}
|
||||
|
||||
@@ -17,10 +17,18 @@ import (
|
||||
)
|
||||
|
||||
type Driver struct {
|
||||
urlPrefix *url.URL
|
||||
sessionId string
|
||||
client *http.Client
|
||||
scale float64
|
||||
urlPrefix *url.URL
|
||||
sessionId string
|
||||
client *http.Client
|
||||
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 {
|
||||
@@ -73,7 +81,15 @@ func (wd *Driver) httpRequest(method string, rawURL string, rawBody []byte) (raw
|
||||
}()
|
||||
|
||||
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") {
|
||||
// avoid printing screenshot data
|
||||
logger.Str("response", string(rawResp))
|
||||
|
||||
BIN
hrp/pkg/uixt/evalite
Normal file
BIN
hrp/pkg/uixt/evalite
Normal file
Binary file not shown.
@@ -155,11 +155,16 @@ type DriverExt struct {
|
||||
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{
|
||||
Device: device,
|
||||
Driver: driver,
|
||||
plugin: plugin,
|
||||
plugin: driverOptions.plugin,
|
||||
cacheStepData: cacheStepData{},
|
||||
interruptSignal: make(chan os.Signal, 1),
|
||||
}
|
||||
@@ -179,21 +184,86 @@ func newDriverExt(device Device, driver WebDriver, plugin funplugin.IPlugin) (dE
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get screen resolution failed")
|
||||
}
|
||||
|
||||
if dExt.ImageService, err = newVEDEMImageService(); err != nil {
|
||||
return nil, err
|
||||
if driverOptions.withImageService {
|
||||
if dExt.ImageService, err = newVEDEMImageService(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// create results directory
|
||||
if err = builtin.EnsureFolderExists(env.ResultsPath); err != nil {
|
||||
return nil, errors.Wrap(err, "create results directory failed")
|
||||
}
|
||||
if err = builtin.EnsureFolderExists(env.ScreenShotsPath); err != nil {
|
||||
return nil, errors.Wrap(err, "create screenshots directory failed")
|
||||
if driverOptions.withResultFolder {
|
||||
// create results directory
|
||||
if err = builtin.EnsureFolderExists(env.ResultsPath); err != nil {
|
||||
return nil, errors.Wrap(err, "create results directory failed")
|
||||
}
|
||||
if err = builtin.EnsureFolderExists(env.ScreenShotsPath); err != nil {
|
||||
return nil, errors.Wrap(err, "create screenshots directory failed")
|
||||
}
|
||||
}
|
||||
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
|
||||
func (dExt *DriverExt) takeScreenShot(fileName string) (raw *bytes.Buffer, path string, err error) {
|
||||
// iOS 优先使用 MJPEG 流进行截图,性能最优
|
||||
@@ -247,7 +317,6 @@ func compressImageBuffer(raw *bytes.Buffer) (compressed *bytes.Buffer, err error
|
||||
|
||||
// 返回压缩后的图像数据
|
||||
return &buf, nil
|
||||
|
||||
}
|
||||
|
||||
// saveScreenShot saves image file with file name
|
||||
@@ -298,7 +367,7 @@ func (dExt *DriverExt) GetStepCacheData() map[string]interface{} {
|
||||
dExt.cacheStepData.screenResults.updatePopupCloseStatus()
|
||||
cacheData["screen_results"] = dExt.cacheStepData.screenResults
|
||||
cacheData["e2e_results"] = dExt.cacheStepData.e2eDelay
|
||||
|
||||
cacheData["driver_request_results"] = dExt.Driver.GetDriverResults()
|
||||
// clear cache
|
||||
dExt.cacheStepData.reset()
|
||||
return cacheData
|
||||
|
||||
48
hrp/pkg/uixt/install.go
Normal file
48
hrp/pkg/uixt/install.go
Normal 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"`
|
||||
}
|
||||
@@ -252,11 +252,7 @@ type Screen struct {
|
||||
}
|
||||
|
||||
type AppInfo struct {
|
||||
ProcessArguments struct {
|
||||
Env interface{} `json:"env"`
|
||||
Args []interface{} `json:"args"`
|
||||
} `json:"processArguments"`
|
||||
Name string `json:"name"`
|
||||
Name string `json:"name,omitempty"`
|
||||
AppBaseInfo
|
||||
}
|
||||
|
||||
@@ -266,6 +262,10 @@ type AppBaseInfo struct {
|
||||
ViewController string `json:"viewController,omitempty"` // ios view controller
|
||||
PackageName string `json:"packageName,omitempty"` // android package name
|
||||
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
|
||||
@@ -376,6 +376,11 @@ func (opt SourceOption) WithFormatAsJson() SourceOption {
|
||||
return opt
|
||||
}
|
||||
|
||||
func (opt SourceOption) WithProcessName(processName string) SourceOption {
|
||||
opt["processName"] = processName
|
||||
return opt
|
||||
}
|
||||
|
||||
// WithFormatAsXml Application elements tree in form of xml string
|
||||
func (opt SourceOption) WithFormatAsXml() SourceOption {
|
||||
opt["format"] = "xml"
|
||||
@@ -448,8 +453,17 @@ type Rect struct {
|
||||
}
|
||||
|
||||
type DriverOptions struct {
|
||||
capabilities Capabilities
|
||||
plugin funplugin.IPlugin
|
||||
capabilities Capabilities
|
||||
plugin funplugin.IPlugin
|
||||
withImageService bool
|
||||
withResultFolder bool
|
||||
}
|
||||
|
||||
func NewDriverOptions() *DriverOptions {
|
||||
return &DriverOptions{
|
||||
withImageService: true,
|
||||
withResultFolder: true,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return func(options *DriverOptions) {
|
||||
options.plugin = plugin
|
||||
@@ -468,6 +494,7 @@ func WithDriverPlugin(plugin funplugin.IPlugin) DriverOption {
|
||||
|
||||
// current implemeted device: IOSDevice, AndroidDevice
|
||||
type Device interface {
|
||||
Init() error // init android device
|
||||
UUID() string // ios udid or android serial
|
||||
LogEnabled() bool
|
||||
NewDriver(...DriverOption) (driverExt *DriverExt, err error)
|
||||
@@ -477,6 +504,10 @@ type Device interface {
|
||||
|
||||
StartPcap() error
|
||||
StopPcap() string
|
||||
|
||||
Uninstall(packageName string) error
|
||||
|
||||
Install(appPath string, opts *InstallOptions) error
|
||||
}
|
||||
|
||||
type ForegroundApp struct {
|
||||
@@ -525,6 +556,8 @@ type WebDriver interface {
|
||||
// Homescreen Forces the device under test to switch to the home screen
|
||||
Homescreen() error
|
||||
|
||||
Unlock() (err error)
|
||||
|
||||
// AppLaunch Launch an application with given bundle identifier in scope of current session.
|
||||
// !This method is only available since Xcode9 SDK
|
||||
AppLaunch(packageName string) error
|
||||
@@ -571,6 +604,8 @@ type WebDriver interface {
|
||||
// It worked when `WDA` was foreground. https://github.com/appium/WebDriverAgent/issues/330
|
||||
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,
|
||||
// otherwise an error is raised.
|
||||
// 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(text string, options ...ActionOption) error
|
||||
|
||||
Clear(packageName string) error
|
||||
|
||||
// PressButton Presses the corresponding hardware button on the device
|
||||
PressButton(devBtn DeviceButton) error
|
||||
|
||||
// PressBack Presses the back button
|
||||
PressBack(options ...ActionOption) error
|
||||
|
||||
PressKeyCode(keyCode KeyCode) (err error)
|
||||
|
||||
Screenshot() (*bytes.Buffer, error)
|
||||
|
||||
// Source Return application elements tree
|
||||
Source(srcOpt ...SourceOption) (string, error)
|
||||
|
||||
LoginNoneUI(packageName, phoneNumber string, captcha string) error
|
||||
|
||||
LogoutNoneUI(packageName string) error
|
||||
|
||||
TapByText(text string, options ...ActionOption) error
|
||||
|
||||
TapByTexts(actions ...TapTextAction) error
|
||||
@@ -610,4 +653,5 @@ type WebDriver interface {
|
||||
// triggers the log capture and returns the log entries
|
||||
StartCaptureLog(identifier ...string) (err error)
|
||||
StopCaptureLog() (result interface{}, err error)
|
||||
GetDriverResults() []*DriverResult
|
||||
}
|
||||
|
||||
@@ -276,6 +276,7 @@ type IOSDevice struct {
|
||||
MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port
|
||||
LogOn bool `json:"log_on,omitempty" yaml:"log_on,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
|
||||
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
|
||||
}
|
||||
|
||||
func (dev *IOSDevice) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dev *IOSDevice) UUID() string {
|
||||
return dev.UDID
|
||||
}
|
||||
@@ -303,7 +308,7 @@ func (dev *IOSDevice) LogEnabled() bool {
|
||||
}
|
||||
|
||||
func (dev *IOSDevice) NewDriver(options ...DriverOption) (driverExt *DriverExt, err error) {
|
||||
driverOptions := &DriverOptions{}
|
||||
driverOptions := NewDriverOptions()
|
||||
for _, option := range options {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -467,6 +472,20 @@ func (dev *IOSDevice) StopPcap() string {
|
||||
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 {
|
||||
log.Info().Int("localPort", localPort).Int("remotePort", remotePort).
|
||||
Str("udid", dev.UDID).Msg("forward tcp port")
|
||||
@@ -631,7 +650,7 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities Capabilities) (driver WebDriver
|
||||
wd := new(wdaDriver)
|
||||
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 {
|
||||
return nil, errors.Wrap(code.IOSDeviceHTTPDriverError, err.Error())
|
||||
}
|
||||
|
||||
@@ -591,6 +591,14 @@ func (wd *wdaDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffe
|
||||
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) {
|
||||
// [[FBRoute POST:@"/wda/keys"] respondWithTarget:self action:@selector(handleKeys:)]
|
||||
actionOptions := NewActionOptions(options...)
|
||||
@@ -607,6 +615,10 @@ func (wd *wdaDriver) Input(text string, options ...ActionOption) (err error) {
|
||||
return wd.SendKeys(text, options...)
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) Clear(packageName string) error {
|
||||
return errDriverNotImplemented
|
||||
}
|
||||
|
||||
// PressBack simulates a short press on the BACK button.
|
||||
func (wd *wdaDriver) PressBack(options ...ActionOption) (err error) {
|
||||
actionOptions := NewActionOptions(options...)
|
||||
@@ -651,6 +663,14 @@ func (wd *wdaDriver) PressButton(devBtn DeviceButton) (err error) {
|
||||
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) {
|
||||
// start camera, alias for app_launch 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
|
||||
}
|
||||
|
||||
func (wd *wdaDriver) GetDriverResults() []*DriverResult {
|
||||
defer func() {
|
||||
wd.Driver.driverResults = nil
|
||||
}()
|
||||
return wd.Driver.driverResults
|
||||
}
|
||||
|
||||
type rawResponse []byte
|
||||
|
||||
func (r rawResponse) checkErr() (err error) {
|
||||
@@ -939,6 +966,13 @@ func (r rawResponse) valueConvertToJsonRawMessage() (raw builtinJSON.RawMessage,
|
||||
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) {
|
||||
str, err := r.valueConvertToString()
|
||||
if err != nil {
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -26,7 +28,7 @@ func setup(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
iOSDriverExt, err = newDriverExt(device, driver, nil)
|
||||
iOSDriverExt, err = newDriverExt(device, driver)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -37,6 +39,15 @@ func TestViaUSB(t *testing.T) {
|
||||
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) {
|
||||
device, _ := NewIOSDevice()
|
||||
if device != nil {
|
||||
|
||||
@@ -62,11 +62,11 @@ func (ete *EndToEndDelay) getCurrentLiveTime(utcTime time.Time) error {
|
||||
// filter ocr texts with time format
|
||||
var liveTimeTexts []string
|
||||
for _, ocrText := range ocrTexts {
|
||||
if len(ocrText.Text) < 10 || strings.Contains(ocrText.Text, ":") {
|
||||
if len(ocrText.Text) < 13 || strings.Contains(ocrText.Text, ":") {
|
||||
continue
|
||||
}
|
||||
// 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 {
|
||||
continue
|
||||
}
|
||||
@@ -81,11 +81,6 @@ func (ete *EndToEndDelay) getCurrentLiveTime(utcTime time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(liveTimeText) < 13 {
|
||||
for (13 - len(liveTimeText)) > 0 {
|
||||
liveTimeText += "0"
|
||||
}
|
||||
}
|
||||
liveTimeInt, err := strconv.Atoi(liveTimeText)
|
||||
if err != nil {
|
||||
liveTimeInt = 0
|
||||
|
||||
@@ -2,6 +2,7 @@ package uixt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
@@ -110,8 +111,6 @@ func (dExt *DriverExt) DoubleTapXY(x, y float64) error {
|
||||
x = x * float64(dExt.windowSize.Height)
|
||||
y = y * float64(dExt.windowSize.Width)
|
||||
}
|
||||
x = x * float64(dExt.windowSize.Width)
|
||||
y = y * float64(dExt.windowSize.Height)
|
||||
return dExt.Driver.DoubleTapFloat(x, y)
|
||||
}
|
||||
|
||||
|
||||
11
hrp/pkg/utf7/utf7_test.go
Normal file
11
hrp/pkg/utf7/utf7_test.go
Normal 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)
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/httprunner/funplugin"
|
||||
"github.com/httprunner/funplugin/myexec"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
"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/code"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/env"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/sdk"
|
||||
"github.com/httprunner/httprunner/v4/hrp/internal/version"
|
||||
"github.com/httprunner/httprunner/v4/hrp/pkg/uixt"
|
||||
@@ -437,11 +435,15 @@ func (r *CaseRunner) parseConfig() error {
|
||||
if err != nil {
|
||||
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))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "init iOS WDA client failed")
|
||||
}
|
||||
r.uiClients[device.UDID] = client
|
||||
|
||||
}
|
||||
for _, androidDeviceConfig := range r.parsedConfig.Android {
|
||||
if androidDeviceConfig.SerialNumber != "" {
|
||||
@@ -456,11 +458,15 @@ func (r *CaseRunner) parseConfig() error {
|
||||
if err != nil {
|
||||
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))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "init Android client failed")
|
||||
}
|
||||
r.uiClients[device.SerialNumber] = client
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -525,11 +531,6 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) error {
|
||||
config := r.caseRunner.testCase.Config
|
||||
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
|
||||
r.InitWithParameters(givenVars)
|
||||
|
||||
@@ -711,6 +712,16 @@ func (r *SessionRunner) GetSummary() (*TestCaseSummary, error) {
|
||||
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.
|
||||
func (r *SessionRunner) updateSummary(stepResult *StepResult) {
|
||||
switch stepResult.StepType {
|
||||
|
||||
@@ -48,6 +48,7 @@ type TStep struct {
|
||||
Validators []interface{} `json:"validate,omitempty" yaml:"validate,omitempty"`
|
||||
Export []string `json:"export,omitempty" yaml:"export,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:
|
||||
|
||||
@@ -595,18 +595,18 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
|
||||
}
|
||||
|
||||
// report GA event
|
||||
sdk.SendGA4Event("hrp_run_ui", map[string]interface{}{
|
||||
go sdk.SendGA4Event("hrp_run_ui", map[string]interface{}{
|
||||
"osType": osType,
|
||||
})
|
||||
|
||||
identifer := mobileStep.Identifier
|
||||
if mobileStep.Options != nil && identifer == "" {
|
||||
identifer = mobileStep.Options.Identifier
|
||||
identifier := mobileStep.Identifier
|
||||
if mobileStep.Options != nil && identifier == "" {
|
||||
identifier = mobileStep.Options.Identifier
|
||||
}
|
||||
if len(mobileStep.Actions) != 0 && identifer == "" {
|
||||
if len(mobileStep.Actions) != 0 && identifier == "" {
|
||||
for _, action := range mobileStep.Actions {
|
||||
if action.Identifier != "" {
|
||||
identifer = action.Identifier
|
||||
identifier = action.Identifier
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -614,7 +614,7 @@ func runStepMobileUI(s *SessionRunner, step *TStep) (stepResult *StepResult, err
|
||||
|
||||
stepResult = &StepResult{
|
||||
Name: step.Name,
|
||||
Identifier: identifer,
|
||||
Identifier: identifier,
|
||||
StepType: StepType(osType),
|
||||
Success: false,
|
||||
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
|
||||
if err2 := uiDriver.ClosePopups(); err2 != nil {
|
||||
log.Error().Err(err2).Str("step", step.Name).Msg("auto handle popup failed")
|
||||
if !step.IgnorePopup && !s.IgnorePopup() {
|
||||
if err2 := uiDriver.ClosePopups(); err2 != nil {
|
||||
log.Error().Err(err2).Str("step", step.Name).Msg("auto handle popup failed")
|
||||
}
|
||||
}
|
||||
|
||||
// save attachments
|
||||
|
||||
Reference in New Issue
Block a user