diff --git a/cmd/browsers.go b/cmd/browsers.go index f04e5dbe..a8a1d93f 100644 --- a/cmd/browsers.go +++ b/cmd/browsers.go @@ -674,6 +674,8 @@ type BrowsersComputerMoveMouseInput struct { X int64 Y int64 HoldKeys []string + Smooth *bool + DurationMs *int64 } type BrowsersComputerScreenshotInput struct { @@ -718,6 +720,8 @@ type BrowsersComputerDragMouseInput struct { StepsPerSegment int64 Button string HoldKeys []string + Smooth *bool + DurationMs *int64 } type BrowsersComputerSetCursorInput struct { @@ -767,6 +771,16 @@ func (b BrowsersCmd) ComputerMoveMouse(ctx context.Context, in BrowsersComputerM if len(in.HoldKeys) > 0 { body.HoldKeys = in.HoldKeys } + extras := map[string]any{} + if in.Smooth != nil { + extras["smooth"] = *in.Smooth + } + if in.DurationMs != nil { + extras["duration_ms"] = *in.DurationMs + } + if len(extras) > 0 { + body.SetExtraFields(extras) + } if err := b.computer.MoveMouse(ctx, br.SessionID, body); err != nil { return util.CleanedUpSdkError{Err: err} } @@ -912,6 +926,16 @@ func (b BrowsersCmd) ComputerDragMouse(ctx context.Context, in BrowsersComputerD if len(in.HoldKeys) > 0 { body.HoldKeys = in.HoldKeys } + extras := map[string]any{} + if in.Smooth != nil { + extras["smooth"] = *in.Smooth + } + if in.DurationMs != nil { + extras["duration_ms"] = *in.DurationMs + } + if len(extras) > 0 { + body.SetExtraFields(extras) + } if err := b.computer.DragMouse(ctx, br.SessionID, body); err != nil { return util.CleanedUpSdkError{Err: err} } @@ -2239,6 +2263,8 @@ func init() { _ = computerMove.MarkFlagRequired("x") _ = computerMove.MarkFlagRequired("y") computerMove.Flags().StringSlice("hold-key", []string{}, "Modifier keys to hold (repeatable)") + computerMove.Flags().Bool("smooth", true, "Use human-like Bezier curve path instead of instant teleport") + computerMove.Flags().Int64("duration-ms", 0, "Target duration in ms for smooth movement (50-5000, 0 for auto)") computerScreenshot := &cobra.Command{Use: "screenshot ", Short: "Capture a screenshot (optionally of a region)", Args: cobra.ExactArgs(1), RunE: runBrowsersComputerScreenshot} computerScreenshot.Flags().Int64("x", 0, "Top-left X") @@ -2278,6 +2304,8 @@ func init() { computerDrag.Flags().Int64("steps-per-segment", 0, "Number of move steps per path segment") computerDrag.Flags().String("button", "left", "Mouse button: left,middle,right") computerDrag.Flags().StringSlice("hold-key", []string{}, "Modifier keys to hold (repeatable)") + computerDrag.Flags().Bool("smooth", true, "Use human-like Bezier curves between waypoints") + computerDrag.Flags().Int64("duration-ms", 0, "Target duration in ms for smooth drag (50-10000, 0 for auto)") // computer set-cursor computerSetCursor := &cobra.Command{Use: "set-cursor ", Short: "Hide or show the cursor", Args: cobra.ExactArgs(1), RunE: runBrowsersComputerSetCursor} @@ -2864,8 +2892,17 @@ func runBrowsersComputerMoveMouse(cmd *cobra.Command, args []string) error { x, _ := cmd.Flags().GetInt64("x") y, _ := cmd.Flags().GetInt64("y") holdKeys, _ := cmd.Flags().GetStringSlice("hold-key") + in := BrowsersComputerMoveMouseInput{Identifier: args[0], X: x, Y: y, HoldKeys: holdKeys} + if cmd.Flags().Changed("smooth") { + v, _ := cmd.Flags().GetBool("smooth") + in.Smooth = &v + } + if cmd.Flags().Changed("duration-ms") { + v, _ := cmd.Flags().GetInt64("duration-ms") + in.DurationMs = &v + } b := BrowsersCmd{browsers: &svc, computer: &svc.Computer} - return b.ComputerMoveMouse(cmd.Context(), BrowsersComputerMoveMouseInput{Identifier: args[0], X: x, Y: y, HoldKeys: holdKeys}) + return b.ComputerMoveMouse(cmd.Context(), in) } func runBrowsersComputerScreenshot(cmd *cobra.Command, args []string) error { @@ -2959,8 +2996,17 @@ func runBrowsersComputerDragMouse(cmd *cobra.Command, args []string) error { path = append(path, []int64{x, y}) } + in := BrowsersComputerDragMouseInput{Identifier: args[0], Path: path, Delay: delay, StepDelayMs: stepDelayMs, StepsPerSegment: stepsPerSegment, Button: button, HoldKeys: holdKeys} + if cmd.Flags().Changed("smooth") { + v, _ := cmd.Flags().GetBool("smooth") + in.Smooth = &v + } + if cmd.Flags().Changed("duration-ms") { + v, _ := cmd.Flags().GetInt64("duration-ms") + in.DurationMs = &v + } b := BrowsersCmd{browsers: &svc, computer: &svc.Computer} - return b.ComputerDragMouse(cmd.Context(), BrowsersComputerDragMouseInput{Identifier: args[0], Path: path, Delay: delay, StepDelayMs: stepDelayMs, StepsPerSegment: stepsPerSegment, Button: button, HoldKeys: holdKeys}) + return b.ComputerDragMouse(cmd.Context(), in) } func runBrowsersComputerSetCursor(cmd *cobra.Command, args []string) error { diff --git a/cmd/browsers_test.go b/cmd/browsers_test.go index 447b6bda..d04ab813 100644 --- a/cmd/browsers_test.go +++ b/cmd/browsers_test.go @@ -1053,6 +1053,109 @@ func TestBrowsersComputerMoveMouse_PrintsSuccess(t *testing.T) { assert.Contains(t, out, "Moved mouse to (5,6)") } +func TestBrowsersComputerMoveMouse_SmoothFalse(t *testing.T) { + setupStdoutCapture(t) + fakeBrowsers := newFakeBrowsersServiceWithSimpleGet() + var capturedBody kernel.BrowserComputerMoveMouseParams + fakeComp := &FakeComputerService{ + MoveMouseFunc: func(ctx context.Context, id string, body kernel.BrowserComputerMoveMouseParams, opts ...option.RequestOption) error { + capturedBody = body + return nil + }, + } + b := BrowsersCmd{browsers: fakeBrowsers, computer: fakeComp} + smooth := false + _ = b.ComputerMoveMouse(context.Background(), BrowsersComputerMoveMouseInput{Identifier: "id", X: 100, Y: 200, Smooth: &smooth}) + extras := capturedBody.ExtraFields() + assert.Contains(t, extras, "smooth") + assert.Equal(t, false, extras["smooth"]) +} + +func TestBrowsersComputerMoveMouse_DurationMs(t *testing.T) { + setupStdoutCapture(t) + fakeBrowsers := newFakeBrowsersServiceWithSimpleGet() + var capturedBody kernel.BrowserComputerMoveMouseParams + fakeComp := &FakeComputerService{ + MoveMouseFunc: func(ctx context.Context, id string, body kernel.BrowserComputerMoveMouseParams, opts ...option.RequestOption) error { + capturedBody = body + return nil + }, + } + b := BrowsersCmd{browsers: fakeBrowsers, computer: fakeComp} + smooth := true + dur := int64(1500) + _ = b.ComputerMoveMouse(context.Background(), BrowsersComputerMoveMouseInput{Identifier: "id", X: 100, Y: 200, Smooth: &smooth, DurationMs: &dur}) + extras := capturedBody.ExtraFields() + assert.Contains(t, extras, "smooth") + assert.Equal(t, true, extras["smooth"]) + assert.Contains(t, extras, "duration_ms") + assert.Equal(t, int64(1500), extras["duration_ms"]) +} + +func TestBrowsersComputerMoveMouse_NoSmoothFlag(t *testing.T) { + setupStdoutCapture(t) + fakeBrowsers := newFakeBrowsersServiceWithSimpleGet() + var capturedBody kernel.BrowserComputerMoveMouseParams + fakeComp := &FakeComputerService{ + MoveMouseFunc: func(ctx context.Context, id string, body kernel.BrowserComputerMoveMouseParams, opts ...option.RequestOption) error { + capturedBody = body + return nil + }, + } + b := BrowsersCmd{browsers: fakeBrowsers, computer: fakeComp} + _ = b.ComputerMoveMouse(context.Background(), BrowsersComputerMoveMouseInput{Identifier: "id", X: 100, Y: 200}) + extras := capturedBody.ExtraFields() + assert.Empty(t, extras) +} + +func TestBrowsersComputerDragMouse_SmoothFalse(t *testing.T) { + setupStdoutCapture(t) + fakeBrowsers := newFakeBrowsersServiceWithSimpleGet() + var capturedBody kernel.BrowserComputerDragMouseParams + fakeComp := &FakeComputerService{ + DragMouseFunc: func(ctx context.Context, id string, body kernel.BrowserComputerDragMouseParams, opts ...option.RequestOption) error { + capturedBody = body + return nil + }, + } + b := BrowsersCmd{browsers: fakeBrowsers, computer: fakeComp} + smooth := false + _ = b.ComputerDragMouse(context.Background(), BrowsersComputerDragMouseInput{ + Identifier: "id", + Path: [][]int64{{100, 200}, {300, 400}}, + Smooth: &smooth, + }) + extras := capturedBody.ExtraFields() + assert.Contains(t, extras, "smooth") + assert.Equal(t, false, extras["smooth"]) +} + +func TestBrowsersComputerDragMouse_DurationMs(t *testing.T) { + setupStdoutCapture(t) + fakeBrowsers := newFakeBrowsersServiceWithSimpleGet() + var capturedBody kernel.BrowserComputerDragMouseParams + fakeComp := &FakeComputerService{ + DragMouseFunc: func(ctx context.Context, id string, body kernel.BrowserComputerDragMouseParams, opts ...option.RequestOption) error { + capturedBody = body + return nil + }, + } + b := BrowsersCmd{browsers: fakeBrowsers, computer: fakeComp} + smooth := true + dur := int64(3000) + _ = b.ComputerDragMouse(context.Background(), BrowsersComputerDragMouseInput{ + Identifier: "id", + Path: [][]int64{{100, 200}, {300, 400}}, + Smooth: &smooth, + DurationMs: &dur, + }) + extras := capturedBody.ExtraFields() + assert.Contains(t, extras, "smooth") + assert.Equal(t, true, extras["smooth"]) + assert.Contains(t, extras, "duration_ms") + assert.Equal(t, int64(3000), extras["duration_ms"]) +} + func TestBrowsersComputerScreenshot_SavesFile(t *testing.T) { setupStdoutCapture(t) dir := t.TempDir()