mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 02:21:29 +08:00
refactor: split ai related logic to pkg/ai
This commit is contained in:
63
pkg/ai/ai.go
Normal file
63
pkg/ai/ai.go
Normal 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
11
pkg/ai/ai_test.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package ai
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestOption(t *testing.T) {
|
||||
options := NewAIService(
|
||||
WithCVService(CVServiceTypeOpenCV),
|
||||
WithLLMService(LLMServiceTypeDeepSeekV3),
|
||||
)
|
||||
t.Log(options)
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
41
pkg/ai/cv_vedem_test.go
Normal 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
20
pkg/ai/llm.go
Normal 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
213
pkg/ai/screen.go
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
29
pkg/uixt/driver_test.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -260,6 +260,9 @@ func NewActionOptions(opts ...ActionOption) *ActionOptions {
|
||||
for _, option := range opts {
|
||||
option(actionOptions)
|
||||
}
|
||||
if actionOptions.MaxRetryTimes == 0 {
|
||||
actionOptions.MaxRetryTimes = 1
|
||||
}
|
||||
return actionOptions
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package option
|
||||
|
||||
import "github.com/httprunner/funplugin"
|
||||
import (
|
||||
"github.com/httprunner/funplugin"
|
||||
)
|
||||
|
||||
type DriverOptions struct {
|
||||
Capabilities Capabilities
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user