Skip to main content
Glama
AgentMenuTests.swift7.59 kB
import Foundation import Testing @testable import PeekabooCLI #if !PEEKABOO_SKIP_AUTOMATION // MARK: - Test Helpers private func runCommand( _ args: [String], allowedExitStatuses: Set<Int32> = [0] ) async throws -> String { let result = try await InProcessCommandRunner.runShared(args, allowedExitCodes: allowedExitStatuses) return result.combinedOutput } @Suite( "Agent Menu Integration Tests", .serialized, .tags(.automation), .enabled(if: CLITestEnvironment.runAutomationActions) ) struct AgentMenuTests { @Test("Agent can discover menus using list subcommand") func agentMenuDiscovery() async throws { #if !os(Linux) guard ProcessInfo.processInfo.environment["OPENAI_API_KEY"] != nil else { return } guard ProcessInfo.processInfo.environment["RUN_LOCAL_TESTS"] != nil else { return } // Ensure Calculator is running _ = try await runCommand(["app", "--action", "launch", "--name", "Calculator"]) try await Task.sleep(for: .seconds(2)) // Test agent discovering menus let output = try await runCommand([ "agent", "List all menus available in the Calculator app", "--json-output", ]) let data = try #require(output.data(using: String.Encoding.utf8)) let json = try JSONDecoder().decode(AgentJSONResponse.self, from: data) #expect(json.success == true) // Check that agent used menu command if let steps = json.data?.steps { let menuStepFound = steps.contains { step in step.tool == "menu" || step.description.lowercased().contains("menu") } #expect(menuStepFound, "Agent should use menu command for menu discovery") } // Check summary mentions menus if let summary = json.data?.summary { #expect(summary.lowercased().contains("menu"), "Summary should mention menus") #expect(summary.contains("View") || summary.contains("Edit"), "Summary should list actual menu names") } #endif } @Test("Agent can navigate menus to perform actions") func agentMenuNavigation() async throws { #if !os(Linux) guard ProcessInfo.processInfo.environment["OPENAI_API_KEY"] != nil else { return } guard ProcessInfo.processInfo.environment["RUN_LOCAL_TESTS"] != nil else { return } // Ensure Calculator is running _ = try await runCommand(["app", "--action", "launch", "--name", "Calculator"]) try await Task.sleep(for: .seconds(2)) // Test agent using menu to switch Calculator mode let output = try await runCommand([ "agent", "Switch Calculator to Scientific mode using the View menu", "--json-output", ]) let data = try #require(output.data(using: String.Encoding.utf8)) let json = try JSONDecoder().decode(AgentJSONResponse.self, from: data) #expect(json.success == true) if let steps = json.data?.steps { // Should have menu discovery and menu click steps let menuSteps = steps.filter { $0.tool == "menu" } #expect(menuSteps.count >= 1, "Should use menu command at least once") // Check for menu click with correct path let hasMenuClick = steps.contains { step in if step.tool == "menu", let args = step.arguments, let jsonData = try? JSONSerialization.data(withJSONObject: args), let parsed = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] { return parsed["subcommand"] as? String == "click" || parsed["path"] as? String == "View > Scientific" || parsed["item"] as? String == "Scientific" } return false } #expect(hasMenuClick || !steps.isEmpty, "Should perform menu click or have steps") } #endif } @Test("Agent uses menu discovery before clicking") func agentMenuDiscoveryBeforeAction() async throws { #if !os(Linux) guard ProcessInfo.processInfo.environment["OPENAI_API_KEY"] != nil else { return } guard ProcessInfo.processInfo.environment["RUN_LOCAL_TESTS"] != nil else { return } // Test with TextEdit _ = try await runCommand(["app", "--action", "launch", "--name", "TextEdit"]) try await Task.sleep(for: .seconds(2)) let output = try await runCommand([ "agent", "Find and use the spell check feature in TextEdit", "--json-output", ]) let data = try #require(output.data(using: String.Encoding.utf8)) let json = try JSONDecoder().decode(AgentJSONResponse.self, from: data) #expect(json.success == true) if let steps = json.data?.steps { // Find menu discovery steps var foundDiscovery = false for step in steps where step.tool == "menu" { if let args = step.arguments, let jsonData = try? JSONSerialization.data(withJSONObject: args), let parsed = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] { if parsed["subcommand"] as? String == "list" { foundDiscovery = true } else if parsed["subcommand"] as? String == "click", foundDiscovery { // Found the action } } } #expect(foundDiscovery || !steps.isEmpty, "Should discover menus or have steps") } #endif } @Test("Agent handles menu errors gracefully") func agentMenuErrorHandling() async throws { #if !os(Linux) guard ProcessInfo.processInfo.environment["OPENAI_API_KEY"] != nil else { return } guard ProcessInfo.processInfo.environment["RUN_LOCAL_TESTS"] != nil else { return } // Test with non-existent menu item let output = try await runCommand([ "agent", "Click on the 'Quantum Computing' menu item in Calculator", "--json-output", ]) let data = try #require(output.data(using: String.Encoding.utf8)) let json = try JSONDecoder().decode(AgentJSONResponse.self, from: data) // Agent should handle this gracefully #expect(json.success == true || json.error != nil) if let summary = json.data?.summary { // Should mention the item wasn't found or similar let handledGracefully = summary.lowercased().contains("not found") || summary.lowercased().contains("doesn't exist") || summary.lowercased().contains("unable to find") || summary.lowercased().contains("couldn't find") #expect(handledGracefully || json.success == true, "Agent should handle missing menu items gracefully") } #endif } } // MARK: - Agent Response Types for Testing struct AgentJSONResponse: Decodable { let success: Bool let data: AgentResultData? let error: AgentErrorData? } struct AgentResultData: Decodable { let steps: [AgentStep] let summary: String? let success: Bool } struct AgentStep: Decodable { let tool: String let arguments: [String: String]? let description: String let output: String? } struct AgentErrorData: Decodable { let message: String let code: String } #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