mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-12 16:01:27 +08:00
feat: capture pcap file for iOS
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
- feat: run xctest before start ios automation
|
- feat: run xctest before start ios automation
|
||||||
- feat: run step with specified loop times
|
- feat: run step with specified loop times
|
||||||
- feat: add options for FindTexts
|
- feat: add options for FindTexts
|
||||||
|
- feat: capture pcap file for iOS
|
||||||
- refactor: move all UI APIs to uixt pkg
|
- refactor: move all UI APIs to uixt pkg
|
||||||
- docs: add examples for UI APIs
|
- docs: add examples for UI APIs
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ func TestIOSDouyinWorldCupLive(t *testing.T) {
|
|||||||
uixt.WithWDAPort(8700),
|
uixt.WithWDAPort(8700),
|
||||||
uixt.WithWDAMjpegPort(8800),
|
uixt.WithWDAMjpegPort(8800),
|
||||||
uixt.WithXCTest("com.gtf.wda.runner.xctrunner"),
|
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{
|
TestSteps: []hrp.IStep{
|
||||||
hrp.NewStep("启动抖音").
|
hrp.NewStep("启动抖音").
|
||||||
|
|||||||
@@ -549,24 +549,16 @@ func (d *device) GetInterfaceOrientation() (orientation libimobiledevice.Orienta
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *device) PcapdService() (pcapd Pcapd, err error) {
|
func (d *device) PcapStart() (lines <-chan []byte, err error) {
|
||||||
// if d.pcapd != nil {
|
if d.pcapd == nil {
|
||||||
// return d.pcapd, nil
|
if _, err = d.lockdownService(); err != nil {
|
||||||
// }
|
return nil, err
|
||||||
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
|
return d.pcapd.Packet(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,8 +61,7 @@ type Device interface {
|
|||||||
Syslog() (lines <-chan string, err error)
|
Syslog() (lines <-chan string, err error)
|
||||||
SyslogStop()
|
SyslogStop()
|
||||||
|
|
||||||
PcapdService() (pcapd Pcapd, err error)
|
PcapStart() (packet <-chan []byte, err error)
|
||||||
Pcap() (packet <-chan []byte, err error)
|
|
||||||
PcapStop()
|
PcapStop()
|
||||||
|
|
||||||
Reboot() error
|
Reboot() error
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ func TestPerfAll(t *testing.T) {
|
|||||||
func TestPcap(t *testing.T) {
|
func TestPcap(t *testing.T) {
|
||||||
setupLockdownSrv(t)
|
setupLockdownSrv(t)
|
||||||
|
|
||||||
data, err := dev.Pcap()
|
data, err := dev.PcapStart()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,10 +134,14 @@ func (dev *AndroidDevice) NewDriver(capabilities Capabilities) (driverExt *Drive
|
|||||||
return nil, errors.Wrap(err, "failed to init UIA driver")
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(code.MobileUIDriverError,
|
return nil, errors.Wrap(code.MobileUIDriverError,
|
||||||
fmt.Sprintf("init UIA driver failed: %v", err))
|
fmt.Sprintf("extend OpenCV failed: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if dev.LogOn {
|
if dev.LogOn {
|
||||||
@@ -147,7 +151,6 @@ func (dev *AndroidDevice) NewDriver(capabilities Capabilities) (driverExt *Drive
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
driverExt.UUID = dev.UUID()
|
|
||||||
return driverExt, nil
|
return driverExt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +192,17 @@ func (dev *AndroidDevice) NewHTTPDriver(capabilities Capabilities) (driver *uiaD
|
|||||||
return driver, nil
|
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) {
|
func getFreePort() (int, error) {
|
||||||
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ func WithThreshold(threshold float64) CVOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DriverExt struct {
|
type DriverExt struct {
|
||||||
UUID string // ios udid or android serial
|
Device Device
|
||||||
Driver WebDriver
|
Driver WebDriver
|
||||||
windowSize Size
|
windowSize Size
|
||||||
frame *bytes.Buffer
|
frame *bytes.Buffer
|
||||||
@@ -214,8 +214,11 @@ type DriverExt struct {
|
|||||||
CVArgs
|
CVArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func extend(driver WebDriver) (dExt *DriverExt, err error) {
|
func NewDriverExt(device Device, driver WebDriver) (dExt *DriverExt, err error) {
|
||||||
dExt = &DriverExt{Driver: driver}
|
dExt = &DriverExt{
|
||||||
|
Device: device,
|
||||||
|
Driver: driver,
|
||||||
|
}
|
||||||
dExt.doneMjpegStream = make(chan bool, 1)
|
dExt.doneMjpegStream = make(chan bool, 1)
|
||||||
|
|
||||||
// get device window size
|
// get device window size
|
||||||
|
|||||||
@@ -927,8 +927,11 @@ func NewData(data map[string]interface{}, options ...DataOption) map[string]inte
|
|||||||
|
|
||||||
// current implemeted device: IOSDevice, AndroidDevice
|
// current implemeted device: IOSDevice, AndroidDevice
|
||||||
type Device interface {
|
type Device interface {
|
||||||
UUID() string
|
UUID() string // ios udid or android serial
|
||||||
NewDriver(capabilities Capabilities) (driverExt *DriverExt, err error)
|
NewDriver(capabilities Capabilities) (driverExt *DriverExt, err error)
|
||||||
|
|
||||||
|
StartPcap() error
|
||||||
|
StopPcap() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebDriver defines methods supported by WebDriver drivers.
|
// WebDriver defines methods supported by WebDriver drivers.
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"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) {
|
func IOSDevices(udid ...string) (devices []gidevice.Device, err error) {
|
||||||
var usbmux gidevice.Usbmux
|
var usbmux gidevice.Usbmux
|
||||||
if usbmux, err = gidevice.NewUsbmux(); err != nil {
|
if usbmux, err = gidevice.NewUsbmux(); err != nil {
|
||||||
@@ -176,6 +183,9 @@ func GetIOSDeviceOptions(dev *IOSDevice) (deviceOptions []IOSDeviceOption) {
|
|||||||
if dev.PerfOptions != nil {
|
if dev.PerfOptions != nil {
|
||||||
deviceOptions = append(deviceOptions, WithIOSPerfOptions(dev.perfOpitons()...))
|
deviceOptions = append(deviceOptions, WithIOSPerfOptions(dev.perfOpitons()...))
|
||||||
}
|
}
|
||||||
|
if dev.PcapOn {
|
||||||
|
deviceOptions = append(deviceOptions, WithIOSPcapOn(true))
|
||||||
|
}
|
||||||
if dev.XCTestBundleID != "" {
|
if dev.XCTestBundleID != "" {
|
||||||
deviceOptions = append(deviceOptions, WithXCTest(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
|
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
|
MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` // WDA remote MJPEG port
|
||||||
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
|
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"`
|
XCTestBundleID string `json:"xctest_bundle_id,omitempty" yaml:"xctest_bundle_id,omitempty"`
|
||||||
|
|
||||||
// switch to iOS springboard before init WDA session
|
// 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"`
|
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"`
|
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"`
|
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 {
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(code.MobileUIDriverError,
|
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{}{
|
settings, err := driverExt.Driver.SetAppiumSettings(map[string]interface{}{
|
||||||
"snapshotMaxDepth": dev.SnapshotMaxDepth,
|
"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
|
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 {
|
func (dev *IOSDevice) forward(localPort, remotePort int) error {
|
||||||
log.Info().Int("localPort", localPort).Int("remotePort", remotePort).
|
log.Info().Int("localPort", localPort).Int("remotePort", remotePort).
|
||||||
Str("udid", dev.UDID).Msg("forward tcp port")
|
Str("udid", dev.UDID).Msg("forward tcp port")
|
||||||
|
|||||||
@@ -42,17 +42,12 @@ const (
|
|||||||
DmNotMatch
|
DmNotMatch
|
||||||
)
|
)
|
||||||
|
|
||||||
// Extend 获得扩展后的 Driver,
|
// extendCV 获得扩展后的 Driver,
|
||||||
// 并指定匹配阀值,
|
// 并指定匹配阀值,
|
||||||
// 获取当前设备的 Scale,
|
// 获取当前设备的 Scale,
|
||||||
// 默认匹配模式为 TmCcoeffNormed,
|
// 默认匹配模式为 TmCcoeffNormed,
|
||||||
// 默认关闭 OpenCV 匹配值计算后的输出
|
// 默认关闭 OpenCV 匹配值计算后的输出
|
||||||
func Extend(driver WebDriver, options ...CVOption) (dExt *DriverExt, err error) {
|
func (dExt *DriverExt) extendCV(options ...CVOption) (err error) {
|
||||||
dExt, err = extend(driver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(&dExt.CVArgs)
|
option(&dExt.CVArgs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Extend(driver WebDriver, options ...CVOption) (dExt *DriverExt, err error) {
|
func (dExt *DriverExt) extendCV(options ...CVOption) (err error) {
|
||||||
return extend(driver)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dExt *DriverExt) FindAllImageRect(search string) (rects []image.Rectangle, err error) {
|
func (dExt *DriverExt) FindAllImageRect(search string) (rects []image.Rectangle, err error) {
|
||||||
|
|||||||
@@ -636,6 +636,7 @@ func (r *SessionRunner) GetSummary() (*TestCaseSummary, error) {
|
|||||||
|
|
||||||
// stop performance monitor
|
// stop performance monitor
|
||||||
logs["performance"] = client.GetPerfData()
|
logs["performance"] = client.GetPerfData()
|
||||||
|
logs["pcap"] = client.Device.StopPcap()
|
||||||
|
|
||||||
caseSummary.Logs = append(caseSummary.Logs, logs)
|
caseSummary.Logs = append(caseSummary.Logs, logs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ func (r *HRPRunner) initUIClient(uuid string, osType string) (client *uixt.Drive
|
|||||||
if r.uiClients == nil {
|
if r.uiClients == nil {
|
||||||
r.uiClients = make(map[string]*uixt.DriverExt)
|
r.uiClients = make(map[string]*uixt.DriverExt)
|
||||||
}
|
}
|
||||||
r.uiClients[client.UUID] = client
|
r.uiClients[client.Device.UUID()] = client
|
||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user