diff --git a/go.mod b/go.mod index 1cc8d8c4..bfa74f35 100644 --- a/go.mod +++ b/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.26.0 golang.org/x/oauth2 v0.8.0 - golang.org/x/text v0.14.0 + golang.org/x/text v0.16.0 google.golang.org/grpc v1.57.0 - google.golang.org/protobuf v1.31.0 + google.golang.org/protobuf v1.34.2 gopkg.in/yaml.v3 v3.0.1 howett.net/plist v1.0.0 ) @@ -39,32 +39,47 @@ 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.9 // 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.4 // 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.22.0 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-plugin v1.4.10 // indirect github.com/hashicorp/yamux v0.1.1 // indirect 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.8 // 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,11 +90,15 @@ 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/mod v0.14.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/tools v0.17.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 40bce52f..24122097 100644 --- a/go.sum +++ b/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.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg= +github.com/bytedance/sonic v1.11.9/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.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= +github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= 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.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.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.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 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= @@ -163,6 +185,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= @@ -203,6 +227,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.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/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 +241,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 +261,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 +287,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 +348,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 +385,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.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= 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= @@ -377,6 +428,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -412,6 +464,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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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= @@ -432,6 +486,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -477,11 +533,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.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.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 +554,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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 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= @@ -540,6 +601,8 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -627,6 +690,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.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 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 +723,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= diff --git a/hrp/cmd/server.go b/hrp/cmd/server.go new file mode 100644 index 00000000..33c30289 --- /dev/null +++ b/hrp/cmd/server.go @@ -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") +} diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index c2f5b69b..2707569b 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.5.8 +v4.6.0 diff --git a/hrp/pkg/gadb/device.go b/hrp/pkg/gadb/device.go index f2d47e83..bb530249 100644 --- a/hrp/pkg/gadb/device.go +++ b/hrp/pkg/gadb/device.go @@ -292,6 +292,33 @@ func (d *Device) ReverseForwardKill(remoteInterface interface{}) error { return err } +func (d *Device) RunShootsCommand(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 diff --git a/hrp/pkg/server/exception.go b/hrp/pkg/server/exception.go new file mode 100644 index 00000000..dbf2dc3e --- /dev/null +++ b/hrp/pkg/server/exception.go @@ -0,0 +1,18 @@ +package server + +// 常见的错误代码和消息 +const ( + InternalServerErrorCode = 100001 + InternalServerErrorMsg = "Invalid Server Error" + + InvalidParamErrorCode = 100002 + InvalidParamErrorMsg = "Invalid %s Param" + + CodeNotFound = 1004 + MsgNotFound = "Resource not found" +) + +const ( + DeviceNotFoundCode = 110001 + DeviceNotFoundMsg = "Device %s Not Found" +) diff --git a/hrp/pkg/server/model.go b/hrp/pkg/server/model.go new file mode 100644 index 00000000..b6ac0b12 --- /dev/null +++ b/hrp/pkg/server/model.go @@ -0,0 +1,44 @@ +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 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"` +} diff --git a/hrp/pkg/server/server.go b/hrp/pkg/server/server.go new file mode 100644 index 00000000..fca459d9 --- /dev/null +++ b/hrp/pkg/server/server.go @@ -0,0 +1,514 @@ +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/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/shoots/source", parseDeviceInfo(), sourceHandler) + router.GET("/api/v1/:platform/:serial/adb/source", parseDeviceInfo(), adbSourceHandler) + router.POST("/api/v1/:platform/:serial/shoots/login", parseDeviceInfo(), loginHandler) + + 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}) + return +} + +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}) + return +} + +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}) + return +} + +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}) + return +} + +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}) + return +} + +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}) + return +} + +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}) + return +} + +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}) + return +} + +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}) + return +} + +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}) + return +} + +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())}) + return +} + +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}) + return +} + +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}) + return +} + +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 get foreground app", c.HandlerName())) + c.JSON(http.StatusInternalServerError, HttpResponse{Result: "", ErrorCode: InternalServerErrorCode, ErrorMsg: InternalServerErrorMsg}) + c.Abort() + return + } + c.JSON(http.StatusOK, HttpResponse{Result: true}) + return +} + +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.WithShoots(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() + } +} diff --git a/hrp/pkg/uixt/action.go b/hrp/pkg/uixt/action.go index 12541954..06e9deaf 100644 --- a/hrp/pkg/uixt/action.go +++ b/hrp/pkg/uixt/action.go @@ -29,6 +29,7 @@ const ( ACTION_SetClipboard ActionMethod = "set_clipboard" ACTION_GetClipboard ActionMethod = "get_clipboard" ACTION_SetIme ActionMethod = "set_ime" + ACTION_GetSource ActionMethod = "get_source" // UI validation // selectors @@ -616,6 +617,15 @@ func (dExt *DriverExt) DoAction(action MobileAction) (err error) { } 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] diff --git a/hrp/pkg/uixt/android_adb_driver.go b/hrp/pkg/uixt/android_adb_driver.go index 2dfe19dc..148749fe 100644 --- a/hrp/pkg/uixt/android_adb_driver.go +++ b/hrp/pkg/uixt/android_adb_driver.go @@ -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 _, err = ad.adbClient.RunShellCommand( "input", "keyevent", fmt.Sprintf("%d", keyCode)) @@ -507,6 +516,10 @@ 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) sourceTree(srcOpt ...SourceOption) (sourceTree *Hierarchy, err error) { source, err := ad.Source() if err != nil { @@ -693,51 +706,21 @@ func (ad *adbDriver) GetDriverResults() []*DriverResult { } 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/eval_tool", "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 } - - 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 - } - } - } - } - - return AppInfo{}, errors.Wrap(code.MobileUIAssertForegroundAppError, "get foreground app failed") + log.Info().Msg(packageInfo) + err = json.Unmarshal([]byte(strings.TrimSpace(packageInfo)), &app) + return } func (ad *adbDriver) GetFocusedPackage() (packageName string, err error) { - res, err := ad.adbClient.RunShellCommand("dumpsys", "window", "windows", "|", "grep", "-E", "'mCurrentFocus|mFocusedApp'") + res, err := ad.adbClient.RunShellCommand("dumpsys", "activity", "activities", "|", "grep", "-E", "'mResumedActivity'") if err != nil { return "", err } - match := regexp.MustCompile("mCurrentFocus.+\\s([^\\s/}]+)/[^\\s/}]+(\\.[^\\s/}]+)}").FindStringSubmatch(res) - if len(match) > 1 { - packageName = match[1] - return - } - match = regexp.MustCompile("mFocusedApp.+Record\\{.*\\s([^\\s/}]+)/([^\\s/}]+)(\\s[^\\s/}]+)*}").FindStringSubmatch(res) + match := regexp.MustCompile(`mResumedActivity:.*? (\S+)/`).FindStringSubmatch(res) if len(match) > 1 { packageName = match[1] return @@ -778,7 +761,7 @@ func (ad *adbDriver) SetIme(imeRegx string) error { currentPackage, err := ad.GetFocusedPackage() log.Info().Str("beforeFocusedPackage", focusedPackage).Str("afterFocusedPackage", currentPackage).Msg("") if err == nil && currentPackage != focusedPackage { - _ = ad.PressKeyCode(KCBack, KMEmpty) + _ = ad.PressKeyCodes(KCBack, KMEmpty) } } } diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index 20e8ffa0..a05bfb14 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -4,9 +4,11 @@ import ( "bufio" "bytes" "context" + "embed" "fmt" "os/exec" "strings" + "time" "github.com/httprunner/funplugin/myexec" "github.com/pkg/errors" @@ -25,6 +27,9 @@ var ( UIA2ServerPort = 6790 ) +//go:embed eval_tool +var evalTool embed.FS + const forwardToPrefix = "forward-to-" type AndroidDeviceOption func(*AndroidDevice) @@ -41,6 +46,12 @@ func WithUIA2(uia2On bool) AndroidDeviceOption { } } +func WithShoots(shootsOn bool) AndroidDeviceOption { + return func(device *AndroidDevice) { + device.SHOOTS = shootsOn + } +} + func WithUIA2IP(ip string) AndroidDeviceOption { return func(device *AndroidDevice) { device.UIA2IP = ip @@ -109,7 +120,14 @@ func NewAndroidDevice(options ...AndroidDeviceOption) (device *AndroidDevice, er device.d = dev device.logcat = NewAdbLogcat(device.SerialNumber) - + evalToolRaw, err := evalTool.ReadFile("eval_tool") + if err != nil { + return nil, errors.Wrap(code.LoadFileError, err.Error()) + } + err = dev.Push(bytes.NewReader(evalToolRaw), "/data/local/tmp/eval_tool", 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 } @@ -152,6 +170,7 @@ type AndroidDevice struct { logcat *AdbLogcat SerialNumber string `json:"serial,omitempty" yaml:"serial,omitempty"` UIA2 bool `json:"uia2,omitempty" yaml:"uia2,omitempty"` // use uiautomator2 + SHOOTS bool `json:"shoots,omitempty" yaml:"uia2,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"` @@ -174,7 +193,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) } @@ -182,6 +201,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.SHOOTS { + driver, err = dev.NewShootsDriver(driverOptions.capabilities) } else { driver, err = dev.NewAdbDriver() } @@ -189,7 +210,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 } @@ -226,6 +247,25 @@ func (dev *AndroidDevice) NewUSBDriver(capabilities Capabilities) (driver WebDri return uiaDriver, nil } +func (dev *AndroidDevice) NewShootsDriver(capabilities Capabilities) (driver *ShootsAndroidDriver, err error) { + localPort, err := dev.d.Forward(ShootsSocketName) + if err != nil { + return nil, errors.Wrap(code.AndroidDeviceConnectionError, + fmt.Sprintf("forward port %d->%s failed: %v", + localPort, ShootsSocketName, err)) + } + + shootsDriver, err := newShootsAndroidDriver(fmt.Sprintf("127.0.0.1:%d", localPort)) + if err != nil { + _ = dev.d.ForwardKill(localPort) + return nil, errors.Wrap(code.AndroidDeviceConnectionError, err.Error()) + } + shootsDriver.adbClient = dev.d + shootsDriver.logcat = dev.logcat + + return shootsDriver, 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) diff --git a/hrp/pkg/uixt/android_shoots_driver.go b/hrp/pkg/uixt/android_shoots_driver.go new file mode 100644 index 00000000..cfe3bb26 --- /dev/null +++ b/hrp/pkg/uixt/android_shoots_driver.go @@ -0,0 +1,235 @@ +package uixt + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "time" + + "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp/internal/json" +) + +type ShootsAndroidDriver struct { + socket net.Conn + seq int + timeout time.Duration + adbDriver +} + +const ShootsSocketName = "com.bytest.device" + +// newShootsAndroidDriver +// 创建shoots Driver address为forward后的端口格式127.0.0.1:${port} +func newShootsAndroidDriver(address string, readTimeout ...time.Duration) (*ShootsAndroidDriver, 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 + } + + return &ShootsAndroidDriver{ + socket: conn, + timeout: timeout, + }, nil +} + +func (sad *ShootsAndroidDriver) NewSession(capabilities Capabilities) (SessionInfo, error) { + return SessionInfo{}, errDriverNotImplemented +} + +func (sad *ShootsAndroidDriver) 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.RunShootsCommand(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 shoots command: %s", resultMap["Error"].(string)) + } + + return resultMap["Result"], nil +} + +func (sad *ShootsAndroidDriver) send(data []byte, readTimeout ...time.Duration) (map[string]interface{}, error) { + timeout := sad.timeout + if len(readTimeout) > 0 { + timeout = readTimeout[0] + } + _ = sad.socket.SetReadDeadline(time.Now().Add(timeout)) + + err := _send(sad.socket, append(data, '\n')) + if err != nil { + sad.close() + return nil, err + } + raw, err := _readAll(sad.socket) + if err != nil { + return nil, err + } + var result map[string]interface{} + if err := json.Unmarshal(raw, &result); err != nil { + log.Printf("error when parse json response: %s\n", raw) + return nil, err + } + return result, nil +} + +func _send(writer io.Writer, msg []byte) (err error) { + for totalSent := 0; totalSent < len(msg); { + var sent int + if sent, err = writer.Write(msg[totalSent:]); err != nil { + return err + } + if sent == 0 { + return errors.New("socket connection broken") + } + totalSent += sent + } + return +} + +func _readN(reader io.Reader, size int) (raw []byte, err error) { + raw = make([]byte, 0, size) + for len(raw) < size { + buf := make([]byte, size-len(raw)) + var n int + if n, err = io.ReadFull(reader, buf); err != nil { + return nil, err + } + if n == 0 { + return nil, errors.New("socket connection broken") + } + raw = append(raw, buf...) + } + return +} + +func _readAll(reader io.Reader) (raw []byte, err error) { + buffer := new(bytes.Buffer) + for true { + lengthBuf := make([]byte, 4) + _, err := io.ReadFull(reader, lengthBuf) + if err != nil { + if err == io.EOF { + return buffer.Bytes(), nil + } else if errors.Is(err, io.ErrUnexpectedEOF) { + err = fmt.Errorf("reached unexpected EOF, read partial data: %s %v", string(buffer.Bytes()), err) + return nil, err + } else { + return nil, err + } + } + length := binary.BigEndian.Uint32(lengthBuf) + + data, err := _readN(reader, int(length)-4) + if err != nil { + return nil, err + } + buffer.Write(data) + + } + return buffer.Bytes(), nil +} + +func (sad *ShootsAndroidDriver) DeleteSession() error { + return sad.close() +} + +func (sad *ShootsAndroidDriver) close() error { + if sad.socket != nil { + return sad.socket.Close() + } + return nil +} + +func (sad *ShootsAndroidDriver) Status() (DeviceStatus, error) { + app, err := sad.GetForegroundApp() + res, err := sad.sendCommand(app.PackageName, "Hello", nil) + if err != nil { + return DeviceStatus{}, err + } + log.Info().Msg(fmt.Sprintf("pint shoots result :%v", res)) + return DeviceStatus{}, nil +} + +func (sad *ShootsAndroidDriver) Source(srcOpt ...SourceOption) (source string, err error) { + app, err := sad.GetForegroundApp() + 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 *ShootsAndroidDriver) LoginNoneUI(packageName, phoneNumber string, 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(5 * 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 *ShootsAndroidDriver) isLogin(packageName string) (login bool, err error) { + params := map[string]interface{}{ + "ClassName": "com.ss.android.ugc.aweme.account.AccountProxyService", + "Method": "userService", + "RetType": "", + "Args": []string{}, + "CacheObject": true, + } + id, err := sad.sendCommand(packageName, "CallStaticMethod", params) + if err != nil { + return false, err + } + + params = map[string]interface{}{ + "Method": "isLogin", + "RetType": "", + "Args": []string{}, + "ObjectId": int(id.(float64)), + } + loginObj, err := sad.sendCommand(packageName, "CallMethod", params) + if err != nil { + return false, err + } + return loginObj.(bool), nil +} diff --git a/hrp/pkg/uixt/android_shoots_driver_test.go b/hrp/pkg/uixt/android_shoots_driver_test.go new file mode 100644 index 00000000..5f320497 --- /dev/null +++ b/hrp/pkg/uixt/android_shoots_driver_test.go @@ -0,0 +1,40 @@ +package uixt + +import "testing" + +var driver *ShootsAndroidDriver + +func setupAndroid(t *testing.T) { + device, err := NewAndroidDevice() + checkErr(t, err) + device.SHOOTS = true + driver, err = device.NewShootsDriver(Capabilities{}) + checkErr(t, err) +} + +func TestHello(t *testing.T) { + setupAndroid(t) + status, err := driver.Status() + if err != nil { + t.Fatal(err) + } + t.Log(status) +} + +func TestSource(t *testing.T) { + setupAndroid(t) + source, err := driver.Source() + if err != nil { + t.Fatal(err) + } + t.Log(source) +} + +func TestLogin(t *testing.T) { + setupAndroid(t) + res, err := driver.isLogin("com.ss.android.ugc.aweme") + if err != nil { + t.Fatal(err) + } + t.Log(res) +} diff --git a/hrp/pkg/uixt/android_uia2_driver.go b/hrp/pkg/uixt/android_uia2_driver.go index 337d632b..c7d7056e 100644 --- a/hrp/pkg/uixt/android_uia2_driver.go +++ b/hrp/pkg/uixt/android_uia2_driver.go @@ -250,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, diff --git a/hrp/pkg/uixt/eval_tool b/hrp/pkg/uixt/eval_tool new file mode 100644 index 00000000..52697e21 Binary files /dev/null and b/hrp/pkg/uixt/eval_tool differ diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index cab6bbde..0a924ca2 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -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 := &DriverOptions{} + 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,17 +184,19 @@ 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 } diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index 58257dc0..0817466a 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -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 @@ -526,6 +552,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 @@ -588,11 +616,15 @@ type WebDriver interface { // 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 + TapByText(text string, options ...ActionOption) error TapByTexts(actions ...TapTextAction) error diff --git a/hrp/pkg/uixt/ios_device.go b/hrp/pkg/uixt/ios_device.go index 6a0f1fbb..e8a161ca 100644 --- a/hrp/pkg/uixt/ios_device.go +++ b/hrp/pkg/uixt/ios_device.go @@ -308,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) } @@ -339,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 } diff --git a/hrp/pkg/uixt/ios_driver.go b/hrp/pkg/uixt/ios_driver.go index 681c8dea..a725b87c 100644 --- a/hrp/pkg/uixt/ios_driver.go +++ b/hrp/pkg/uixt/ios_driver.go @@ -595,6 +595,10 @@ 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...) @@ -655,6 +659,10 @@ func (wd *wdaDriver) PressButton(devBtn DeviceButton) (err error) { return } +func (wd *wdaDriver) LoginNoneUI(packageName, phoneNumber string, captcha 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")