Skip to main content
Glama
MoveCommandTests.swift6.43 kB
import CoreGraphics import Foundation import PeekabooCore import PeekabooFoundation import Testing @testable import PeekabooCLI #if !PEEKABOO_SKIP_AUTOMATION @Suite( "MoveCommand Tests", .serialized, .tags(.safe), .enabled(if: CLITestEnvironment.runAutomationRead) ) struct MoveCommandTests { @Test("move --help lists options") func moveHelp() async throws { let context = await self.makeContext() let result = try await self.runMove(arguments: ["--help"], context: context) #expect(result.exitStatus == 0) #expect(self.output(from: result).contains("Move the mouse cursor")) } @Test("Coordinate moves call automation service") func coordinateMove() async throws { let context = await self.makeContext() let result = try await self.runMove( arguments: ["100,200", "--duration", "750", "--steps", "10"], context: context ) #expect(result.exitStatus == 0) let moveCalls = await self.automationState(context) { $0.moveMouseCalls } let call = try #require(moveCalls.first) #expect(call.destination == CGPoint(x: 100, y: 200)) #expect(call.duration == 750) #expect(call.steps == 10) #expect(call.profile == .linear) } @Test("Move command requires a target") func requiresTarget() async throws { let context = await self.makeContext() let result = try await self.runMove(arguments: [], context: context) #expect(result.exitStatus != 0) let moveCalls = await self.automationState(context) { $0.moveMouseCalls } #expect(moveCalls.isEmpty) } @Test("Move by element ID resolves using stored detection results") func moveByElementId() async throws { let context = await self.makeContext() let element = DetectedElement( id: "B1", type: .button, label: "Submit", bounds: CGRect(x: 50, y: 70, width: 120, height: 40) ) let detection = ElementDetectionResult( sessionId: "session-id", screenshotPath: "/tmp/screenshot.png", elements: DetectedElements(buttons: [element]), metadata: DetectionMetadata(detectionTime: 0, elementCount: 1, method: "stub") ) try await context.sessions.storeDetectionResult(sessionId: "session-id", result: detection) let result = try await self.runMove( arguments: ["--id", "B1", "--session", "session-id", "--json-output"], context: context ) #expect(result.exitStatus == 0) let moveCalls = await self.automationState(context) { $0.moveMouseCalls } let call = try #require(moveCalls.first) #expect(call.destination.x == element.bounds.midX) #expect(call.destination.y == element.bounds.midY) #expect(call.profile == .linear) } @Test("Move by query waits for element using automation service") func moveByQuery() async throws { let context = await self.makeContext { automation, sessions in sessions.mostRecentSessionId = "session-query" let element = DetectedElement( id: "B2", type: .button, label: "Continue", bounds: CGRect(x: 200, y: 300, width: 80, height: 24) ) automation.setWaitForElementResult( WaitForElementResult(found: true, element: element, waitTime: 0.05), for: .query("Continue") ) } let result = try await self.runMove(arguments: ["--to", "Continue"], context: context) #expect(result.exitStatus == 0) let waitCalls = await self.automationState(context) { $0.waitForElementCalls } #expect(waitCalls.count == 1) let moveCalls = await self.automationState(context) { $0.moveMouseCalls } let call = try #require(moveCalls.first) #expect(call.destination == CGPoint(x: 240, y: 312)) // mid-point of element bounds #expect(call.profile == .linear) } @Test("JSON output contains expected shape") func jsonOutput() async throws { let context = await self.makeContext() let result = try await self.runMove(arguments: ["150,250", "--json-output"], context: context) #expect(result.exitStatus == 0) let data = try #require(self.output(from: result).data(using: .utf8)) let payload = try JSONDecoder().decode(MoveResult.self, from: data) #expect(payload.success) #expect(payload.targetDescription.contains("Coordinates")) #expect(payload.targetLocation["x"] == 150) #expect(payload.targetLocation["y"] == 250) #expect(payload.profile == "linear") } @Test("Human profile toggles movement mode") func humanProfileSelection() async throws { let context = await self.makeContext() let result = try await self.runMove(arguments: ["100,200", "--profile", "human"], context: context) #expect(result.exitStatus == 0) let moveCalls = await self.automationState(context) { $0.moveMouseCalls } let call = try #require(moveCalls.first) #expect(call.profile == .human()) #expect(call.steps >= 30) #expect(call.duration >= 280) } // MARK: - Helpers private func runMove( arguments: [String], context: TestServicesFactory.AutomationTestContext ) async throws -> CommandRunResult { try await InProcessCommandRunner.run(["move"] + arguments, services: context.services) } private func output(from result: CommandRunResult) -> String { result.stdout.isEmpty ? result.stderr : result.stdout } private func makeContext( configure: (@MainActor (StubAutomationService, StubSessionManager) -> Void)? = nil ) async -> TestServicesFactory.AutomationTestContext { await MainActor.run { let context = TestServicesFactory.makeAutomationTestContext() configure?(context.automation, context.sessions) return context } } private func automationState<T: Sendable>( _ context: TestServicesFactory.AutomationTestContext, _ operation: @MainActor (StubAutomationService) -> T ) async -> T { await MainActor.run { operation(context.automation) } } } #endif

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/steipete/Peekaboo'

If you have feedback or need assistance with the MCP directory API, please join our Discord server