Files
httprunner/hrp/internal/uixt/ext.go
2022-09-21 15:37:10 +08:00

238 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package uixt
import (
"bytes"
"fmt"
"image"
"image/jpeg"
"image/png"
"mime"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/electricbubble/gwda"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
)
// TemplateMatchMode is the type of the template matching operation.
type TemplateMatchMode int
type DriverExt struct {
gwda.WebDriver
windowSize gwda.Size
frame *bytes.Buffer
doneMjpegStream chan bool
// OpenCV
scale float64
matchMode TemplateMatchMode
threshold float64
}
// Extend 获得扩展后的 Driver
// 并指定匹配阀值,
// 获取当前设备的 Scale
// 默认匹配模式为 TmCcoeffNormed
// 默认关闭 OpenCV 匹配值计算后的输出
func Extend(driver gwda.WebDriver, threshold float64, matchMode ...TemplateMatchMode) (dExt *DriverExt, err error) {
dExt = &DriverExt{WebDriver: driver}
dExt.doneMjpegStream = make(chan bool, 1)
// get device window size
dExt.windowSize, err = dExt.WebDriver.WindowSize()
if err != nil {
return nil, errors.Wrap(err, "failed to get windows size")
}
err = dExt.extendOpenCV(threshold, matchMode...)
return dExt, err
}
func (dExt *DriverExt) ConnectMjpegStream(httpClient *http.Client) (err error) {
if httpClient == nil {
return errors.New(`'httpClient' can't be nil`)
}
var req *http.Request
if req, err = http.NewRequest(http.MethodGet, "http://*", nil); err != nil {
return err
}
var resp *http.Response
if resp, err = httpClient.Do(req); err != nil {
return err
}
// defer func() { _ = resp.Body.Close() }()
var boundary string
if _, param, err := mime.ParseMediaType(resp.Header.Get("Content-Type")); err != nil {
return err
} else {
boundary = strings.Trim(param["boundary"], "-")
}
mjpegReader := multipart.NewReader(resp.Body, boundary)
go func() {
for {
select {
case <-dExt.doneMjpegStream:
_ = resp.Body.Close()
return
default:
var part *multipart.Part
if part, err = mjpegReader.NextPart(); err != nil {
dExt.frame = nil
continue
}
raw := new(bytes.Buffer)
if _, err = raw.ReadFrom(part); err != nil {
dExt.frame = nil
continue
}
dExt.frame = raw
}
}
}()
return
}
func (dExt *DriverExt) CloseMjpegStream() {
dExt.doneMjpegStream <- true
}
func (dExt *DriverExt) takeScreenShot() (raw *bytes.Buffer, err error) {
// 优先使用 MJPEG 流进行截图,性能最优
// 如果 MJPEG 流未开启,则使用 WebDriver 的截图接口
if dExt.frame != nil {
return dExt.frame, nil
}
if raw, err = dExt.WebDriver.Screenshot(); err != nil {
log.Error().Err(err).Msgf("screenshot failed: %v", err)
return nil, err
}
return
}
// saveScreenShot saves image file to $CWD/screenshots/ folder
func (dExt *DriverExt) saveScreenShot(raw *bytes.Buffer, fileName string) (string, error) {
img, format, err := image.Decode(raw)
if err != nil {
return "", errors.Wrap(err, "decode screenshot image failed")
}
dir, _ := os.Getwd()
screenshotsDir := filepath.Join(dir, "screenshots")
if err = os.MkdirAll(screenshotsDir, os.ModePerm); err != nil {
return "", errors.Wrap(err, "create screenshots directory failed")
}
screenshotPath := filepath.Join(screenshotsDir,
fmt.Sprintf("%s.%s", fileName, format))
file, err := os.Create(screenshotPath)
if err != nil {
return "", errors.Wrap(err, "create screenshot image file failed")
}
defer func() {
_ = file.Close()
}()
switch format {
case "png":
err = png.Encode(file, img)
case "jpeg":
err = jpeg.Encode(file, img, nil)
default:
return "", fmt.Errorf("unsupported image format: %s", format)
}
if err != nil {
return "", errors.Wrap(err, "encode screenshot image failed")
}
return screenshotPath, nil
}
// ScreenShot takes screenshot and saves image file to $CWD/screenshots/ folder
func (dExt *DriverExt) ScreenShot(fileName string) (string, error) {
raw, err := dExt.takeScreenShot()
if err != nil {
return "", errors.Wrap(err, "screenshot by WDA failed")
}
return dExt.saveScreenShot(raw, fileName)
}
// isPathExists returns true if path exists, whether path is file or dir
func isPathExists(path string) bool {
if _, err := os.Stat(path); os.IsNotExist(err) {
return false
}
return true
}
func (dExt *DriverExt) FindUIElement(param string) (ele gwda.WebElement, err error) {
var selector gwda.BySelector
if strings.HasPrefix(param, "/") {
// xpath
selector = gwda.BySelector{
XPath: param,
}
} else {
// name
selector = gwda.BySelector{
LinkText: gwda.NewElementAttribute().WithName(param),
}
}
return dExt.WebDriver.FindElement(selector)
}
func (dExt *DriverExt) FindUIRectInUIKit(search string) (x, y, width, height float64, err error) {
// click on text, using OCR
if !isPathExists(search) {
return dExt.FindTextByOCR(search)
}
// click on image, using opencv
return dExt.FindImageRectInUIKit(search)
}
func (dExt *DriverExt) PerformTouchActions(touchActions *gwda.TouchActions) error {
return dExt.PerformAppiumTouchActions(touchActions)
}
func (dExt *DriverExt) PerformActions(actions *gwda.W3CActions) error {
return dExt.PerformW3CActions(actions)
}
func (dExt *DriverExt) IsNameExist(name string) bool {
selector := gwda.BySelector{
LinkText: gwda.NewElementAttribute().WithName(name),
}
_, err := dExt.FindElement(selector)
return err == nil
}
func (dExt *DriverExt) IsLabelExist(label string) bool {
selector := gwda.BySelector{
LinkText: gwda.NewElementAttribute().WithLabel(label),
}
_, err := dExt.FindElement(selector)
return err == nil
}
func (dExt *DriverExt) IsOCRExist(text string) bool {
_, _, _, _, err := dExt.FindTextByOCR(text)
return err == nil
}
func (dExt *DriverExt) IsImageExist(text string) bool {
_, _, _, _, err := dExt.FindImageRectInUIKit(text)
return err == nil
}