refactor: split ai related logic to pkg/ai

This commit is contained in:
lilong.129
2025-02-08 10:38:48 +08:00
parent 06d7a7e721
commit 5e45eb7836
32 changed files with 798 additions and 487 deletions

63
pkg/ai/ai.go Normal file
View File

@@ -0,0 +1,63 @@
package ai
import (
"os"
"github.com/httprunner/httprunner/v5/code"
"github.com/rs/zerolog/log"
)
func NewAIService(opts ...AIServiceOption) *AIServices {
services := &AIServices{}
for _, option := range opts {
option(services)
}
return services
}
type AIServices struct {
ICVService
ILLMService
}
type AIServiceOption func(*AIServices)
type CVServiceType string
const (
CVServiceTypeVEDEM CVServiceType = "vedem"
CVServiceTypeOpenCV CVServiceType = "opencv"
)
func WithCVService(service CVServiceType) AIServiceOption {
return func(opts *AIServices) {
if service == CVServiceTypeVEDEM {
var err error
opts.ICVService, err = NewVEDEMImageService()
if err != nil {
log.Error().Err(err).Msg("init vedem image service failed")
os.Exit(code.GetErrorCode(err))
}
}
}
}
type LLMServiceType string
const (
LLMServiceTypeGPT4o LLMServiceType = "gpt-4o"
LLMServiceTypeDeepSeekV3 LLMServiceType = "deepseek-v3"
)
func WithLLMService(service LLMServiceType) AIServiceOption {
return func(opts *AIServices) {
if service == LLMServiceTypeGPT4o {
var err error
opts.ILLMService, err = NewGPT4oLLMService()
if err != nil {
log.Error().Err(err).Msg("init gpt-4o llm service failed")
os.Exit(code.GetErrorCode(err))
}
}
}
}

11
pkg/ai/ai_test.go Normal file
View File

@@ -0,0 +1,11 @@
package ai
import "testing"
func TestOption(t *testing.T) {
options := NewAIService(
WithCVService(CVServiceTypeOpenCV),
WithLLMService(LLMServiceTypeDeepSeekV3),
)
t.Log(options)
}

View File

@@ -1,4 +1,4 @@
package uixt
package ai
import (
"bytes"
@@ -7,19 +7,18 @@ import (
"math"
"regexp"
"github.com/pkg/errors"
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
"github.com/pkg/errors"
)
type IImageService interface {
// GetImage returns image result including ocr texts, uploaded image url, etc
GetImage(imageBuf *bytes.Buffer, opts ...option.ActionOption) (imageResult *ImageResult, err error)
type ICVService interface {
// returns CV result including ocr texts, uploaded image url, etc
ReadFromBuffer(imageBuf *bytes.Buffer, opts ...ScreenShotOption) (*CVResult, error)
ReadFromPath(imagePath string, opts ...ScreenShotOption) (*CVResult, error)
}
type ImageResult struct {
type CVResult struct {
URL string `json:"url,omitempty"` // image uploaded url
OCRResult OCRResults `json:"ocrResult,omitempty"` // OCR texts
// NoLive非直播间
@@ -106,7 +105,7 @@ func (t OCRTexts) texts() (texts []string) {
return texts
}
func (t OCRTexts) FilterScope(scope option.AbsScope) (results OCRTexts) {
func (t OCRTexts) FilterScope(scope AbsScope) (results OCRTexts) {
for _, ocrText := range t {
rect := ocrText.Rect
@@ -128,12 +127,12 @@ func (t OCRTexts) FilterScope(scope option.AbsScope) (results OCRTexts) {
// FindText returns matched text with options
// Notice: filter scope should be specified with WithAbsScope
func (t OCRTexts) FindText(text string, opts ...option.ActionOption) (result OCRText, err error) {
actionOptions := option.NewActionOptions(opts...)
func (t OCRTexts) FindText(text string, opts ...ScreenFilterOption) (result OCRText, err error) {
options := NewScreenFilterOptions(opts...)
var results []OCRText
for _, ocrText := range t.FilterScope(actionOptions.AbsScope) {
if actionOptions.Regex {
for _, ocrText := range t.FilterScope(options.AbsScope) {
if options.Regex {
// regex on, check if match regex
if !regexp.MustCompile(text).MatchString(ocrText.Text) {
continue
@@ -148,7 +147,7 @@ func (t OCRTexts) FindText(text string, opts ...option.ActionOption) (result OCR
results = append(results, ocrText)
// return the first one matched exactly when index not specified
if ocrText.Text == text && actionOptions.Index == 0 {
if ocrText.Text == text && options.Index == 0 {
return ocrText, nil
}
}
@@ -159,7 +158,7 @@ func (t OCRTexts) FindText(text string, opts ...option.ActionOption) (result OCR
}
// get index
idx := actionOptions.Index
idx := options.Index
if idx < 0 {
idx = len(results) + idx
}
@@ -173,8 +172,8 @@ func (t OCRTexts) FindText(text string, opts ...option.ActionOption) (result OCR
return results[idx], nil
}
func (t OCRTexts) FindTexts(texts []string, opts ...option.ActionOption) (results OCRTexts, err error) {
actionOptions := option.NewActionOptions(opts...)
func (t OCRTexts) FindTexts(texts []string, opts ...ScreenFilterOption) (results OCRTexts, err error) {
options := NewScreenFilterOptions(opts...)
for _, text := range texts {
ocrText, err := t.FindText(text, opts...)
if err != nil {
@@ -183,7 +182,7 @@ func (t OCRTexts) FindTexts(texts []string, opts ...option.ActionOption) (result
results = append(results, ocrText)
// found one, skip searching and return
if actionOptions.MatchOne {
if options.MatchOne {
return results, nil
}
}
@@ -240,7 +239,7 @@ func (box Box) Center() PointF {
type UIResults []UIResult
func (u UIResults) FilterScope(scope option.AbsScope) (results UIResults) {
func (u UIResults) FilterScope(scope AbsScope) (results UIResults) {
for _, uiResult := range u {
rect := image.Rectangle{
Min: image.Point{
@@ -268,16 +267,15 @@ func (u UIResults) FilterScope(scope option.AbsScope) (results UIResults) {
return
}
func (u UIResults) GetUIResult(opts ...option.ActionOption) (UIResult, error) {
actionOptions := option.NewActionOptions(opts...)
uiResults := u.FilterScope(actionOptions.AbsScope)
func (u UIResults) GetUIResult(opts ...ScreenFilterOption) (UIResult, error) {
options := NewScreenFilterOptions(opts...)
uiResults := u.FilterScope(options.AbsScope)
if len(uiResults) == 0 {
return UIResult{}, errors.Wrap(code.CVResultNotFoundError,
"ui types not found in scope")
}
// get index
idx := actionOptions.Index
idx := options.Index
if idx < 0 {
idx = len(uiResults) + idx
}
@@ -301,3 +299,32 @@ type ClosePopupsResult struct {
func (c ClosePopupsResult) IsEmpty() bool {
return c.PopupArea.IsEmpty() && c.CloseArea.IsEmpty()
}
type Point struct {
X int `json:"x"` // upper left X coordinate of selected element
Y int `json:"y"` // upper left Y coordinate of selected element
}
type PointF struct {
X float64 `json:"x"`
Y float64 `json:"y"`
}
func (p PointF) IsIdentical(p2 PointF) bool {
// set the coordinate precision to 1 pixel
return math.Abs(p.X-p2.X) < 1 && math.Abs(p.Y-p2.Y) < 1
}
type Size struct {
Width int `json:"width"`
Height int `json:"height"`
}
func (s Size) IsNil() bool {
return s.Width == 0 && s.Height == 0
}
type Screen struct {
StatusBarSize Size `json:"statusBarSize"`
Scale float64 `json:"scale"`
}

View File

@@ -1,4 +1,4 @@
package uixt
package ai
import (
"bytes"
@@ -16,7 +16,6 @@ import (
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/internal/json"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
var client = &http.Client{
@@ -24,19 +23,19 @@ var client = &http.Client{
}
type APIResponseImage struct {
Code int `json:"code"`
Message string `json:"message"`
Result ImageResult `json:"result"`
Code int `json:"code"`
Message string `json:"message"`
Result CVResult `json:"result"`
}
func newVEDEMImageService() (*veDEMImageService, error) {
func NewVEDEMImageService() (*vedemCVService, error) {
if err := checkEnv(); err != nil {
return nil, err
}
return &veDEMImageService{}, nil
return &vedemCVService{}, nil
}
// veDEMImageService implements IImageService interface
// vedemCVService implements IImageService interface
// actions:
//
// ocr - get ocr texts
@@ -45,11 +44,24 @@ func newVEDEMImageService() (*veDEMImageService, error) {
// popup - get popup windows
// close - get close popup
// ui - get ui position by type(s)
type veDEMImageService struct{}
type vedemCVService struct{}
func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer, opts ...option.ActionOption) (imageResult *ImageResult, err error) {
actionOptions := option.NewActionOptions(opts...)
screenshotActions := actionOptions.ScreenshotActions()
func (s *vedemCVService) ReadFromPath(imagePath string, opts ...ScreenShotOption) (
imageResult *CVResult, err error) {
imageBuf, err := os.ReadFile(imagePath)
if err != nil {
err = errors.Wrap(code.CVRequestError,
fmt.Sprintf("read image file error: %v", err))
return
}
imageResult, err = s.ReadFromBuffer(bytes.NewBuffer(imageBuf), opts...)
return
}
func (s *vedemCVService) ReadFromBuffer(imageBuf *bytes.Buffer, opts ...ScreenShotOption) (
imageResult *CVResult, err error) {
actionOptions := NewScreenShotOptions(opts...)
screenshotActions := actionOptions.List()
if len(screenshotActions) == 0 {
// skip
return nil, nil
@@ -98,11 +110,7 @@ func (s *veDEMImageService) GetImage(imageBuf *bytes.Buffer, opts ...option.Acti
bodyWriter.WriteField("ocrCluster", actionOptions.ScreenShotWithOCRCluster)
}
if actionOptions.Timeout > 0 {
bodyWriter.WriteField("timeout", fmt.Sprintf("%v", actionOptions.Timeout))
} else {
bodyWriter.WriteField("timeout", fmt.Sprintf("%v", 10))
}
bodyWriter.WriteField("timeout", fmt.Sprintf("%v", 10))
formWriter, err := bodyWriter.CreateFormFile("image", "screenshot.png")
if err != nil {

41
pkg/ai/cv_vedem_test.go Normal file
View File

@@ -0,0 +1,41 @@
//go:build localtest
package ai
import (
"bytes"
"fmt"
"os"
"testing"
)
func TestGetImageFromBuffer(t *testing.T) {
imagePath := "/Users/debugtalk/Downloads/s1.png"
file, err := os.ReadFile(imagePath)
if err != nil {
t.Fatal(err)
}
buf := new(bytes.Buffer)
buf.Read(file)
service := NewAIService(
WithCVService(CVServiceTypeVEDEM),
)
cvResult, err := service.ReadFromBuffer(buf)
if err != nil {
t.Fatal(err)
}
fmt.Println(fmt.Sprintf("cvResult: %v", cvResult))
}
func TestGetImageFromPath(t *testing.T) {
imagePath := "/Users/debugtalk/Downloads/s1.png"
service := NewAIService(
WithCVService(CVServiceTypeVEDEM),
)
cvResult, err := service.ReadFromPath(imagePath)
if err != nil {
t.Fatal(err)
}
fmt.Println(fmt.Sprintf("cvResult: %v", cvResult))
}

20
pkg/ai/llm.go Normal file
View File

@@ -0,0 +1,20 @@
package ai
import "context"
type ILLMService interface {
Call(ctx context.Context, prompt string) (string, error)
}
func NewGPT4oLLMService() (*openaiLLMService, error) {
if err := checkEnv(); err != nil {
return nil, err
}
return &openaiLLMService{}, nil
}
type openaiLLMService struct{}
func (s openaiLLMService) Call(ctx context.Context, prompt string) (string, error) {
return "", nil
}

213
pkg/ai/screen.go Normal file
View File

@@ -0,0 +1,213 @@
package ai
func NewScreenShotOptions(opts ...ScreenShotOption) *ScreenShotOptions {
options := &ScreenShotOptions{}
for _, option := range opts {
option(options)
}
return options
}
type ScreenShotOptions struct {
ScreenShotWithOCR bool `json:"screenshot_with_ocr,omitempty" yaml:"screenshot_with_ocr,omitempty"`
ScreenShotWithUpload bool `json:"screenshot_with_upload,omitempty" yaml:"screenshot_with_upload,omitempty"`
ScreenShotWithLiveType bool `json:"screenshot_with_live_type,omitempty" yaml:"screenshot_with_live_type,omitempty"`
ScreenShotWithLivePopularity bool `json:"screenshot_with_live_popularity,omitempty" yaml:"screenshot_with_live_popularity,omitempty"`
ScreenShotWithUITypes []string `json:"screenshot_with_ui_types,omitempty" yaml:"screenshot_with_ui_types,omitempty"`
ScreenShotWithClosePopups bool `json:"screenshot_with_close_popups,omitempty" yaml:"screenshot_with_close_popups,omitempty"`
ScreenShotWithOCRCluster string `json:"screenshot_with_ocr_cluster,omitempty" yaml:"screenshot_with_ocr_cluster,omitempty"`
ScreenShotFileName string `json:"screenshot_file_name,omitempty" yaml:"screenshot_file_name,omitempty"`
}
func (o *ScreenShotOptions) Options() []ScreenShotOption {
options := make([]ScreenShotOption, 0)
if o == nil {
return options
}
// screenshot options
if o.ScreenShotWithOCR {
options = append(options, WithScreenShotOCR(true))
}
if o.ScreenShotWithUpload {
options = append(options, WithScreenShotUpload(true))
}
if o.ScreenShotWithLiveType {
options = append(options, WithScreenShotLiveType(true))
}
if o.ScreenShotWithLivePopularity {
options = append(options, WithScreenShotLivePopularity(true))
}
if len(o.ScreenShotWithUITypes) > 0 {
options = append(options, WithScreenShotUITypes(o.ScreenShotWithUITypes...))
}
if o.ScreenShotWithClosePopups {
options = append(options, WithScreenShotClosePopups(true))
}
if o.ScreenShotWithOCRCluster != "" {
options = append(options, WithScreenOCRCluster(o.ScreenShotWithOCRCluster))
}
if o.ScreenShotFileName != "" {
options = append(options, WithScreenShotFileName(o.ScreenShotFileName))
}
return options
}
func (o *ScreenShotOptions) List() []string {
options := []string{}
if o.ScreenShotWithUpload {
options = append(options, "upload")
}
if o.ScreenShotWithOCR {
options = append(options, "ocr")
}
if o.ScreenShotWithLiveType {
options = append(options, "liveType")
}
if o.ScreenShotWithLivePopularity {
options = append(options, "livePopularity")
}
// UI detection
if len(o.ScreenShotWithUITypes) > 0 {
options = append(options, "ui")
}
if o.ScreenShotWithClosePopups {
options = append(options, "close")
}
return options
}
type ScreenShotOption func(o *ScreenShotOptions)
func WithScreenShotOCR(ocrOn bool) ScreenShotOption {
return func(o *ScreenShotOptions) {
o.ScreenShotWithOCR = ocrOn
}
}
func WithScreenShotUpload(uploadOn bool) ScreenShotOption {
return func(o *ScreenShotOptions) {
o.ScreenShotWithUpload = uploadOn
}
}
func WithScreenShotLiveType(liveTypeOn bool) ScreenShotOption {
return func(o *ScreenShotOptions) {
o.ScreenShotWithLiveType = liveTypeOn
}
}
func WithScreenShotLivePopularity(livePopularityOn bool) ScreenShotOption {
return func(o *ScreenShotOptions) {
o.ScreenShotWithLivePopularity = livePopularityOn
}
}
func WithScreenShotUITypes(uiTypes ...string) ScreenShotOption {
return func(o *ScreenShotOptions) {
o.ScreenShotWithUITypes = uiTypes
}
}
func WithScreenShotClosePopups(closeOn bool) ScreenShotOption {
return func(o *ScreenShotOptions) {
o.ScreenShotWithClosePopups = closeOn
}
}
func WithScreenOCRCluster(ocrCluster string) ScreenShotOption {
return func(o *ScreenShotOptions) {
o.ScreenShotWithOCRCluster = ocrCluster
}
}
func WithScreenShotFileName(fileName string) ScreenShotOption {
return func(o *ScreenShotOptions) {
o.ScreenShotFileName = fileName
}
}
// (x1, y1) is the top left corner, (x2, y2) is the bottom right corner
// [x1, y1, x2, y2] in percentage of the screen
type Scope []float64
func (s Scope) ToAbs(windowSize Size) AbsScope {
x1, y1, x2, y2 := s[0], s[1], s[2], s[3]
// convert relative scope to absolute scope
absX1 := int(x1 * float64(windowSize.Width))
absY1 := int(y1 * float64(windowSize.Height))
absX2 := int(x2 * float64(windowSize.Width))
absY2 := int(y2 * float64(windowSize.Height))
return AbsScope{absX1, absY1, absX2, absY2}
}
// [x1, y1, x2, y2] in absolute pixels
type AbsScope []int
func (s AbsScope) Option() ScreenFilterOption {
return WithAbsScope(s[0], s[1], s[2], s[3])
}
func NewScreenFilterOptions(opts ...ScreenFilterOption) *ScreenFilterOptions {
options := &ScreenFilterOptions{}
for _, option := range opts {
option(options)
}
return options
}
type ScreenFilterOptions struct {
// scope related
Scope Scope `json:"scope,omitempty" yaml:"scope,omitempty"`
AbsScope AbsScope `json:"abs_scope,omitempty" yaml:"abs_scope,omitempty"`
Regex bool `json:"regex,omitempty" yaml:"regex,omitempty"` // use regex to match text
Offset []int `json:"offset,omitempty" yaml:"offset,omitempty"` // used to tap offset of point
OffsetRandomRange []int `json:"offset_random_range,omitempty" yaml:"offset_random_range,omitempty"` // set random range [min, max] for tap/swipe points
Index int `json:"index,omitempty" yaml:"index,omitempty"` // index of the target element
MatchOne bool `json:"match_one,omitempty" yaml:"match_one,omitempty"` // match one of the targets if existed
}
type ScreenFilterOption func(o *ScreenFilterOptions)
// WithScope inputs area of [(x1,y1), (x2,y2)]
// x1, y1, x2, y2 are all in [0, 1], which means the relative position of the screen
func WithScope(x1, y1, x2, y2 float64) ScreenFilterOption {
return func(o *ScreenFilterOptions) {
o.Scope = Scope{x1, y1, x2, y2}
}
}
// WithAbsScope inputs area of [(x1,y1), (x2,y2)]
// x1, y1, x2, y2 are all absolute position of the screen
func WithAbsScope(x1, y1, x2, y2 int) ScreenFilterOption {
return func(o *ScreenFilterOptions) {
o.AbsScope = AbsScope{x1, y1, x2, y2}
}
}
// tap [x, y] with offset [offsetX, offsetY]
func WithTapOffset(offsetX, offsetY int) ScreenFilterOption {
return func(o *ScreenFilterOptions) {
o.Offset = []int{offsetX, offsetY}
}
}
func WithRegex(regex bool) ScreenFilterOption {
return func(o *ScreenFilterOptions) {
o.Regex = regex
}
}
func WithMatchOne(matchOne bool) ScreenFilterOption {
return func(o *ScreenFilterOptions) {
o.MatchOne = matchOne
}
}
func WithIndex(index int) ScreenFilterOption {
return func(o *ScreenFilterOptions) {
o.Index = index
}
}

View File

@@ -1,80 +0,0 @@
//go:build localtest
package uixt
import (
"bytes"
"fmt"
"os"
"testing"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
func checkOCR(buff *bytes.Buffer) error {
service, err := newVEDEMImageService()
if err != nil {
return err
}
imageResult, err := service.GetImage(buff)
if err != nil {
return err
}
fmt.Println(fmt.Sprintf("imageResult: %v", imageResult))
return nil
}
func TestOCRWithScreenshot(t *testing.T) {
setupAndroidAdbDriver(t)
raw, err := driverExt.Driver.Screenshot()
if err != nil {
t.Fatal(err)
}
if err := checkOCR(raw); err != nil {
t.Fatal(err)
}
}
func TestOCRWithLocalFile(t *testing.T) {
imagePath := "/Users/debugtalk/Downloads/s1.png"
file, err := os.ReadFile(imagePath)
if err != nil {
t.Fatal(err)
}
buf := new(bytes.Buffer)
buf.Read(file)
if err := checkOCR(buf); err != nil {
t.Fatal(err)
}
}
func TestTapUIWithScreenshot(t *testing.T) {
serialNumber := os.Getenv("SERIAL_NUMBER")
device, _ := NewAndroidDevice(option.WithSerialNumber(serialNumber))
driver, err := device.NewDriver()
if err != nil {
t.Fatal(err)
}
err = driver.TapByUIDetection(
option.WithScreenShotUITypes("dyhouse", "shoppingbag"))
if err != nil {
t.Fatal(err)
}
}
func TestDriverExtOCR(t *testing.T) {
driverExt, err := iosDevice.NewDriver()
checkErr(t, err)
point, err := driverExt.FindScreenText("抖音")
checkErr(t, err)
t.Logf("point.X: %v, point.Y: %v", point.X, point.Y)
driverExt.Driver.Tap(point.X, point.Y-20)
}

View File

@@ -152,7 +152,7 @@ func (dev *AndroidDevice) NewDriver(opts ...option.DriverOption) (driverExt *Dri
if dev.UIA2 || dev.LogOn {
driver, err = NewUIA2Driver(dev)
} else if dev.STUB {
driver, err = NewStubDriver(dev)
driver, err = NewStubAndroidDriver(dev)
} else {
driver, err = NewADBDriver(dev)
}

View File

@@ -23,6 +23,7 @@ import (
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/config"
"github.com/httprunner/httprunner/v5/internal/utf7"
"github.com/httprunner/httprunner/v5/pkg/ai"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
@@ -39,6 +40,7 @@ func NewADBDriver(device *AndroidDevice) (*ADBDriver, error) {
type ADBDriver struct {
*AndroidDevice
*Session
// DriverExt
}
func (ad *ADBDriver) runShellCommand(cmd string, args ...string) (output string, err error) {
@@ -89,6 +91,10 @@ func (ad *ADBDriver) Status() (deviceStatus DeviceStatus, err error) {
return
}
func (ad *ADBDriver) GetDevice() IDevice {
return ad.AndroidDevice
}
func (ad *ADBDriver) DeviceInfo() (deviceInfo DeviceInfo, err error) {
err = errDriverNotImplemented
return
@@ -104,7 +110,7 @@ func (ad *ADBDriver) BatteryInfo() (batteryInfo BatteryInfo, err error) {
return
}
func (ad *ADBDriver) getWindowSize() (size Size, err error) {
func (ad *ADBDriver) getWindowSize() (size ai.Size, err error) {
// adb shell wm size
output, err := ad.runShellCommand("wm", "size")
if err != nil {
@@ -130,14 +136,14 @@ func (ad *ADBDriver) getWindowSize() (size Size, err error) {
ss := strings.Split(resolution, "x")
width, _ := strconv.Atoi(ss[0])
height, _ := strconv.Atoi(ss[1])
return Size{Width: width, Height: height}, nil
return ai.Size{Width: width, Height: height}, nil
}
}
err = errors.New("physical window size not found by adb")
return
}
func (ad *ADBDriver) WindowSize() (size Size, err error) {
func (ad *ADBDriver) WindowSize() (size ai.Size, err error) {
if !ad.windowSize.IsNil() {
// use cached window size
return ad.windowSize, nil
@@ -163,7 +169,7 @@ func (ad *ADBDriver) WindowSize() (size Size, err error) {
return size, nil
}
func (ad *ADBDriver) Screen() (screen Screen, err error) {
func (ad *ADBDriver) Screen() (screen ai.Screen, err error) {
err = errDriverNotImplemented
return
}
@@ -172,21 +178,6 @@ func (ad *ADBDriver) Scale() (scale float64, err error) {
return 1, nil
}
func (ad *ADBDriver) GetTimestamp() (timestamp int64, err error) {
// adb shell date +%s
output, err := ad.runShellCommand("date", "+%s")
if err != nil {
return 0, errors.Wrap(err, "failed to get timestamp by adb")
}
timestamp, err = strconv.ParseInt(strings.TrimSpace(output), 10, 64)
if err != nil {
return 0, errors.Wrap(err, "convert timestamp failed")
}
return timestamp, nil
}
// PressBack simulates a short press on the BACK button.
func (ad *ADBDriver) PressBack(opts ...option.ActionOption) (err error) {
// adb shell input keyevent 4
@@ -1088,6 +1079,10 @@ func (ad *ADBDriver) RecordScreen(folderPath string, duration time.Duration) (vi
return filepath.Abs(fileName)
}
func (ad *ADBDriver) Setup() error {
return nil
}
func (ad *ADBDriver) TearDown() error {
return nil
}

View File

@@ -22,7 +22,7 @@ const (
DouyinServerPort = 32316
)
func NewStubDriver(device *AndroidDevice) (driver *StubAndroidDriver, err error) {
func NewStubAndroidDriver(device *AndroidDevice) (driver *StubAndroidDriver, err error) {
socketLocalPort, err := device.Forward(StubSocketName)
if err != nil {
return nil, errors.Wrap(code.DeviceConnectionError,

View File

@@ -14,7 +14,7 @@ func setupStubDriver(t *testing.T) {
device, err := NewAndroidDevice()
checkErr(t, err)
device.STUB = true
androidStubDriver, err = NewStubDriver(device)
androidStubDriver, err = NewStubAndroidDriver(device)
checkErr(t, err)
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/utf7"
"github.com/httprunner/httprunner/v5/pkg/ai"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
@@ -182,7 +183,7 @@ func (ud *UIA2Driver) BatteryInfo() (batteryInfo BatteryInfo, err error) {
return
}
func (ud *UIA2Driver) WindowSize() (size Size, err error) {
func (ud *UIA2Driver) WindowSize() (size ai.Size, err error) {
// register(getHandler, new GetDeviceSize("/wd/hub/session/:sessionId/window/:windowHandle/size"))
if !ud.windowSize.IsNil() {
// use cached window size
@@ -191,11 +192,11 @@ func (ud *UIA2Driver) WindowSize() (size Size, err error) {
var rawResp rawResponse
if rawResp, err = ud.httpGET("/session", ud.sessionID, "window/:windowHandle/size"); err != nil {
return Size{}, errors.Wrap(err, "get window size failed by UIA2 request")
return ai.Size{}, errors.Wrap(err, "get window size failed by UIA2 request")
}
reply := new(struct{ Value struct{ Size } })
reply := new(struct{ Value struct{ ai.Size } })
if err = json.Unmarshal(rawResp, reply); err != nil {
return Size{}, errors.Wrap(err, "get window size failed by UIA2 response")
return ai.Size{}, errors.Wrap(err, "get window size failed by UIA2 response")
}
size = reply.Value.Size

View File

@@ -6,17 +6,16 @@ import (
// current implemeted device: IOSDevice, AndroidDevice, HarmonyDevice
type IDevice interface {
UUID() string // ios udid or android serial
Setup() error
Teardown() error
UUID() string // ios udid or android serial
LogEnabled() bool
// TODO: remove
NewDriver(...option.DriverOption) (driverExt *DriverExt, err error)
Install(appPath string, opts ...option.InstallOption) error
Uninstall(packageName string) error
GetPackageInfo(packageName string) (AppInfo, error)
// TODO: remove?
LogEnabled() bool
}

View File

@@ -16,9 +16,17 @@ import (
"github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/internal/config"
"github.com/httprunner/httprunner/v5/internal/json"
"github.com/httprunner/httprunner/v5/pkg/ai"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
var (
_ IDriver = (*ADBDriver)(nil)
_ IDriver = (*UIA2Driver)(nil)
_ IDriver = (*WDADriver)(nil)
_ IDriver = (*HDCDriver)(nil)
)
// current implemeted driver: ADBDriver, UIA2Driver, WDADriver, HDCDriver
type IDriver interface {
// NewSession starts a new session and returns the DriverSession.
@@ -34,6 +42,7 @@ type IDriver interface {
Status() (DeviceStatus, error)
GetDevice() IDevice
DeviceInfo() (DeviceInfo, error)
// Location Returns device location data.
@@ -53,13 +62,10 @@ type IDriver interface {
// WindowSize Return the width and height in portrait mode.
// when getting the window size in wda/ui2/adb, if the device is in landscape mode,
// the width and height will be reversed.
WindowSize() (Size, error)
Screen() (Screen, error)
WindowSize() (ai.Size, error)
Screen() (ai.Screen, error)
Scale() (float64, error)
// GetTimestamp returns the timestamp of the mobile device
GetTimestamp() (timestamp int64, err error)
// Homescreen Forces the device under test to switch to the home screen
Homescreen() error
@@ -162,40 +168,66 @@ type IDriver interface {
GetDriverResults() []*DriverRequests
RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error)
Setup() error
TearDown() error
}
type IDriverExt interface {
GetDriver() IDriver // get original driver
GetScreenResult(opts ...option.ActionOption) (screenResult *ScreenResult, err error)
GetScreenTexts(opts ...option.ActionOption) (ocrTexts ai.OCRTexts, err error)
// swipe
SwipeRelative(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error
SwipeUp(opts ...option.ActionOption) error
SwipeDown(opts ...option.ActionOption) error
SwipeLeft(opts ...option.ActionOption) error
SwipeRight(opts ...option.ActionOption) error
}
func NewDriverExt(driver IDriver, opts ...ai.AIServiceOption) (IDriverExt, error) {
services := ai.NewAIService(opts...)
driverExt := &DriverExt{
Driver: driver,
ImageService: services.ImageService,
}
// create results directory
if err := builtin.EnsureFolderExists(config.ResultsPath); err != nil {
return nil, errors.Wrap(err, "create results directory failed")
}
if err := builtin.EnsureFolderExists(config.ScreenShotsPath); err != nil {
return nil, errors.Wrap(err, "create screenshots directory failed")
}
return driverExt, nil
}
type DriverExt struct {
Device IDevice
Driver IDriver
ImageService IImageService // used to extract image data
ImageService ai.IImageService // used to extract image data
}
func newDriverExt(device IDevice, driver IDriver, opts ...option.DriverOption) (dExt *DriverExt, err error) {
options := option.NewDriverOptions(opts...)
dExt = &DriverExt{
Device: device,
Driver: driver,
}
if options.WithImageService {
if dExt.ImageService, err = newVEDEMImageService(); err != nil {
if dExt.ImageService, err = ai.NewVEDEMImageService(); err != nil {
return nil, err
}
}
if options.WithResultFolder {
// create results directory
if err = builtin.EnsureFolderExists(config.ResultsPath); err != nil {
return nil, errors.Wrap(err, "create results directory failed")
}
if err = builtin.EnsureFolderExists(config.ScreenShotsPath); err != nil {
return nil, errors.Wrap(err, "create screenshots directory failed")
}
}
return dExt, nil
}
func (dExt *DriverExt) GetDriver() IDriver {
return dExt.Driver
}
func (dExt *DriverExt) Setup() error {
// unlock device screen
err := dExt.Driver.Unlock()

View File

@@ -42,7 +42,7 @@ func (dExt *DriverExt) InstallByUrl(url string, opts ...option.InstallOption) er
}
func (dExt *DriverExt) Install(filePath string, opts ...option.InstallOption) error {
if _, ok := dExt.Device.(*AndroidDevice); ok {
if _, ok := dExt.Driver.GetDevice().(*AndroidDevice); ok {
stopChan := make(chan struct{})
go func() {
ticker := time.NewTicker(5 * time.Second)
@@ -87,12 +87,12 @@ func (dExt *DriverExt) Install(filePath string, opts ...option.InstallOption) er
}()
}
return dExt.Device.Install(filePath, opts...)
return dExt.Driver.GetDevice().Install(filePath, opts...)
}
func (dExt *DriverExt) Uninstall(packageName string, opts ...option.ActionOption) error {
actionOptions := option.NewActionOptions(opts...)
err := dExt.Device.Uninstall(packageName)
err := dExt.Driver.GetDevice().Uninstall(packageName)
if err != nil {
log.Warn().Err(err).Msg("failed to uninstall")
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/pkg/ai"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
@@ -25,7 +26,7 @@ var popups = [][]string{
{"管理使用时间", ".*忽略.*"},
}
func findTextPopup(screenTexts OCRTexts) (closePoint *OCRText) {
func findTextPopup(screenTexts ai.OCRTexts) (closePoint *ai.OCRText) {
for _, popup := range popups {
if len(popup) != 2 {
continue
@@ -42,7 +43,7 @@ func findTextPopup(screenTexts OCRTexts) (closePoint *OCRText) {
return
}
func (dExt *DriverExt) handleTextPopup(screenTexts OCRTexts) error {
func (dExt *DriverExt) handleTextPopup(screenTexts ai.OCRTexts) error {
closePoint := findTextPopup(screenTexts)
if closePoint == nil {
// no popup found
@@ -76,13 +77,13 @@ func (dExt *DriverExt) AutoPopupHandler() error {
}
type PopupInfo struct {
*ClosePopupsResult
ClosePoints []PointF `json:"close_points,omitempty"` // CV 识别的所有关闭按钮(仅关闭按钮,可能存在多个)
PicName string `json:"pic_name"`
PicURL string `json:"pic_url"`
*ai.ClosePopupsResult
ClosePoints []ai.PointF `json:"close_points,omitempty"` // CV 识别的所有关闭按钮(仅关闭按钮,可能存在多个)
PicName string `json:"pic_name"`
PicURL string `json:"pic_url"`
}
func (p *PopupInfo) ClosePoint() *PointF {
func (p *PopupInfo) ClosePoint() *ai.PointF {
closeResult := p.ClosePopupsResult
if closeResult == nil {
return nil

View File

@@ -18,21 +18,22 @@ import (
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/internal/config"
"github.com/httprunner/httprunner/v5/pkg/ai"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
type ScreenResult struct {
bufSource *bytes.Buffer // raw image buffer bytes
ImagePath string `json:"image_path"` // image file path
Resolution Size `json:"resolution"`
UploadedURL string `json:"uploaded_url"` // uploaded image url
Texts OCRTexts `json:"texts"` // dumped raw OCRTexts
Icons UIResultMap `json:"icons"` // CV 识别的图标
Tags []string `json:"tags"` // tags for image, e.g. ["feed", "ad", "live"]
Popup *PopupInfo `json:"popup,omitempty"`
bufSource *bytes.Buffer // raw image buffer bytes
ImagePath string `json:"image_path"` // image file path
Resolution ai.Size `json:"resolution"`
UploadedURL string `json:"uploaded_url"` // uploaded image url
Texts ai.OCRTexts `json:"texts"` // dumped raw OCRTexts
Icons ai.UIResultMap `json:"icons"` // CV 识别的图标
Tags []string `json:"tags"` // tags for image, e.g. ["feed", "ad", "live"]
Popup *PopupInfo `json:"popup,omitempty"`
}
func (s *ScreenResult) FilterTextsByScope(x1, y1, x2, y2 float64) OCRTexts {
func (s *ScreenResult) FilterTextsByScope(x1, y1, x2, y2 float64) ai.OCRTexts {
if x1 > 1 || y1 > 1 || x2 > 1 || y2 > 1 {
log.Warn().Msg("x1, y1, x2, y2 should be in percentage, skip filter scope")
return s.Texts
@@ -46,9 +47,6 @@ func (s *ScreenResult) FilterTextsByScope(x1, y1, x2, y2 float64) OCRTexts {
// GetScreenResult takes a screenshot, returns the image recognition result
func (dExt *DriverExt) GetScreenResult(opts ...option.ActionOption) (screenResult *ScreenResult, err error) {
actionOptions := option.NewActionOptions(opts...)
if actionOptions.MaxRetryTimes == 0 {
actionOptions.MaxRetryTimes = 1
}
var fileName string
screenshotActions := actionOptions.ScreenshotActions()
@@ -61,9 +59,9 @@ func (dExt *DriverExt) GetScreenResult(opts ...option.ActionOption) (screenResul
}
var bufSource *bytes.Buffer
var imageResult *ImageResult
var imageResult *ai.ImageResult
var imagePath string
var windowSize Size
var windowSize ai.Size
var lastErr error
// get screenshot info with retry
@@ -87,9 +85,9 @@ func (dExt *DriverExt) GetScreenResult(opts ...option.ActionOption) (screenResul
Tags: nil,
Resolution: windowSize,
}
imageResult, err = dExt.ImageService.GetImage(bufSource, opts...)
imageResult, err = dExt.ImageService.GetImageFromBuffer(bufSource, opts...)
if err != nil {
log.Error().Err(err).Msg("GetImage from ImageService failed")
log.Error().Err(err).Msg("GetImageFromBuffer from ImageService failed")
lastErr = err
continue
}
@@ -130,7 +128,7 @@ func (dExt *DriverExt) GetScreenResult(opts ...option.ActionOption) (screenResul
return screenResult, nil
}
func (dExt *DriverExt) GetScreenTexts(opts ...option.ActionOption) (ocrTexts OCRTexts, err error) {
func (dExt *DriverExt) GetScreenTexts(opts ...option.ActionOption) (ocrTexts ai.OCRTexts, err error) {
actionOptions := option.NewActionOptions(opts...)
if actionOptions.ScreenShotFileName == "" {
opts = append(opts, option.WithScreenShotFileName("get_screen_texts"))
@@ -143,7 +141,7 @@ func (dExt *DriverExt) GetScreenTexts(opts ...option.ActionOption) (ocrTexts OCR
return screenResult.Texts, nil
}
func (dExt *DriverExt) FindUIRectInUIKit(search string, opts ...option.ActionOption) (point PointF, err error) {
func (dExt *DriverExt) FindUIRectInUIKit(search string, opts ...option.ActionOption) (point ai.PointF, err error) {
// find text using OCR
if !builtin.IsPathExists(search) {
return dExt.FindScreenText(search, opts...)
@@ -153,7 +151,7 @@ func (dExt *DriverExt) FindUIRectInUIKit(search string, opts ...option.ActionOpt
return
}
func (dExt *DriverExt) FindScreenText(text string, opts ...option.ActionOption) (point PointF, err error) {
func (dExt *DriverExt) FindScreenText(text string, opts ...option.ActionOption) (point ai.PointF, err error) {
actionOptions := option.NewActionOptions(opts...)
if actionOptions.ScreenShotFileName == "" {
opts = append(opts, option.WithScreenShotFileName(fmt.Sprintf("find_screen_text_%s", text)))
@@ -175,7 +173,7 @@ func (dExt *DriverExt) FindScreenText(text string, opts ...option.ActionOption)
return
}
func (dExt *DriverExt) FindUIResult(opts ...option.ActionOption) (point PointF, err error) {
func (dExt *DriverExt) FindUIResult(opts ...option.ActionOption) (point ai.PointF, err error) {
actionOptions := option.NewActionOptions(opts...)
if actionOptions.ScreenShotFileName == "" {
opts = append(opts, option.WithScreenShotFileName(

View File

@@ -12,6 +12,7 @@ import (
"time"
"github.com/httprunner/httprunner/v5/internal/json"
"github.com/httprunner/httprunner/v5/pkg/ai"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
@@ -24,7 +25,7 @@ type Session struct {
// cache to avoid repeated query
scale float64
windowSize Size
windowSize ai.Size
// cache uia2/wda request and response
requests []*DriverRequests

View File

@@ -10,6 +10,7 @@ import (
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/pkg/ai"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
@@ -146,7 +147,7 @@ func (dExt *DriverExt) swipeToTapTexts(texts []string, opts ...option.ActionOpti
actionOptions := option.NewActionOptions(opts...)
actionOptions.Identifier = ""
optionsWithoutIdentifier := actionOptions.Options()
var point PointF
var point ai.PointF
findTexts := func(d *DriverExt) error {
var err error
screenResult, err := d.GetScreenResult(

29
pkg/uixt/driver_test.go Normal file
View File

@@ -0,0 +1,29 @@
package uixt
import (
"testing"
"github.com/httprunner/httprunner/v5/pkg/ai"
)
func TestNewDriverExt(t *testing.T) {
device, _ := NewAndroidDevice()
var driver IDriver
var err error
if device.UIA2 || device.LogOn {
driver, err = NewUIA2Driver(device)
} else if device.STUB {
driver, err = NewStubAndroidDriver(device)
} else {
driver, err = NewADBDriver(device)
}
if err != nil {
t.Fatal(err)
}
driverExt, _ := NewDriverExt(driver,
ai.WithCVService(ai.CVServiceTypeVEDEM))
driverExt.GetDriver()
t.Log(driverExt)
}

View File

@@ -98,31 +98,6 @@ func (dev *HarmonyDevice) LogEnabled() bool {
return dev.LogOn
}
func (dev *HarmonyDevice) NewDriver(opts ...option.DriverOption) (driverExt *DriverExt, err error) {
driver, err := newHarmonyDriver(dev.Device)
if err != nil {
log.Error().Err(err).Msg("failed to new harmony driver")
return nil, err
}
driverExt, err = newDriverExt(dev, driver, opts...)
if err != nil {
return nil, err
}
return driverExt, nil
}
func (dev *HarmonyDevice) NewUSBDriver(opts ...option.DriverOption) (driver IDriver, err error) {
harmonyDriver, err := newHarmonyDriver(dev.Device)
if err != nil {
log.Error().Err(err).Msg("failed to new harmony driver")
return nil, err
}
return harmonyDriver, nil
}
func (dev *HarmonyDevice) Install(appPath string, opts ...option.InstallOption) error {
return nil
}

View File

@@ -10,12 +10,15 @@ import (
"code.byted.org/iesqa/ghdc"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v5/pkg/ai"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
type hdcDriver struct {
type HDCDriver struct {
*HarmonyDevice
*Session
IDriver
*DriverExt
points []ExportPoint
uiDriver *ghdc.UIDriver
}
@@ -28,10 +31,10 @@ const (
POWER_STATUS_ON PowerStatus = "POWER_STATUS_ON"
)
func newHarmonyDriver(device *ghdc.Device) (driver *hdcDriver, err error) {
driver = new(hdcDriver)
driver.Device = device
uiDriver, err := ghdc.NewUIDriver(*device)
func NewHDCDriver(device *HarmonyDevice) (driver *HDCDriver, err error) {
driver = new(HDCDriver)
driver.HarmonyDevice = device
uiDriver, err := ghdc.NewUIDriver(*device.Device)
if err != nil {
log.Error().Err(err).Msg("failed to new harmony ui driver")
return nil, err
@@ -41,64 +44,64 @@ func newHarmonyDriver(device *ghdc.Device) (driver *hdcDriver, err error) {
return
}
func (hd *hdcDriver) NewSession(capabilities option.Capabilities) (Session, error) {
func (hd *HDCDriver) NewSession(capabilities option.Capabilities) (Session, error) {
hd.Reset()
hd.Unlock()
return Session{}, errDriverNotImplemented
}
func (hd *hdcDriver) DeleteSession() error {
func (hd *HDCDriver) DeleteSession() error {
return errDriverNotImplemented
}
func (hd *hdcDriver) GetSession() *Session {
func (hd *HDCDriver) GetSession() *Session {
return hd.Session
}
func (hd *hdcDriver) Status() (DeviceStatus, error) {
func (hd *HDCDriver) Status() (DeviceStatus, error) {
return DeviceStatus{}, errDriverNotImplemented
}
func (hd *hdcDriver) DeviceInfo() (DeviceInfo, error) {
func (hd *HDCDriver) GetDevice() IDevice {
return hd.HarmonyDevice
}
func (hd *HDCDriver) DeviceInfo() (DeviceInfo, error) {
return DeviceInfo{}, errDriverNotImplemented
}
func (hd *hdcDriver) Location() (Location, error) {
func (hd *HDCDriver) Location() (Location, error) {
return Location{}, errDriverNotImplemented
}
func (hd *hdcDriver) BatteryInfo() (BatteryInfo, error) {
func (hd *HDCDriver) BatteryInfo() (BatteryInfo, error) {
return BatteryInfo{}, errDriverNotImplemented
}
func (hd *hdcDriver) WindowSize() (size Size, err error) {
func (hd *HDCDriver) WindowSize() (size ai.Size, err error) {
display, err := hd.uiDriver.GetDisplaySize()
if err != nil {
log.Error().Err(err).Msg("failed to get window size")
return Size{}, err
return ai.Size{}, err
}
size.Width = display.Width
size.Height = display.Height
return size, err
}
func (hd *hdcDriver) Screen() (Screen, error) {
return Screen{}, errDriverNotImplemented
func (hd *HDCDriver) Screen() (ai.Screen, error) {
return ai.Screen{}, errDriverNotImplemented
}
func (hd *hdcDriver) Scale() (float64, error) {
func (hd *HDCDriver) Scale() (float64, error) {
return 1, nil
}
func (hd *hdcDriver) GetTimestamp() (timestamp int64, err error) {
return 0, errDriverNotImplemented
}
func (hd *hdcDriver) Homescreen() error {
func (hd *HDCDriver) Homescreen() error {
return hd.uiDriver.PressKey(ghdc.KEYCODE_HOME)
}
func (hd *hdcDriver) Unlock() (err error) {
func (hd *HDCDriver) Unlock() (err error) {
// Todo 检查是否锁屏 hdc shell hidumper -s RenderService -a screen
screenInfo, err := hd.RunShellCommand("hidumper", "-s", "RenderService", "-a", "screen")
if err != nil {
@@ -120,12 +123,12 @@ func (hd *hdcDriver) Unlock() (err error) {
return hd.Swipe(500, 1500, 500, 500)
}
func (hd *hdcDriver) AppLaunch(packageName string) error {
func (hd *HDCDriver) AppLaunch(packageName string) error {
// Todo
return errDriverNotImplemented
}
func (hd *hdcDriver) AppTerminate(packageName string) (bool, error) {
func (hd *HDCDriver) AppTerminate(packageName string) (bool, error) {
_, err := hd.RunShellCommand("aa", "force-stop", packageName)
if err != nil {
log.Error().Err(err).Msg("failed to terminal app")
@@ -134,29 +137,29 @@ func (hd *hdcDriver) AppTerminate(packageName string) (bool, error) {
return true, nil
}
func (hd *hdcDriver) GetForegroundApp() (app AppInfo, err error) {
func (hd *HDCDriver) GetForegroundApp() (app AppInfo, err error) {
// Todo
return AppInfo{}, errDriverNotImplemented
}
func (hd *hdcDriver) AssertForegroundApp(packageName string, activityType ...string) error {
func (hd *HDCDriver) AssertForegroundApp(packageName string, activityType ...string) error {
// Todo
return nil
}
func (hd *hdcDriver) StartCamera() error {
func (hd *HDCDriver) StartCamera() error {
return errDriverNotImplemented
}
func (hd *hdcDriver) StopCamera() error {
func (hd *HDCDriver) StopCamera() error {
return errDriverNotImplemented
}
func (hd *hdcDriver) Orientation() (orientation Orientation, err error) {
func (hd *HDCDriver) Orientation() (orientation Orientation, err error) {
return OrientationPortrait, nil
}
func (hd *hdcDriver) Tap(x, y float64, opts ...option.ActionOption) error {
func (hd *HDCDriver) Tap(x, y float64, opts ...option.ActionOption) error {
actionOptions := option.NewActionOptions(opts...)
if len(actionOptions.Offset) == 2 {
@@ -173,20 +176,20 @@ func (hd *hdcDriver) Tap(x, y float64, opts ...option.ActionOption) error {
return hd.uiDriver.InjectGesture(ghdc.NewGesture().Start(ghdc.Point{X: int(x), Y: int(y)}).Pause(100))
}
func (hd *hdcDriver) DoubleTap(x, y float64, opts ...option.ActionOption) error {
func (hd *HDCDriver) DoubleTap(x, y float64, opts ...option.ActionOption) error {
return errDriverNotImplemented
}
func (hd *hdcDriver) TouchAndHold(x, y float64, opts ...option.ActionOption) (err error) {
func (hd *HDCDriver) TouchAndHold(x, y float64, opts ...option.ActionOption) (err error) {
return errDriverNotImplemented
}
func (hd *hdcDriver) Drag(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error {
func (hd *HDCDriver) Drag(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error {
return errDriverNotImplemented
}
// Swipe works like Drag, but `pressForDuration` value is 0
func (hd *hdcDriver) Swipe(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error {
func (hd *HDCDriver) Swipe(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error {
actionOptions := option.NewActionOptions(opts...)
if len(actionOptions.Offset) == 4 {
fromX += float64(actionOptions.Offset[0])
@@ -210,51 +213,51 @@ func (hd *hdcDriver) Swipe(fromX, fromY, toX, toY float64, opts ...option.Action
return hd.uiDriver.InjectGesture(ghdc.NewGesture().Start(ghdc.Point{X: int(fromX), Y: int(fromY)}).MoveTo(ghdc.Point{X: int(toX), Y: int(toY)}, duration))
}
func (hd *hdcDriver) SetPasteboard(contentType PasteboardType, content string) error {
func (hd *HDCDriver) SetPasteboard(contentType PasteboardType, content string) error {
return errDriverNotImplemented
}
func (hd *hdcDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error) {
func (hd *HDCDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error) {
return nil, errDriverNotImplemented
}
func (hd *hdcDriver) SetIme(ime string) error {
func (hd *HDCDriver) SetIme(ime string) error {
return errDriverNotImplemented
}
func (hd *hdcDriver) SendKeys(text string, opts ...option.ActionOption) error {
func (hd *HDCDriver) SendKeys(text string, opts ...option.ActionOption) error {
return hd.uiDriver.InputText(text)
}
func (hd *hdcDriver) Input(text string, opts ...option.ActionOption) error {
func (hd *HDCDriver) Input(text string, opts ...option.ActionOption) error {
return hd.uiDriver.InputText(text)
}
func (hd *hdcDriver) Clear(packageName string) error {
func (hd *HDCDriver) Clear(packageName string) error {
return errDriverNotImplemented
}
func (hd *hdcDriver) PressButton(devBtn DeviceButton) error {
func (hd *HDCDriver) PressButton(devBtn DeviceButton) error {
return errDriverNotImplemented
}
func (hd *hdcDriver) PressBack(opts ...option.ActionOption) error {
func (hd *HDCDriver) PressBack(opts ...option.ActionOption) error {
return hd.uiDriver.PressBack()
}
func (hd *hdcDriver) Backspace(count int, opts ...option.ActionOption) (err error) {
func (hd *HDCDriver) Backspace(count int, opts ...option.ActionOption) (err error) {
return nil
}
func (hd *hdcDriver) PressKeyCode(keyCode KeyCode) (err error) {
func (hd *HDCDriver) PressKeyCode(keyCode KeyCode) (err error) {
return errDriverNotImplemented
}
func (hd *hdcDriver) PressHarmonyKeyCode(keyCode ghdc.KeyCode) (err error) {
func (hd *HDCDriver) PressHarmonyKeyCode(keyCode ghdc.KeyCode) (err error) {
return hd.uiDriver.PressKey(keyCode)
}
func (hd *hdcDriver) Screenshot() (*bytes.Buffer, error) {
func (hd *HDCDriver) Screenshot() (*bytes.Buffer, error) {
tempDir := os.TempDir()
screenshotPath := fmt.Sprintf("%s/screenshot_%d.png", tempDir, time.Now().Unix())
err := hd.uiDriver.Screenshot(screenshotPath)
@@ -274,74 +277,78 @@ func (hd *hdcDriver) Screenshot() (*bytes.Buffer, error) {
return bytes.NewBuffer(raw), nil
}
func (hd *hdcDriver) Source(srcOpt ...option.SourceOption) (string, error) {
func (hd *HDCDriver) Source(srcOpt ...option.SourceOption) (string, error) {
return "", nil
}
func (hd *hdcDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
func (hd *HDCDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
err = errDriverNotImplemented
return
}
func (hd *hdcDriver) LogoutNoneUI(packageName string) error {
func (hd *HDCDriver) LogoutNoneUI(packageName string) error {
return errDriverNotImplemented
}
func (hd *hdcDriver) TapByText(text string, opts ...option.ActionOption) error {
func (hd *HDCDriver) TapByText(text string, opts ...option.ActionOption) error {
return errDriverNotImplemented
}
func (hd *hdcDriver) TapByTexts(actions ...TapTextAction) error {
func (hd *HDCDriver) TapByTexts(actions ...TapTextAction) error {
return errDriverNotImplemented
}
func (hd *hdcDriver) AccessibleSource() (string, error) {
func (hd *HDCDriver) AccessibleSource() (string, error) {
return "", errDriverNotImplemented
}
func (hd *hdcDriver) HealthCheck() error {
func (hd *HDCDriver) HealthCheck() error {
return errDriverNotImplemented
}
func (hd *hdcDriver) GetAppiumSettings() (map[string]interface{}, error) {
func (hd *HDCDriver) GetAppiumSettings() (map[string]interface{}, error) {
return nil, errDriverNotImplemented
}
func (hd *hdcDriver) SetAppiumSettings(settings map[string]interface{}) (map[string]interface{}, error) {
func (hd *HDCDriver) SetAppiumSettings(settings map[string]interface{}) (map[string]interface{}, error) {
return nil, errDriverNotImplemented
}
func (hd *hdcDriver) IsHealthy() (bool, error) {
func (hd *HDCDriver) IsHealthy() (bool, error) {
return false, errDriverNotImplemented
}
func (hd *hdcDriver) StartCaptureLog(identifier ...string) (err error) {
func (hd *HDCDriver) StartCaptureLog(identifier ...string) (err error) {
return errDriverNotImplemented
}
func (hd *hdcDriver) StopCaptureLog() (result interface{}, err error) {
func (hd *HDCDriver) StopCaptureLog() (result interface{}, err error) {
// defer clear(hd.points)
return hd.points, nil
}
func (hd *hdcDriver) GetDriverResults() []*DriverRequests {
func (hd *HDCDriver) GetDriverResults() []*DriverRequests {
return nil
}
func (hd *hdcDriver) RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error) {
func (hd *HDCDriver) RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error) {
return "", nil
}
func (hd *hdcDriver) TearDown() error {
func (hd *HDCDriver) Setup() error {
return nil
}
func (hd *hdcDriver) Rotation() (rotation Rotation, err error) {
func (hd *HDCDriver) TearDown() error {
return nil
}
func (hd *HDCDriver) Rotation() (rotation Rotation, err error) {
err = errDriverNotImplemented
return
}
func (hd *hdcDriver) SetRotation(rotation Rotation) (err error) {
func (hd *HDCDriver) SetRotation(rotation Rotation) (err error) {
err = errDriverNotImplemented
return
}

View File

@@ -7,18 +7,14 @@ import (
"testing"
)
var harmonyDriverExt *DriverExt
var hdcDriver *HDCDriver
func setupHarmonyDevice(t *testing.T) {
device, err := NewHarmonyDevice()
if err != nil {
t.Fatal(err)
}
driver, err = device.NewUSBDriver()
if err != nil {
t.Fatal(err)
}
harmonyDriverExt, err = newDriverExt(device, driver)
hdcDriver, err = NewHDCDriver(device)
if err != nil {
t.Fatal(err)
}
@@ -35,7 +31,7 @@ func TestWindowSize(t *testing.T) {
func TestHarmonyTap(t *testing.T) {
setupHarmonyDevice(t)
err := harmonyDriverExt.TapAbsXY(200, 2000)
err := hdcDriver.TapAbsXY(200, 2000)
if err != nil {
t.Fatal(err)
}
@@ -43,7 +39,7 @@ func TestHarmonyTap(t *testing.T) {
func TestHarmonySwipe(t *testing.T) {
setupHarmonyDevice(t)
err := harmonyDriverExt.SwipeLeft()
err := hdcDriver.SwipeLeft()
if err != nil {
t.Fatal(err)
}
@@ -51,7 +47,7 @@ func TestHarmonySwipe(t *testing.T) {
func TestHarmonyInput(t *testing.T) {
setupHarmonyDevice(t)
err := harmonyDriverExt.Input("test")
err := hdcDriver.Input("test")
if err != nil {
t.Fatal(err)
}

View File

@@ -552,7 +552,7 @@ func (dev *IOSDevice) NewHTTPDriver(capabilities option.Capabilities) (driver ID
Int("localPort", localPort).Int("localMjpegPort", localMjpegPort).
Msg("init WDA HTTP driver")
wd := new(wdaDriver)
wd := new(WDADriver)
wd.IOSDevice = dev
wd.udid = dev.UDID
wd.client = &http.Client{

View File

@@ -9,11 +9,12 @@ import (
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v5/pkg/ai"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
type stubIOSDriver struct {
*wdaDriver
*WDADriver
bightInsightPrefix string
serverPrefix string
@@ -38,7 +39,7 @@ func newStubIOSDriver(bightInsightAddr, serverAddr string, dev *IOSDevice, readT
}
func (s *stubIOSDriver) setUpWda() (err error) {
if s.wdaDriver == nil {
if s.WDADriver == nil {
capabilities := option.NewCapabilities()
capabilities.WithDefaultAlertAction(option.AlertActionAccept)
driver, err := s.device.NewHTTPDriver(capabilities)
@@ -46,7 +47,7 @@ func (s *stubIOSDriver) setUpWda() (err error) {
log.Error().Err(err).Msg("stub driver failed to init wda driver")
return err
}
s.wdaDriver = driver.(*wdaDriver)
s.WDADriver = driver.(*WDADriver)
}
return nil
}
@@ -57,7 +58,7 @@ func (s *stubIOSDriver) NewSession(capabilities option.Capabilities) (Session, e
if err != nil {
return Session{}, err
}
return s.wdaDriver.NewSession(capabilities)
return s.WDADriver.NewSession(capabilities)
}
// DeleteSession Kills application associated with that session and removes session
@@ -68,7 +69,7 @@ func (s *stubIOSDriver) DeleteSession() error {
if err != nil {
return err
}
return s.wdaDriver.DeleteSession()
return s.WDADriver.DeleteSession()
}
func (s *stubIOSDriver) Status() (DeviceStatus, error) {
@@ -76,7 +77,7 @@ func (s *stubIOSDriver) Status() (DeviceStatus, error) {
if err != nil {
return DeviceStatus{}, err
}
return s.wdaDriver.Status()
return s.WDADriver.Status()
}
func (s *stubIOSDriver) DeviceInfo() (DeviceInfo, error) {
@@ -84,7 +85,7 @@ func (s *stubIOSDriver) DeviceInfo() (DeviceInfo, error) {
if err != nil {
return DeviceInfo{}, err
}
return s.wdaDriver.DeviceInfo()
return s.WDADriver.DeviceInfo()
}
func (s *stubIOSDriver) Location() (Location, error) {
@@ -92,7 +93,7 @@ func (s *stubIOSDriver) Location() (Location, error) {
if err != nil {
return Location{}, err
}
return s.wdaDriver.Location()
return s.WDADriver.Location()
}
func (s *stubIOSDriver) BatteryInfo() (BatteryInfo, error) {
@@ -100,26 +101,26 @@ func (s *stubIOSDriver) BatteryInfo() (BatteryInfo, error) {
if err != nil {
return BatteryInfo{}, err
}
return s.wdaDriver.BatteryInfo()
return s.WDADriver.BatteryInfo()
}
// WindowSize Return the width and height in portrait mode.
// when getting the window size in wda/ui2/adb, if the device is in landscape mode,
// the width and height will be reversed.
func (s *stubIOSDriver) WindowSize() (Size, error) {
func (s *stubIOSDriver) WindowSize() (ai.Size, error) {
err := s.setUpWda()
if err != nil {
return Size{}, err
return ai.Size{}, err
}
return s.wdaDriver.WindowSize()
return s.WDADriver.WindowSize()
}
func (s *stubIOSDriver) Screen() (Screen, error) {
func (s *stubIOSDriver) Screen() (ai.Screen, error) {
err := s.setUpWda()
if err != nil {
return Screen{}, err
return ai.Screen{}, err
}
return s.wdaDriver.Screen()
return s.WDADriver.Screen()
}
func (s *stubIOSDriver) Scale() (float64, error) {
@@ -127,16 +128,7 @@ func (s *stubIOSDriver) Scale() (float64, error) {
if err != nil {
return 0, err
}
return s.wdaDriver.Scale()
}
// GetTimestamp returns the timestamp of the mobile device
func (s *stubIOSDriver) GetTimestamp() (timestamp int64, err error) {
err = s.setUpWda()
if err != nil {
return 0, err
}
return s.wdaDriver.GetTimestamp()
return s.WDADriver.Scale()
}
// Homescreen Forces the device under test to switch to the home screen
@@ -145,7 +137,7 @@ func (s *stubIOSDriver) Homescreen() error {
if err != nil {
return err
}
return s.wdaDriver.Homescreen()
return s.WDADriver.Homescreen()
}
func (s *stubIOSDriver) Unlock() (err error) {
@@ -153,7 +145,7 @@ func (s *stubIOSDriver) Unlock() (err error) {
if err != nil {
return err
}
return s.wdaDriver.Unlock()
return s.WDADriver.Unlock()
}
// AppLaunch Launch an application with given bundle identifier in scope of current session.
@@ -163,7 +155,7 @@ func (s *stubIOSDriver) AppLaunch(packageName string) error {
if err != nil {
return err
}
return s.wdaDriver.AppLaunch(packageName)
return s.WDADriver.AppLaunch(packageName)
}
// AppTerminate Terminate an application with the given package name.
@@ -173,7 +165,7 @@ func (s *stubIOSDriver) AppTerminate(packageName string) (bool, error) {
if err != nil {
return false, err
}
return s.wdaDriver.AppTerminate(packageName)
return s.WDADriver.AppTerminate(packageName)
}
// GetForegroundApp returns current foreground app package name and activity name
@@ -182,16 +174,7 @@ func (s *stubIOSDriver) GetForegroundApp() (app AppInfo, err error) {
if err != nil {
return AppInfo{}, err
}
return s.wdaDriver.GetForegroundApp()
}
// AssertForegroundApp returns nil if the given package and activity are in foreground
func (s *stubIOSDriver) AssertForegroundApp(packageName string, activityType ...string) error {
err := s.setUpWda()
if err != nil {
return err
}
return s.wdaDriver.AssertForegroundApp(packageName, activityType...)
return s.WDADriver.GetForegroundApp()
}
// StartCamera Starts a new camera for recording
@@ -200,7 +183,7 @@ func (s *stubIOSDriver) StartCamera() error {
if err != nil {
return err
}
return s.wdaDriver.StartCamera()
return s.WDADriver.StartCamera()
}
// StopCamera Stops the camera for recording
@@ -209,7 +192,7 @@ func (s *stubIOSDriver) StopCamera() error {
if err != nil {
return err
}
return s.wdaDriver.StopCamera()
return s.WDADriver.StopCamera()
}
func (s *stubIOSDriver) Orientation() (orientation Orientation, err error) {
@@ -217,7 +200,7 @@ func (s *stubIOSDriver) Orientation() (orientation Orientation, err error) {
if err != nil {
return OrientationPortrait, err
}
return s.wdaDriver.Orientation()
return s.WDADriver.Orientation()
}
// Tap Sends a tap event at the coordinate.
@@ -226,7 +209,7 @@ func (s *stubIOSDriver) Tap(x, y float64, opts ...option.ActionOption) error {
if err != nil {
return err
}
return s.wdaDriver.Tap(x, y, opts...)
return s.WDADriver.Tap(x, y, opts...)
}
// DoubleTap Sends a double tap event at the coordinate.
@@ -235,7 +218,7 @@ func (s *stubIOSDriver) DoubleTap(x, y float64, opts ...option.ActionOption) err
if err != nil {
return err
}
return s.wdaDriver.DoubleTap(x, y, opts...)
return s.WDADriver.DoubleTap(x, y, opts...)
}
// TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration.
@@ -246,7 +229,7 @@ func (s *stubIOSDriver) TouchAndHold(x, y float64, opts ...option.ActionOption)
if err != nil {
return err
}
return s.wdaDriver.TouchAndHold(x, y, opts...)
return s.WDADriver.TouchAndHold(x, y, opts...)
}
// Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate.
@@ -256,7 +239,7 @@ func (s *stubIOSDriver) Drag(fromX, fromY, toX, toY float64, opts ...option.Acti
if err != nil {
return err
}
return s.wdaDriver.Drag(fromX, fromY, toX, toY, opts...)
return s.WDADriver.Drag(fromX, fromY, toX, toY, opts...)
}
// SetPasteboard Sets data to the general pasteboard
@@ -265,7 +248,7 @@ func (s *stubIOSDriver) SetPasteboard(contentType PasteboardType, content string
if err != nil {
return err
}
return s.wdaDriver.SetPasteboard(contentType, content)
return s.WDADriver.SetPasteboard(contentType, content)
}
// GetPasteboard Gets the data contained in the general pasteboard.
@@ -276,7 +259,7 @@ func (s *stubIOSDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Bu
if err != nil {
return nil, err
}
return s.wdaDriver.GetPasteboard(contentType)
return s.WDADriver.GetPasteboard(contentType)
}
func (s *stubIOSDriver) SetIme(ime string) error {
@@ -284,7 +267,7 @@ func (s *stubIOSDriver) SetIme(ime string) error {
if err != nil {
return err
}
return s.wdaDriver.SetIme(ime)
return s.WDADriver.SetIme(ime)
}
// SendKeys Types a string into active element. There must be element with keyboard focus,
@@ -295,7 +278,7 @@ func (s *stubIOSDriver) SendKeys(text string, opts ...option.ActionOption) error
if err != nil {
return err
}
return s.wdaDriver.SendKeys(text, opts...)
return s.WDADriver.SendKeys(text, opts...)
}
// Input works like SendKeys
@@ -304,7 +287,7 @@ func (s *stubIOSDriver) Input(text string, opts ...option.ActionOption) error {
if err != nil {
return err
}
return s.wdaDriver.Input(text, opts...)
return s.WDADriver.Input(text, opts...)
}
func (s *stubIOSDriver) Clear(packageName string) error {
@@ -312,7 +295,7 @@ func (s *stubIOSDriver) Clear(packageName string) error {
if err != nil {
return err
}
return s.wdaDriver.Clear(packageName)
return s.WDADriver.Clear(packageName)
}
// PressButton Presses the corresponding hardware button on the device
@@ -321,7 +304,7 @@ func (s *stubIOSDriver) PressButton(devBtn DeviceButton) error {
if err != nil {
return err
}
return s.wdaDriver.PressButton(devBtn)
return s.WDADriver.PressButton(devBtn)
}
// PressBack Presses the back button
@@ -330,7 +313,7 @@ func (s *stubIOSDriver) PressBack(opts ...option.ActionOption) error {
if err != nil {
return err
}
return s.wdaDriver.PressBack(opts...)
return s.WDADriver.PressBack(opts...)
}
func (s *stubIOSDriver) PressKeyCode(keyCode KeyCode) (err error) {
@@ -338,7 +321,7 @@ func (s *stubIOSDriver) PressKeyCode(keyCode KeyCode) (err error) {
if err != nil {
return err
}
return s.wdaDriver.PressKeyCode(keyCode)
return s.WDADriver.PressKeyCode(keyCode)
}
func (s *stubIOSDriver) Screenshot() (*bytes.Buffer, error) {
@@ -346,7 +329,7 @@ func (s *stubIOSDriver) Screenshot() (*bytes.Buffer, error) {
if err != nil {
return nil, err
}
return s.wdaDriver.Screenshot()
return s.WDADriver.Screenshot()
//screenshotService, err := instruments.NewScreenshotService(s.device.d)
//if err != nil {
// log.Error().Err(err).Msg("Starting screenshot service failed")
@@ -367,7 +350,7 @@ func (s *stubIOSDriver) TapByText(text string, opts ...option.ActionOption) erro
if err != nil {
return err
}
return s.wdaDriver.TapByText(text, opts...)
return s.WDADriver.TapByText(text, opts...)
}
func (s *stubIOSDriver) TapByTexts(actions ...TapTextAction) error {
@@ -375,7 +358,7 @@ func (s *stubIOSDriver) TapByTexts(actions ...TapTextAction) error {
if err != nil {
return err
}
return s.wdaDriver.TapByTexts(actions...)
return s.WDADriver.TapByTexts(actions...)
}
// AccessibleSource Return application elements accessibility tree
@@ -384,7 +367,7 @@ func (s *stubIOSDriver) AccessibleSource() (string, error) {
if err != nil {
return "", err
}
return s.wdaDriver.AccessibleSource()
return s.WDADriver.AccessibleSource()
}
// HealthCheck Health check might modify simulator state so it should only be called in-between testing sessions
@@ -397,7 +380,7 @@ func (s *stubIOSDriver) HealthCheck() error {
if err != nil {
return err
}
return s.wdaDriver.HealthCheck()
return s.WDADriver.HealthCheck()
}
func (s *stubIOSDriver) GetAppiumSettings() (map[string]interface{}, error) {
@@ -405,7 +388,7 @@ func (s *stubIOSDriver) GetAppiumSettings() (map[string]interface{}, error) {
if err != nil {
return nil, err
}
return s.wdaDriver.GetAppiumSettings()
return s.WDADriver.GetAppiumSettings()
}
func (s *stubIOSDriver) SetAppiumSettings(settings map[string]interface{}) (map[string]interface{}, error) {
@@ -413,7 +396,7 @@ func (s *stubIOSDriver) SetAppiumSettings(settings map[string]interface{}) (map[
if err != nil {
return nil, err
}
return s.wdaDriver.SetAppiumSettings(settings)
return s.WDADriver.SetAppiumSettings(settings)
}
func (s *stubIOSDriver) IsHealthy() (bool, error) {
@@ -421,7 +404,7 @@ func (s *stubIOSDriver) IsHealthy() (bool, error) {
if err != nil {
return false, err
}
return s.wdaDriver.IsHealthy()
return s.WDADriver.IsHealthy()
}
// triggers the log capture and returns the log entries
@@ -430,7 +413,7 @@ func (s *stubIOSDriver) StartCaptureLog(identifier ...string) (err error) {
if err != nil {
return err
}
return s.wdaDriver.StartCaptureLog(identifier...)
return s.WDADriver.StartCaptureLog(identifier...)
}
func (s *stubIOSDriver) StopCaptureLog() (result interface{}, err error) {
@@ -438,7 +421,7 @@ func (s *stubIOSDriver) StopCaptureLog() (result interface{}, err error) {
if err != nil {
return nil, err
}
return s.wdaDriver.StopCaptureLog()
return s.WDADriver.StopCaptureLog()
}
func (s *stubIOSDriver) GetDriverResults() []*DriverRequests {
@@ -446,7 +429,7 @@ func (s *stubIOSDriver) GetDriverResults() []*DriverRequests {
if err != nil {
return nil
}
return s.wdaDriver.GetDriverResults()
return s.WDADriver.GetDriverResults()
}
func (s *stubIOSDriver) Source(srcOpt ...option.SourceOption) (string, error) {

View File

@@ -23,19 +23,31 @@ import (
"github.com/httprunner/httprunner/v5/code"
"github.com/httprunner/httprunner/v5/internal/builtin"
"github.com/httprunner/httprunner/v5/internal/json"
"github.com/httprunner/httprunner/v5/pkg/ai"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
type wdaDriver struct {
func NewWDADriver(device *IOSDevice) (*WDADriver, error) {
log.Info().Interface("device", device).Msg("init ios WDA driver")
driver := &WDADriver{
IOSDevice: device,
Session: &Session{},
}
driver.NewSession(nil)
return driver, nil
}
type WDADriver struct {
*IOSDevice
*Session
DriverExt
udid string
mjpegHTTPConn net.Conn // via HTTP
mjpegClient *http.Client
mjpegUrl string
}
func (wd *wdaDriver) resetSession() error {
func (wd *WDADriver) resetSession() error {
capabilities := option.NewCapabilities()
capabilities.WithDefaultAlertAction(option.AlertActionAccept)
@@ -59,7 +71,7 @@ func (wd *wdaDriver) resetSession() error {
return nil
}
func (wd *wdaDriver) httpRequest(method string, rawURL string, rawBody []byte) (rawResp rawResponse, err error) {
func (wd *WDADriver) httpRequest(method string, rawURL string, rawBody []byte) (rawResp rawResponse, err error) {
retryInterval := 3 * time.Second
for retryCount := 1; retryCount <= 3; retryCount++ {
rawResp, err = wd.Request(method, rawURL, rawBody)
@@ -93,11 +105,11 @@ func (wd *wdaDriver) httpRequest(method string, rawURL string, rawBody []byte) (
return
}
func (wd *wdaDriver) httpGET(pathElem ...string) (rawResp rawResponse, err error) {
func (wd *WDADriver) httpGET(pathElem ...string) (rawResp rawResponse, err error) {
return wd.httpRequest(http.MethodGet, wd.concatURL(nil, pathElem...), nil)
}
func (wd *wdaDriver) httpPOST(data interface{}, pathElem ...string) (rawResp rawResponse, err error) {
func (wd *WDADriver) httpPOST(data interface{}, pathElem ...string) (rawResp rawResponse, err error) {
var bsJSON []byte = nil
if data != nil {
if bsJSON, err = json.Marshal(data); err != nil {
@@ -107,15 +119,15 @@ func (wd *wdaDriver) httpPOST(data interface{}, pathElem ...string) (rawResp raw
return wd.httpRequest(http.MethodPost, wd.concatURL(nil, pathElem...), bsJSON)
}
func (wd *wdaDriver) httpDELETE(pathElem ...string) (rawResp rawResponse, err error) {
func (wd *WDADriver) httpDELETE(pathElem ...string) (rawResp rawResponse, err error) {
return wd.httpRequest(http.MethodDelete, wd.concatURL(nil, pathElem...), nil)
}
func (wd *wdaDriver) GetMjpegClient() *http.Client {
func (wd *WDADriver) GetMjpegClient() *http.Client {
return wd.mjpegClient
}
func (wd *wdaDriver) NewSession(capabilities option.Capabilities) (sessionInfo Session, err error) {
func (wd *WDADriver) NewSession(capabilities option.Capabilities) (sessionInfo Session, err error) {
// [[FBRoute POST:@"/session"].withoutSession respondWithTarget:self action:@selector(handleCreateSession:)]
data := make(map[string]interface{})
if len(capabilities) == 0 {
@@ -136,7 +148,7 @@ func (wd *wdaDriver) NewSession(capabilities option.Capabilities) (sessionInfo S
return
}
func (wd *wdaDriver) DeleteSession() (err error) {
func (wd *WDADriver) DeleteSession() (err error) {
if wd.mjpegClient != nil {
wd.mjpegClient.CloseIdleConnections()
}
@@ -149,7 +161,7 @@ func (wd *wdaDriver) DeleteSession() (err error) {
return
}
func (wd *wdaDriver) Status() (deviceStatus DeviceStatus, err error) {
func (wd *WDADriver) Status() (deviceStatus DeviceStatus, err error) {
// [[FBRoute GET:@"/status"].withoutSession respondWithTarget:self action:@selector(handleGetStatus:)]
var rawResp rawResponse
// Notice: use Driver.GET instead of httpGET to avoid loop calling
@@ -164,7 +176,11 @@ func (wd *wdaDriver) Status() (deviceStatus DeviceStatus, err error) {
return
}
func (wd *wdaDriver) DeviceInfo() (deviceInfo DeviceInfo, err error) {
func (wd *WDADriver) GetDevice() IDevice {
return wd.IOSDevice
}
func (wd *WDADriver) DeviceInfo() (deviceInfo DeviceInfo, err error) {
// [[FBRoute GET:@"/wda/device/info"] respondWithTarget:self action:@selector(handleGetDeviceInfo:)]
// [[FBRoute GET:@"/wda/device/info"].withoutSession
var rawResp rawResponse
@@ -179,7 +195,7 @@ func (wd *wdaDriver) DeviceInfo() (deviceInfo DeviceInfo, err error) {
return
}
func (wd *wdaDriver) Location() (location Location, err error) {
func (wd *WDADriver) Location() (location Location, err error) {
// [[FBRoute GET:@"/wda/device/location"] respondWithTarget:self action:@selector(handleGetLocation:)]
// [[FBRoute GET:@"/wda/device/location"].withoutSession
var rawResp rawResponse
@@ -194,7 +210,7 @@ func (wd *wdaDriver) Location() (location Location, err error) {
return
}
func (wd *wdaDriver) BatteryInfo() (batteryInfo BatteryInfo, err error) {
func (wd *WDADriver) BatteryInfo() (batteryInfo BatteryInfo, err error) {
// [[FBRoute GET:@"/wda/batteryInfo"] respondWithTarget:self action:@selector(handleGetBatteryInfo:)]
var rawResp rawResponse
if rawResp, err = wd.httpGET("/session", wd.sessionID, "/wda/batteryInfo"); err != nil {
@@ -208,7 +224,7 @@ func (wd *wdaDriver) BatteryInfo() (batteryInfo BatteryInfo, err error) {
return
}
func (wd *wdaDriver) WindowSize() (size Size, err error) {
func (wd *WDADriver) WindowSize() (size ai.Size, err error) {
// [[FBRoute GET:@"/window/size"] respondWithTarget:self action:@selector(handleGetWindowSize:)]
if !wd.windowSize.IsNil() {
// use cached window size
@@ -217,16 +233,16 @@ func (wd *wdaDriver) WindowSize() (size Size, err error) {
var rawResp rawResponse
if rawResp, err = wd.httpGET("/session", wd.sessionID, "/window/size"); err != nil {
return Size{}, errors.Wrap(err, "get window size failed by WDA request")
return ai.Size{}, errors.Wrap(err, "get window size failed by WDA request")
}
reply := new(struct{ Value struct{ Size } })
reply := new(struct{ Value struct{ ai.Size } })
if err = json.Unmarshal(rawResp, reply); err != nil {
return Size{}, errors.Wrap(err, "get window size failed by WDA response")
return ai.Size{}, errors.Wrap(err, "get window size failed by WDA response")
}
size = reply.Value.Size
scale, err := wd.Scale()
if err != nil {
return Size{}, errors.Wrap(err, "get window size scale failed")
return ai.Size{}, errors.Wrap(err, "get window size scale failed")
}
size.Height = size.Height * int(scale)
size.Width = size.Width * int(scale)
@@ -235,26 +251,21 @@ func (wd *wdaDriver) WindowSize() (size Size, err error) {
return wd.windowSize, nil
}
func (wd *wdaDriver) Screen() (screen Screen, err error) {
func (wd *WDADriver) Screen() (screen ai.Screen, err error) {
// [[FBRoute GET:@"/wda/screen"] respondWithTarget:self action:@selector(handleGetScreen:)]
var rawResp rawResponse
if rawResp, err = wd.httpGET("/session", wd.sessionID, "/wda/screen"); err != nil {
return Screen{}, err
return ai.Screen{}, err
}
reply := new(struct{ Value struct{ Screen } })
reply := new(struct{ Value struct{ ai.Screen } })
if err = json.Unmarshal(rawResp, reply); err != nil {
return Screen{}, err
return ai.Screen{}, err
}
screen = reply.Value.Screen
return
}
func (wd *wdaDriver) GetTimestamp() (timestamp int64, err error) {
return 0, errors.Wrap(errDriverNotImplemented,
"GetTimestamp not implemented for ios")
}
func (wd *wdaDriver) Scale() (float64, error) {
func (wd *WDADriver) Scale() (float64, error) {
if !builtin.IsZeroFloat64(wd.scale) {
return wd.scale, nil
}
@@ -266,11 +277,11 @@ func (wd *wdaDriver) Scale() (float64, error) {
return screen.Scale, nil
}
func (wd *wdaDriver) toScale(x float64) float64 {
func (wd *WDADriver) toScale(x float64) float64 {
return x / wd.scale
}
func (wd *wdaDriver) ActiveAppInfo() (info AppInfo, err error) {
func (wd *WDADriver) ActiveAppInfo() (info AppInfo, err error) {
// [[FBRoute GET:@"/wda/activeAppInfo"] respondWithTarget:self action:@selector(handleActiveAppInfo:)]
// [[FBRoute GET:@"/wda/activeAppInfo"].withoutSession
var rawResp rawResponse
@@ -285,7 +296,7 @@ func (wd *wdaDriver) ActiveAppInfo() (info AppInfo, err error) {
return
}
func (wd *wdaDriver) ActiveAppsList() (appsList []AppBaseInfo, err error) {
func (wd *WDADriver) ActiveAppsList() (appsList []AppBaseInfo, err error) {
// [[FBRoute GET:@"/wda/apps/list"] respondWithTarget:self action:@selector(handleGetActiveAppsList:)]
var rawResp rawResponse
if rawResp, err = wd.httpGET("/session", wd.sessionID, "/wda/apps/list"); err != nil {
@@ -299,7 +310,7 @@ func (wd *wdaDriver) ActiveAppsList() (appsList []AppBaseInfo, err error) {
return
}
func (wd *wdaDriver) AppState(bundleId string) (runState AppState, err error) {
func (wd *WDADriver) AppState(bundleId string) (runState AppState, err error) {
// [[FBRoute POST:@"/wda/apps/state"] respondWithTarget:self action:@selector(handleSessionAppState:)]
data := map[string]interface{}{"bundleId": bundleId}
var rawResp rawResponse
@@ -315,7 +326,7 @@ func (wd *wdaDriver) AppState(bundleId string) (runState AppState, err error) {
return
}
func (wd *wdaDriver) IsLocked() (locked bool, err error) {
func (wd *WDADriver) IsLocked() (locked bool, err error) {
// [[FBRoute GET:@"/wda/locked"] respondWithTarget:self action:@selector(handleIsLocked:)]
// [[FBRoute GET:@"/wda/locked"].withoutSession
var rawResp rawResponse
@@ -328,27 +339,27 @@ func (wd *wdaDriver) IsLocked() (locked bool, err error) {
return
}
func (wd *wdaDriver) Unlock() (err error) {
func (wd *WDADriver) Unlock() (err error) {
// [[FBRoute POST:@"/wda/unlock"] respondWithTarget:self action:@selector(handleUnlock:)]
// [[FBRoute POST:@"/wda/unlock"].withoutSession
_, err = wd.httpPOST(nil, "/session", wd.sessionID, "/wda/unlock")
return
}
func (wd *wdaDriver) Lock() (err error) {
func (wd *WDADriver) Lock() (err error) {
// [[FBRoute POST:@"/wda/lock"] respondWithTarget:self action:@selector(handleLock:)]
// [[FBRoute POST:@"/wda/lock"].withoutSession
_, err = wd.httpPOST(nil, "/session", wd.sessionID, "/wda/lock")
return
}
func (wd *wdaDriver) Homescreen() (err error) {
func (wd *WDADriver) Homescreen() (err error) {
// [[FBRoute POST:@"/wda/homescreen"].withoutSession respondWithTarget:self action:@selector(handleHomescreenCommand:)]
_, err = wd.httpPOST(nil, "/wda/homescreen")
return
}
func (wd *wdaDriver) AlertText() (text string, err error) {
func (wd *WDADriver) AlertText() (text string, err error) {
// [[FBRoute GET:@"/alert/text"] respondWithTarget:self action:@selector(handleAlertGetTextCommand:)]
// [[FBRoute GET:@"/alert/text"].withoutSession
var rawResp rawResponse
@@ -361,7 +372,7 @@ func (wd *wdaDriver) AlertText() (text string, err error) {
return
}
func (wd *wdaDriver) AlertButtons() (btnLabels []string, err error) {
func (wd *WDADriver) AlertButtons() (btnLabels []string, err error) {
// [[FBRoute GET:@"/wda/alert/buttons"] respondWithTarget:self action:@selector(handleGetAlertButtonsCommand:)]
var rawResp rawResponse
if rawResp, err = wd.httpGET("/session", wd.sessionID, "/wda/alert/buttons"); err != nil {
@@ -375,7 +386,7 @@ func (wd *wdaDriver) AlertButtons() (btnLabels []string, err error) {
return
}
func (wd *wdaDriver) AlertAccept(label ...string) (err error) {
func (wd *WDADriver) AlertAccept(label ...string) (err error) {
// [[FBRoute POST:@"/alert/accept"] respondWithTarget:self action:@selector(handleAlertAcceptCommand:)]
// [[FBRoute POST:@"/alert/accept"].withoutSession
data := make(map[string]interface{})
@@ -386,7 +397,7 @@ func (wd *wdaDriver) AlertAccept(label ...string) (err error) {
return
}
func (wd *wdaDriver) AlertDismiss(label ...string) (err error) {
func (wd *WDADriver) AlertDismiss(label ...string) (err error) {
// [[FBRoute POST:@"/alert/dismiss"] respondWithTarget:self action:@selector(handleAlertDismissCommand:)]
// [[FBRoute POST:@"/alert/dismiss"].withoutSession
data := make(map[string]interface{})
@@ -397,14 +408,14 @@ func (wd *wdaDriver) AlertDismiss(label ...string) (err error) {
return
}
func (wd *wdaDriver) AlertSendKeys(text string) (err error) {
func (wd *WDADriver) AlertSendKeys(text string) (err error) {
// [[FBRoute POST:@"/alert/text"] respondWithTarget:self action:@selector(handleAlertSetTextCommand:)]
data := map[string]interface{}{"value": strings.Split(text, "")}
_, err = wd.httpPOST(data, "/session", wd.sessionID, "/alert/text")
return
}
func (wd *wdaDriver) AppLaunch(bundleId string) (err error) {
func (wd *WDADriver) AppLaunch(bundleId string) (err error) {
// [[FBRoute POST:@"/wda/apps/launch"] respondWithTarget:self action:@selector(handleSessionAppLaunch:)]
data := make(map[string]interface{})
data["bundleId"] = bundleId
@@ -419,7 +430,7 @@ func (wd *wdaDriver) AppLaunch(bundleId string) (err error) {
return nil
}
func (wd *wdaDriver) AppLaunchUnattached(bundleId string) (err error) {
func (wd *WDADriver) AppLaunchUnattached(bundleId string) (err error) {
// [[FBRoute POST:@"/wda/apps/launchUnattached"].withoutSession respondWithTarget:self action:@selector(handleLaunchUnattachedApp:)]
data := map[string]interface{}{"bundleId": bundleId}
_, err = wd.httpPOST(data, "/wda/apps/launchUnattached")
@@ -430,7 +441,7 @@ func (wd *wdaDriver) AppLaunchUnattached(bundleId string) (err error) {
return nil
}
func (wd *wdaDriver) AppTerminate(bundleId string) (successful bool, err error) {
func (wd *WDADriver) AppTerminate(bundleId string) (successful bool, err error) {
// [[FBRoute POST:@"/wda/apps/terminate"] respondWithTarget:self action:@selector(handleSessionAppTerminate:)]
data := map[string]interface{}{"bundleId": bundleId}
var rawResp rawResponse
@@ -443,14 +454,14 @@ func (wd *wdaDriver) AppTerminate(bundleId string) (successful bool, err error)
return
}
func (wd *wdaDriver) AppActivate(bundleId string) (err error) {
func (wd *WDADriver) AppActivate(bundleId string) (err error) {
// [[FBRoute POST:@"/wda/apps/activate"] respondWithTarget:self action:@selector(handleSessionAppActivate:)]
data := map[string]interface{}{"bundleId": bundleId}
_, err = wd.httpPOST(data, "/session", wd.sessionID, "/wda/apps/activate")
return
}
func (wd *wdaDriver) AppDeactivate(second float64) (err error) {
func (wd *WDADriver) AppDeactivate(second float64) (err error) {
// [[FBRoute POST:@"/wda/deactivateApp"] respondWithTarget:self action:@selector(handleDeactivateAppCommand:)]
if second < 3 {
second = 3.0
@@ -460,7 +471,7 @@ func (wd *wdaDriver) AppDeactivate(second float64) (err error) {
return
}
func (wd *wdaDriver) GetForegroundApp() (appInfo AppInfo, err error) {
func (wd *WDADriver) GetForegroundApp() (appInfo AppInfo, err error) {
activeAppInfo, err := wd.ActiveAppInfo()
appInfo.BundleId = activeAppInfo.BundleId
if err != nil {
@@ -484,7 +495,7 @@ func (wd *wdaDriver) GetForegroundApp() (appInfo AppInfo, err error) {
return appInfo, err
}
func (wd *wdaDriver) AssertForegroundApp(bundleId string, viewControllerType ...string) error {
func (wd *WDADriver) AssertForegroundApp(bundleId string, viewControllerType ...string) error {
log.Debug().Str("bundleId", bundleId).
Strs("viewControllerType", viewControllerType).
Msg("assert ios foreground bundleId")
@@ -507,7 +518,7 @@ func (wd *wdaDriver) AssertForegroundApp(bundleId string, viewControllerType ...
return nil
}
func (wd *wdaDriver) Tap(x, y float64, opts ...option.ActionOption) (err error) {
func (wd *WDADriver) Tap(x, y float64, opts ...option.ActionOption) (err error) {
// [[FBRoute POST:@"/wda/tap/:uuid"] respondWithTarget:self action:@selector(handleTap:)]
actionOptions := option.NewActionOptions(opts...)
@@ -531,7 +542,7 @@ func (wd *wdaDriver) Tap(x, y float64, opts ...option.ActionOption) (err error)
return
}
func (wd *wdaDriver) DoubleTap(x, y float64, opts ...option.ActionOption) (err error) {
func (wd *WDADriver) DoubleTap(x, y float64, opts ...option.ActionOption) (err error) {
// [[FBRoute POST:@"/wda/doubleTap"] respondWithTarget:self action:@selector(handleDoubleTapCoordinate:)]
actionOptions := option.NewActionOptions(opts...)
x = wd.toScale(x)
@@ -551,7 +562,7 @@ func (wd *wdaDriver) DoubleTap(x, y float64, opts ...option.ActionOption) (err e
return
}
func (wd *wdaDriver) TouchAndHold(x, y float64, opts ...option.ActionOption) (err error) {
func (wd *WDADriver) TouchAndHold(x, y float64, opts ...option.ActionOption) (err error) {
actionOptions := option.NewActionOptions(opts...)
if actionOptions.Duration == 0 {
opts = append(opts, option.WithDuration(1))
@@ -559,7 +570,7 @@ func (wd *wdaDriver) TouchAndHold(x, y float64, opts ...option.ActionOption) (er
return wd.Tap(x, y, opts...)
}
func (wd *wdaDriver) Drag(fromX, fromY, toX, toY float64, opts ...option.ActionOption) (err error) {
func (wd *WDADriver) Drag(fromX, fromY, toX, toY float64, opts ...option.ActionOption) (err error) {
// [[FBRoute POST:@"/wda/dragfromtoforduration"] respondWithTarget:self action:@selector(handleDragCoordinate:)]
actionOptions := option.NewActionOptions(opts...)
@@ -596,11 +607,11 @@ func (wd *wdaDriver) Drag(fromX, fromY, toX, toY float64, opts ...option.ActionO
return
}
func (wd *wdaDriver) Swipe(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error {
func (wd *WDADriver) Swipe(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error {
return wd.Drag(fromX, fromY, toX, toY, opts...)
}
func (wd *wdaDriver) SetPasteboard(contentType PasteboardType, content string) (err error) {
func (wd *WDADriver) SetPasteboard(contentType PasteboardType, content string) (err error) {
// [[FBRoute POST:@"/wda/setPasteboard"] respondWithTarget:self action:@selector(handleSetPasteboard:)]
data := map[string]interface{}{
"contentType": contentType,
@@ -610,7 +621,7 @@ func (wd *wdaDriver) SetPasteboard(contentType PasteboardType, content string) (
return
}
func (wd *wdaDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error) {
func (wd *WDADriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error) {
// [[FBRoute POST:@"/wda/getPasteboard"] respondWithTarget:self action:@selector(handleGetPasteboard:)]
data := map[string]interface{}{"contentType": contentType}
var rawResp rawResponse
@@ -623,15 +634,15 @@ func (wd *wdaDriver) GetPasteboard(contentType PasteboardType) (raw *bytes.Buffe
return
}
func (wd *wdaDriver) SetIme(ime string) error {
func (wd *WDADriver) SetIme(ime string) error {
return errDriverNotImplemented
}
func (wd *wdaDriver) PressKeyCode(keyCode KeyCode) (err error) {
func (wd *WDADriver) PressKeyCode(keyCode KeyCode) (err error) {
return errDriverNotImplemented
}
func (wd *wdaDriver) SendKeys(text string, opts ...option.ActionOption) (err error) {
func (wd *WDADriver) SendKeys(text string, opts ...option.ActionOption) (err error) {
// [[FBRoute POST:@"/wda/keys"] respondWithTarget:self action:@selector(handleKeys:)]
actionOptions := option.NewActionOptions(opts...)
data := map[string]interface{}{"value": strings.Split(text, "")}
@@ -643,7 +654,7 @@ func (wd *wdaDriver) SendKeys(text string, opts ...option.ActionOption) (err err
return
}
func (wd *wdaDriver) Backspace(count int, opts ...option.ActionOption) (err error) {
func (wd *WDADriver) Backspace(count int, opts ...option.ActionOption) (err error) {
if count == 0 {
return nil
}
@@ -657,16 +668,16 @@ func (wd *wdaDriver) Backspace(count int, opts ...option.ActionOption) (err erro
return
}
func (wd *wdaDriver) Input(text string, opts ...option.ActionOption) (err error) {
func (wd *WDADriver) Input(text string, opts ...option.ActionOption) (err error) {
return wd.SendKeys(text, opts...)
}
func (wd *wdaDriver) Clear(packageName string) error {
func (wd *WDADriver) Clear(packageName string) error {
return errDriverNotImplemented
}
// PressBack simulates a short press on the BACK button.
func (wd *wdaDriver) PressBack(opts ...option.ActionOption) (err error) {
func (wd *WDADriver) PressBack(opts ...option.ActionOption) (err error) {
actionOptions := option.NewActionOptions(opts...)
windowSize, err := wd.WindowSize()
@@ -702,27 +713,27 @@ func (wd *wdaDriver) PressBack(opts ...option.ActionOption) (err error) {
return
}
func (wd *wdaDriver) PressButton(devBtn DeviceButton) (err error) {
func (wd *WDADriver) PressButton(devBtn DeviceButton) (err error) {
// [[FBRoute POST:@"/wda/pressButton"] respondWithTarget:self action:@selector(handlePressButtonCommand:)]
data := map[string]interface{}{"name": devBtn}
_, err = wd.httpPOST(data, "/session", wd.sessionID, "/wda/pressButton")
return
}
func (wd *wdaDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
func (wd *WDADriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
return info, errDriverNotImplemented
}
func (wd *wdaDriver) LogoutNoneUI(packageName string) error {
func (wd *WDADriver) LogoutNoneUI(packageName string) error {
return errDriverNotImplemented
}
func (wd *wdaDriver) StartCamera() (err error) {
func (wd *WDADriver) StartCamera() (err error) {
// start camera, alias for app_launch com.apple.camera
return wd.AppLaunch("com.apple.camera")
}
func (wd *wdaDriver) StopCamera() (err error) {
func (wd *WDADriver) StopCamera() (err error) {
// stop camera, alias for app_terminate com.apple.camera
success, err := wd.AppTerminate("com.apple.camera")
if err != nil {
@@ -734,7 +745,7 @@ func (wd *wdaDriver) StopCamera() (err error) {
return nil
}
func (wd *wdaDriver) Orientation() (orientation Orientation, err error) {
func (wd *WDADriver) Orientation() (orientation Orientation, err error) {
// [[FBRoute GET:@"/orientation"] respondWithTarget:self action:@selector(handleGetOrientation:)]
var rawResp rawResponse
if rawResp, err = wd.httpGET("/session", wd.sessionID, "/orientation"); err != nil {
@@ -748,14 +759,14 @@ func (wd *wdaDriver) Orientation() (orientation Orientation, err error) {
return
}
func (wd *wdaDriver) SetOrientation(orientation Orientation) (err error) {
func (wd *WDADriver) SetOrientation(orientation Orientation) (err error) {
// [[FBRoute POST:@"/orientation"] respondWithTarget:self action:@selector(handleSetOrientation:)]
data := map[string]interface{}{"orientation": orientation}
_, err = wd.httpPOST(data, "/session", wd.sessionID, "/orientation")
return
}
func (wd *wdaDriver) Rotation() (rotation Rotation, err error) {
func (wd *WDADriver) Rotation() (rotation Rotation, err error) {
// [[FBRoute GET:@"/rotation"] respondWithTarget:self action:@selector(handleGetRotation:)]
var rawResp rawResponse
if rawResp, err = wd.httpGET("/session", wd.sessionID, "/rotation"); err != nil {
@@ -769,13 +780,13 @@ func (wd *wdaDriver) Rotation() (rotation Rotation, err error) {
return
}
func (wd *wdaDriver) SetRotation(rotation Rotation) (err error) {
func (wd *WDADriver) SetRotation(rotation Rotation) (err error) {
// [[FBRoute POST:@"/rotation"] respondWithTarget:self action:@selector(handleSetRotation:)]
_, err = wd.httpPOST(rotation, "/session", wd.sessionID, "/rotation")
return
}
func (wd *wdaDriver) Screenshot() (raw *bytes.Buffer, err error) {
func (wd *WDADriver) Screenshot() (raw *bytes.Buffer, err error) {
// [[FBRoute GET:@"/screenshot"] respondWithTarget:self action:@selector(handleGetScreenshot:)]
// [[FBRoute GET:@"/screenshot"].withoutSession respondWithTarget:self action:@selector(handleGetScreenshot:)]
var rawResp rawResponse
@@ -791,7 +802,7 @@ func (wd *wdaDriver) Screenshot() (raw *bytes.Buffer, err error) {
return
}
func (wd *wdaDriver) Source(srcOpt ...option.SourceOption) (source string, err error) {
func (wd *WDADriver) Source(srcOpt ...option.SourceOption) (source string, err error) {
// [[FBRoute GET:@"/source"] respondWithTarget:self action:@selector(handleGetSourceCommand:)]
// [[FBRoute GET:@"/source"].withoutSession
tmp, _ := url.Parse(wd.concatURL(nil, "/session", wd.sessionID))
@@ -825,15 +836,15 @@ func (wd *wdaDriver) Source(srcOpt ...option.SourceOption) (source string, err e
return
}
func (wd *wdaDriver) TapByText(text string, opts ...option.ActionOption) error {
func (wd *WDADriver) TapByText(text string, opts ...option.ActionOption) error {
return errDriverNotImplemented
}
func (wd *wdaDriver) TapByTexts(actions ...TapTextAction) error {
func (wd *WDADriver) TapByTexts(actions ...TapTextAction) error {
return errDriverNotImplemented
}
func (wd *wdaDriver) AccessibleSource() (source string, err error) {
func (wd *WDADriver) AccessibleSource() (source string, err error) {
// [[FBRoute GET:@"/wda/accessibleSource"] respondWithTarget:self action:@selector(handleGetAccessibleSourceCommand:)]
// [[FBRoute GET:@"/wda/accessibleSource"].withoutSession
var rawResp rawResponse
@@ -848,13 +859,13 @@ func (wd *wdaDriver) AccessibleSource() (source string, err error) {
return
}
func (wd *wdaDriver) HealthCheck() (err error) {
func (wd *WDADriver) HealthCheck() (err error) {
// [[FBRoute GET:@"/wda/healthcheck"].withoutSession respondWithTarget:self action:@selector(handleGetHealthCheck:)]
_, err = wd.httpGET("/wda/healthcheck")
return
}
func (wd *wdaDriver) GetAppiumSettings() (settings map[string]interface{}, err error) {
func (wd *WDADriver) GetAppiumSettings() (settings map[string]interface{}, err error) {
// [[FBRoute GET:@"/appium/settings"] respondWithTarget:self action:@selector(handleGetSettings:)]
var rawResp rawResponse
if rawResp, err = wd.httpGET("/session", wd.sessionID, "/appium/settings"); err != nil {
@@ -868,7 +879,7 @@ func (wd *wdaDriver) GetAppiumSettings() (settings map[string]interface{}, err e
return
}
func (wd *wdaDriver) SetAppiumSettings(settings map[string]interface{}) (ret map[string]interface{}, err error) {
func (wd *WDADriver) SetAppiumSettings(settings map[string]interface{}) (ret map[string]interface{}, err error) {
// [[FBRoute POST:@"/appium/settings"] respondWithTarget:self action:@selector(handleSetSettings:)]
data := map[string]interface{}{"settings": settings}
var rawResp rawResponse
@@ -883,7 +894,7 @@ func (wd *wdaDriver) SetAppiumSettings(settings map[string]interface{}) (ret map
return
}
func (wd *wdaDriver) IsHealthy() (healthy bool, err error) {
func (wd *WDADriver) IsHealthy() (healthy bool, err error) {
var rawResp rawResponse
if rawResp, err = wd.httpGET("/health"); err != nil {
return false, err
@@ -894,17 +905,17 @@ func (wd *wdaDriver) IsHealthy() (healthy bool, err error) {
return true, nil
}
func (wd *wdaDriver) WdaShutdown() (err error) {
func (wd *WDADriver) WdaShutdown() (err error) {
_, err = wd.httpGET("/wda/shutdown")
return
}
func (wd *wdaDriver) triggerWDALog(data map[string]interface{}) (rawResp []byte, err error) {
func (wd *WDADriver) triggerWDALog(data map[string]interface{}) (rawResp []byte, err error) {
// [[FBRoute POST:@"/gtf/automation/log"].withoutSession respondWithTarget:self action:@selector(handleAutomationLog:)]
return wd.httpPOST(data, "/gtf/automation/log")
}
func (wd *wdaDriver) RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error) {
func (wd *WDADriver) RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error) {
// 获取当前时间戳
timestamp := time.Now().Format("20060102_150405") + fmt.Sprintf("_%03d", time.Now().UnixNano()/1e6%1000)
// 创建文件名
@@ -968,7 +979,7 @@ func (wd *wdaDriver) RecordScreen(folderPath string, duration time.Duration) (vi
return filepath.Abs(fileName)
}
func (wd *wdaDriver) StartCaptureLog(identifier ...string) error {
func (wd *WDADriver) StartCaptureLog(identifier ...string) error {
log.Info().Msg("start WDA log recording")
if identifier == nil {
identifier = []string{""}
@@ -988,7 +999,7 @@ type wdaResponse struct {
SessionID string `json:"sessionId"`
}
func (wd *wdaDriver) StopCaptureLog() (result interface{}, err error) {
func (wd *WDADriver) StopCaptureLog() (result interface{}, err error) {
log.Info().Msg("stop log recording")
data := map[string]interface{}{"action": "stop"}
rawResp, err := wd.triggerWDALog(data)
@@ -1007,18 +1018,22 @@ func (wd *wdaDriver) StopCaptureLog() (result interface{}, err error) {
return reply.Value, nil
}
func (wd *wdaDriver) GetSession() *Session {
func (wd *WDADriver) GetSession() *Session {
return wd.Session
}
func (wd *wdaDriver) GetDriverResults() []*DriverRequests {
func (wd *WDADriver) GetDriverResults() []*DriverRequests {
defer func() {
wd.requests = nil
}()
return wd.requests
}
func (wd *wdaDriver) TearDown() error {
func (wd *WDADriver) Setup() error {
return nil
}
func (wd *WDADriver) TearDown() error {
wd.mjpegClient.CloseIdleConnections()
wd.client.CloseIdleConnections()
return nil

View File

@@ -471,7 +471,7 @@ func Test_remoteWD_AccessibleSource(t *testing.T) {
func TestRecord(t *testing.T) {
setup(t)
path, err := driver.(*wdaDriver).RecordScreen("", 5*time.Second)
path, err := driver.(*WDADriver).RecordScreen("", 5*time.Second)
if err != nil {
t.Fatal(err)
}

View File

@@ -260,6 +260,9 @@ func NewActionOptions(opts ...ActionOption) *ActionOptions {
for _, option := range opts {
option(actionOptions)
}
if actionOptions.MaxRetryTimes == 0 {
actionOptions.MaxRetryTimes = 1
}
return actionOptions
}

View File

@@ -1,6 +1,8 @@
package option
import "github.com/httprunner/funplugin"
import (
"github.com/httprunner/funplugin"
)
type DriverOptions struct {
Capabilities Capabilities

View File

@@ -2,7 +2,6 @@ package uixt
import (
"fmt"
"math"
)
type DeviceStatus struct {
@@ -153,20 +152,6 @@ func (bs BatteryStatus) String() string {
}
}
type Size struct {
Width int `json:"width"`
Height int `json:"height"`
}
func (s Size) IsNil() bool {
return s.Width == 0 && s.Height == 0
}
type Screen struct {
StatusBarSize Size `json:"statusBarSize"`
Scale float64 `json:"scale"`
}
type AppInfo struct {
Name string `json:"name,omitempty"`
AppBaseInfo
@@ -274,18 +259,3 @@ const (
DirectionLeft Direction = "left"
DirectionRight Direction = "right"
)
type Point struct {
X int `json:"x"` // upper left X coordinate of selected element
Y int `json:"y"` // upper left Y coordinate of selected element
}
type PointF struct {
X float64 `json:"x"`
Y float64 `json:"y"`
}
func (p PointF) IsIdentical(p2 PointF) bool {
// set the coordinate precision to 1 pixel
return math.Abs(p.X-p2.X) < 1 && math.Abs(p.Y-p2.Y) < 1
}