From 2bdfe45f1d6cba8adaa8b400c3da7e7743cef240 Mon Sep 17 00:00:00 2001 From: "lilong.129" Date: Tue, 5 Nov 2024 16:02:56 +0800 Subject: [PATCH] refactor: hrp ui server --- hrp/code/code.go | 2 + hrp/internal/version/VERSION | 2 +- hrp/pkg/server/app.go | 112 ++++++ hrp/pkg/server/context.go | 103 +++++ hrp/pkg/server/exception.go | 15 - hrp/pkg/server/key.go | 80 ++++ hrp/pkg/server/main.go | 50 +++ hrp/pkg/server/server.go | 701 ----------------------------------- hrp/pkg/server/source.go | 89 +++++ hrp/pkg/server/stub.go | 148 ++++++++ hrp/pkg/server/ui.go | 140 +++++++ 11 files changed, 725 insertions(+), 717 deletions(-) create mode 100644 hrp/pkg/server/app.go create mode 100644 hrp/pkg/server/context.go delete mode 100644 hrp/pkg/server/exception.go create mode 100644 hrp/pkg/server/key.go create mode 100644 hrp/pkg/server/main.go delete mode 100644 hrp/pkg/server/server.go create mode 100644 hrp/pkg/server/source.go create mode 100644 hrp/pkg/server/stub.go create mode 100644 hrp/pkg/server/ui.go diff --git a/hrp/code/code.go b/hrp/code/code.go index 9a65ab65..623157fc 100644 --- a/hrp/code/code.go +++ b/hrp/code/code.go @@ -30,6 +30,7 @@ var ( UnsupportedFileExtension = errors.New("unsupported file extension") // 16 ReferencedFileNotFound = errors.New("referenced file not found") // 17 InvalidPluginFile = errors.New("invalid plugin file") // 18 + InvalidParamError = errors.New("invalid param error") // 19 ) // parser: [20, 30) @@ -135,6 +136,7 @@ var errorsMap = map[error]int{ UnsupportedFileExtension: 16, ReferencedFileNotFound: 17, InvalidPluginFile: 18, + InvalidParamError: 19, // parser ParseError: 20, diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index ca8ef730..23d273a4 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v5.0.0+2411051527 +v5.0.0+2411051626 diff --git a/hrp/pkg/server/app.go b/hrp/pkg/server/app.go new file mode 100644 index 00000000..cf8d68d4 --- /dev/null +++ b/hrp/pkg/server/app.go @@ -0,0 +1,112 @@ +package server + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/httprunner/httprunner/v4/hrp/code" + "github.com/rs/zerolog/log" +) + +func foregroundAppHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + appInfo, err := dExt.Driver.GetForegroundApp() + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to unlick screen", c.HandlerName())) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + c.JSON(http.StatusOK, HttpResponse{Result: appInfo}) +} + +func clearAppHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + var appClearReq AppClearRequest + if err := c.ShouldBindJSON(&appClearReq); err != nil { + handlerValidateRequestFailedContext(c, err) + return + } + + err = dExt.Driver.Clear(appClearReq.PackageName) + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to unlick screen", c.HandlerName())) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) +} + +func launchAppHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + var appLaunchReq AppLaunchRequest + if err := c.ShouldBindJSON(&appLaunchReq); err != nil { + handlerValidateRequestFailedContext(c, err) + return + } + + err = dExt.Driver.AppLaunch(appLaunchReq.PackageName) + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to launch app %s", c.HandlerName(), appLaunchReq.PackageName)) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) +} + +func terminalAppHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + var appTerminalReq AppTerminalRequest + if err := c.ShouldBindJSON(&appTerminalReq); err != nil { + handlerValidateRequestFailedContext(c, err) + return + } + + success, err := dExt.Driver.AppTerminate(appTerminalReq.PackageName) + if !success { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to launch app %s", c.HandlerName(), appTerminalReq.PackageName)) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) +} diff --git a/hrp/pkg/server/context.go b/hrp/pkg/server/context.go new file mode 100644 index 00000000..b5954564 --- /dev/null +++ b/hrp/pkg/server/context.go @@ -0,0 +1,103 @@ +package server + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/httprunner/httprunner/v4/hrp/code" + "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" + "github.com/rs/zerolog/log" +) + +var uiClients = make(map[string]*uixt.DriverExt) // UI automation clients for iOS and Android, key is udid/serial + +func handleDeviceContext() gin.HandlerFunc { + return func(c *gin.Context) { + platform := c.Param("platform") + serial := c.Param("serial") + if serial == "" { + log.Error().Str("platform", platform).Msg(fmt.Sprintf("[%s]: serial is empty", c.HandlerName())) + c.JSON(http.StatusBadRequest, HttpResponse{ + Code: code.GetErrorCode(code.InvalidParamError), + Message: "serial is empty", + }) + c.Abort() + return + } + // get cached driver + if driver, ok := uiClients[serial]; ok { + c.Set("driver", driver) + c.Next() + return + } + + switch strings.ToLower(platform) { + case "android": + device, err := uixt.NewAndroidDevice(uixt.WithSerialNumber(serial), uixt.WithStub(true)) + if err != nil { + log.Error().Err(err).Str("platform", platform).Str("serial", serial).Msg(fmt.Sprintf("[%s]: Device Not Found", c.HandlerName())) + c.JSON(http.StatusBadRequest, HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }) + c.Abort() + return + } + driver, err := device.NewDriver(uixt.WithDriverImageService(true), uixt.WithDriverResultFolder(true)) + if err != nil { + log.Error().Err(err).Str("platform", platform).Str("serial", serial).Msg(fmt.Sprintf("[%s]: Failed New Driver", c.HandlerName())) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + c.Set("driver", driver) + // cache driver + uiClients[serial] = driver + default: + c.JSON(http.StatusBadRequest, HttpResponse{ + Code: code.GetErrorCode(code.InvalidParamError), + Message: fmt.Sprintf("unsupported platform %s", platform), + }) + c.Abort() + return + } + c.Next() + } +} + +func getContextDriver(c *gin.Context) (*uixt.DriverExt, error) { + driverObj, exists := c.Get("driver") + if !exists { + handlerInitDeviceDriverFailedContext(c) + return nil, fmt.Errorf("driver not found") + } + dExt := driverObj.(*uixt.DriverExt) + return dExt, nil +} + +func handlerInitDeviceDriverFailedContext(c *gin.Context) { + log.Error().Msg("init device driver failed") + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(code.MobileUIDriverError), + Message: "init driver failed", + }, + ) + c.Abort() +} + +func handlerValidateRequestFailedContext(c *gin.Context, err error) { + log.Error().Err(err).Msg("validate request failed") + c.JSON(http.StatusBadRequest, HttpResponse{ + Code: code.GetErrorCode(code.InvalidParamError), + Message: fmt.Sprintf("validate request param failed: %s", err.Error()), + }) + c.Abort() +} diff --git a/hrp/pkg/server/exception.go b/hrp/pkg/server/exception.go deleted file mode 100644 index 592a9066..00000000 --- a/hrp/pkg/server/exception.go +++ /dev/null @@ -1,15 +0,0 @@ -package server - -// 常见的错误代码和消息 -const ( - InternalServerErrorCode = 100001 - InternalServerErrorMsg = "Internal Server Error" - - InvalidParamErrorCode = 100002 - InvalidParamErrorMsg = "Invalid %s Param" -) - -const ( - DeviceNotFoundCode = 110001 - DeviceNotFoundMsg = "Device %s Not Found" -) diff --git a/hrp/pkg/server/key.go b/hrp/pkg/server/key.go new file mode 100644 index 00000000..7a703384 --- /dev/null +++ b/hrp/pkg/server/key.go @@ -0,0 +1,80 @@ +package server + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/httprunner/httprunner/v4/hrp/code" + "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" + "github.com/rs/zerolog/log" +) + +func unlockHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + err = dExt.Driver.Unlock() + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to unlick screen", c.HandlerName())) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) +} + +func homeHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + err = dExt.Driver.Homescreen() + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to enter homescreen", c.HandlerName())) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) +} + +func keycodeHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + var keycodeReq KeycodeRequest + if err := c.ShouldBindJSON(&keycodeReq); err != nil { + handlerValidateRequestFailedContext(c, err) + return + } + + err = dExt.Driver.PressKeyCode(uixt.KeyCode(keycodeReq.Keycode)) + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to input keycode %d", c.HandlerName(), keycodeReq.Keycode)) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) +} diff --git a/hrp/pkg/server/main.go b/hrp/pkg/server/main.go new file mode 100644 index 00000000..99afcb4a --- /dev/null +++ b/hrp/pkg/server/main.go @@ -0,0 +1,50 @@ +package server + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" +) + +func NewServer(port int) error { + router := gin.Default() + router.GET("/ping", pingHandler) + + apiV1Platform := router.Group("/api/v1").Group("/:platform") + apiV1Platform.GET("/devices", listDeviceHandler) + + apiV1PlatformSerial := apiV1Platform.Group("/:serial") + // UI operations + apiV1PlatformSerial.POST("/ui/tap", handleDeviceContext(), tapHandler) + apiV1PlatformSerial.POST("/ui/drag", handleDeviceContext(), dragHandler) + apiV1PlatformSerial.POST("/ui/input", handleDeviceContext(), inputHandler) + // Key operations + apiV1PlatformSerial.POST("/key/unlock", handleDeviceContext(), unlockHandler) + apiV1PlatformSerial.POST("/key/home", handleDeviceContext(), homeHandler) + apiV1PlatformSerial.POST("/key", handleDeviceContext(), keycodeHandler) + // App operations + apiV1PlatformSerial.GET("/app/foreground", handleDeviceContext(), foregroundAppHandler) + apiV1PlatformSerial.POST("/app/clear", handleDeviceContext(), clearAppHandler) + apiV1PlatformSerial.POST("/app/launch", handleDeviceContext(), launchAppHandler) + apiV1PlatformSerial.POST("/app/terminal", handleDeviceContext(), terminalAppHandler) + // get screen info + apiV1PlatformSerial.GET("/screenshot", handleDeviceContext(), screenshotHandler) + apiV1PlatformSerial.GET("/stub/source", handleDeviceContext(), sourceHandler) + apiV1PlatformSerial.GET("/adb/source", handleDeviceContext(), adbSourceHandler) + // Stub operations + apiV1PlatformSerial.POST("/stub/login", handleDeviceContext(), loginHandler) + apiV1PlatformSerial.POST("/stub/logout", handleDeviceContext(), logoutHandler) + + err := router.Run(fmt.Sprintf("127.0.0.1:%d", port)) + if err != nil { + log.Err(err).Msg("failed to start http server") + return err + } + return nil +} + +func pingHandler(c *gin.Context) { + c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) +} diff --git a/hrp/pkg/server/server.go b/hrp/pkg/server/server.go deleted file mode 100644 index 071d1865..00000000 --- a/hrp/pkg/server/server.go +++ /dev/null @@ -1,701 +0,0 @@ -package server - -import ( - "encoding/base64" - "fmt" - "net/http" - "strings" - - "github.com/gin-gonic/gin" - "github.com/rs/zerolog/log" - - "github.com/httprunner/httprunner/v4/hrp/code" - "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" - "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" -) - -func NewServer(port int) error { - router := gin.Default() - router.GET("/ping", pingHandler) - - apiV1Platform := router.Group("/api/v1").Group("/:platform") - apiV1Platform.GET("/devices", listDeviceHandler) - - apiV1PlatformSerial := apiV1Platform.Group("/:serial") - // UI operations - apiV1PlatformSerial.POST("/ui/tap", parseDeviceInfo(), tapHandler) - apiV1PlatformSerial.POST("/ui/drag", parseDeviceInfo(), dragHandler) - apiV1PlatformSerial.POST("/ui/input", parseDeviceInfo(), inputHandler) - // Key operations - apiV1PlatformSerial.POST("/key/unlock", parseDeviceInfo(), unlockHandler) - apiV1PlatformSerial.POST("/key/home", parseDeviceInfo(), homeHandler) - apiV1PlatformSerial.POST("/key", parseDeviceInfo(), keycodeHandler) - // App operations - apiV1PlatformSerial.GET("/app/foreground", parseDeviceInfo(), foregroundAppHandler) - apiV1PlatformSerial.POST("/app/clear", parseDeviceInfo(), clearAppHandler) - apiV1PlatformSerial.POST("/app/launch", parseDeviceInfo(), launchAppHandler) - apiV1PlatformSerial.POST("/app/terminal", parseDeviceInfo(), terminalAppHandler) - // get screen info - apiV1PlatformSerial.GET("/screenshot", parseDeviceInfo(), screenshotHandler) - apiV1PlatformSerial.GET("/stub/source", parseDeviceInfo(), sourceHandler) - apiV1PlatformSerial.GET("/adb/source", parseDeviceInfo(), adbSourceHandler) - // Stub operations - apiV1PlatformSerial.POST("/stub/login", parseDeviceInfo(), loginHandler) - apiV1PlatformSerial.POST("/stub/logout", parseDeviceInfo(), logoutHandler) - - err := router.Run(fmt.Sprintf("127.0.0.1:%d", port)) - if err != nil { - log.Err(err).Msg("failed to start http server") - return err - } - return nil -} - -func pingHandler(c *gin.Context) { - c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) -} - -func listDeviceHandler(c *gin.Context) { - platform := c.Param("platform") - switch strings.ToLower(platform) { - case "android": - { - client, err := gadb.NewClient() - if err != nil { - log.Err(err).Msg("failed to init adb client") - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - devices, err := client.DeviceList() - if err != nil && strings.Contains(err.Error(), "no android device found") { - c.JSON(http.StatusOK, HttpResponse{Result: nil}) - return - } else if err != nil { - log.Err(err).Msg("failed to list devices") - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - var deviceList []interface{} - for _, device := range devices { - brand, err := device.Brand() - if err != nil { - log.Err(err).Msg("failed to get device brand") - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - model, err := device.Model() - if err != nil { - log.Err(err).Msg("failed to get device model") - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - deviceInfo := map[string]interface{}{ - "serial": device.Serial(), - "brand": brand, - "model": model, - "platform": "android", - } - deviceList = append(deviceList, deviceInfo) - } - c.JSON(http.StatusOK, HttpResponse{Result: deviceList}) - return - } - default: - { - c.JSON(http.StatusBadRequest, HttpResponse{ - Code: InvalidParamErrorCode, - Message: fmt.Sprintf(InvalidParamErrorMsg, "platform"), - }) - c.Abort() - return - } - } -} - -func tapHandler(c *gin.Context) { - var tapReq TapRequest - if err := c.ShouldBindJSON(&tapReq); err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: false, Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "request")}) - c.Abort() - return - } - - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: false, Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - var actionOptions []uixt.ActionOption - if tapReq.Options != nil { - actionOptions = tapReq.Options.Options() - } - - dExt := driverObj.(*uixt.DriverExt) - if tapReq.Text != "" { - err := dExt.TapByOCR(tapReq.Text, actionOptions...) - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to tap text %s", c.HandlerName(), tapReq.Text)) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - } else if tapReq.X < 1 && tapReq.Y < 1 { - err := dExt.TapXY(tapReq.X, tapReq.Y, actionOptions...) - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to tap %f, %f", c.HandlerName(), tapReq.X, tapReq.Y)) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - } else { - err := dExt.TapAbsXY(tapReq.X, tapReq.Y, actionOptions...) - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to tap %f, %f", c.HandlerName(), tapReq.X, tapReq.Y)) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - } - c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) -} - -func dragHandler(c *gin.Context) { - var dragReq DragRequest - if err := c.ShouldBindJSON(&dragReq); err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: false, Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "request")}) - c.Abort() - return - } - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: false, Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - dExt := driverObj.(*uixt.DriverExt) - if dragReq.FromX < 1 && dragReq.FromY < 1 && dragReq.ToX < 1 && dragReq.ToY < 1 { - err := dExt.SwipeRelative(dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY, uixt.WithPressDuration(dragReq.Duration)) - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to drag from %f, %f to %f, %f", c.HandlerName(), dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY)) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - } else { - err := dExt.Driver.SwipeFloat(dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY, uixt.WithPressDuration(dragReq.Duration)) - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to drag from %f, %f to %f, %f", c.HandlerName(), dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY)) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - } - c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) -} - -func inputHandler(c *gin.Context) { - var inputReq InputRequest - if err := c.ShouldBindJSON(&inputReq); err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: false, Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "request")}) - c.Abort() - return - } - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: false, Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - dExt := driverObj.(*uixt.DriverExt) - err := dExt.Driver.SendKeys(inputReq.Text, uixt.WithFrequency(inputReq.Frequency)) - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to input text %s", c.HandlerName(), inputReq.Text)) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) -} - -func unlockHandler(c *gin.Context) { - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: false, Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - dExt := driverObj.(*uixt.DriverExt) - err := dExt.Driver.Unlock() - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to unlick screen", c.HandlerName())) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) -} - -func homeHandler(c *gin.Context) { - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: false, Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - dExt := driverObj.(*uixt.DriverExt) - err := dExt.Driver.Homescreen() - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to enter homescreen", c.HandlerName())) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) -} - -func keycodeHandler(c *gin.Context) { - var keycodeReq KeycodeRequest - if err := c.ShouldBindJSON(&keycodeReq); err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: false, Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "request")}) - c.Abort() - return - } - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: false, Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - dExt := driverObj.(*uixt.DriverExt) - err := dExt.Driver.PressKeyCode(uixt.KeyCode(keycodeReq.Keycode)) - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to input keycode %d", c.HandlerName(), keycodeReq.Keycode)) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) -} - -func foregroundAppHandler(c *gin.Context) { - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: false, Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - dExt := driverObj.(*uixt.DriverExt) - appInfo, err := dExt.Driver.GetForegroundApp() - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to unlick screen", c.HandlerName())) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - c.JSON(http.StatusOK, HttpResponse{Result: appInfo}) -} - -func clearAppHandler(c *gin.Context) { - var appClearReq AppClearRequest - if err := c.ShouldBindJSON(&appClearReq); err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: "", Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "request")}) - c.Abort() - return - } - - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: false, Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - dExt := driverObj.(*uixt.DriverExt) - err := dExt.Driver.Clear(appClearReq.PackageName) - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to unlick screen", c.HandlerName())) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) -} - -func launchAppHandler(c *gin.Context) { - var appLaunchReq AppLaunchRequest - if err := c.ShouldBindJSON(&appLaunchReq); err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: "", Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "request")}) - c.Abort() - return - } - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: "", Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - dExt := driverObj.(*uixt.DriverExt) - err := dExt.Driver.AppLaunch(appLaunchReq.PackageName) - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to launch app %s", c.HandlerName(), appLaunchReq.PackageName)) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) -} - -func terminalAppHandler(c *gin.Context) { - var appTerminalReq AppTerminalRequest - if err := c.ShouldBindJSON(&appTerminalReq); err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: "", Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "request")}) - c.Abort() - return - } - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: "", Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - dExt := driverObj.(*uixt.DriverExt) - success, err := dExt.Driver.AppTerminate(appTerminalReq.PackageName) - if !success { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to launch app %s", c.HandlerName(), appTerminalReq.PackageName)) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) -} - -func screenshotHandler(c *gin.Context) { - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: "", Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - dExt := driverObj.(*uixt.DriverExt) - raw, err := dExt.Driver.Screenshot() - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get screenshot", c.HandlerName())) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - - c.JSON(http.StatusOK, HttpResponse{Result: base64.StdEncoding.EncodeToString(raw.Bytes())}) -} - -func sourceHandler(c *gin.Context) { - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: "", Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - dExt := driverObj.(*uixt.DriverExt) - app, err := dExt.Driver.GetForegroundApp() - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get foreground app", c.HandlerName())) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - source, err := dExt.Driver.Source(uixt.NewSourceOption().WithProcessName(app.PackageName)) - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get source %s", c.HandlerName(), app.PackageName)) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - - c.JSON(http.StatusOK, HttpResponse{Result: source}) -} - -func adbSourceHandler(c *gin.Context) { - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: "", Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - driver := driverObj.(*uixt.DriverExt) - source, err := driver.Driver.Source() - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get adb source", c.HandlerName())) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - c.JSON(http.StatusOK, HttpResponse{Result: source}) -} - -func loginHandler(c *gin.Context) { - var loginReq LoginRequest - if err := c.ShouldBindJSON(&loginReq); err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: "", Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "request")}) - c.Abort() - return - } - - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: "", Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - dExt := driverObj.(*uixt.DriverExt) - err := dExt.Driver.LoginNoneUI(loginReq.PackageName, loginReq.PhoneNumber, loginReq.Captcha) - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to login", c.HandlerName())) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) -} - -func logoutHandler(c *gin.Context) { - var logoutReq LogoutRequest - if err := c.ShouldBindJSON(&logoutReq); err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: Invalid Request", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: "", Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "request")}) - c.Abort() - return - } - - driverObj, exists := c.Get("driver") - if !exists { - log.Error().Msg(fmt.Sprintf("[%s]: Driver Not exsit", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{Result: "", Code: InvalidParamErrorCode, Message: fmt.Sprintf(InvalidParamErrorMsg, "driver")}) - c.Abort() - return - } - - dExt := driverObj.(*uixt.DriverExt) - err := dExt.Driver.LogoutNoneUI(logoutReq.PackageName) - if err != nil { - log.Err(err).Msg(fmt.Sprintf("[%s]: failed to login", c.HandlerName())) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) -} - -var uiClients = make(map[string]*uixt.DriverExt) // UI automation clients for iOS and Android, key is udid/serial - -func parseDeviceInfo() gin.HandlerFunc { - return func(c *gin.Context) { - platform := c.Param("platform") - serial := c.Param("serial") - if serial == "" { - log.Error().Str("platform", platform).Msg(fmt.Sprintf("[%s]: serial is empty", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{ - Code: InvalidParamErrorCode, - Message: fmt.Sprintf(InvalidParamErrorMsg, "serial"), - }) - c.Abort() - return - } - // get cached driver - if driver, ok := uiClients[serial]; ok { - c.Set("driver", driver) - c.Next() - return - } - - switch strings.ToLower(platform) { - case "android": - device, err := uixt.NewAndroidDevice(uixt.WithSerialNumber(serial), uixt.WithStub(true)) - if err != nil { - log.Error().Err(err).Str("platform", platform).Str("serial", serial).Msg(fmt.Sprintf("[%s]: Device Not Found", c.HandlerName())) - c.JSON(http.StatusBadRequest, HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }) - c.Abort() - return - } - driver, err := device.NewDriver(uixt.WithDriverImageService(true), uixt.WithDriverResultFolder(true)) - if err != nil { - log.Error().Err(err).Str("platform", platform).Str("serial", serial).Msg(fmt.Sprintf("[%s]: Failed New Driver", c.HandlerName())) - c.JSON(http.StatusInternalServerError, - HttpResponse{ - Code: code.GetErrorCode(err), - Message: err.Error(), - }, - ) - c.Abort() - return - } - c.Set("driver", driver) - // cache driver - uiClients[serial] = driver - default: - c.JSON(http.StatusBadRequest, HttpResponse{ - Code: InvalidParamErrorCode, - Message: fmt.Sprintf(InvalidParamErrorMsg, "platform"), - }) - c.Abort() - return - } - c.Next() - } -} diff --git a/hrp/pkg/server/source.go b/hrp/pkg/server/source.go new file mode 100644 index 00000000..73882cfa --- /dev/null +++ b/hrp/pkg/server/source.go @@ -0,0 +1,89 @@ +package server + +import ( + "encoding/base64" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/httprunner/httprunner/v4/hrp/code" + "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" + "github.com/rs/zerolog/log" +) + +func screenshotHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + raw, err := dExt.Driver.Screenshot() + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get screenshot", c.HandlerName())) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + + c.JSON(http.StatusOK, HttpResponse{Result: base64.StdEncoding.EncodeToString(raw.Bytes())}) +} + +func sourceHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + app, err := dExt.Driver.GetForegroundApp() + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get foreground app", c.HandlerName())) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + source, err := dExt.Driver.Source(uixt.NewSourceOption().WithProcessName(app.PackageName)) + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get source %s", c.HandlerName(), app.PackageName)) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + + c.JSON(http.StatusOK, HttpResponse{Result: source}) +} + +func adbSourceHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + source, err := dExt.Driver.Source() + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to get adb source", c.HandlerName())) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + c.JSON(http.StatusOK, HttpResponse{Result: source}) +} diff --git a/hrp/pkg/server/stub.go b/hrp/pkg/server/stub.go new file mode 100644 index 00000000..ff0c6bd9 --- /dev/null +++ b/hrp/pkg/server/stub.go @@ -0,0 +1,148 @@ +package server + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp/code" + "github.com/httprunner/httprunner/v4/hrp/pkg/gadb" +) + +func listDeviceHandler(c *gin.Context) { + platform := c.Param("platform") + switch strings.ToLower(platform) { + case "android": + { + client, err := gadb.NewClient() + if err != nil { + log.Err(err).Msg("failed to init adb client") + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + devices, err := client.DeviceList() + if err != nil && strings.Contains(err.Error(), "no android device found") { + c.JSON(http.StatusOK, HttpResponse{Result: nil}) + return + } else if err != nil { + log.Err(err).Msg("failed to list devices") + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + var deviceList []interface{} + for _, device := range devices { + brand, err := device.Brand() + if err != nil { + log.Err(err).Msg("failed to get device brand") + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + model, err := device.Model() + if err != nil { + log.Err(err).Msg("failed to get device model") + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + deviceInfo := map[string]interface{}{ + "serial": device.Serial(), + "brand": brand, + "model": model, + "platform": "android", + } + deviceList = append(deviceList, deviceInfo) + } + c.JSON(http.StatusOK, HttpResponse{Result: deviceList}) + return + } + default: + { + c.JSON(http.StatusBadRequest, HttpResponse{ + Code: code.GetErrorCode(code.InvalidParamError), + Message: fmt.Sprintf("unsupported platform %s", platform), + }) + c.Abort() + return + } + } +} + +func loginHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + var loginReq LoginRequest + if err := c.ShouldBindJSON(&loginReq); err != nil { + handlerValidateRequestFailedContext(c, err) + return + } + + err = dExt.Driver.LoginNoneUI(loginReq.PackageName, loginReq.PhoneNumber, loginReq.Captcha) + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to login", c.HandlerName())) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) +} + +func logoutHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + var logoutReq LogoutRequest + if err := c.ShouldBindJSON(&logoutReq); err != nil { + handlerValidateRequestFailedContext(c, err) + return + } + + err = dExt.Driver.LogoutNoneUI(logoutReq.PackageName) + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to login", c.HandlerName())) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) +} diff --git a/hrp/pkg/server/ui.go b/hrp/pkg/server/ui.go new file mode 100644 index 00000000..cd520e99 --- /dev/null +++ b/hrp/pkg/server/ui.go @@ -0,0 +1,140 @@ +package server + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/httprunner/httprunner/v4/hrp/code" + "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" + "github.com/rs/zerolog/log" +) + +func tapHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + var tapReq TapRequest + if err := c.ShouldBindJSON(&tapReq); err != nil { + handlerValidateRequestFailedContext(c, err) + return + } + + var actionOptions []uixt.ActionOption + if tapReq.Options != nil { + actionOptions = tapReq.Options.Options() + } + + if tapReq.Text != "" { + err := dExt.TapByOCR(tapReq.Text, actionOptions...) + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to tap text %s", c.HandlerName(), tapReq.Text)) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + } else if tapReq.X < 1 && tapReq.Y < 1 { + err := dExt.TapXY(tapReq.X, tapReq.Y, actionOptions...) + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to tap %f, %f", c.HandlerName(), tapReq.X, tapReq.Y)) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + } else { + err := dExt.TapAbsXY(tapReq.X, tapReq.Y, actionOptions...) + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to tap %f, %f", c.HandlerName(), tapReq.X, tapReq.Y)) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + } + c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) +} + +func dragHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + var dragReq DragRequest + if err := c.ShouldBindJSON(&dragReq); err != nil { + handlerValidateRequestFailedContext(c, err) + return + } + + if dragReq.FromX < 1 && dragReq.FromY < 1 && dragReq.ToX < 1 && dragReq.ToY < 1 { + err := dExt.SwipeRelative(dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY, uixt.WithPressDuration(dragReq.Duration)) + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to drag from %f, %f to %f, %f", c.HandlerName(), dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY)) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + } else { + err := dExt.Driver.SwipeFloat(dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY, uixt.WithPressDuration(dragReq.Duration)) + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to drag from %f, %f to %f, %f", c.HandlerName(), dragReq.FromX, dragReq.FromY, dragReq.ToX, dragReq.ToY)) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + } + c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) +} + +func inputHandler(c *gin.Context) { + dExt, err := getContextDriver(c) + if err != nil { + return + } + + var inputReq InputRequest + if err := c.ShouldBindJSON(&inputReq); err != nil { + handlerValidateRequestFailedContext(c, err) + return + } + + err = dExt.Driver.SendKeys(inputReq.Text, uixt.WithFrequency(inputReq.Frequency)) + if err != nil { + log.Err(err).Msg(fmt.Sprintf("[%s]: failed to input text %s", c.HandlerName(), inputReq.Text)) + c.JSON(http.StatusInternalServerError, + HttpResponse{ + Code: code.GetErrorCode(err), + Message: err.Error(), + }, + ) + c.Abort() + return + } + c.JSON(http.StatusOK, HttpResponse{Code: 0, Message: "success"}) +}