refactor: relocate code

This commit is contained in:
lilong.129
2025-02-06 18:08:46 +08:00
parent 82001882b8
commit 0e5eb8b303
4 changed files with 351 additions and 388 deletions

View File

@@ -1,204 +0,0 @@
package uixt
import (
"bytes"
"context"
"encoding/json"
"io"
"net"
"net/http"
"net/url"
"path"
"strings"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
type DriverSession struct {
ID string
// cache uia2/wda request and response
requests []*DriverResult
// cache screenshot ocr results
screenResults []*ScreenResult // list of actions
// cache e2e delay
e2eDelay []timeLog
}
func (d *DriverSession) addScreenResult(screenResult *ScreenResult) {
d.screenResults = append(d.screenResults, screenResult)
}
func (d *DriverSession) addRequestResult(driverResult *DriverResult) {
d.requests = append(d.requests, driverResult)
}
func (d *DriverSession) Reset() {
d.screenResults = make([]*ScreenResult, 0)
d.requests = make([]*DriverResult, 0)
d.e2eDelay = nil
}
type Attachments map[string]interface{}
func (d *DriverSession) Get(withReset bool) Attachments {
data := Attachments{
"screen_results": d.screenResults,
}
if len(d.requests) != 0 {
data["requests"] = d.requests
}
if d.e2eDelay != nil {
data["e2e_results"] = d.e2eDelay
}
if withReset {
d.Reset()
}
return data
}
type Driver struct {
urlPrefix *url.URL
client *http.Client
// cache to avoid repeated query
scale float64
windowSize Size
driverResults []*DriverResult
// cache session data
session DriverSession
}
type DriverResult struct {
RequestMethod string `json:"request_method"`
RequestUrl string `json:"request_url"`
RequestBody string `json:"request_body,omitempty"`
RequestTime time.Time `json:"request_time"`
Success bool `json:"success"`
ResponseStatus int `json:"response_status"`
ResponseDuration int64 `json:"response_duration(ms)"` // ms
ResponseBody string `json:"response_body"`
Error string `json:"error,omitempty"`
}
func (wd *Driver) concatURL(u *url.URL, elem ...string) string {
var tmp *url.URL
if u == nil {
u = wd.urlPrefix
}
tmp, _ = url.Parse(u.String())
tmp.Path = path.Join(append([]string{u.Path}, elem...)...)
return tmp.String()
}
func (wd *Driver) GET(pathElem ...string) (rawResp rawResponse, err error) {
return wd.Request(http.MethodGet, wd.concatURL(nil, pathElem...), nil)
}
func (wd *Driver) POST(data interface{}, pathElem ...string) (rawResp rawResponse, err error) {
var bsJSON []byte = nil
if data != nil {
if bsJSON, err = json.Marshal(data); err != nil {
return nil, err
}
}
return wd.Request(http.MethodPost, wd.concatURL(nil, pathElem...), bsJSON)
}
func (wd *Driver) DELETE(pathElem ...string) (rawResp rawResponse, err error) {
return wd.Request(http.MethodDelete, wd.concatURL(nil, pathElem...), nil)
}
func (wd *Driver) Request(method string, rawURL string, rawBody []byte) (rawResp rawResponse, err error) {
driverResult := &DriverResult{
RequestMethod: method,
RequestUrl: rawURL,
RequestBody: string(rawBody),
}
defer func() {
wd.session.addRequestResult(driverResult)
var logger *zerolog.Event
if err != nil {
driverResult.Success = false
driverResult.Error = err.Error()
logger = log.Error().Bool("success", false).Err(err)
} else {
driverResult.Success = true
logger = log.Debug().Bool("success", true)
}
logger = logger.Str("request_method", method).Str("request_url", rawURL).
Str("request_body", string(rawBody))
if !driverResult.RequestTime.IsZero() {
logger = logger.Int64("request_time", driverResult.RequestTime.UnixMilli())
}
if driverResult.ResponseStatus != 0 {
logger = logger.
Int("response_status", driverResult.ResponseStatus).
Int64("response_duration(ms)", driverResult.ResponseDuration).
Str("response_body", driverResult.ResponseBody)
}
logger.Msg("request uixt driver")
}()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var req *http.Request
if req, err = http.NewRequestWithContext(ctx, method, rawURL, bytes.NewBuffer(rawBody)); err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Accept", "application/json")
driverResult.RequestTime = time.Now()
var resp *http.Response
if resp, err = wd.client.Do(req); err != nil {
return nil, err
}
defer func() {
// https://github.com/etcd-io/etcd/blob/v3.3.25/pkg/httputil/httputil.go#L16-L22
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
rawResp, err = io.ReadAll(resp.Body)
duration := time.Since(driverResult.RequestTime)
driverResult.ResponseDuration = duration.Milliseconds()
driverResult.ResponseStatus = resp.StatusCode
if strings.HasSuffix(rawURL, "screenshot") {
// avoid printing screenshot data
driverResult.ResponseBody = "OMITTED"
} else {
driverResult.ResponseBody = string(rawResp)
}
if err != nil {
return nil, err
}
if err = rawResp.checkErr(); err != nil {
if resp.StatusCode == http.StatusOK {
return rawResp, nil
}
return nil, err
}
return
}
func convertToHTTPClient(conn net.Conn) *http.Client {
return &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return conn, nil
},
},
Timeout: 30 * time.Second,
}
}

View File

@@ -1,29 +1,12 @@
package uixt
import (
"bytes"
"math"
"strings"
"time"
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
var (
DefaultWaitTimeout = 60 * time.Second
DefaultWaitInterval = 400 * time.Millisecond
)
type SessionInfo struct {
SessionId string `json:"sessionId"`
Capabilities struct {
Device string `json:"device"`
BrowserName string `json:"browserName"`
SdkVersion string `json:"sdkVersion"`
CFBundleIdentifier string `json:"CFBundleIdentifier"`
} `json:"capabilities"`
}
type DeviceStatus struct {
Message string `json:"message"`
State string `json:"state"`
@@ -217,12 +200,6 @@ const (
TextDelete string = "\u007F"
)
// type KeyboardKeyLabel string
//
// const (
// KeyboardKeyReturn = "return"
// )
// DeviceButton A physical button on an iOS device.
type DeviceButton string
@@ -323,13 +300,6 @@ const (
DirectionRight Direction = "right"
)
type PickerWheelOrder string
const (
PickerWheelOrderNext PickerWheelOrder = "next"
PickerWheelOrderPrevious PickerWheelOrder = "previous"
)
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
@@ -345,11 +315,6 @@ func (p PointF) IsIdentical(p2 PointF) bool {
return math.Abs(p.X-p2.X) < 1 && math.Abs(p.Y-p2.Y) < 1
}
type Rect struct {
Point
Size
}
// current implemeted device: IOSDevice, AndroidDevice, HarmonyDevice
type IDevice interface {
Init() error // init android device
@@ -367,151 +332,3 @@ type IDevice interface {
// Teardown() error
}
type ForegroundApp struct {
PackageName string
Activity string
}
// IWebDriver defines methods supported by IWebDriver drivers.
type IWebDriver interface {
// NewSession starts a new session and returns the SessionInfo.
NewSession(capabilities option.Capabilities) (SessionInfo, error)
// DeleteSession Kills application associated with that session and removes session
// 1) alertsMonitor disable
// 2) testedApplicationBundleId terminate
DeleteSession() error
// GetSession returns session cache, including requests, screenshots, etc.
GetSession() *DriverSession
Status() (DeviceStatus, error)
DeviceInfo() (DeviceInfo, error)
// Location Returns device location data.
//
// It requires to configure location access permission by manual.
// The response of 'latitude', 'longitude' and 'altitude' are always zero (0) without authorization.
// 'authorizationStatus' indicates current authorization status. '3' is 'Always'.
// https://developer.apple.com/documentation/corelocation/clauthorizationstatus
//
// Settings -> Privacy -> Location Service -> WebDriverAgent-Runner -> Always
//
// The return value could be zero even if the permission is set to 'Always'
// since the location service needs some time to update the location data.
Location() (Location, error)
BatteryInfo() (BatteryInfo, error)
// 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)
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
Unlock() (err error)
// AppLaunch Launch an application with given bundle identifier in scope of current session.
// !This method is only available since Xcode9 SDK
AppLaunch(packageName string) error
// AppTerminate Terminate an application with the given package name.
// Either `true` if the app has been successfully terminated or `false` if it was not running
AppTerminate(packageName string) (bool, error)
// GetForegroundApp returns current foreground app package name and activity name
GetForegroundApp() (app AppInfo, err error)
// AssertForegroundApp returns nil if the given package and activity are in foreground
AssertForegroundApp(packageName string, activityType ...string) error
// StartCamera Starts a new camera for recording
StartCamera() error
// StopCamera Stops the camera for recording
StopCamera() error
Orientation() (orientation Orientation, err error)
// Tap Sends a tap event at the coordinate.
Tap(x, y float64, opts ...option.ActionOption) error
// DoubleTap Sends a double tap event at the coordinate.
DoubleTap(x, y float64, opts ...option.ActionOption) error
// TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration.
// second: The default value is 1
TouchAndHold(x, y float64, opts ...option.ActionOption) error
// Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate.
// WithPressDurationOption option can be used to set pressForDuration (default to 1 second).
Drag(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error
// Swipe works like Drag, but `pressForDuration` value is 0
Swipe(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error
// SetPasteboard Sets data to the general pasteboard
SetPasteboard(contentType PasteboardType, content string) error
// GetPasteboard Gets the data contained in the general pasteboard.
// It worked when `WDA` was foreground. https://github.com/appium/WebDriverAgent/issues/330
GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error)
SetIme(ime string) error
// SendKeys Types a string into active element. There must be element with keyboard focus,
// otherwise an error is raised.
// WithFrequency option can be used to set frequency of typing (letters per sec). The default value is 60
SendKeys(text string, opts ...option.ActionOption) error
// Input works like SendKeys
Input(text string, opts ...option.ActionOption) error
Clear(packageName string) error
// PressButton Presses the corresponding hardware button on the device
PressButton(devBtn DeviceButton) error
// PressBack Presses the back button
PressBack(opts ...option.ActionOption) error
PressKeyCode(keyCode KeyCode) (err error)
Backspace(count int, opts ...option.ActionOption) (err error)
Screenshot() (*bytes.Buffer, error)
// Source Return application elements tree
Source(srcOpt ...SourceOption) (string, error)
LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error)
LogoutNoneUI(packageName string) error
TapByText(text string, opts ...option.ActionOption) error
TapByTexts(actions ...TapTextAction) error
// AccessibleSource Return application elements accessibility tree
AccessibleSource() (string, error)
// HealthCheck Health check might modify simulator state so it should only be called in-between testing sessions
// Checks health of XCTest by:
// 1) Querying application for some elements,
// 2) Triggering some device events.
HealthCheck() error
GetAppiumSettings() (map[string]interface{}, error)
SetAppiumSettings(settings map[string]interface{}) (map[string]interface{}, error)
IsHealthy() (bool, error)
// triggers the log capture and returns the log entries
StartCaptureLog(identifier ...string) (err error)
StopCaptureLog() (result interface{}, err error)
GetDriverResults() []*DriverResult
RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error)
TearDown() error
}

View File

@@ -1,13 +1,23 @@
package uixt
import (
"bytes"
"context"
"encoding/json"
"fmt"
_ "image/gif"
_ "image/png"
"io"
"net"
"net/http"
"net/url"
"path"
"strings"
"time"
"github.com/httprunner/funplugin"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/httprunner/httprunner/v5/internal/builtin"
@@ -15,6 +25,346 @@ import (
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
)
// IWebDriver defines methods supported by IWebDriver drivers.
type IWebDriver interface {
// NewSession starts a new session and returns the SessionInfo.
NewSession(capabilities option.Capabilities) (SessionInfo, error)
// DeleteSession Kills application associated with that session and removes session
// 1) alertsMonitor disable
// 2) testedApplicationBundleId terminate
DeleteSession() error
// GetSession returns session cache, including requests, screenshots, etc.
GetSession() *DriverSession
Status() (DeviceStatus, error)
DeviceInfo() (DeviceInfo, error)
// Location Returns device location data.
//
// It requires to configure location access permission by manual.
// The response of 'latitude', 'longitude' and 'altitude' are always zero (0) without authorization.
// 'authorizationStatus' indicates current authorization status. '3' is 'Always'.
// https://developer.apple.com/documentation/corelocation/clauthorizationstatus
//
// Settings -> Privacy -> Location Service -> WebDriverAgent-Runner -> Always
//
// The return value could be zero even if the permission is set to 'Always'
// since the location service needs some time to update the location data.
Location() (Location, error)
BatteryInfo() (BatteryInfo, error)
// 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)
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
Unlock() (err error)
// AppLaunch Launch an application with given bundle identifier in scope of current session.
// !This method is only available since Xcode9 SDK
AppLaunch(packageName string) error
// AppTerminate Terminate an application with the given package name.
// Either `true` if the app has been successfully terminated or `false` if it was not running
AppTerminate(packageName string) (bool, error)
// GetForegroundApp returns current foreground app package name and activity name
GetForegroundApp() (app AppInfo, err error)
// AssertForegroundApp returns nil if the given package and activity are in foreground
AssertForegroundApp(packageName string, activityType ...string) error
// StartCamera Starts a new camera for recording
StartCamera() error
// StopCamera Stops the camera for recording
StopCamera() error
Orientation() (orientation Orientation, err error)
// Tap Sends a tap event at the coordinate.
Tap(x, y float64, opts ...option.ActionOption) error
// DoubleTap Sends a double tap event at the coordinate.
DoubleTap(x, y float64, opts ...option.ActionOption) error
// TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration.
// second: The default value is 1
TouchAndHold(x, y float64, opts ...option.ActionOption) error
// Drag Initiates a press-and-hold gesture at the coordinate, then drags to another coordinate.
// WithPressDurationOption option can be used to set pressForDuration (default to 1 second).
Drag(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error
// Swipe works like Drag, but `pressForDuration` value is 0
Swipe(fromX, fromY, toX, toY float64, opts ...option.ActionOption) error
// SetPasteboard Sets data to the general pasteboard
SetPasteboard(contentType PasteboardType, content string) error
// GetPasteboard Gets the data contained in the general pasteboard.
// It worked when `WDA` was foreground. https://github.com/appium/WebDriverAgent/issues/330
GetPasteboard(contentType PasteboardType) (raw *bytes.Buffer, err error)
SetIme(ime string) error
// SendKeys Types a string into active element. There must be element with keyboard focus,
// otherwise an error is raised.
// WithFrequency option can be used to set frequency of typing (letters per sec). The default value is 60
SendKeys(text string, opts ...option.ActionOption) error
// Input works like SendKeys
Input(text string, opts ...option.ActionOption) error
Clear(packageName string) error
// PressButton Presses the corresponding hardware button on the device
PressButton(devBtn DeviceButton) error
// PressBack Presses the back button
PressBack(opts ...option.ActionOption) error
PressKeyCode(keyCode KeyCode) (err error)
Backspace(count int, opts ...option.ActionOption) (err error)
Screenshot() (*bytes.Buffer, error)
// Source Return application elements tree
Source(srcOpt ...SourceOption) (string, error)
LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error)
LogoutNoneUI(packageName string) error
TapByText(text string, opts ...option.ActionOption) error
TapByTexts(actions ...TapTextAction) error
// AccessibleSource Return application elements accessibility tree
AccessibleSource() (string, error)
// HealthCheck Health check might modify simulator state so it should only be called in-between testing sessions
// Checks health of XCTest by:
// 1) Querying application for some elements,
// 2) Triggering some device events.
HealthCheck() error
GetAppiumSettings() (map[string]interface{}, error)
SetAppiumSettings(settings map[string]interface{}) (map[string]interface{}, error)
IsHealthy() (bool, error)
// triggers the log capture and returns the log entries
StartCaptureLog(identifier ...string) (err error)
StopCaptureLog() (result interface{}, err error)
GetDriverResults() []*DriverResult
RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error)
TearDown() error
}
type SessionInfo struct {
SessionId string `json:"sessionId"`
Capabilities struct {
Device string `json:"device"`
BrowserName string `json:"browserName"`
SdkVersion string `json:"sdkVersion"`
CFBundleIdentifier string `json:"CFBundleIdentifier"`
} `json:"capabilities"`
}
type DriverSession struct {
ID string
// cache uia2/wda request and response
requests []*DriverResult
// cache screenshot ocr results
screenResults []*ScreenResult // list of actions
// cache e2e delay
e2eDelay []timeLog
}
func (d *DriverSession) addScreenResult(screenResult *ScreenResult) {
d.screenResults = append(d.screenResults, screenResult)
}
func (d *DriverSession) addRequestResult(driverResult *DriverResult) {
d.requests = append(d.requests, driverResult)
}
func (d *DriverSession) Reset() {
d.screenResults = make([]*ScreenResult, 0)
d.requests = make([]*DriverResult, 0)
d.e2eDelay = nil
}
type Attachments map[string]interface{}
func (d *DriverSession) Get(withReset bool) Attachments {
data := Attachments{
"screen_results": d.screenResults,
}
if len(d.requests) != 0 {
data["requests"] = d.requests
}
if d.e2eDelay != nil {
data["e2e_results"] = d.e2eDelay
}
if withReset {
d.Reset()
}
return data
}
type Driver struct {
urlPrefix *url.URL
client *http.Client
// cache to avoid repeated query
scale float64
windowSize Size
driverResults []*DriverResult
// cache session data
session DriverSession
}
type DriverResult struct {
RequestMethod string `json:"request_method"`
RequestUrl string `json:"request_url"`
RequestBody string `json:"request_body,omitempty"`
RequestTime time.Time `json:"request_time"`
Success bool `json:"success"`
ResponseStatus int `json:"response_status"`
ResponseDuration int64 `json:"response_duration(ms)"` // ms
ResponseBody string `json:"response_body"`
Error string `json:"error,omitempty"`
}
func (wd *Driver) concatURL(u *url.URL, elem ...string) string {
var tmp *url.URL
if u == nil {
u = wd.urlPrefix
}
tmp, _ = url.Parse(u.String())
tmp.Path = path.Join(append([]string{u.Path}, elem...)...)
return tmp.String()
}
func (wd *Driver) GET(pathElem ...string) (rawResp rawResponse, err error) {
return wd.Request(http.MethodGet, wd.concatURL(nil, pathElem...), nil)
}
func (wd *Driver) POST(data interface{}, pathElem ...string) (rawResp rawResponse, err error) {
var bsJSON []byte = nil
if data != nil {
if bsJSON, err = json.Marshal(data); err != nil {
return nil, err
}
}
return wd.Request(http.MethodPost, wd.concatURL(nil, pathElem...), bsJSON)
}
func (wd *Driver) DELETE(pathElem ...string) (rawResp rawResponse, err error) {
return wd.Request(http.MethodDelete, wd.concatURL(nil, pathElem...), nil)
}
func (wd *Driver) Request(method string, rawURL string, rawBody []byte) (rawResp rawResponse, err error) {
driverResult := &DriverResult{
RequestMethod: method,
RequestUrl: rawURL,
RequestBody: string(rawBody),
}
defer func() {
wd.session.addRequestResult(driverResult)
var logger *zerolog.Event
if err != nil {
driverResult.Success = false
driverResult.Error = err.Error()
logger = log.Error().Bool("success", false).Err(err)
} else {
driverResult.Success = true
logger = log.Debug().Bool("success", true)
}
logger = logger.Str("request_method", method).Str("request_url", rawURL).
Str("request_body", string(rawBody))
if !driverResult.RequestTime.IsZero() {
logger = logger.Int64("request_time", driverResult.RequestTime.UnixMilli())
}
if driverResult.ResponseStatus != 0 {
logger = logger.
Int("response_status", driverResult.ResponseStatus).
Int64("response_duration(ms)", driverResult.ResponseDuration).
Str("response_body", driverResult.ResponseBody)
}
logger.Msg("request uixt driver")
}()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
var req *http.Request
if req, err = http.NewRequestWithContext(ctx, method, rawURL, bytes.NewBuffer(rawBody)); err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Accept", "application/json")
driverResult.RequestTime = time.Now()
var resp *http.Response
if resp, err = wd.client.Do(req); err != nil {
return nil, err
}
defer func() {
// https://github.com/etcd-io/etcd/blob/v3.3.25/pkg/httputil/httputil.go#L16-L22
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
rawResp, err = io.ReadAll(resp.Body)
duration := time.Since(driverResult.RequestTime)
driverResult.ResponseDuration = duration.Milliseconds()
driverResult.ResponseStatus = resp.StatusCode
if strings.HasSuffix(rawURL, "screenshot") {
// avoid printing screenshot data
driverResult.ResponseBody = "OMITTED"
} else {
driverResult.ResponseBody = string(rawResp)
}
if err != nil {
return nil, err
}
if err = rawResp.checkErr(); err != nil {
if resp.StatusCode == http.StatusOK {
return rawResp, nil
}
return nil, err
}
return
}
func convertToHTTPClient(conn net.Conn) *http.Client {
return &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return conn, nil
},
},
Timeout: 30 * time.Second,
}
}
type DriverExt struct {
Ctx context.Context
Device IDevice