feat: add browser driver
This commit is contained in:
parent
1b18976620
commit
e3d4cbf281
|
@ -0,0 +1,75 @@
|
|||
package uixt
|
||||
|
||||
import (
|
||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
||||
"github.com/httprunner/httprunner/v5/pkg/uixt/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type BrowserDevice struct {
|
||||
BrowserId string `json:"browser_id,omitempty" yaml:"browser_id,omitempty"`
|
||||
LogOn bool `json:"log_on,omitempty" yaml:"log_on,omitempty"`
|
||||
}
|
||||
type BrowserDeviceOption func(*BrowserDevice)
|
||||
|
||||
func WithBrowserId(serial string) BrowserDeviceOption {
|
||||
return func(device *BrowserDevice) {
|
||||
device.BrowserId = serial
|
||||
}
|
||||
}
|
||||
|
||||
func NewBrowserDevice(options ...BrowserDeviceOption) (device *BrowserDevice, err error) {
|
||||
device = &BrowserDevice{}
|
||||
for _, option := range options {
|
||||
option(device)
|
||||
}
|
||||
|
||||
if device.BrowserId == "" {
|
||||
browserInfo, err := CreateBrowser(3600)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to create browser")
|
||||
return nil, err
|
||||
}
|
||||
device.BrowserId = browserInfo.ContextId
|
||||
}
|
||||
|
||||
return device, nil
|
||||
}
|
||||
|
||||
func (dev *BrowserDevice) UUID() string {
|
||||
return dev.BrowserId
|
||||
}
|
||||
|
||||
func (dev *BrowserDevice) Setup() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (dev *BrowserDevice) LogEnabled() bool {
|
||||
return dev.LogOn
|
||||
}
|
||||
|
||||
func (dev *BrowserDevice) Teardown() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (dev *BrowserDevice) Install(appPath string, opts ...option.InstallOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (dev *BrowserDevice) Uninstall(packageName string) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (dev *BrowserDevice) GetPackageInfo(packageName string) (types.AppInfo, error) {
|
||||
return types.AppInfo{}, errors.New("not support")
|
||||
}
|
||||
|
||||
func (dev *BrowserDevice) NewDriver() (driver IDriver, err error) {
|
||||
// var driver WebDriver
|
||||
driver, err = newBrowserWebDriver(dev.UUID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driver, nil
|
||||
}
|
|
@ -0,0 +1,658 @@
|
|||
package uixt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
||||
"github.com/httprunner/httprunner/v5/pkg/uixt/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const BROWSER_LOCAL_ADDRESS = "localhost:8093"
|
||||
|
||||
type WebAgentResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"msg"`
|
||||
Data interface{} `json:"data"`
|
||||
Result interface{} `json:"result"`
|
||||
}
|
||||
|
||||
type CreateBrowserResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"msg"`
|
||||
Data BrowserInfo `json:"data"`
|
||||
}
|
||||
|
||||
type BrowserWebDriver struct {
|
||||
urlPrefix *url.URL
|
||||
sessionId string
|
||||
scale float64
|
||||
}
|
||||
|
||||
type BrowserInfo struct {
|
||||
ContextId string `json:"context_id"`
|
||||
}
|
||||
|
||||
func CreateBrowser(timeout int) (browserInfo *BrowserInfo, err error) {
|
||||
data := map[string]interface{}{
|
||||
"timeout": timeout,
|
||||
}
|
||||
|
||||
var bsJSON []byte = nil
|
||||
if data != nil {
|
||||
if bsJSON, err = json.Marshal(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
rawURL := "http://" + BROWSER_LOCAL_ADDRESS + "/api/v1/create_browser"
|
||||
req, err := http.NewRequest(http.MethodPost, rawURL, bytes.NewBuffer(bsJSON))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second, // 设置超时时间为5秒
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawResp, err := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(resp.Status)
|
||||
}
|
||||
|
||||
var result CreateBrowserResponse
|
||||
if err = json.Unmarshal(rawResp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.Code != 0 {
|
||||
return nil, errors.New(result.Message)
|
||||
}
|
||||
|
||||
return &result.Data, nil
|
||||
}
|
||||
|
||||
func newBrowserWebDriver(browserId string) (driver *BrowserWebDriver, err error) {
|
||||
log.Info().Msg("init newBrowserWebDriver driver")
|
||||
driver = new(BrowserWebDriver)
|
||||
driver.urlPrefix = &url.URL{}
|
||||
driver.urlPrefix.Host = BROWSER_LOCAL_ADDRESS
|
||||
driver.urlPrefix.Scheme = "http"
|
||||
driver.scale = 1.0
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create browser session failed")
|
||||
}
|
||||
driver.sessionId = browserId
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("adb forward: %w", err)
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Drag(fromX, fromY, toX, toY float64, options ...option.ActionOption) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"from_x": fromX,
|
||||
"from_y": fromY,
|
||||
"to_x": toX,
|
||||
"to_y": toY,
|
||||
}
|
||||
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
|
||||
if actionOptions.Duration > 0 {
|
||||
data["duration"] = actionOptions.Duration
|
||||
}
|
||||
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/drag")
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) AppLaunch(packageName string) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"url": packageName,
|
||||
}
|
||||
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/page_launch")
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) DeleteSession() (err error) {
|
||||
|
||||
url := wd.concatURL("context", wd.sessionId)
|
||||
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
client := &http.Client{
|
||||
Timeout: 60 * time.Second, // 设置超时时间为5秒
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawResp, err := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New(resp.Status)
|
||||
}
|
||||
|
||||
var result CreateBrowserResponse
|
||||
if err = json.Unmarshal(rawResp, &result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.Code != 0 {
|
||||
return errors.New(result.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Scroll(delta int) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"delta": delta,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/scroll")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) CreateNetListener() (*websocket.Conn, error) {
|
||||
webSocketUrl := "ws://localhost:8093/websocket_net_listen"
|
||||
c, _, err := websocket.DefaultDialer.Dial(webSocketUrl, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 发送消息
|
||||
initMessage := fmt.Sprintf(`{
|
||||
"type":"create_net_listener",
|
||||
"context_id":"%v"
|
||||
}`, wd.sessionId)
|
||||
err = c.WriteMessage(websocket.TextMessage, []byte(initMessage))
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) ClosePage(pageIndex int) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"page_index": pageIndex,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/page_close")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) HoverBySelector(selector string, options ...option.ActionOption) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"selector": selector,
|
||||
}
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
if actionOptions.Index > 0 {
|
||||
data["element_index"] = actionOptions.Index
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/hover")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) tapBySelector(selector string, options ...option.ActionOption) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"selector": selector,
|
||||
}
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
if actionOptions.Index > 0 {
|
||||
data["element_index"] = actionOptions.Index
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/tap")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) RightClick(x, y int) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/right_click")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) RightclickbySelector(selector string, options ...option.ActionOption) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"selector": selector,
|
||||
}
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
if actionOptions.Index > 0 {
|
||||
data["element_index"] = actionOptions.Index
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/right_click")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) GetElementTextBySelector(selector string, options ...option.ActionOption) (text string, err error) {
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
uri := "ui/element_text?selector=" + selector
|
||||
if actionOptions.Index > 0 {
|
||||
uri = uri + "&element_index=" + fmt.Sprintf("%v", actionOptions.Index)
|
||||
}
|
||||
resp, err := wd.httpGet(http.MethodGet, wd.sessionId, uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data := resp.Data.(map[string]interface{})
|
||||
return data["text"].(string), nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) GetPageUrl(options ...option.ActionOption) (text string, err error) {
|
||||
uri := "ui/page_url"
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
if actionOptions.Index > 0 {
|
||||
uri = uri + "?page_index=" + fmt.Sprintf("%v", actionOptions.Index)
|
||||
}
|
||||
resp, err := wd.httpGet(http.MethodGet, wd.sessionId, uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data := resp.Data.(map[string]interface{})
|
||||
return data["url"].(string), nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) IsElementExistBySelector(selector string) (bool, error) {
|
||||
resp, err := wd.httpGet(wd.sessionId, "ui/element_exist", "?selector=", selector)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
data := resp.Data.(map[string]interface{})
|
||||
return data["exist"].(bool), nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Hover(x, y float64) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/hover")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) DoubleTapXY(x, y float64, option ...option.ActionOption) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/double_tap")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Input(text string, option ...option.ActionOption) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"text": text,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/input")
|
||||
return err
|
||||
}
|
||||
|
||||
// Source Return application elements tree
|
||||
func (wd *BrowserWebDriver) Source(srcOpt ...option.SourceOption) (string, error) {
|
||||
resp, err := wd.httpGet(http.MethodGet, wd.sessionId, "stub/source")
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(resp.Data)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(jsonData), err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) ScreenShot(options ...option.ActionOption) (*bytes.Buffer, error) {
|
||||
resp, err := wd.httpGet(http.MethodGet, wd.sessionId, "screenshot")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := resp.Data.(map[string]interface{})
|
||||
screenshotBase64 := data["screenshot"].(string)
|
||||
screenRaw, err := base64.StdEncoding.DecodeString(screenshotBase64)
|
||||
res := bytes.NewBuffer(screenRaw)
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) httpPOST(data interface{}, pathElem ...string) (response *WebAgentResponse, err error) {
|
||||
var bsJSON []byte = nil
|
||||
if data != nil {
|
||||
if bsJSON, err = json.Marshal(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return wd.httpRequest(http.MethodPost, wd.concatURL(pathElem...), bsJSON)
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) httpGet(data interface{}, pathElem ...string) (response *WebAgentResponse, err error) {
|
||||
|
||||
return wd.httpRequest(http.MethodGet, wd.concatURL(pathElem...), nil)
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) concatURL(elem ...string) string {
|
||||
tmp, _ := url.Parse(wd.urlPrefix.String())
|
||||
commonPath := path.Join(append([]string{wd.urlPrefix.Path}, "api/v1/")...)
|
||||
tmp.Path = path.Join(append([]string{commonPath}, elem...)...)
|
||||
return tmp.String()
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) httpRequest(method string, rawURL string, rawBody []byte, disableRetry ...bool) (response *WebAgentResponse, err error) {
|
||||
req, err := http.NewRequest(method, rawURL, bytes.NewBuffer(rawBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 新建http client
|
||||
client := &http.Client{
|
||||
Timeout: 60 * time.Second, // 设置超时时间为5秒
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawResp, err := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(resp.Status)
|
||||
}
|
||||
|
||||
// 将结果解析为 JSON
|
||||
var result WebAgentResponse
|
||||
if err = json.Unmarshal(rawResp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.Code != 0 {
|
||||
log.Info().Msgf("%v", result.Message)
|
||||
return nil, errors.New(result.Message)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Status() (deviceStatus types.DeviceStatus, err error) {
|
||||
log.Warn().Msg("Status not implemented in ADBDriver")
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) DeviceInfo() (deviceInfo types.DeviceInfo, err error) {
|
||||
log.Warn().Msg("DeviceInfo not implemented in ADBDriver")
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) BatteryInfo() (batteryInfo types.BatteryInfo, err error) {
|
||||
log.Warn().Msg("BatteryInfo not implemented in ADBDriver")
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) WindowSize() (types.Size, error) {
|
||||
resp, err := wd.httpGet(http.MethodGet, wd.sessionId, "window_size")
|
||||
if err != nil {
|
||||
return types.Size{}, err
|
||||
}
|
||||
data := resp.Data.(map[string]interface{})
|
||||
width := data["width"]
|
||||
height := data["height"]
|
||||
return types.Size{
|
||||
Width: int(width.(float64)),
|
||||
Height: int(height.(float64)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Screen() (Screen, error) {
|
||||
return Screen{}, errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) Scale() (float64, error) {
|
||||
return 0, errors.New("not support")
|
||||
}
|
||||
|
||||
// GetTimestamp returns the timestamp of the mobile device
|
||||
func (wd *BrowserWebDriver) GetTimestamp() (timestamp int64, err error) {
|
||||
return 0, errors.New("not support")
|
||||
}
|
||||
|
||||
// Homescreen Forces the device under test to switch to the home screen
|
||||
func (wd *BrowserWebDriver) Homescreen() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Unlock() (err error) {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
// 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
|
||||
func (wd *BrowserWebDriver) AppTerminate(packageName string) (bool, error) {
|
||||
return false, errors.New("not support")
|
||||
}
|
||||
|
||||
// AssertForegroundApp returns nil if the given package and activity are in foreground
|
||||
func (wd *BrowserWebDriver) AssertForegroundApp(packageName string, activityType ...string) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Back() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) AppClear(packageName string) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) ClearImages() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) PushImage(localPath string) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Orientation() (orientation types.Orientation, err error) {
|
||||
log.Warn().Msg("Orientation not implemented in ADBDriver")
|
||||
return
|
||||
}
|
||||
|
||||
// Tap Sends a tap event at the coordinate.
|
||||
func (wd *BrowserWebDriver) Tap(x, y int, options ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) TapFloat(x, y float64, options ...option.ActionOption) error {
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
duration := 0.1
|
||||
if actionOptions.Duration > 0 {
|
||||
duration = actionOptions.Duration
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
"duration": duration,
|
||||
}
|
||||
_, err := wd.httpPOST(data, wd.sessionId, "ui/tap")
|
||||
return err
|
||||
}
|
||||
|
||||
// DoubleTap Sends a double tap event at the coordinate.
|
||||
func (wd *BrowserWebDriver) DoubleTap(x, y float64, options ...option.ActionOption) error {
|
||||
data := map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
}
|
||||
_, err := wd.httpPOST(data, wd.sessionId, "ui/double_tap")
|
||||
return err
|
||||
}
|
||||
func (wd *BrowserWebDriver) UploadFile(x, y float64, FileUrl, FileFormat string) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
"file_url": FileUrl,
|
||||
"file_format": FileFormat,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/upload")
|
||||
return err
|
||||
}
|
||||
|
||||
// TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration.
|
||||
//
|
||||
// second: The default value is 1
|
||||
func (wd *BrowserWebDriver) TouchAndHold(x, y float64, options ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
// Swipe works like Drag, but `pressForDuration` value is 0
|
||||
func (wd *BrowserWebDriver) Swipe(fromX, fromY, toX, toY float64, options ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) SetIme(ime string) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
// 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
|
||||
func (wd *BrowserWebDriver) SendKeys(text string, options ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Clear(packageName string) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) Setup() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) GetDevice() IDevice {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) ForegroundInfo() (app types.AppInfo, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// PressBack Presses the back button
|
||||
func (wd *BrowserWebDriver) PressBack(options ...option.ActionOption) error {
|
||||
_, err := wd.httpPOST(map[string]interface{}{}, wd.sessionId, "ui/back")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) PressKeyCode(keyCode KeyCode) (err error) {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Backspace(count int, options ...option.ActionOption) (err error) {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) LogoutNoneUI(packageName string) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) TapByText(text string, options ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
// AccessibleSource Return application elements accessibility tree
|
||||
func (wd *BrowserWebDriver) AccessibleSource() (string, error) {
|
||||
return "", errors.New("not support")
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (wd *BrowserWebDriver) HealthCheck() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) GetAppiumSettings() (map[string]interface{}, error) {
|
||||
return nil, errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) SetAppiumSettings(settings map[string]interface{}) (map[string]interface{}, error) {
|
||||
return nil, errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) IsHealthy() (bool, error) {
|
||||
return false, errors.New("not support")
|
||||
}
|
||||
|
||||
// triggers the log capture and returns the log entries
|
||||
func (wd *BrowserWebDriver) StartCaptureLog(identifier ...string) (err error) {
|
||||
return errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) StopCaptureLog() (result interface{}, err error) {
|
||||
return nil, errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error) {
|
||||
return "", errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) TearDown() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) InitSession(capabilities option.Capabilities) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) GetSession() *DriverSession {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) ScreenRecord(duration time.Duration) (videoPath string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Rotation() (rotation types.Rotation, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) SetRotation(rotation types.Rotation) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Home() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) TapXY(x, y float64, opts ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) TapAbsXY(x, y float64, opts ...option.ActionOption) error {
|
||||
return wd.TapFloat(x, y, opts...)
|
||||
}
|
|
@ -0,0 +1,678 @@
|
|||
package driver_ext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
||||
"github.com/httprunner/httprunner/v5/pkg/uixt/types"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const BROWSER_LOCAL_ADDRESS = "localhost:8093"
|
||||
|
||||
type WebAgentResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"msg"`
|
||||
Data interface{} `json:"data"`
|
||||
Result interface{} `json:"result"`
|
||||
}
|
||||
|
||||
type CreateBrowserResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"msg"`
|
||||
Data BrowserInfo `json:"data"`
|
||||
}
|
||||
|
||||
type BrowserWebDriver struct {
|
||||
*uixt.BrowserWebDriver
|
||||
urlPrefix *url.URL
|
||||
sessionId string
|
||||
scale float64
|
||||
}
|
||||
|
||||
type BrowserInfo struct {
|
||||
ContextId string `json:"context_id"`
|
||||
}
|
||||
|
||||
func CreateBrowser(timeout int) (browserInfo *BrowserInfo, err error) {
|
||||
data := map[string]interface{}{
|
||||
"timeout": timeout,
|
||||
}
|
||||
|
||||
var bsJSON []byte = nil
|
||||
if data != nil {
|
||||
if bsJSON, err = json.Marshal(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
rawURL := "http://" + BROWSER_LOCAL_ADDRESS + "/api/v1/create_browser"
|
||||
req, err := http.NewRequest(http.MethodPost, rawURL, bytes.NewBuffer(bsJSON))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second, // 设置超时时间为5秒
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawResp, err := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(resp.Status)
|
||||
}
|
||||
|
||||
var result CreateBrowserResponse
|
||||
if err = json.Unmarshal(rawResp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.Code != 0 {
|
||||
return nil, errors.New(result.Message)
|
||||
}
|
||||
|
||||
return &result.Data, nil
|
||||
}
|
||||
|
||||
func NewStubBrowserDriver(browserId string) (driver *BrowserWebDriver, err error) {
|
||||
log.Info().Msg("init NewStubBrowserDriver driver")
|
||||
driver = new(BrowserWebDriver)
|
||||
driver.urlPrefix = &url.URL{}
|
||||
driver.urlPrefix.Host = BROWSER_LOCAL_ADDRESS
|
||||
driver.urlPrefix.Scheme = "http"
|
||||
driver.scale = 1.0
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "create browser session failed")
|
||||
}
|
||||
driver.sessionId = browserId
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("adb forward: %w", err)
|
||||
}
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Drag(fromX, fromY, toX, toY float64, options ...option.ActionOption) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"from_x": fromX,
|
||||
"from_y": fromY,
|
||||
"to_x": toX,
|
||||
"to_y": toY,
|
||||
}
|
||||
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
|
||||
if actionOptions.Duration > 0 {
|
||||
data["duration"] = actionOptions.Duration
|
||||
}
|
||||
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/drag")
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) AppLaunch(packageName string) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"url": packageName,
|
||||
}
|
||||
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/page_launch")
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) DeleteSession() (err error) {
|
||||
|
||||
url := wd.concatURL("context", wd.sessionId)
|
||||
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
client := &http.Client{
|
||||
Timeout: 60 * time.Second, // 设置超时时间为5秒
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawResp, err := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errors.New(resp.Status)
|
||||
}
|
||||
|
||||
var result CreateBrowserResponse
|
||||
if err = json.Unmarshal(rawResp, &result); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if result.Code != 0 {
|
||||
return errors.New(result.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Scroll(delta int) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"delta": delta,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/scroll")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) CreateNetListener() (*websocket.Conn, error) {
|
||||
webSocketUrl := "ws://localhost:8093/websocket_net_listen"
|
||||
c, _, err := websocket.DefaultDialer.Dial(webSocketUrl, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 发送消息
|
||||
initMessage := fmt.Sprintf(`{
|
||||
"type":"create_net_listener",
|
||||
"context_id":"%v"
|
||||
}`, wd.sessionId)
|
||||
err = c.WriteMessage(websocket.TextMessage, []byte(initMessage))
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) ClosePage(pageIndex int) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"page_index": pageIndex,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/page_close")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) HoverBySelector(selector string, options ...option.ActionOption) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"selector": selector,
|
||||
}
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
if actionOptions.Index > 0 {
|
||||
data["element_index"] = actionOptions.Index
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/hover")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) tapBySelector(selector string, options ...option.ActionOption) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"selector": selector,
|
||||
}
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
if actionOptions.Index > 0 {
|
||||
data["element_index"] = actionOptions.Index
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/tap")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) RightClick(x, y int) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/right_click")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) RightclickbySelector(selector string, options ...option.ActionOption) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"selector": selector,
|
||||
}
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
if actionOptions.Index > 0 {
|
||||
data["element_index"] = actionOptions.Index
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/right_click")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) GetElementTextBySelector(selector string, options ...option.ActionOption) (text string, err error) {
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
uri := "ui/element_text?selector=" + selector
|
||||
if actionOptions.Index > 0 {
|
||||
uri = uri + "&element_index=" + fmt.Sprintf("%v", actionOptions.Index)
|
||||
}
|
||||
resp, err := wd.httpGet(http.MethodGet, wd.sessionId, uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data := resp.Data.(map[string]interface{})
|
||||
return data["text"].(string), nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) GetPageUrl(options ...option.ActionOption) (text string, err error) {
|
||||
uri := "ui/page_url"
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
if actionOptions.Index > 0 {
|
||||
uri = uri + "?page_index=" + fmt.Sprintf("%v", actionOptions.Index)
|
||||
}
|
||||
resp, err := wd.httpGet(http.MethodGet, wd.sessionId, uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
data := resp.Data.(map[string]interface{})
|
||||
return data["url"].(string), nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) IsElementExistBySelector(selector string) (bool, error) {
|
||||
resp, err := wd.httpGet(wd.sessionId, "ui/element_exist", "?selector=", selector)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
data := resp.Data.(map[string]interface{})
|
||||
return data["exist"].(bool), nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Hover(x, y float64) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/hover")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) DoubleTapXY(x, y float64, option ...option.ActionOption) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/double_tap")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Input(text string, option ...option.ActionOption) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"text": text,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/input")
|
||||
return err
|
||||
}
|
||||
|
||||
// Source Return application elements tree
|
||||
func (wd *BrowserWebDriver) Source(srcOpt ...option.SourceOption) (string, error) {
|
||||
resp, err := wd.httpGet(http.MethodGet, wd.sessionId, "stub/source")
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(resp.Data)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(jsonData), err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) ScreenShot(options ...option.ActionOption) (*bytes.Buffer, error) {
|
||||
resp, err := wd.httpGet(http.MethodGet, wd.sessionId, "screenshot")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := resp.Data.(map[string]interface{})
|
||||
screenshotBase64 := data["screenshot"].(string)
|
||||
screenRaw, err := base64.StdEncoding.DecodeString(screenshotBase64)
|
||||
res := bytes.NewBuffer(screenRaw)
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) httpPOST(data interface{}, pathElem ...string) (response *WebAgentResponse, err error) {
|
||||
var bsJSON []byte = nil
|
||||
if data != nil {
|
||||
if bsJSON, err = json.Marshal(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return wd.httpRequest(http.MethodPost, wd.concatURL(pathElem...), bsJSON)
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) httpGet(data interface{}, pathElem ...string) (response *WebAgentResponse, err error) {
|
||||
|
||||
return wd.httpRequest(http.MethodGet, wd.concatURL(pathElem...), nil)
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) concatURL(elem ...string) string {
|
||||
tmp, _ := url.Parse(wd.urlPrefix.String())
|
||||
commonPath := path.Join(append([]string{wd.urlPrefix.Path}, "api/v1/")...)
|
||||
tmp.Path = path.Join(append([]string{commonPath}, elem...)...)
|
||||
return tmp.String()
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) httpRequest(method string, rawURL string, rawBody []byte, disableRetry ...bool) (response *WebAgentResponse, err error) {
|
||||
req, err := http.NewRequest(method, rawURL, bytes.NewBuffer(rawBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 新建http client
|
||||
client := &http.Client{
|
||||
Timeout: 60 * time.Second, // 设置超时时间为5秒
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawResp, err := io.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(resp.Status)
|
||||
}
|
||||
|
||||
// 将结果解析为 JSON
|
||||
var result WebAgentResponse
|
||||
if err = json.Unmarshal(rawResp, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.Code != 0 {
|
||||
log.Info().Msgf("%v", result.Message)
|
||||
return nil, errors.New(result.Message)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) LoginNoneUI(packageName, phoneNumber string, captcha, password string) (info AppLoginInfo, err error) {
|
||||
data := map[string]interface{}{
|
||||
"url": packageName,
|
||||
"web_cookie": password,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "stub/login")
|
||||
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
loginSuccss := AppLoginInfo{
|
||||
IsLogin: true,
|
||||
Uid: wd.sessionId,
|
||||
Did: password,
|
||||
}
|
||||
return loginSuccss, err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Status() (deviceStatus types.DeviceStatus, err error) {
|
||||
log.Warn().Msg("Status not implemented in ADBDriver")
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) DeviceInfo() (deviceInfo types.DeviceInfo, err error) {
|
||||
log.Warn().Msg("DeviceInfo not implemented in ADBDriver")
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) BatteryInfo() (batteryInfo types.BatteryInfo, err error) {
|
||||
log.Warn().Msg("BatteryInfo not implemented in ADBDriver")
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) WindowSize() (types.Size, error) {
|
||||
resp, err := wd.httpGet(http.MethodGet, wd.sessionId, "window_size")
|
||||
if err != nil {
|
||||
return types.Size{}, err
|
||||
}
|
||||
data := resp.Data.(map[string]interface{})
|
||||
width := data["width"]
|
||||
height := data["height"]
|
||||
return types.Size{
|
||||
Width: int(width.(float64)),
|
||||
Height: int(height.(float64)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Screen() (uixt.Screen, error) {
|
||||
return uixt.Screen{}, errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) Scale() (float64, error) {
|
||||
return 0, errors.New("not support")
|
||||
}
|
||||
|
||||
// GetTimestamp returns the timestamp of the mobile device
|
||||
func (wd *BrowserWebDriver) GetTimestamp() (timestamp int64, err error) {
|
||||
return 0, errors.New("not support")
|
||||
}
|
||||
|
||||
// Homescreen Forces the device under test to switch to the home screen
|
||||
func (wd *BrowserWebDriver) Homescreen() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Unlock() (err error) {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
// 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
|
||||
func (wd *BrowserWebDriver) AppTerminate(packageName string) (bool, error) {
|
||||
return false, errors.New("not support")
|
||||
}
|
||||
|
||||
// AssertForegroundApp returns nil if the given package and activity are in foreground
|
||||
func (wd *BrowserWebDriver) AssertForegroundApp(packageName string, activityType ...string) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Back() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) AppClear(packageName string) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) ClearImages() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) PushImage(localPath string) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Orientation() (orientation types.Orientation, err error) {
|
||||
log.Warn().Msg("Orientation not implemented in ADBDriver")
|
||||
return
|
||||
}
|
||||
|
||||
// Tap Sends a tap event at the coordinate.
|
||||
func (wd *BrowserWebDriver) Tap(x, y int, options ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) TapFloat(x, y float64, options ...option.ActionOption) error {
|
||||
actionOptions := option.NewActionOptions(options...)
|
||||
duration := 0.1
|
||||
if actionOptions.Duration > 0 {
|
||||
duration = actionOptions.Duration
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
"duration": duration,
|
||||
}
|
||||
_, err := wd.httpPOST(data, wd.sessionId, "ui/tap")
|
||||
return err
|
||||
}
|
||||
|
||||
// DoubleTap Sends a double tap event at the coordinate.
|
||||
func (wd *BrowserWebDriver) DoubleTap(x, y float64, options ...option.ActionOption) error {
|
||||
data := map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
}
|
||||
_, err := wd.httpPOST(data, wd.sessionId, "ui/double_tap")
|
||||
return err
|
||||
}
|
||||
func (wd *BrowserWebDriver) UploadFile(x, y float64, FileUrl, FileFormat string) (err error) {
|
||||
data := map[string]interface{}{
|
||||
"x": x,
|
||||
"y": y,
|
||||
"file_url": FileUrl,
|
||||
"file_format": FileFormat,
|
||||
}
|
||||
_, err = wd.httpPOST(data, wd.sessionId, "ui/upload")
|
||||
return err
|
||||
}
|
||||
|
||||
// TouchAndHold Initiates a long-press gesture at the coordinate, holding for the specified duration.
|
||||
//
|
||||
// second: The default value is 1
|
||||
func (wd *BrowserWebDriver) TouchAndHold(x, y float64, options ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
// Swipe works like Drag, but `pressForDuration` value is 0
|
||||
func (wd *BrowserWebDriver) Swipe(fromX, fromY, toX, toY float64, options ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) SwipeFloat(fromX, fromY, toX, toY float64, options ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) SetIme(ime string) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
// 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
|
||||
func (wd *BrowserWebDriver) SendKeys(text string, options ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Clear(packageName string) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) Setup() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) GetDevice() uixt.IDevice {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) ForegroundInfo() (app types.AppInfo, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// PressBack Presses the back button
|
||||
func (wd *BrowserWebDriver) PressBack(options ...option.ActionOption) error {
|
||||
_, err := wd.httpPOST(map[string]interface{}{}, wd.sessionId, "ui/back")
|
||||
return err
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) PressKeyCode(keyCode uixt.KeyCode) (err error) {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Backspace(count int, options ...option.ActionOption) (err error) {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) LogoutNoneUI(packageName string) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) TapByText(text string, options ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
// AccessibleSource Return application elements accessibility tree
|
||||
func (wd *BrowserWebDriver) AccessibleSource() (string, error) {
|
||||
return "", errors.New("not support")
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (wd *BrowserWebDriver) HealthCheck() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) GetAppiumSettings() (map[string]interface{}, error) {
|
||||
return nil, errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) SetAppiumSettings(settings map[string]interface{}) (map[string]interface{}, error) {
|
||||
return nil, errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) IsHealthy() (bool, error) {
|
||||
return false, errors.New("not support")
|
||||
}
|
||||
|
||||
// triggers the log capture and returns the log entries
|
||||
func (wd *BrowserWebDriver) StartCaptureLog(identifier ...string) (err error) {
|
||||
return errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) StopCaptureLog() (result interface{}, err error) {
|
||||
return nil, errors.New("not support")
|
||||
}
|
||||
func (wd *BrowserWebDriver) RecordScreen(folderPath string, duration time.Duration) (videoPath string, err error) {
|
||||
return "", errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) TearDown() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) InitSession(capabilities option.Capabilities) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) GetSession() *uixt.DriverSession {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) ScreenRecord(duration time.Duration) (videoPath string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Rotation() (rotation types.Rotation, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) SetRotation(rotation types.Rotation) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) Home() error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) TapXY(x, y float64, opts ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
||||
|
||||
func (wd *BrowserWebDriver) TapAbsXY(x, y float64, opts ...option.ActionOption) error {
|
||||
return errors.New("not support")
|
||||
}
|
|
@ -66,6 +66,12 @@ func GetDevice(c *gin.Context) (device uixt.IDevice, err error) {
|
|||
RenderErrorInitDriver(c, err)
|
||||
return
|
||||
}
|
||||
case "browser":
|
||||
device, err = uixt.NewBrowserDevice(uixt.WithBrowserId(serial))
|
||||
if err != nil {
|
||||
RenderErrorInitDriver(c, err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("[%s]: invalid platform", c.HandlerName())
|
||||
return
|
||||
|
|
|
@ -88,6 +88,36 @@ func listDeviceHandler(c *gin.Context) {
|
|||
RenderSuccess(c, deviceList)
|
||||
}
|
||||
|
||||
func createBrowserHandler(c *gin.Context) {
|
||||
var createBrowserReq CreateBrowserRequest
|
||||
if err := c.ShouldBindJSON(&createBrowserReq); err != nil {
|
||||
RenderErrorValidateRequest(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
browserInfo, err := uixt.CreateBrowser(createBrowserReq.Timeout)
|
||||
if err != nil {
|
||||
RenderError(c, err)
|
||||
return
|
||||
}
|
||||
RenderSuccess(c, browserInfo)
|
||||
return
|
||||
}
|
||||
|
||||
func deleteBrowserHandler(c *gin.Context) {
|
||||
driver, err := GetDriver(c)
|
||||
if err != nil {
|
||||
RenderError(c, err)
|
||||
return
|
||||
}
|
||||
err = driver.DeleteSession()
|
||||
if err != nil {
|
||||
RenderError(c, err)
|
||||
return
|
||||
}
|
||||
RenderSuccess(c, true)
|
||||
}
|
||||
|
||||
func pushImageHandler(c *gin.Context) {
|
||||
var pushMediaReq PushMediaRequest
|
||||
if err := c.ShouldBindJSON(&pushMediaReq); err != nil {
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
func GetDriver(c *gin.Context) (driverExt *driver_ext.XTDriver, err error) {
|
||||
platform := c.Param("platform")
|
||||
serial := c.Param("serial")
|
||||
deviceObj, exists := c.Get("device")
|
||||
var device uixt.IDevice
|
||||
var driver uixt.IDriver
|
||||
|
@ -29,6 +30,8 @@ func GetDriver(c *gin.Context) (driverExt *driver_ext.XTDriver, err error) {
|
|||
driver, err = driver_ext.NewStubAndroidDriver(device.(*uixt.AndroidDevice))
|
||||
case "ios":
|
||||
driver, err = driver_ext.NewStubIOSDriver(device.(*uixt.IOSDevice))
|
||||
case "browser":
|
||||
driver, err = driver_ext.NewStubBrowserDriver(serial)
|
||||
}
|
||||
if err != nil {
|
||||
server.RenderErrorInitDriver(c, err)
|
||||
|
|
|
@ -9,7 +9,7 @@ type AppInstallRequest struct {
|
|||
|
||||
type LoginRequest struct {
|
||||
PackageName string `json:"packageName"`
|
||||
PhoneNumber string `json:"phoneNumber" binding:"required"`
|
||||
PhoneNumber string `json:"phoneNumber"`
|
||||
Captcha string `json:"captcha" binding:"required_without=Password"`
|
||||
Password string `json:"password" binding:"required_without=Captcha"`
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ func (r *Router) Init() {
|
|||
r.Engine.GET("/", pingHandler)
|
||||
r.Engine.POST("/", pingHandler)
|
||||
r.Engine.GET("/api/v1/devices", listDeviceHandler)
|
||||
r.Engine.POST("/api/v1/browser/create_browser", createBrowserHandler)
|
||||
|
||||
apiV1PlatformSerial := r.Group("/api/v1").Group("/:platform").Group("/:serial")
|
||||
|
||||
|
@ -36,6 +37,9 @@ func (r *Router) Init() {
|
|||
apiV1PlatformSerial.POST("/ui/drag", dragHandler)
|
||||
apiV1PlatformSerial.POST("/ui/input", inputHandler)
|
||||
apiV1PlatformSerial.POST("/ui/home", homeHandler)
|
||||
apiV1PlatformSerial.POST("/ui/upload", uploadHandler)
|
||||
apiV1PlatformSerial.POST("/ui/hover", hoverHandler)
|
||||
apiV1PlatformSerial.POST("/ui/scroll", scrollHandler)
|
||||
|
||||
// Key operations
|
||||
apiV1PlatformSerial.POST("/key/unlock", unlockHandler)
|
||||
|
@ -53,6 +57,7 @@ func (r *Router) Init() {
|
|||
|
||||
// Device operations
|
||||
apiV1PlatformSerial.GET("/screenshot", screenshotHandler)
|
||||
apiV1PlatformSerial.DELETE("/close_browser", deleteBrowserHandler)
|
||||
apiV1PlatformSerial.GET("/video", videoHandler)
|
||||
apiV1PlatformSerial.POST("/device/push_image", pushImageHandler)
|
||||
apiV1PlatformSerial.POST("/device/clear_image", clearImageHandler)
|
||||
|
|
|
@ -10,7 +10,12 @@ type TapRequest struct {
|
|||
Duration float64 `json:"duration"`
|
||||
Options *option.ActionOptions `json:"options,omitempty"`
|
||||
}
|
||||
|
||||
type uploadRequest struct {
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
FileUrl string `json:"file_url"`
|
||||
FileFormat string `json:"file_format"`
|
||||
}
|
||||
type DragRequest struct {
|
||||
FromX float64 `json:"from_x" binding:"required"`
|
||||
FromY float64 `json:"from_y" binding:"required"`
|
||||
|
|
64
server/ui.go
64
server/ui.go
|
@ -2,6 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/httprunner/httprunner/v5/pkg/uixt"
|
||||
"github.com/httprunner/httprunner/v5/pkg/uixt/option"
|
||||
)
|
||||
|
||||
|
@ -29,6 +30,69 @@ func tapHandler(c *gin.Context) {
|
|||
RenderSuccess(c, true)
|
||||
}
|
||||
|
||||
func uploadHandler(c *gin.Context) {
|
||||
var uploadRequest uploadRequest
|
||||
if err := c.ShouldBindJSON(&uploadRequest); err != nil {
|
||||
RenderErrorValidateRequest(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
driver, err := GetDriver(c)
|
||||
if err != nil {
|
||||
RenderError(c, err)
|
||||
return
|
||||
}
|
||||
err = driver.IDriver.(*uixt.BrowserWebDriver).UploadFile(uploadRequest.X, uploadRequest.Y, uploadRequest.FileUrl, uploadRequest.FileFormat)
|
||||
if err != nil {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
RenderSuccess(c, true)
|
||||
}
|
||||
func hoverHandler(c *gin.Context) {
|
||||
var hoverReq HoverRequest
|
||||
if err := c.ShouldBindJSON(&hoverReq); err != nil {
|
||||
RenderErrorValidateRequest(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
driver, err := GetDriver(c)
|
||||
if err != nil {
|
||||
RenderError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = driver.IDriver.(*uixt.BrowserWebDriver).Hover(hoverReq.X, hoverReq.Y)
|
||||
|
||||
if err != nil {
|
||||
RenderError(c, err)
|
||||
return
|
||||
}
|
||||
RenderSuccess(c, true)
|
||||
}
|
||||
|
||||
func scrollHandler(c *gin.Context) {
|
||||
var scrollReq ScrollRequest
|
||||
if err := c.ShouldBindJSON(&scrollReq); err != nil {
|
||||
RenderErrorValidateRequest(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
driver, err := GetDriver(c)
|
||||
if err != nil {
|
||||
RenderError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = driver.IDriver.(*uixt.BrowserWebDriver).Scroll(scrollReq.Delta)
|
||||
|
||||
if err != nil {
|
||||
RenderError(c, err)
|
||||
return
|
||||
}
|
||||
RenderSuccess(c, true)
|
||||
}
|
||||
|
||||
func doubleTapHandler(c *gin.Context) {
|
||||
var tapReq TapRequest
|
||||
if err := c.ShouldBindJSON(&tapReq); err != nil {
|
||||
|
|
Loading…
Reference in New Issue