feat: 支持shoots协议,新增UIAgent驱动接口

This commit is contained in:
余泓铮
2024-07-16 20:30:39 +08:00
parent 9cf1809ac5
commit e2a7c29acf
19 changed files with 1149 additions and 76 deletions

37
go.mod
View File

@@ -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

67
go.sum
View File

@@ -48,6 +48,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.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=

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

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

View File

@@ -1 +1 @@
v4.5.8
v4.6.0

View File

@@ -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

View File

@@ -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"
)

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

@@ -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"`
}

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

@@ -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()
}
}

View File

@@ -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]

View File

@@ -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))
@@ -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)
}
}
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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,

BIN
hrp/pkg/uixt/eval_tool Normal file

Binary file not shown.

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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")