Skip to main content
Glama
WindowCommandTests.swift9.39 kB
import Foundation import Testing @testable import PeekabooCLI #if !PEEKABOO_SKIP_AUTOMATION @Suite("WindowCommand Tests", .serialized, .tags(.automation), .enabled(if: CLITestEnvironment.runAutomationRead)) struct WindowCommandTests { @Test func windowCommandHelp() async throws { let output = try await runPeekabooCommand(["window", "--help"]) #expect(output.contains("Manipulate application windows")) #expect(output.contains("close")) #expect(output.contains("minimize")) #expect(output.contains("maximize")) #expect(output.contains("move")) #expect(output.contains("resize")) #expect(output.contains("set-bounds")) #expect(output.contains("focus")) #expect(output.contains("list")) } @Test func windowCloseHelp() async throws { let output = try await runPeekabooCommand(["window", "close", "--help"]) #expect(output.contains("Close a window")) #expect(output.contains("--app")) #expect(output.contains("--window-title")) #expect(output.contains("--window-index")) } @Test func windowListCommand() async throws { // Test that window list delegates to list windows command let output = try await runPeekabooCommand(["window", "list", "--app", "Finder", "--json-output"]) let data = try JSONDecoder().decode(JSONResponse.self, from: output.data(using: .utf8)!) #expect(data.success == true || data.error != nil) // Either succeeds or fails with proper error } @Test func windowCommandWithoutApp() async throws { // Test that window commands require --app let commands = ["close", "minimize", "maximize", "focus"] for command in commands { do { _ = try await self.runPeekabooCommand(["window", command]) Issue.record("Expected command to fail without --app") } catch { // Expected to fail #expect(error.localizedDescription.contains("--app must be specified") || error.localizedDescription.contains("Exit status: 1") ) } } } @Test func windowMoveRequiresCoordinates() async throws { do { _ = try await self.runPeekabooCommand(["window", "move", "--app", "Finder"]) Issue.record("Expected command to fail without coordinates") } catch { // Expected to fail - missing required x and y #expect(error.localizedDescription.contains("Missing expected argument") || error.localizedDescription.contains("Exit status: 64") ) } } @Test func windowResizeRequiresDimensions() async throws { do { _ = try await self.runPeekabooCommand(["window", "resize", "--app", "Finder"]) Issue.record("Expected command to fail without dimensions") } catch { // Expected to fail - missing required width and height #expect(error.localizedDescription.contains("Missing expected argument") || error.localizedDescription.contains("Exit status: 64") ) } } @Test func windowSetBoundsRequiresAllParameters() async throws { do { _ = try await self.runPeekabooCommand([ "window", "set-bounds", "--app", "Finder", "--x", "100", "--y", "100", ]) Issue.record("Expected command to fail without all parameters") } catch { // Expected to fail - missing required width and height #expect(error.localizedDescription.contains("Missing expected argument") || error.localizedDescription.contains("Exit status: 64") ) } } // Helper function to run peekaboo commands private func runPeekabooCommand( _ arguments: [String], allowedExitStatuses: Set<Int32> = [0, 64] ) async throws -> String { do { let result = try await InProcessCommandRunner.runShared( arguments, allowedExitCodes: allowedExitStatuses ) return result.combinedOutput } catch let error as CommandExecutionError { let output = error.stdout.isEmpty ? error.stderr : error.stdout throw TestError.commandFailed(status: error.status, output: output) } } enum TestError: Error, LocalizedError { case commandFailed(status: Int32, output: String) case binaryMissing var errorDescription: String? { switch self { case let .commandFailed(status, output): "Command failed with exit status: \(status). Output: \(output)" case .binaryMissing: "Peekaboo binary missing" } } } } // MARK: - Local Integration Tests @Suite("Window Command Local Integration Tests", .serialized, .tags(.automation), .enabled(if: CLITestEnvironment.runAutomationActions)) struct WindowCommandLocalIntegrationTests { @Test(.enabled(if: ProcessInfo.processInfo.environment["RUN_LOCAL_TESTS"] == "true")) func windowMinimizeTextEdit() async throws { // This test requires TextEdit to be running and local permissions // First, ensure TextEdit is running and has a window let launchResult = try await runPeekabooCommand(["image", "--app", "TextEdit", "--json-output"]) let launchData = try JSONDecoder().decode(JSONResponse.self, from: launchResult.data(using: .utf8)!) guard launchData.success else { Issue.record("TextEdit must be running for this test") return } // Try to minimize TextEdit window let result = try await runPeekabooCommand(["window", "minimize", "--app", "TextEdit", "--json-output"]) let data = try JSONDecoder().decode(JSONResponse.self, from: result.data(using: .utf8)!) if let error = data.error { if error.code == "PERMISSION_ERROR_ACCESSIBILITY" { Issue.record("Accessibility permission required for window manipulation") return } } #expect(data.success == true) // Wait a bit for the animation try await Task.sleep(nanoseconds: 1_000_000_000) // 1 second } @Test(.enabled(if: ProcessInfo.processInfo.environment["RUN_LOCAL_TESTS"] == "true")) func windowMoveTextEdit() async throws { // This test requires TextEdit to be running and local permissions // Try to move TextEdit window let result = try await runPeekabooCommand([ "window", "move", "--app", "TextEdit", "--x", "200", "--y", "200", "--json-output", ]) let data = try JSONDecoder().decode(JSONResponse.self, from: result.data(using: .utf8)!) if let error = data.error { if error.code == "PERMISSION_ERROR_ACCESSIBILITY" { Issue.record("Accessibility permission required for window manipulation") return } } #expect(data.success == true) if let responseData = data.data as? [String: Any], let newBounds = responseData["new_bounds"] as? [String: Any] { #expect(newBounds["x"] as? Int == 200) #expect(newBounds["y"] as? Int == 200) } } @Test(.enabled(if: ProcessInfo.processInfo.environment["RUN_LOCAL_TESTS"] == "true")) func windowFocusTextEdit() async throws { // This test requires TextEdit to be running // Try to focus TextEdit window let result = try await runPeekabooCommand([ "window", "focus", "--app", "TextEdit", "--json-output", ]) let data = try JSONDecoder().decode(JSONResponse.self, from: result.data(using: .utf8)!) if let error = data.error { if error.code == "PERMISSION_ERROR_ACCESSIBILITY" { Issue.record("Accessibility permission required for window manipulation") return } } #expect(data.success == true) } // Helper function for local tests private func runPeekabooCommand( _ arguments: [String], allowedExitStatuses: Set<Int32> = [0, 1, 64] ) async throws -> String { do { let result = try await InProcessCommandRunner.runShared( arguments, allowedExitCodes: allowedExitStatuses ) return result.combinedOutput } catch let error as CommandExecutionError { let output = error.stdout.isEmpty ? error.stderr : error.stdout throw TestError.commandFailed(status: error.status, output: output) } } enum TestError: Error, LocalizedError { case commandFailed(status: Int32, output: String) case binaryMissing var errorDescription: String? { switch self { case let .commandFailed(status, output): "Exit status: \(status)" case .binaryMissing: "Peekaboo binary missing" } } } } #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