From f4af2d131a69d3f6bbf73be2065212c6f05f4bae Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 15 Dec 2022 17:04:58 +0800 Subject: [PATCH] feat: capture pcap file for iOS --- docs/CHANGELOG.md | 1 + examples/worldcup/main_test.go | 5 ++ hrp/pkg/gidevice/device.go | 24 ++++------ hrp/pkg/gidevice/idevice.go | 3 +- hrp/pkg/gidevice/perfd_test.go | 2 +- hrp/pkg/uixt/android_device.go | 20 ++++++-- hrp/pkg/uixt/ext.go | 9 ++-- hrp/pkg/uixt/interface.go | 5 +- hrp/pkg/uixt/ios_device.go | 84 ++++++++++++++++++++++++++++++++-- hrp/pkg/uixt/opencv.go | 9 +--- hrp/pkg/uixt/opencv_off.go | 4 +- hrp/runner.go | 1 + hrp/step_mobile_ui.go | 2 +- 13 files changed, 130 insertions(+), 39 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 6ce849b5..0d3afe7c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -8,6 +8,7 @@ - feat: run xctest before start ios automation - feat: run step with specified loop times - feat: add options for FindTexts +- feat: capture pcap file for iOS - refactor: move all UI APIs to uixt pkg - docs: add examples for UI APIs diff --git a/examples/worldcup/main_test.go b/examples/worldcup/main_test.go index 314b94a5..ad194769 100644 --- a/examples/worldcup/main_test.go +++ b/examples/worldcup/main_test.go @@ -60,6 +60,11 @@ func TestIOSDouyinWorldCupLive(t *testing.T) { uixt.WithWDAPort(8700), uixt.WithWDAMjpegPort(8800), uixt.WithXCTest("com.gtf.wda.runner.xctrunner"), + // uixt.WithIOSPerfOptions( + // uixt.WithIOSPerfNetwork(true), + // uixt.WithIOSPerfBundleID("com.ss.iphone.ugc.Aweme"), + // ), + uixt.WithIOSPcapOn(true), ), TestSteps: []hrp.IStep{ hrp.NewStep("启动抖音"). diff --git a/hrp/pkg/gidevice/device.go b/hrp/pkg/gidevice/device.go index 00f79783..3ebbe844 100644 --- a/hrp/pkg/gidevice/device.go +++ b/hrp/pkg/gidevice/device.go @@ -549,24 +549,16 @@ func (d *device) GetInterfaceOrientation() (orientation libimobiledevice.Orienta return } -func (d *device) PcapdService() (pcapd Pcapd, err error) { - // if d.pcapd != nil { - // return d.pcapd, nil - // } - if _, err = d.lockdownService(); err != nil { - return nil, err +func (d *device) PcapStart() (lines <-chan []byte, err error) { + if d.pcapd == nil { + if _, err = d.lockdownService(); err != nil { + return nil, err + } + if d.pcapd, err = d.lockdown.PcapdService(); err != nil { + return nil, err + } } - if d.pcapd, err = d.lockdown.PcapdService(); err != nil { - return nil, err - } - pcapd = d.pcapd - return -} -func (d *device) Pcap() (lines <-chan []byte, err error) { - if _, err = d.PcapdService(); err != nil { - return nil, err - } return d.pcapd.Packet(), nil } diff --git a/hrp/pkg/gidevice/idevice.go b/hrp/pkg/gidevice/idevice.go index 7398ff5b..dfff07ae 100644 --- a/hrp/pkg/gidevice/idevice.go +++ b/hrp/pkg/gidevice/idevice.go @@ -61,8 +61,7 @@ type Device interface { Syslog() (lines <-chan string, err error) SyslogStop() - PcapdService() (pcapd Pcapd, err error) - Pcap() (packet <-chan []byte, err error) + PcapStart() (packet <-chan []byte, err error) PcapStop() Reboot() error diff --git a/hrp/pkg/gidevice/perfd_test.go b/hrp/pkg/gidevice/perfd_test.go index 4c340332..646d829f 100644 --- a/hrp/pkg/gidevice/perfd_test.go +++ b/hrp/pkg/gidevice/perfd_test.go @@ -163,7 +163,7 @@ func TestPerfAll(t *testing.T) { func TestPcap(t *testing.T) { setupLockdownSrv(t) - data, err := dev.Pcap() + data, err := dev.PcapStart() if err != nil { t.Fatal(err) } diff --git a/hrp/pkg/uixt/android_device.go b/hrp/pkg/uixt/android_device.go index 6391dfac..d1e6f0c7 100644 --- a/hrp/pkg/uixt/android_device.go +++ b/hrp/pkg/uixt/android_device.go @@ -134,10 +134,14 @@ func (dev *AndroidDevice) NewDriver(capabilities Capabilities) (driverExt *Drive return nil, errors.Wrap(err, "failed to init UIA driver") } - driverExt, err = Extend(driver) + driverExt, err = NewDriverExt(dev, driver) + if err != nil { + return nil, err + } + err = driverExt.extendCV() if err != nil { return nil, errors.Wrap(code.MobileUIDriverError, - fmt.Sprintf("init UIA driver failed: %v", err)) + fmt.Sprintf("extend OpenCV failed: %v", err)) } if dev.LogOn { @@ -147,7 +151,6 @@ func (dev *AndroidDevice) NewDriver(capabilities Capabilities) (driverExt *Drive } } - driverExt.UUID = dev.UUID() return driverExt, nil } @@ -189,6 +192,17 @@ func (dev *AndroidDevice) NewHTTPDriver(capabilities Capabilities) (driver *uiaD return driver, nil } +func (dev *AndroidDevice) StartPcap() error { + // TODO + return nil +} + +// StopPcap stops pcap monitor and returns the saved pcap file path +func (dev *AndroidDevice) StopPcap() string { + // TODO + return "" +} + func getFreePort() (int, error) { addr, err := net.ResolveTCPAddr("tcp", "localhost:0") if err != nil { diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index 903c3558..78d13f06 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -199,7 +199,7 @@ func WithThreshold(threshold float64) CVOption { } type DriverExt struct { - UUID string // ios udid or android serial + Device Device Driver WebDriver windowSize Size frame *bytes.Buffer @@ -214,8 +214,11 @@ type DriverExt struct { CVArgs } -func extend(driver WebDriver) (dExt *DriverExt, err error) { - dExt = &DriverExt{Driver: driver} +func NewDriverExt(device Device, driver WebDriver) (dExt *DriverExt, err error) { + dExt = &DriverExt{ + Device: device, + Driver: driver, + } dExt.doneMjpegStream = make(chan bool, 1) // get device window size diff --git a/hrp/pkg/uixt/interface.go b/hrp/pkg/uixt/interface.go index 59c0b643..5b81fbe3 100644 --- a/hrp/pkg/uixt/interface.go +++ b/hrp/pkg/uixt/interface.go @@ -927,8 +927,11 @@ func NewData(data map[string]interface{}, options ...DataOption) map[string]inte // current implemeted device: IOSDevice, AndroidDevice type Device interface { - UUID() string + UUID() string // ios udid or android serial NewDriver(capabilities Capabilities) (driverExt *DriverExt, err error) + + StartPcap() error + StopPcap() string } // WebDriver defines methods supported by WebDriver drivers. diff --git a/hrp/pkg/uixt/ios_device.go b/hrp/pkg/uixt/ios_device.go index 6236a3ee..d9e68a0a 100644 --- a/hrp/pkg/uixt/ios_device.go +++ b/hrp/pkg/uixt/ios_device.go @@ -14,6 +14,7 @@ import ( "net/http" "net/url" "os" + "path/filepath" "regexp" "strconv" "strings" @@ -130,6 +131,12 @@ func WithIOSPerfOptions(options ...gidevice.PerfOption) IOSDeviceOption { } } +func WithIOSPcapOn(pcapOn bool) IOSDeviceOption { + return func(device *IOSDevice) { + device.PcapOn = pcapOn + } +} + func IOSDevices(udid ...string) (devices []gidevice.Device, err error) { var usbmux gidevice.Usbmux if usbmux, err = gidevice.NewUsbmux(); err != nil { @@ -176,6 +183,9 @@ func GetIOSDeviceOptions(dev *IOSDevice) (deviceOptions []IOSDeviceOption) { if dev.PerfOptions != nil { deviceOptions = append(deviceOptions, WithIOSPerfOptions(dev.perfOpitons()...)) } + if dev.PcapOn { + deviceOptions = append(deviceOptions, WithIOSPcapOn(true)) + } if dev.XCTestBundleID != "" { deviceOptions = append(deviceOptions, WithXCTest(dev.XCTestBundleID)) } @@ -243,6 +253,7 @@ type IOSDevice struct { Port int `json:"port,omitempty" yaml:"port,omitempty"` // WDA remote port MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"` + PcapOn bool `json:"pcap_on,omitempty" yaml:"pcap_on,omitempty"` XCTestBundleID string `json:"xctest_bundle_id,omitempty" yaml:"xctest_bundle_id,omitempty"` // switch to iOS springboard before init WDA session @@ -252,6 +263,10 @@ type IOSDevice struct { SnapshotMaxDepth int `json:"snapshot_max_depth,omitempty" yaml:"snapshot_max_depth,omitempty"` AcceptAlertButtonSelector string `json:"accept_alert_button_selector,omitempty" yaml:"accept_alert_button_selector,omitempty"` DismissAlertButtonSelector string `json:"dismiss_alert_button_selector,omitempty" yaml:"dismiss_alert_button_selector,omitempty"` + + // pcap monitor + pcapStop chan struct{} // stop pcap monitor + pcapFile string // saved pcap file path } func (dev *IOSDevice) UUID() string { @@ -284,10 +299,14 @@ func (dev *IOSDevice) NewDriver(capabilities Capabilities) (driverExt *DriverExt } } - driverExt, err = Extend(driver) + driverExt, err = NewDriverExt(dev, driver) + if err != nil { + return nil, err + } + err = driverExt.extendCV() if err != nil { return nil, errors.Wrap(code.MobileUIDriverError, - fmt.Sprintf("extend WebDriver failed: %v", err)) + fmt.Sprintf("extend OpenCV failed: %v", err)) } settings, err := driverExt.Driver.SetAppiumSettings(map[string]interface{}{ "snapshotMaxDepth": dev.SnapshotMaxDepth, @@ -327,10 +346,69 @@ func (dev *IOSDevice) NewDriver(capabilities Capabilities) (driverExt *DriverExt }() } - driverExt.UUID = dev.UUID() + if dev.PcapOn { + if err := dev.StartPcap(); err != nil { + return nil, err + } + } + return driverExt, nil } +func (dev *IOSDevice) StartPcap() error { + packets, err := dev.d.PcapStart() + if err != nil { + return err + } + + rootDir, _ := os.Getwd() + dev.pcapFile = filepath.Join(rootDir, + fmt.Sprintf("dump_%s.pcap", time.Now().Format("20060102150405"))) + + log.Info().Str("pcapFile", dev.pcapFile).Msg("create pcap file") + file, err := os.OpenFile(dev.pcapFile, + os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o755) + if err != nil { + return err + } + + // pcap magic number + // https://www.ietf.org/archive/id/draft-gharris-opsawg-pcap-01.html + _, _ = file.Write([]byte{ + 0xd4, 0xc3, 0xb2, 0xa1, 0x02, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + }) + + dev.pcapStop = make(chan struct{}) + // start pcap monitor + go func() { + for { + select { + case <-dev.pcapStop: + file.Close() + dev.d.PcapStop() + return + case d := <-packets: + _, err = file.Write(d) + if err != nil { + log.Error().Err(err).Msg("write pcap data failed") + } + } + } + }() + return nil +} + +// StopPcap stops pcap monitor and returns the saved pcap file path +func (dev *IOSDevice) StopPcap() string { + if dev.pcapStop == nil { + return "" + } + close(dev.pcapStop) + return dev.pcapFile +} + func (dev *IOSDevice) forward(localPort, remotePort int) error { log.Info().Int("localPort", localPort).Int("remotePort", remotePort). Str("udid", dev.UDID).Msg("forward tcp port") diff --git a/hrp/pkg/uixt/opencv.go b/hrp/pkg/uixt/opencv.go index b8f7fe8d..6a5b3bb8 100644 --- a/hrp/pkg/uixt/opencv.go +++ b/hrp/pkg/uixt/opencv.go @@ -42,17 +42,12 @@ const ( DmNotMatch ) -// Extend 获得扩展后的 Driver, +// extendCV 获得扩展后的 Driver, // 并指定匹配阀值, // 获取当前设备的 Scale, // 默认匹配模式为 TmCcoeffNormed, // 默认关闭 OpenCV 匹配值计算后的输出 -func Extend(driver WebDriver, options ...CVOption) (dExt *DriverExt, err error) { - dExt, err = extend(driver) - if err != nil { - return nil, err - } - +func (dExt *DriverExt) extendCV(options ...CVOption) (err error) { for _, option := range options { option(&dExt.CVArgs) } diff --git a/hrp/pkg/uixt/opencv_off.go b/hrp/pkg/uixt/opencv_off.go index ade2c4cd..445c4350 100644 --- a/hrp/pkg/uixt/opencv_off.go +++ b/hrp/pkg/uixt/opencv_off.go @@ -8,8 +8,8 @@ import ( "github.com/rs/zerolog/log" ) -func Extend(driver WebDriver, options ...CVOption) (dExt *DriverExt, err error) { - return extend(driver) +func (dExt *DriverExt) extendCV(options ...CVOption) (err error) { + return nil } func (dExt *DriverExt) FindAllImageRect(search string) (rects []image.Rectangle, err error) { diff --git a/hrp/runner.go b/hrp/runner.go index 561d509e..d97cf93c 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -636,6 +636,7 @@ func (r *SessionRunner) GetSummary() (*TestCaseSummary, error) { // stop performance monitor logs["performance"] = client.GetPerfData() + logs["pcap"] = client.Device.StopPcap() caseSummary.Logs = append(caseSummary.Logs, logs) } diff --git a/hrp/step_mobile_ui.go b/hrp/step_mobile_ui.go index e56f16f9..3e1124c2 100644 --- a/hrp/step_mobile_ui.go +++ b/hrp/step_mobile_ui.go @@ -514,7 +514,7 @@ func (r *HRPRunner) initUIClient(uuid string, osType string) (client *uixt.Drive if r.uiClients == nil { r.uiClients = make(map[string]*uixt.DriverExt) } - r.uiClients[client.UUID] = client + r.uiClients[client.Device.UUID()] = client return client, nil }