diff --git a/README.md b/README.md index e9735b2..30bb242 100644 --- a/README.md +++ b/README.md @@ -56,12 +56,12 @@ Supports Linux, macOS and Windows - notes below - Appium test execution - each device has its Appium server proxied on a provider endpoint for easier access - Optionally Selenium Grid 4 nodes can be registered for each device Appium server - macOS - - Supports both Android and iOS + - Supports both Android / iOS - Linux - - Supports both Android and iOS < 17 + - Supports both Android / iOS < 17 && iOS >= 17.4 - Has some limitations to Appium execution with iOS devices due to actual Xcode tools being unavailable on Linux - Windows 10 - - Supports Android and iOS < 17 + - Supports Android / iOS < 17 && ios >= 17.4 - Has some limitations to Appium execution with iOS devices due to actual Xcode tools being unavailable on Windows Developed and tested on Ubuntu 18.04 LTS, Ubuntu 20.04 LTS, Windows 10, macOS Ventura 13.5.1 diff --git a/common/models/config.go b/common/models/config.go index f7c6975..c782c98 100644 --- a/common/models/config.go +++ b/common/models/config.go @@ -1,24 +1,27 @@ package models +import "github.com/danielpaulus/go-ios/ios/tunnel" + type Provider struct { - OS string `json:"os" bson:"os"` - Nickname string `json:"nickname" bson:"nickname"` - HostAddress string `json:"host_address" bson:"host_address"` - Port int `json:"port" bson:"port"` - UseSeleniumGrid bool `json:"use_selenium_grid" bson:"use_selenium_grid"` - SeleniumGrid string `json:"selenium_grid" bson:"selenium_grid"` - ProvideAndroid bool `json:"provide_android" bson:"provide_android"` - ProvideIOS bool `json:"provide_ios" bson:"provide_ios"` - WdaBundleID string `json:"wda_bundle_id" bson:"wda_bundle_id"` - WdaRepoPath string `json:"wda_repo_path" bson:"wda_repo_path"` - SupervisionPassword string `json:"supervision_password" bson:"supervision_password"` - ProviderFolder string `json:"-" bson:"-"` - LastUpdatedTimestamp int64 `json:"last_updated" bson:"last_updated"` - ProvidedDevices []Device `json:"provided_devices" bson:"provided_devices"` - WebDriverBinary string `json:"-" bson:"-"` - UseGadsIosStream bool `json:"use_gads_ios_stream" bson:"use_gads_ios_stream"` - UseCustomWDA bool `json:"use_custom_wda" bson:"use_custom_wda"` - HubAddress string `json:"hub_address" bson:"-"` + OS string `json:"os" bson:"os"` + Nickname string `json:"nickname" bson:"nickname"` + HostAddress string `json:"host_address" bson:"host_address"` + Port int `json:"port" bson:"port"` + UseSeleniumGrid bool `json:"use_selenium_grid" bson:"use_selenium_grid"` + SeleniumGrid string `json:"selenium_grid" bson:"selenium_grid"` + ProvideAndroid bool `json:"provide_android" bson:"provide_android"` + ProvideIOS bool `json:"provide_ios" bson:"provide_ios"` + WdaBundleID string `json:"wda_bundle_id" bson:"wda_bundle_id"` + WdaRepoPath string `json:"wda_repo_path" bson:"wda_repo_path"` + SupervisionPassword string `json:"supervision_password" bson:"supervision_password"` + ProviderFolder string `json:"-" bson:"-"` + LastUpdatedTimestamp int64 `json:"last_updated" bson:"last_updated"` + ProvidedDevices []Device `json:"provided_devices" bson:"provided_devices"` + WebDriverBinary string `json:"-" bson:"-"` + UseGadsIosStream bool `json:"use_gads_ios_stream" bson:"use_gads_ios_stream"` + UseCustomWDA bool `json:"use_custom_wda" bson:"use_custom_wda"` + HubAddress string `json:"hub_address" bson:"-"` + GoIOSPairRecordManager tunnel.PairRecordManager `json:"-" bson:"-"` } type ProviderData struct { diff --git a/common/models/models.go b/common/models/models.go index 86cf5f5..b87dfa3 100644 --- a/common/models/models.go +++ b/common/models/models.go @@ -4,7 +4,9 @@ import ( "context" "sync" + "github.com/Masterminds/semver" "github.com/danielpaulus/go-ios/ios" + "github.com/danielpaulus/go-ios/ios/tunnel" ) type CustomLogger interface { @@ -70,6 +72,9 @@ type Device struct { Logger CustomLogger `json:"-" bson:"-"` // CustomLogger object for the device AppiumLogger AppiumLogger `json:"-" bson:"-"` // AppiumLogger object for logging appium actions Mutex sync.Mutex `json:"-" bson:"-"` // Mutex to lock resources - especially on device reset + GoIOSTunnel tunnel.Tunnel `json:"-" bson:"-"` // Tunnel obj for go-ios handling of iOS 17.4+ + SemVer *semver.Version `json:"-" bson:"-"` // Semantic version of device for checks around the provider + InitialSetupDone bool `json:"-" bson:"-"` // On provider startup some data is prepared for devices like logger, Mongo collection, etc. This is true if all is done } type LocalHubDevice struct { diff --git a/docs/provider.md b/docs/provider.md index 01366dd..c24863d 100644 --- a/docs/provider.md +++ b/docs/provider.md @@ -42,8 +42,6 @@ Refer to the `--provider-folder` flag in [Running a provider instance](#running- - Enable [USB Debugging](#usb-debugging) on each Android device #### iOS -- Install [go-ios](#go-ios) if providing iOS devices -- Install [usbmuxd](#usbmuxd) if providing iOS devices - Prepare [WebDriverAgent](#prepare-webdriveragent-on-macos) - [Optional] [Supervise](#supervise-devices) your iOS devices @@ -56,7 +54,6 @@ Refer to the `--provider-folder` flag in [Running a provider instance](#running- - Enabled [USB Debugging](#usb-debugging) on each Android device #### iOS -- Install [go-ios](#go-ios) if providing iOS devices - Install [usbmuxd](#usbmuxd) if providing iOS devices - Prepare [WebDriverAgent](#prepare-webdriveragent-file---linux-windows) file - [Optional] [Supervise](#supervise-devices) your iOS devices @@ -74,7 +71,6 @@ Refer to the `--provider-folder` flag in [Running a provider instance](#running- - Enabled [USB Debugging](#usb-debugging) on each Android device #### iOS -- Install [go-ios](#go-ios) if providing iOS devices - Install [iTunes](#itunes) if providing iOS devices - Prepare [WebDriverAgent](#prepare-webdriveragent-file---linux-windows) file - [Optional] [Supervise](#supervise-devices) your iOS devices @@ -107,13 +103,6 @@ Installation is pretty similar for all operating systems, you just have to find `adb` (Android Debug Bridge) is mandatory when providing Android devices. You can skip installing it if no Android devices will be provided. - Install `adb` in a valid way for the provider OS. It should be available in PATH so it can be directly accessed via terminal -#### go-ios -`go-ios` is mandatory when providing iOS devices on any host OS -- Download the latest release of [go-ios](https://github.com/danielpaulus/go-ios) for the provider host OS and unzip it -- On macOS - Add it to `/usr/local/bin` with `sudo cp ios /usr/local/bin` or to PATH -- On Linux - Add it to `/usr/local/bin` with `sudo cp ios /usr/local/bin` or to PATH -- On Windows - add it to system PATH so that it is available in terminal - #### iTunes `iTunes` is needed only on Windows and mandatory when providing iOS devices. Install it through an installation package or Microsoft Store, shouldn't really matter diff --git a/go.mod b/go.mod index 8a707d2..08006d3 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,12 @@ module GADS -go 1.21 +go 1.22.0 + +toolchain go1.22.6 require ( github.com/Masterminds/semver v1.5.0 - github.com/danielpaulus/go-ios v1.0.123 + github.com/danielpaulus/go-ios v1.0.135 github.com/gin-contrib/static v0.0.1 github.com/gin-gonic/gin v1.9.0 github.com/gobwas/ws v1.4.0 @@ -16,33 +18,50 @@ require ( require ( github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/golang/snappy v0.0.1 // indirect + github.com/google/btree v1.1.2 // indirect + github.com/google/gopacket v1.1.19 // indirect + github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/grandcat/zeroconf v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.13.6 // indirect + github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect github.com/miekg/dns v1.1.61 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/onsi/ginkgo/v2 v2.9.5 // indirect + github.com/pierrec/lz4 v2.6.1+incompatible // indirect + github.com/quic-go/qtls-go1-20 v0.4.1 // indirect + github.com/quic-go/quic-go v0.40.1-0.20231203135336-87ef8ec48d55 // indirect + github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect github.com/stretchr/testify v1.8.4 // indirect + github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect + go.uber.org/mock v0.3.0 // indirect + golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect golang.org/x/mod v0.18.0 // indirect golang.org/x/net v0.26.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gvisor.dev/gvisor v0.0.0-20240405191320-0878b34101b5 // indirect howett.net/plist v1.0.1 // indirect software.sslmate.com/src/go-pkcs12 v0.4.0 // indirect ) diff --git a/go.sum b/go.sum index d12b7d7..8a07fea 100644 --- a/go.sum +++ b/go.sum @@ -8,13 +8,20 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +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/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/danielpaulus/go-ios v1.0.123 h1:Pyv+9xdFIaaGXJFQHL11l1rBde1pJhSA+N0Id1M9x+A= github.com/danielpaulus/go-ios v1.0.123/go.mod h1:tCOjUoiimx1wL8WJ7GoTF1bT9o4xAN1Fpif+vzSHXkc= +github.com/danielpaulus/go-ios v1.0.135 h1:o7EZImQ83+2yI/OMNmprspUGzkYEkNUDZxiKtE5QwGs= +github.com/danielpaulus/go-ios v1.0.135/go.mod h1:YdQ7zSYbkIM0eSMo2LHc07w9zXIYKmj/AoAAOQ8rBco= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -40,6 +47,8 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI= github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= @@ -53,15 +62,22 @@ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE= github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -85,6 +101,8 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA= github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +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/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= @@ -100,20 +118,30 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us= github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= +github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= +github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/quic-go v0.40.1-0.20231203135336-87ef8ec48d55 h1:I4N3ZRnkZPbDN935Tg8QDf8fRpHp3bZ0U0/L42jBgNE= +github.com/quic-go/quic-go v0.40.1-0.20231203135336-87ef8ec48d55/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= +github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -132,6 +160,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/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/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9 h1:aeN+ghOV0b2VCmKKO3gqnDQ8mLbpABZgRR2FVYx4ouI= +github.com/tadglines/go-pkgs v0.0.0-20210623144937-b983b20f54f9/go.mod h1:roo6cZ/uqpwKMuvPG0YmzI5+AmUiMWfjCBZpGXqbTxE= 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 v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -153,6 +183,8 @@ go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecq go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak= go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= +go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -163,6 +195,9 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= +golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= @@ -183,6 +218,7 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -207,15 +243,20 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= 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= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= +golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= @@ -233,6 +274,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gvisor.dev/gvisor v0.0.0-20240405191320-0878b34101b5 h1:DOUDfNS+CFMM46k18FRF5k/0yz5NhZYMiUQxf4xglIU= +gvisor.dev/gvisor v0.0.0-20240405191320-0878b34101b5/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/provider/devices/common.go b/provider/devices/common.go index c392488..b5d6079 100644 --- a/provider/devices/common.go +++ b/provider/devices/common.go @@ -17,7 +17,9 @@ import ( "sync" "time" + "github.com/Masterminds/semver" "github.com/danielpaulus/go-ios/ios" + "github.com/danielpaulus/go-ios/ios/tunnel" "github.com/pelletier/go-toml/v2" "GADS/common/constants" @@ -41,6 +43,13 @@ func Listener() { DBDeviceMap = getDBProviderDevices() setupDevices() + // Create pair record manager for go-ios tunnel handling of iOS 17.4+ + pm, err := tunnel.NewPairRecordManager(config.ProviderConfig.ProviderFolder) + if err != nil { + os.Exit(1) + } + config.ProviderConfig.GoIOSPairRecordManager = pm + // Start updating devices each 10 seconds in a goroutine go updateDevices() // Start updating the local devices data to the hub in a goroutine @@ -98,15 +107,24 @@ func updateProviderHub() { } } +// When provider is started and respective devices are taken from the DB, we do the initial device data setup here func setupDevices() { for _, dbDevice := range DBDeviceMap { dbDevice.ProviderState = "init" dbDevice.Connected = false dbDevice.LastUpdatedTimestamp = 0 dbDevice.IsResetting = false + dbDevice.InitialSetupDone = false dbDevice.Host = fmt.Sprintf("%s:%v", config.ProviderConfig.HostAddress, config.ProviderConfig.Port) + semver, err := semver.NewVersion(dbDevice.OSVersion) + if err != nil { + logger.ProviderLogger.Errorf("updateDevices: Failed to get semver for device `%s` - %s", dbDevice, err) + continue + } + dbDevice.SemVer = semver + // Check if a capped Appium logs collection already exists for the current device exists, err := db.CollectionExists("appium_logs", dbDevice.UDID) if err != nil { @@ -157,6 +175,7 @@ func setupDevices() { continue } dbDevice.AppiumLogger = appiumLogger + dbDevice.InitialSetupDone = true } } @@ -190,6 +209,9 @@ func updateDevices() { dbDevice.ProviderState = "init" dbDevice.IsResetting = false dbDevice.Connected = false + if dbDevice.GoIOSTunnel.Address != "" { + dbDevice.GoIOSTunnel.Close() + } } } } @@ -315,7 +337,7 @@ func setupAndroidDevice(device *models.Device) { case <-device.AppiumReadyChan: logger.ProviderLogger.LogInfo("ios_device_setup", fmt.Sprintf("Successfully started Appium for device `%v` on port %v", device.UDID, device.AppiumPort)) break - case <-time.After(60 * time.Second): + case <-time.After(30 * time.Second): logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Did not successfully start Appium for device `%v` in 60 seconds", device.UDID)) resetLocalDevice(device) return @@ -333,6 +355,12 @@ func setupIOSDevice(device *models.Device) { device.ProviderState = "preparing" logger.ProviderLogger.LogInfo("ios_device_setup", fmt.Sprintf("Running setup for device `%v`", device.UDID)) + if device.SemVer.Major() >= 17 && device.SemVer.Minor() < 4 && config.ProviderConfig.OS != "darwin" { + logger.ProviderLogger.LogInfo("ios_device_setup", fmt.Sprintf("Windows/Linux support only iOS < 17 and iOS >= 17.4, setup for device `%s` will be skipped", device.UDID)) + device.ProviderState = "init" + return + } + goIosDeviceEntry, err := ios.GetDevice(device.UDID) if err != nil { logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Could not get `go-ios` DeviceEntry for device - %v, err - %v", device.UDID, err)) @@ -342,6 +370,17 @@ func setupIOSDevice(device *models.Device) { device.GoIOSDeviceEntry = goIosDeviceEntry + // Pair the device with go-ios + err = pairIOS(device) + if err != nil { + logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Failed to pair device `%s` - %v", device.UDID, err)) + resetLocalDevice(device) + return + } + + // Mount the DDI on the device + mountDeveloperImageIOS(device) + // Get device info with go-ios to get the hardware model plistValues, err := ios.GetValuesPlist(device.GoIOSDeviceEntry) if err != nil { @@ -352,37 +391,49 @@ func setupIOSDevice(device *models.Device) { // Update hardware model got from plist device.HardwareModel = plistValues["HardwareModel"].(string) - // Mount the DDI on the device - err = mountDeveloperImageIOS(device) - if err != nil { - logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Could not mount DDI on device `%s` - %v", device.UDID, err)) - resetLocalDevice(device) - return + // If Selenium Grid is used attempt to create a TOML file for the grid connection + if config.ProviderConfig.UseSeleniumGrid { + err := createGridTOML(device) + if err != nil { + logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Selenium Grid use is enabled but couldn't create TOML for device `%s` - %s", device.UDID, err)) + resetLocalDevice(device) + return + } } - isAboveIOS17 := isAboveIOS17(device) + tunnelPort, err := providerutil.GetFreePort() if err != nil { - logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Could not determine if device `%s` is above iOS 17 - %v", device.UDID, err)) + logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Could not allocate free WebDriverAgent port for device `%v` - %v", device.UDID, err)) resetLocalDevice(device) return } + intTunnelPort, _ := strconv.Atoi(tunnelPort) + device.GoIOSDeviceEntry.UserspaceTUNPort = intTunnelPort - if isAboveIOS17 && config.ProviderConfig.OS != "darwin" { - logger.ProviderLogger.LogInfo("ios_device_setup", "Device `%s` is iOS 17+ which is not supported on Windows/Linux, setup will be skipped") - device.ProviderState = "init" - return - } + // Create userspace tunnel for devices iOS 17.4+ + if device.SemVer.Major() >= 17 && device.SemVer.Minor() >= 4 { + deviceTunnel, err := createGoIOSTunnel(device.Context, device) + if err != nil { + logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Failed to create userspace tunnel for device `%s` - %v", device.UDID, err)) + resetLocalDevice(device) + return + } + device.GoIOSTunnel = deviceTunnel - // If Selenium Grid is used attempt to create a TOML file for the grid connection - if config.ProviderConfig.UseSeleniumGrid { - err := createGridTOML(device) + // Set the ports from the tunnel on the GoIOSDeviceEntry + device.GoIOSDeviceEntry.UserspaceTUNPort = device.GoIOSTunnel.UserspaceTUNPort + device.GoIOSDeviceEntry.UserspaceTUN = device.GoIOSTunnel.UserspaceTUN + + err = goIosDeviceWithRsdProvider(device) if err != nil { - logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Selenium Grid use is enabled but couldn't create TOML for device `%s` - %s", device.UDID, err)) + logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Failed to create go-ios device entry with rsd provider for device `%s` - %v", device.UDID, err)) resetLocalDevice(device) return } } + time.Sleep(1 * time.Second) + wdaPort, err := providerutil.GetFreePort() if err != nil { logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Could not allocate free WebDriverAgent port for device `%v` - %v", device.UDID, err)) @@ -416,34 +467,28 @@ func setupIOSDevice(device *models.Device) { device.AppiumPort = appiumPort // Forward the WebDriverAgent server and stream to the host - go goIOSForward(device, device.WDAPort, "8100") - go goIOSForward(device, device.StreamPort, "9500") - go goIOSForward(device, device.WDAStreamPort, "9100") + go goIosForward(device, device.WDAPort, "8100") + go goIosForward(device, device.StreamPort, "9500") + go goIosForward(device, device.WDAStreamPort, "9100") - // If on Linux or Windows use the prebuilt and provided WebDriverAgent.ipa/app file + wdaPath := "" if config.ProviderConfig.OS != "darwin" { - wdaPath := fmt.Sprintf("%s/%s", config.ProviderConfig.ProviderFolder, config.ProviderConfig.WebDriverBinary) + wdaPath = fmt.Sprintf("%s/%s", config.ProviderConfig.ProviderFolder, config.ProviderConfig.WebDriverBinary) + } else { + wdaRepoPath := strings.TrimSuffix(config.ProviderConfig.WdaRepoPath, "/") + wdaPath = fmt.Sprintf("%s/build/Build/Products/Debug-iphoneos/WebDriverAgentRunner-Runner.app", wdaRepoPath) + } + + if device.SemVer.Major() < 17 || (device.SemVer.Major() >= 17 && device.SemVer.Minor() >= 4) { err = installAppIOS(device, wdaPath) if err != nil { logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Could not install WebDriverAgent on device `%s` - %s", device.UDID, err)) resetLocalDevice(device) return } - go startXCTestWithGoIOS(device, config.ProviderConfig.WdaBundleID, "WebDriverAgentRunner.xctest") + go runWDAGoIOS(device) } else { - if !isAboveIOS17 { - wdaRepoPath := strings.TrimSuffix(config.ProviderConfig.WdaRepoPath, "/") - wdaPath := fmt.Sprintf("%s/build/Build/Products/Debug-iphoneos/WebDriverAgentRunner-Runner.app", wdaRepoPath) - err = installAppIOS(device, wdaPath) - if err != nil { - logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Could not install WebDriverAgent on device `%s` - %s", device.UDID, err)) - resetLocalDevice(device) - return - } - go startXCTestWithGoIOS(device, config.ProviderConfig.WdaBundleID, "WebDriverAgentRunner.xctest") - } else { - go startWdaWithXcodebuild(device) - } + go startWdaWithXcodebuild(device) } go checkWebDriverAgentUp(device) @@ -475,7 +520,7 @@ func setupIOSDevice(device *models.Device) { case <-device.AppiumReadyChan: logger.ProviderLogger.LogInfo("ios_device_setup", fmt.Sprintf("Successfully started Appium for device `%v` on port %v", device.UDID, device.AppiumPort)) break - case <-time.After(60 * time.Second): + case <-time.After(30 * time.Second): logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Did not successfully start Appium for device `%v` in 60 seconds", device.UDID)) resetLocalDevice(device) return @@ -575,6 +620,9 @@ func resetLocalDevice(device *models.Device) { device.CtxCancel() device.ProviderState = "init" device.IsResetting = false + if device.GoIOSTunnel.Address != "" { + device.GoIOSTunnel.Close() + } // Free any used ports from the map where we keep them delete(providerutil.UsedPorts, device.WDAPort) @@ -815,3 +863,28 @@ func getAndroidDeviceHardwareModel(device *models.Device) { device.HardwareModel = fmt.Sprintf("%s %s", strings.TrimSpace(brand), strings.TrimSpace(model)) } + +func checkAppiumUp(device *models.Device) { + var netClient = &http.Client{ + Timeout: time.Second * 30, + } + + req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%v/status", device.AppiumPort), nil) + + loops := 0 + for { + if loops >= 30 { + return + } + resp, err := netClient.Do(req) + if err != nil { + time.Sleep(1 * time.Second) + } else { + if resp.StatusCode == http.StatusOK { + device.AppiumReadyChan <- true + return + } + } + loops++ + } +} diff --git a/provider/devices/ios.go b/provider/devices/ios.go index 1f503f1..cf8b45b 100644 --- a/provider/devices/ios.go +++ b/provider/devices/ios.go @@ -2,7 +2,6 @@ package devices import ( "bufio" - "bytes" "context" "encoding/json" "fmt" @@ -10,38 +9,38 @@ import ( "net/http" "os" "os/exec" + "strconv" "strings" - "sync" "time" "GADS/common/models" "GADS/provider/config" "GADS/provider/logger" - "github.com/Masterminds/semver" "github.com/danielpaulus/go-ios/ios" + "github.com/danielpaulus/go-ios/ios/forward" + "github.com/danielpaulus/go-ios/ios/imagemounter" + "github.com/danielpaulus/go-ios/ios/installationproxy" + "github.com/danielpaulus/go-ios/ios/testmanagerd" + "github.com/danielpaulus/go-ios/ios/tunnel" + "github.com/danielpaulus/go-ios/ios/zipconduit" ) -// Forward iOS device ports using `go-ios` CLI, for some reason using the library doesn't work properly -func goIOSForward(device *models.Device, hostPort string, devicePort string) { - cmd := exec.CommandContext(device.Context, "ios", - "forward", - hostPort, - devicePort, - fmt.Sprintf("--udid=%s", device.UDID)) - logger.ProviderLogger.LogDebug("ios_device_setup", fmt.Sprintf("goIOSForward: Forwarding port with command `%s`", cmd.Args)) - - // Start the port forward command - err := cmd.Start() +func goIosForward(device *models.Device, hostPort string, devicePort string) { + hostPortInt, _ := strconv.Atoi(hostPort) + devicePortInt, _ := strconv.Atoi(devicePort) + + cl, err := forward.Forward(device.GoIOSDeviceEntry, uint16(hostPortInt), uint16(devicePortInt)) if err != nil { - logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("goIOSForward: Error executing `ios forward` for device `%v` - %v", device.UDID, err)) + logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Failed to forward device port %s to host port %s for device `%s` - %s", devicePort, hostPort, device.UDID, err)) resetLocalDevice(device) return } - if err := cmd.Wait(); err != nil { - logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("goIOSForward: Error waiting `ios forward` to finish for device `%v` - %v", device.UDID, err)) - resetLocalDevice(device) + // Close the forward connection if device context is done + select { + case <-device.Context.Done(): + cl.Close() return } } @@ -168,105 +167,23 @@ func createWebDriverAgentSession(device *models.Device) error { return nil } -func startXCTestWithGoIOS(device *models.Device, bundleId string, xctestConfig string) { - cmd := exec.CommandContext(context.Background(), - "ios", - "runtest", - fmt.Sprintf("--bundle-id=%s", bundleId), - fmt.Sprintf("--test-runner-bundle-id=%s", bundleId), - fmt.Sprintf("--xctest-config=%s", xctestConfig), - fmt.Sprintf("--udid=%s", device.UDID)) - logger.ProviderLogger.LogDebug("device_setup", fmt.Sprintf("startWdaWithGoIOS: Starting with command `%v`", cmd.Args)) - // Create a pipe to capture the command's output - stdout, err := cmd.StdoutPipe() - if err != nil { - logger.ProviderLogger.LogError("device_setup", fmt.Sprintf("startWdaWithGoIOS: Error creating stdoutpipe while running WebDriverAgent with go-ios for device `%v` - %v", device.UDID, err)) - resetLocalDevice(device) - return - } - - // Create a pipe to capture the command's error output - stderr, err := cmd.StderrPipe() - if err != nil { - logger.ProviderLogger.LogError("device_setup", fmt.Sprintf("startWdaWithGoIOS: Error creating stderrpipe while running WebDriverAgent with go-ios for device `%v` - %v", device.UDID, err)) - resetLocalDevice(device) - return - } +func mountDeveloperImageIOS(device *models.Device) { + basedir := fmt.Sprintf("%s/devimages", config.ProviderConfig.ProviderFolder) - err = cmd.Start() + path, err := imagemounter.DownloadImageFor(device.GoIOSDeviceEntry, basedir) if err != nil { - logger.ProviderLogger.LogError("device_setup", fmt.Sprintf("startWdaWithGoIOS: Failed executing `%s` - %v", cmd.Args, err)) + logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Failed to download DDI for device `%s` to path `%s` - %s", device.UDID, basedir, err)) resetLocalDevice(device) return } - // Create a combined reader from stdout and stderr - combinedReader := io.MultiReader(stderr, stdout) - // Create a scanner to read the command's output line by line - scanner := bufio.NewScanner(combinedReader) - - for scanner.Scan() { - line := scanner.Text() - - device.Logger.LogDebug("webdriveragent", strings.TrimSpace(line)) - - //if strings.Contains(line, "ServerURLHere") { - // // device.DeviceIP = strings.Split(strings.Split(line, "//")[1], ":")[0] - // device.WdaReadyChan <- true - //} - } - - err = cmd.Wait() + err = imagemounter.MountImage(device.GoIOSDeviceEntry, path) if err != nil { - device.Logger.LogError("webdriveragent", fmt.Sprintf("startWdaWithGoIOS: Error waiting for `%s` to finish, it errored out or device `%v` was disconnected - %v", cmd.Args, device.UDID, err)) + logger.ProviderLogger.LogError("ios_device_setup", fmt.Sprintf("Failed to mount DDI on device `%s` from path `%s` - %s", device.UDID, path, err)) resetLocalDevice(device) } } -// cmd := exec.CommandContext(context.Background(), "ios", "runwda", "--bundleid=com.shamanec.iosstreamUITests.xctrunner", "--testrunnerbundleid=com.shamanec.iosstreamUITests.xctrunner", "--xctestconfig=iosstreamUITests.xctest", "--udid="+device.UDID) - -// Mount a developer disk image on an iOS device with the go-ios library -func mountDeveloperImageIOS(device *models.Device) error { - basedir := fmt.Sprintf("%s/devimages", config.ProviderConfig.ProviderFolder) - - cmd := exec.CommandContext(device.Context, "ios", "image", "auto", fmt.Sprintf("--basedir=%s", basedir)) - logger.ProviderLogger.LogInfo("ios_device_setup", fmt.Sprintf("Mounting DDI on device `%s` with command `%s`, image will be stored/found in `%s`", device.UDID, cmd.Args, basedir)) - - // Create a pipe to capture the command's output - stdout, err := cmd.StdoutPipe() - if err != nil { - return fmt.Errorf("mountDeveloperImageIOS: Failed creating stdout pipe - %s", err) - } - - // Create a pipe to capture the command's error output - stderr, err := cmd.StderrPipe() - if err != nil { - return fmt.Errorf("mountDeveloperImageIOS: Failed creating stderr pipe - %s", err) - } - - err = cmd.Start() - if err != nil { - return fmt.Errorf("mountDeveloperImageIOS: Failed starting command `%s` - %s", cmd.Args, err) - } - - // Create a combined reader from stdout and stderr - combinedReader := io.MultiReader(stderr, stdout) - // Create a scanner to read the command's output line by line - scanner := bufio.NewScanner(combinedReader) - - for scanner.Scan() { - //line := scanner.Text() - //fmt.Println(line) - } - - err = cmd.Wait() - if err != nil { - return fmt.Errorf("mountDeveloperImageIOS: Failed to run command to mount DDI - %s", err) - } - - return nil -} - // Pair an iOS device with host with/without supervision func pairIOS(device *models.Device) error { logger.ProviderLogger.LogInfo("ios_device_setup", fmt.Sprintf("Pairing device `%s`", device.UDID)) @@ -289,78 +206,36 @@ func pairIOS(device *models.Device) error { return nil } -// Get all installed apps on an iOS device func GetInstalledAppsIOS(device *models.Device) []string { var installedApps []string - cmd := exec.CommandContext(device.Context, "ios", "apps", "--udid="+device.UDID) - - device.InstalledApps = []string{} - - var outBuffer bytes.Buffer - cmd.Stdout = &outBuffer - if err := cmd.Run(); err != nil { - device.Logger.LogError("get_installed_apps", fmt.Sprintf("GetInstalledAppsIOS: Failed executing `%s` to get installed apps - %v", cmd.Args, err)) + svc, err := installationproxy.New(device.GoIOSDeviceEntry) + if err != nil { + logger.ProviderLogger.LogError("get_installed_apps", fmt.Sprintf("Failed to create installation proxy connection for device `%s` when getting installed apps - %s", device.UDID, err)) return installedApps } - // Get the command output json string - jsonString := strings.TrimSpace(outBuffer.String()) - - var appsData []struct { - BundleID string `json:"CFBundleIdentifier"` - } - - err := json.Unmarshal([]byte(jsonString), &appsData) + response, err := svc.BrowseUserApps() if err != nil { - device.Logger.LogError("get_installed_apps", fmt.Sprintf("GetInstalledAppsIOS: Error unmarshalling `%s` output json - %v", cmd.Args, err)) + logger.ProviderLogger.LogError("get_installed_apps", fmt.Sprintf("Failed to get installed apsp for device `%s` - %s", device.UDID, err)) return installedApps } - var mu sync.RWMutex - mu.Lock() - defer mu.Unlock() - for _, appData := range appsData { - installedApps = append(installedApps, appData.BundleID) + for _, appInfo := range response { + installedApps = append(installedApps, appInfo.CFBundleIdentifier) } return installedApps } -// To use for iOS 17+ when stable -func StartIOSTunnel() { - cmd := exec.CommandContext(context.Background(), "ios", "tunnel", "start") - - // Create a pipe to capture the command's output - stdout, err := cmd.StdoutPipe() - if err != nil { - } - - // Create a pipe to capture the command's error output - stderr, err := cmd.StderrPipe() - if err != nil { - } - - err = cmd.Start() +func uninstallAppIOS(device *models.Device, bundleID string) error { + svc, err := installationproxy.New(device.GoIOSDeviceEntry) if err != nil { + device.Logger.LogError("uninstall_app", fmt.Sprintf("uninstallAppIOS: Failed creating installation proxy connection - %v", bundleID, err)) + return err } - - // Create a combined reader from stdout and stderr - combinedReader := io.MultiReader(stderr, stdout) - // Create a scanner to read the command's output line by line - scanner := bufio.NewScanner(combinedReader) - - for scanner.Scan() { - line := scanner.Text() - fmt.Println(line) - } -} - -// Uninstall an app on an iOS device by bundle identifier -func uninstallAppIOS(device *models.Device, bundleID string) error { - cmd := exec.CommandContext(device.Context, "ios", "uninstall", bundleID, "--udid="+device.UDID) - err := cmd.Run() + err = svc.Uninstall(bundleID) if err != nil { - device.Logger.LogError("uninstall_app", fmt.Sprintf("uninstallAppIOS: Failed executing `%s` - %v", cmd.Args, err)) + device.Logger.LogError("uninstall_app", fmt.Sprintf("uninstallAppIOS: Failed uninstalling app with bundleID `%s` - %v", bundleID, err)) return err } @@ -378,53 +253,20 @@ func installAppIOS(device *models.Device, appPath string) error { appPath = strings.TrimPrefix(appPath, "./") } - if config.ProviderConfig.OS == "darwin" && isAboveIOS16(device) { - cmd := exec.CommandContext(device.Context, - "xcrun", - "devicectl", - "device", - "install", - "app", - "--device", - device.UDID, - appPath, - ) - logger.ProviderLogger.LogInfo("install_app_ios", fmt.Sprintf("Attempting to install app `%s` on device `%s` with command `%s`", appPath, device.UDID, cmd.Args)) - if err := cmd.Run(); err != nil { - return err - } - } else { - cmd := exec.CommandContext(device.Context, - "ios", - "install", - fmt.Sprintf("--path=%s", appPath), - fmt.Sprintf("--udid=%s", device.UDID), - ) - logger.ProviderLogger.LogInfo("install_app_ios", fmt.Sprintf("Attempting to install app `%s` on device `%s` with command `%s`", appPath, device.UDID, cmd.Args)) - if err := cmd.Run(); err != nil { - device.Logger.LogError("install_app_ios", fmt.Sprintf("Failed executing `%s` - %v", cmd.Args, err)) - return err - } + logger.ProviderLogger.LogInfo("install_app_ios", fmt.Sprintf("Attempting to install app `%s` on device `%s`", appPath, device.UDID)) + conn, err := zipconduit.New(device.GoIOSDeviceEntry) + if err != nil { + logger.ProviderLogger.LogInfo("install_app_ios", fmt.Sprintf("Failed to create zipconduit connection when installing app `%s` on device `%s`", appPath, device.UDID)) + return err } - return nil -} - -// Check if a device is above iOS 17 -func isAboveIOS17(device *models.Device) bool { - deviceOSVersion, _ := semver.NewVersion(device.OSVersion) + err = conn.SendFile(appPath) - return deviceOSVersion.Major() >= 17 -} - -func isAboveIOS16(device *models.Device) bool { - deviceOSVersion, _ := semver.NewVersion(device.OSVersion) - - return deviceOSVersion.Major() >= 16 + return nil } func checkWebDriverAgentUp(device *models.Device) { var netClient = &http.Client{ - Timeout: time.Second * 120, + Timeout: time.Second * 30, } req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%v/status", device.WDAPort), nil) @@ -447,27 +289,48 @@ func checkWebDriverAgentUp(device *models.Device) { } } -func checkAppiumUp(device *models.Device) { - var netClient = &http.Client{ - Timeout: time.Second * 120, +// Only for iOS 17.4+ +func createGoIOSTunnel(ctx context.Context, device *models.Device) (tunnel.Tunnel, error) { + tun, err := tunnel.ConnectUserSpaceTunnelLockdown(device.GoIOSDeviceEntry, device.GoIOSDeviceEntry.UserspaceTUNPort) + tun.UserspaceTUN = true + + tun.UserspaceTUNPort = device.GoIOSDeviceEntry.UserspaceTUNPort + return tun, err +} + +func goIosDeviceWithRsdProvider(device *models.Device) error { + var err error + rsdService, err := ios.NewWithAddrPort(device.GoIOSTunnel.Address, device.GoIOSTunnel.RsdPort, device.GoIOSDeviceEntry) + if err != nil { + return err + } + defer rsdService.Close() + rsdProvider, err := rsdService.Handshake() + if err != nil { + return err + } + newEntry, err := ios.GetDeviceWithAddress(device.UDID, device.GoIOSTunnel.Address, rsdProvider) + newEntry.UserspaceTUN = device.GoIOSDeviceEntry.UserspaceTUN + newEntry.UserspaceTUNPort = device.GoIOSDeviceEntry.UserspaceTUNPort + device.GoIOSDeviceEntry = newEntry + if err != nil { + return err } - req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("http://localhost:%v/status", device.AppiumPort), nil) + return nil +} - loops := 0 - for { - if loops >= 30 { - return - } - resp, err := netClient.Do(req) - if err != nil { - time.Sleep(1 * time.Second) - } else { - if resp.StatusCode == http.StatusOK { - device.AppiumReadyChan <- true - return - } - } - loops++ +func runWDAGoIOS(device *models.Device) { + _, err := testmanagerd.RunXCUITest( + config.ProviderConfig.WdaBundleID, + config.ProviderConfig.WdaBundleID, + "WebDriverAgentRunner.xctest", + device.GoIOSDeviceEntry, + nil, + nil, + nil, + testmanagerd.NewTestListener(io.Discard, io.Discard, os.TempDir())) + if err != nil { + resetLocalDevice(device) } } diff --git a/provider/provider.go b/provider/provider.go index 2a08cda..a72a92d 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -106,11 +106,6 @@ func StartProvider(flags *pflag.FlagSet) { } if config.ProviderConfig.ProvideIOS { - // Check if the `go-ios` binary is available on PATH as explained in the setup readme - if !providerutil.GoIOSAvailable() { - log.Fatal("`go-ios` is not available, you need to set it up on the host as explained in the readme") - } - // If on Linux or Windows and iOS devices provision enabled check for WebDriverAgent.ipa/app if config.ProviderConfig.OS != "darwin" { logger.ProviderLogger.LogInfo( diff --git a/provider/providerutil/providerutil.go b/provider/providerutil/providerutil.go index 7f94b01..2032723 100644 --- a/provider/providerutil/providerutil.go +++ b/provider/providerutil/providerutil.go @@ -87,19 +87,6 @@ func AppiumAvailable() bool { return true } -// Check if go-ios binary is available -func GoIOSAvailable() bool { - logger.ProviderLogger.LogInfo("provider_setup", "Checking if go-ios binary is set up and available on the host PATH") - - cmd := exec.Command("ios", "-h") - err := cmd.Run() - if err != nil { - logger.ProviderLogger.LogDebug("provider_setup", fmt.Sprintf("goIOSAvailable: go-ios is not available on host or command failed - %s", err)) - return false - } - return true -} - // Build WebDriverAgent for testing with `xcodebuild` func BuildWebDriverAgent() error { cmd := exec.Command("xcodebuild", "-project", "WebDriverAgent.xcodeproj", "-scheme", "WebDriverAgentRunner", "-destination", "generic/platform=iOS", "build-for-testing", "-derivedDataPath", "./build")