feat: get wda logs

This commit is contained in:
debugtalk
2022-09-22 21:19:15 +08:00
parent 976e715357
commit 7a94afaf7e
17 changed files with 201 additions and 60 deletions
+10 -2
View File
@@ -1,6 +1,13 @@
{ {
"config": { "config": {
"name": "通过 feed 卡片进入微信直播间" "name": "通过 feed 卡片进入微信直播间",
"ios": [
{
"port": 8700,
"mjpeg_port": 8800,
"log_on": true
}
]
}, },
"teststeps": [ "teststeps": [
{ {
@@ -40,7 +47,8 @@
}, },
{ {
"method": "tap_ocr", "method": "tap_ocr",
"params": "视频号" "params": "视频号",
"identifier": "进入视频号"
} }
] ]
} }
+5
View File
@@ -1,5 +1,9 @@
config: config:
name: 通过 feed 卡片进入微信直播间 name: 通过 feed 卡片进入微信直播间
ios:
- port: 8700
mjpeg_port: 8800
log_on: true
teststeps: teststeps:
- name: 启动微信 - name: 启动微信
ios: ios:
@@ -22,6 +26,7 @@ teststeps:
params: 发现 params: 发现
- method: tap_ocr - method: tap_ocr
params: 视频号 params: 视频号
identifier: 进入视频号
- name: 处理青少年弹窗 - name: 处理青少年弹窗
ios: ios:
actions: actions:
+3 -2
View File
@@ -9,7 +9,8 @@ import (
func TestIOSWeixinLive(t *testing.T) { func TestIOSWeixinLive(t *testing.T) {
testCase := &hrp.TestCase{ testCase := &hrp.TestCase{
Config: hrp.NewConfig("通过 feed 卡片进入微信直播间"), // .SetIOS(hrp.WDADevice{Port: 8700, MjpegPort: 8800}) Config: hrp.NewConfig("通过 feed 卡片进入微信直播间").
SetIOS(hrp.WithLogOn(true), hrp.WithPort(8700), hrp.WithMjpegPort(8800)),
TestSteps: []hrp.IStep{ TestSteps: []hrp.IStep{
hrp.NewStep("启动微信"). hrp.NewStep("启动微信").
IOS(). IOS().
@@ -21,7 +22,7 @@ func TestIOSWeixinLive(t *testing.T) {
hrp.NewStep("进入直播页"). hrp.NewStep("进入直播页").
IOS(). IOS().
Tap("发现"). // 进入「发现页」 Tap("发现"). // 进入「发现页」
TapByOCR("视频号"), // 通过 OCR 识别「视频号」 TapByOCR("视频号", hrp.WithIdentifier("进入视频号")), // 通过 OCR 识别「视频号」
hrp.NewStep("处理青少年弹窗"). hrp.NewStep("处理青少年弹窗").
IOS(). IOS().
TapByOCR("我知道了", hrp.WithIgnoreNotFoundError(true)), TapByOCR("我知道了", hrp.WithIgnoreNotFoundError(true)),
+11 -11
View File
@@ -29,7 +29,7 @@ type TConfig struct {
ParametersSetting *TParamsConfig `json:"parameters_setting,omitempty" yaml:"parameters_setting,omitempty"` ParametersSetting *TParamsConfig `json:"parameters_setting,omitempty" yaml:"parameters_setting,omitempty"`
ThinkTimeSetting *ThinkTimeConfig `json:"think_time,omitempty" yaml:"think_time,omitempty"` ThinkTimeSetting *ThinkTimeConfig `json:"think_time,omitempty" yaml:"think_time,omitempty"`
WebSocketSetting *WebSocketConfig `json:"websocket,omitempty" yaml:"websocket,omitempty"` WebSocketSetting *WebSocketConfig `json:"websocket,omitempty" yaml:"websocket,omitempty"`
IOS []*IOSConfig `json:"ios,omitempty" yaml:"ios,omitempty"` IOS []*WDAOptions `json:"ios,omitempty" yaml:"ios,omitempty"`
Timeout float64 `json:"timeout,omitempty" yaml:"timeout,omitempty"` // global timeout in seconds Timeout float64 `json:"timeout,omitempty" yaml:"timeout,omitempty"` // global timeout in seconds
Export []string `json:"export,omitempty" yaml:"export,omitempty"` Export []string `json:"export,omitempty" yaml:"export,omitempty"`
Weight int `json:"weight,omitempty" yaml:"weight,omitempty"` Weight int `json:"weight,omitempty" yaml:"weight,omitempty"`
@@ -100,23 +100,23 @@ func (c *TConfig) SetWebSocket(times, interval, timeout, size int64) *TConfig {
return c return c
} }
func (c *TConfig) SetIOS(device WDADevice) *TConfig { func (c *TConfig) SetIOS(options ...WDAOption) *TConfig {
wdaOptions := &WDAOptions{}
for _, option := range options {
option(wdaOptions)
}
// each device can have its own settings // each device can have its own settings
if device.UDID != "" { if wdaOptions.UDID != "" {
c.IOS = append(c.IOS, &IOSConfig{ c.IOS = append(c.IOS, wdaOptions)
WDADevice: device,
})
return c return c
} }
// device UDID is not specified, settings will be shared // device UDID is not specified, settings will be shared
iosConfig := &IOSConfig{
WDADevice: device,
}
if len(c.IOS) == 0 { if len(c.IOS) == 0 {
c.IOS = append(c.IOS, iosConfig) c.IOS = append(c.IOS, wdaOptions)
} else { } else {
c.IOS[0] = iosConfig c.IOS[0] = wdaOptions
} }
return c return c
} }
+1 -1
View File
@@ -1,7 +1,6 @@
package dial package dial
import ( import (
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
@@ -18,6 +17,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/builtin"
"github.com/httprunner/httprunner/v4/hrp/internal/json"
) )
const ( const (
+1 -1
View File
@@ -5,7 +5,7 @@ import (
) )
func TestDriverExt_Drag(t *testing.T) { func TestDriverExt_Drag(t *testing.T) {
driverExt, err := InitWDAClient() driverExt, err := InitWDAClient(nil)
checkErr(t, err) checkErr(t, err)
pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/IMG_map.png" pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/IMG_map.png"
+1
View File
@@ -46,6 +46,7 @@ type DriverExt struct {
frame *bytes.Buffer frame *bytes.Buffer
doneMjpegStream chan bool doneMjpegStream chan bool
scale float64 scale float64
host string
CVArgs CVArgs
} }
+119 -2
View File
@@ -1,9 +1,16 @@
package uixt package uixt
import ( import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"github.com/electricbubble/gwda" "github.com/electricbubble/gwda"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/internal/json"
) )
const ( const (
@@ -21,9 +28,53 @@ const (
dismissAlertButtonSelector = "**/XCUIElementTypeButton[`label IN {'不允许','暂不'}`]" dismissAlertButtonSelector = "**/XCUIElementTypeButton[`label IN {'不允许','暂不'}`]"
) )
func InitWDAClient(options ...gwda.DeviceOption) (*DriverExt, error) { type WDAOptions struct {
UDID string `json:"udid,omitempty" yaml:"udid,omitempty"`
Port int `json:"port,omitempty" yaml:"port,omitempty"`
MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"`
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
}
type WDAOption func(*WDAOptions)
func WithUDID(udid string) WDAOption {
return func(device *WDAOptions) {
device.UDID = udid
}
}
func WithPort(port int) WDAOption {
return func(device *WDAOptions) {
device.Port = port
}
}
func WithMjpegPort(port int) WDAOption {
return func(device *WDAOptions) {
device.MjpegPort = port
}
}
func WithLogOn(logOn bool) WDAOption {
return func(device *WDAOptions) {
device.LogOn = logOn
}
}
func InitWDAClient(options *WDAOptions) (*DriverExt, error) {
var deviceOptions []gwda.DeviceOption
if options.UDID != "" {
deviceOptions = append(deviceOptions, gwda.WithSerialNumber(options.UDID))
}
if options.Port != 0 {
deviceOptions = append(deviceOptions, gwda.WithPort(options.Port))
}
if options.MjpegPort != 0 {
deviceOptions = append(deviceOptions, gwda.WithMjpegPort(options.MjpegPort))
}
// init wda device // init wda device
targetDevice, err := gwda.NewDevice(options...) targetDevice, err := gwda.NewDevice(deviceOptions...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -58,5 +109,71 @@ func InitWDAClient(options ...gwda.DeviceOption) (*DriverExt, error) {
} }
log.Info().Interface("appiumWDASettings", settings).Msg("set appium WDA settings") log.Info().Interface("appiumWDASettings", settings).Msg("set appium WDA settings")
driverExt.host = fmt.Sprintf("http://127.0.0.1:%d", targetDevice.Port)
if options.LogOn {
err = driverExt.StartWDALog("hrp_wda_log")
if err != nil {
return nil, err
}
}
return driverExt, nil return driverExt, nil
} }
type wdaResponse struct {
Value string `json:"value"`
SessionID string `json:"sessionId"`
}
func (dExt *DriverExt) StartWDALog(identifier string) error {
log.Info().Msg("start WDA log recording")
data := map[string]interface{}{"action": "start", "type": 2, "identifier": identifier}
_, err := dExt.triggerWDALog(data)
if err != nil {
return errors.Wrap(err, "failed to start WDA log recording")
}
return nil
}
func (dExt *DriverExt) GetWDALog() (string, error) {
log.Info().Msg("stop WDA log recording")
data := map[string]interface{}{"action": "stop"}
reply, err := dExt.triggerWDALog(data)
if err != nil {
return "", errors.Wrap(err, "failed to get WDA logs")
}
return reply.Value, nil
}
func (dExt *DriverExt) triggerWDALog(data map[string]interface{}) (*wdaResponse, error) {
// [[FBRoute POST:@"/gtf/automation/log"].withoutSession respondWithTarget:self action:@selector(handleAutomationLog:)]
postJSON, err := json.Marshal(data)
if err != nil {
return nil, err
}
url := fmt.Sprintf("%s/gtf/automation/log", dExt.host)
log.Info().Str("url", url).Interface("data", data).Msg("trigger WDA log")
resp, err := http.DefaultClient.Post(url, "application/json", bytes.NewBuffer(postJSON))
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.Errorf("failed to trigger wda log, response status code: %d", resp.StatusCode)
}
rawResp, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
reply := new(wdaResponse)
if err = json.Unmarshal(rawResp, reply); err != nil {
return nil, err
}
return reply, nil
}
+2 -1
View File
@@ -5,7 +5,6 @@ package uixt
import ( import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"encoding/json"
"fmt" "fmt"
"image" "image"
"io/ioutil" "io/ioutil"
@@ -13,6 +12,8 @@ import (
"net/http" "net/http"
"strings" "strings"
"time" "time"
"github.com/httprunner/httprunner/v4/hrp/internal/json"
) )
var client = &http.Client{ var client = &http.Client{
+1 -1
View File
@@ -5,7 +5,7 @@ import (
) )
func TestSwipeUntil(t *testing.T) { func TestSwipeUntil(t *testing.T) {
driverExt, err := InitWDAClient() driverExt, err := InitWDAClient(nil)
checkErr(t, err) checkErr(t, err)
var x, y, width, height float64 var x, y, width, height float64
+3 -3
View File
@@ -5,7 +5,7 @@ import (
) )
func TestDriverExt_TapWithNumber(t *testing.T) { func TestDriverExt_TapWithNumber(t *testing.T) {
driverExt, err := InitWDAClient() driverExt, err := InitWDAClient(nil)
checkErr(t, err) checkErr(t, err)
pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/flag7.png" pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/flag7.png"
@@ -20,7 +20,7 @@ func TestDriverExt_TapWithNumber(t *testing.T) {
} }
func TestDriverExt_TapXY(t *testing.T) { func TestDriverExt_TapXY(t *testing.T) {
driverExt, err := InitWDAClient() driverExt, err := InitWDAClient(nil)
checkErr(t, err) checkErr(t, err)
err = driverExt.TapXY(0.4, 0.5, "") err = driverExt.TapXY(0.4, 0.5, "")
@@ -28,7 +28,7 @@ func TestDriverExt_TapXY(t *testing.T) {
} }
func TestDriverExt_TapWithOCR(t *testing.T) { func TestDriverExt_TapWithOCR(t *testing.T) {
driverExt, err := InitWDAClient() driverExt, err := InitWDAClient(nil)
checkErr(t, err) checkErr(t, err)
// 需要点击文字上方的图标 // 需要点击文字上方的图标
+2 -2
View File
@@ -5,7 +5,7 @@ import (
) )
func TestDriverExt_ForceTouch(t *testing.T) { func TestDriverExt_ForceTouch(t *testing.T) {
driverExt, err := InitWDAClient() driverExt, err := InitWDAClient(nil)
checkErr(t, err) checkErr(t, err)
pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/IMG_ft.png" pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/IMG_ft.png"
@@ -21,7 +21,7 @@ func TestDriverExt_ForceTouch(t *testing.T) {
} }
func TestDriverExt_TouchAndHold(t *testing.T) { func TestDriverExt_TouchAndHold(t *testing.T) {
driverExt, err := InitWDAClient() driverExt, err := InitWDAClient(nil)
checkErr(t, err) checkErr(t, err)
pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/IMG_ft.png" pathSearch := "/Users/hero/Documents/temp/2020-05/opencv/IMG_ft.png"
+1 -1
View File
@@ -386,7 +386,7 @@ func (r *testCaseRunner) parseConfig() error {
// init iOS WDA clients // init iOS WDA clients
for _, iosDeviceConfig := range r.parsedConfig.IOS { for _, iosDeviceConfig := range r.parsedConfig.IOS {
_, err := r.hrpRunner.InitWDAClient(iosDeviceConfig.WDADevice) _, err := r.hrpRunner.InitWDAClient(iosDeviceConfig)
if err != nil { if err != nil {
return errors.Wrap(err, "init iOS WDA client failed") return errors.Wrap(err, "init iOS WDA client failed")
} }
+17
View File
@@ -7,6 +7,8 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/internal/json"
) )
// SessionRunner is used to run testcase and its steps. // SessionRunner is used to run testcase and its steps.
@@ -160,5 +162,20 @@ func (r *SessionRunner) GetSummary() *TestCaseSummary {
} }
caseSummary.InOut.ExportVars = exportVars caseSummary.InOut.ExportVars = exportVars
caseSummary.InOut.ConfigVars = r.parsedConfig.Variables caseSummary.InOut.ConfigVars = r.parsedConfig.Variables
logs := make(map[string]string)
for udid, client := range r.hrpRunner.wdaClients {
log, err := client.GetWDALog()
if err != nil {
logs[udid] = err.Error()
} else {
logs[udid] = log
}
}
logsStr, _ := json.Marshal(logs)
caseSummary.Logs = string(logsStr)
// caseSummary.Log
return caseSummary return caseSummary
} }
+19 -28
View File
@@ -6,25 +6,26 @@ import (
"strings" "strings"
"time" "time"
"github.com/electricbubble/gwda"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v4/hrp/internal/uixt" "github.com/httprunner/httprunner/v4/hrp/internal/uixt"
) )
type IOSConfig struct { type (
WDADevice WDAOptions = uixt.WDAOptions
} WDAOption = uixt.WDAOption
)
type WDADevice struct { var (
UDID string `json:"udid,omitempty" yaml:"udid,omitempty"` WithUDID = uixt.WithUDID
Port int `json:"port,omitempty" yaml:"port,omitempty"` WithPort = uixt.WithPort
MjpegPort int `json:"mjpeg_port,omitempty" yaml:"mjpeg_port,omitempty"` WithMjpegPort = uixt.WithMjpegPort
} WithLogOn = uixt.WithLogOn
)
type IOSStep struct { type IOSStep struct {
WDADevice `yaml:",inline"` // inline refers to https://pkg.go.dev/gopkg.in/yaml.v3#Marshal WDAOptions `yaml:",inline"` // inline refers to https://pkg.go.dev/gopkg.in/yaml.v3#Marshal
MobileAction `yaml:",inline"` MobileAction `yaml:",inline"`
Actions []MobileAction `json:"actions,omitempty" yaml:"actions,omitempty"` Actions []MobileAction `json:"actions,omitempty" yaml:"actions,omitempty"`
} }
@@ -462,36 +463,26 @@ func (s *StepIOSValidation) Run(r *SessionRunner) (*StepResult, error) {
return runStepIOS(r, s.step) return runStepIOS(r, s.step)
} }
func (r *HRPRunner) InitWDAClient(device WDADevice) (client *uiDriver, err error) { func (r *HRPRunner) InitWDAClient(options *WDAOptions) (client *uiDriver, err error) {
// avoid duplicate init // avoid duplicate init
if device.UDID == "" && len(r.wdaClients) == 1 { if options.UDID == "" && len(r.wdaClients) == 1 {
for _, v := range r.wdaClients { for _, v := range r.wdaClients {
return v, nil return v, nil
} }
} }
// avoid duplicate init // avoid duplicate init
if device.UDID != "" { if options.UDID != "" {
if client, ok := r.wdaClients[device.UDID]; ok { if client, ok := r.wdaClients[options.UDID]; ok {
return client, nil return client, nil
} }
} }
var deviceOptions []gwda.DeviceOption driverExt, err := uixt.InitWDAClient(options)
if device.UDID != "" {
deviceOptions = append(deviceOptions, gwda.WithSerialNumber(device.UDID))
}
if device.Port != 0 {
deviceOptions = append(deviceOptions, gwda.WithPort(device.Port))
}
if device.MjpegPort != 0 {
deviceOptions = append(deviceOptions, gwda.WithMjpegPort(device.MjpegPort))
}
driverExt, err := uixt.InitWDAClient(deviceOptions...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
client = &uiDriver{ client = &uiDriver{
DriverExt: *driverExt, DriverExt: *driverExt,
} }
@@ -500,7 +491,7 @@ func (r *HRPRunner) InitWDAClient(device WDADevice) (client *uiDriver, err error
if r.wdaClients == nil { if r.wdaClients == nil {
r.wdaClients = make(map[string]*uiDriver) r.wdaClients = make(map[string]*uiDriver)
} }
r.wdaClients[device.UDID] = client r.wdaClients[options.UDID] = client
return client, nil return client, nil
} }
@@ -515,7 +506,7 @@ func runStepIOS(s *SessionRunner, step *TStep) (stepResult *StepResult, err erro
screenshots := make([]string, 0) screenshots := make([]string, 0)
// init wdaClient driver // init wdaClient driver
wdaClient, err := s.hrpRunner.InitWDAClient(step.IOS.WDADevice) wdaClient, err := s.hrpRunner.InitWDAClient(&step.IOS.WDAOptions)
if err != nil { if err != nil {
return return
} }
+2 -2
View File
@@ -50,7 +50,7 @@ func TestIOSSearchApp(t *testing.T) {
func TestIOSAppLaunch(t *testing.T) { func TestIOSAppLaunch(t *testing.T) {
testCase := &TestCase{ testCase := &TestCase{
Config: NewConfig("启动 & 关闭 App"). Config: NewConfig("启动 & 关闭 App").
SetIOS(WDADevice{Port: 8100, MjpegPort: 9100}), SetIOS(WithPort(8100), WithMjpegPort(9100)),
TestSteps: []IStep{ TestSteps: []IStep{
NewStep("终止今日头条"). NewStep("终止今日头条").
IOS().AppTerminate("com.ss.iphone.article.News"), IOS().AppTerminate("com.ss.iphone.article.News"),
@@ -73,7 +73,7 @@ func TestIOSAppLaunch(t *testing.T) {
func TestIOSWeixinLive(t *testing.T) { func TestIOSWeixinLive(t *testing.T) {
testCase := &TestCase{ testCase := &TestCase{
Config: NewConfig("ios ui action on 微信直播"). Config: NewConfig("ios ui action on 微信直播").
SetIOS(WDADevice{Port: 8100, MjpegPort: 9100}), SetIOS(WithLogOn(true), WithPort(8100), WithMjpegPort(9100)),
TestSteps: []IStep{ TestSteps: []IStep{
NewStep("启动微信"). NewStep("启动微信").
IOS(). IOS().
+1 -1
View File
@@ -151,7 +151,7 @@ type TestCaseSummary struct {
Stat *TestStepStat `json:"stat" yaml:"stat"` Stat *TestStepStat `json:"stat" yaml:"stat"`
Time *TestCaseTime `json:"time" yaml:"time"` Time *TestCaseTime `json:"time" yaml:"time"`
InOut *TestCaseInOut `json:"in_out" yaml:"in_out"` InOut *TestCaseInOut `json:"in_out" yaml:"in_out"`
Log string `json:"log,omitempty" yaml:"log,omitempty"` // TODO Logs string `json:"logs,omitempty" yaml:"logs,omitempty"` // TODO
Records []*StepResult `json:"records" yaml:"records"` Records []*StepResult `json:"records" yaml:"records"`
RootDir string `json:"root_dir" yaml:"root_dir"` RootDir string `json:"root_dir" yaml:"root_dir"`
} }