From 3bb4cfbe77b0862bd3dd33e049378c890d57df71 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 2 Jun 2025 21:50:35 +0100 Subject: [PATCH] Update to Swift 6.0 tools version and fix test indentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- Package.swift | 2 +- .../AXorcistTests/QueryIntegrationTests.swift | 718 +++++++++--------- 2 files changed, 360 insertions(+), 360 deletions(-) diff --git a/Package.swift b/Package.swift index ed41ca6..683051f 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.9 +// swift-tools-version:6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/Tests/AXorcistTests/QueryIntegrationTests.swift b/Tests/AXorcistTests/QueryIntegrationTests.swift index 92c943b..66b2a06 100644 --- a/Tests/AXorcistTests/QueryIntegrationTests.swift +++ b/Tests/AXorcistTests/QueryIntegrationTests.swift @@ -5,385 +5,385 @@ import XCTest // MARK: - Query Command Tests class QueryIntegrationTests: XCTestCase { -func testLaunchAndQueryTextEdit() async throws { - await closeTextEdit() - try await Task.sleep(for: .milliseconds(500)) + func testLaunchAndQueryTextEdit() async throws { + await closeTextEdit() + try await Task.sleep(for: .milliseconds(500)) - let (pid, _) = try await setupTextEditAndGetInfo() - XCTAssertNotEqual(pid , 0, "PID should not be zero after TextEdit setup") + let (pid, _) = try await setupTextEditAndGetInfo() + XCTAssertNotEqual(pid , 0, "PID should not be zero after TextEdit setup") - let commandId = "focused_textedit_test_\(UUID().uuidString)" - let attributesToFetch: [String] = [ - ApplicationServices.kAXRoleAttribute as String, - ApplicationServices.kAXRoleDescriptionAttribute as String, - ApplicationServices.kAXValueAttribute as String, - "AXPlaceholderValue", - ] + let commandId = "focused_textedit_test_\(UUID().uuidString)" + let attributesToFetch: [String] = [ + ApplicationServices.kAXRoleAttribute as String, + ApplicationServices.kAXRoleDescriptionAttribute as String, + ApplicationServices.kAXValueAttribute as String, + "AXPlaceholderValue", + ] - let commandEnvelope = createCommandEnvelope( - commandId: commandId, - command: .getFocusedElement, - application: "com.apple.TextEdit", - attributes: attributesToFetch - ) - - let inputJSON = try encodeCommandToJSON(commandEnvelope) - - print("Input JSON for axorc:\n\(inputJSON)") - - let result = try runAXORCCommandWithStdin( - inputJSON: inputJSON, - arguments: ["--debug"] - ) - - print("axorc STDOUT:\n\(result.output ?? "nil")") - print("axorc STDERR:\n\(result.errorOutput ?? "nil")") - print("axorc Termination Status: \(result.exitCode)") - - let outputJSONString = try validateCommandExecution( - output: result.output, - errorOutput: result.errorOutput, - exitCode: result.exitCode, - commandName: "getFocusedElement" - ) - - let queryResponse = try decodeQueryResponse(from: outputJSONString, commandName: "getFocusedElement") - validateQueryResponseBasics(queryResponse, expectedCommandId: commandId, expectedCommand: .getFocusedElement) - - guard let elementData = queryResponse.data else { - throw TestError - .generic( - "QueryResponse data is nil. Error: \(queryResponse.error?.message ?? "N/A"). " + - "Logs: \(queryResponse.debugLogs?.joined(separator: "\n") ?? "")" - ) - } - - let expectedRole = ApplicationServices.kAXTextAreaRole as String - let actualRole = elementData.attributes?[ApplicationServices.kAXRoleAttribute as String]?.value as? String - let attributeKeys = elementData.attributes?.keys.map { Array($0) } ?? [] - XCTAssertEqual( - actualRole , expectedRole, - Comment(rawValue: "Focused element role should be '\(expectedRole)'. Got: '\(actualRole ?? "nil")'. " + - "Attributes: \(attributeKeys)") - ) - - XCTAssertTrue( - elementData.attributes?.keys.contains(ApplicationServices.kAXValueAttribute as String) == true, - "Focused element attributes should contain kAXValueAttribute as it was requested." - ) - - if let logs = queryResponse.debugLogs, !logs.isEmpty { - print("axorc Debug Logs:") - logs.forEach { print($0) } - } - - await closeTextEdit() -} - -func testGetAttributesForTextEditApplication() async throws { - let commandId = "getattributes-textedit-app-\(UUID().uuidString)" - let textEditBundleId = "com.apple.TextEdit" - let requestedAttributes = ["AXRole", "AXTitle", "AXWindows", "AXFocusedWindow", "AXMainWindow", "AXIdentifier"] - - do { - _ = try await setupTextEditAndGetInfo() - print("TextEdit setup completed for getAttributes test.") - } catch { - throw TestError.generic("TextEdit setup failed for getAttributes: \(error.localizedDescription)") - } - defer { - Task { await closeTextEdit() } - print("TextEdit close process initiated for getAttributes test.") - } - - let appLocator = Locator(criteria: []) - - let commandEnvelope = createCommandEnvelope( - commandId: commandId, - command: .getAttributes, - application: textEditBundleId, - attributes: requestedAttributes, - locator: appLocator - ) - - let jsonString = try encodeCommandToJSON(commandEnvelope) - - print("Sending getAttributes command to axorc: \(jsonString)") - let result = try runAXORCCommand(arguments: [jsonString]) - - let outputString = try validateCommandExecution( - output: result.output, - errorOutput: result.errorOutput, - exitCode: result.exitCode, - commandName: "getAttributes" - ) - - let queryResponse = try decodeQueryResponse(from: outputString, commandName: "getAttributes") - validateQueryResponseBasics(queryResponse, expectedCommandId: commandId, expectedCommand: .getAttributes) - XCTAssertNotEqual(queryResponse.data?.attributes , nil, "AXElement attributes should not be nil.") - - let attributes = queryResponse.data?.attributes - XCTAssertEqual( - attributes?["AXRole"]?.value as? String , "AXApplication", - Comment( - rawValue: "Application role should be AXApplication. Got: \(String(describing: attributes?["AXRole"]?.value))" + let commandEnvelope = createCommandEnvelope( + commandId: commandId, + command: .getFocusedElement, + application: "com.apple.TextEdit", + attributes: attributesToFetch ) - ) - XCTAssertEqual( - attributes?["AXTitle"]?.value as? String , "TextEdit", - Comment( - rawValue: "Application title should be TextEdit. Got: \(String(describing: attributes?["AXTitle"]?.value))" - ) - ) - if let windowsAttr = attributes?["AXWindows"] { - XCTAssertTrue( - windowsAttr.value is [Any], - Comment(rawValue: "AXWindows should be an array. Type: \(type(of: windowsAttr.value))") + let inputJSON = try encodeCommandToJSON(commandEnvelope) + + print("Input JSON for axorc:\n\(inputJSON)") + + let result = try runAXORCCommandWithStdin( + inputJSON: inputJSON, + arguments: ["--debug"] ) - if let windowsArray = windowsAttr.value as? [AnyCodable] { - XCTAssertTrue(!windowsArray.isEmpty, "AXWindows array should not be empty if TextEdit has windows.") - } else if let windowsArray = windowsAttr.value as? [Any] { - XCTAssertTrue(!windowsArray.isEmpty, "AXWindows array should not be empty (general type check).") + + print("axorc STDOUT:\n\(result.output ?? "nil")") + print("axorc STDERR:\n\(result.errorOutput ?? "nil")") + print("axorc Termination Status: \(result.exitCode)") + + let outputJSONString = try validateCommandExecution( + output: result.output, + errorOutput: result.errorOutput, + exitCode: result.exitCode, + commandName: "getFocusedElement" + ) + + let queryResponse = try decodeQueryResponse(from: outputJSONString, commandName: "getFocusedElement") + validateQueryResponseBasics(queryResponse, expectedCommandId: commandId, expectedCommand: .getFocusedElement) + + guard let elementData = queryResponse.data else { + throw TestError + .generic( + "QueryResponse data is nil. Error: \(queryResponse.error?.message ?? "N/A"). " + + "Logs: \(queryResponse.debugLogs?.joined(separator: "\n") ?? "")" + ) } - } else { - XCTAssertNotEqual(attributes?["AXWindows"] , nil, "AXWindows attribute should be present.") - } - XCTAssertNotEqual(queryResponse.debugLogs , nil, "Debug logs should be present.") - XCTAssertTrue( - queryResponse.debugLogs? - .contains { - $0.contains("Handling getAttributes command") || $0.contains("handleGetAttributes completed") - } == - true, - "Debug logs should indicate getAttributes execution." - ) -} - -func testQueryForTextEditTextArea() async throws { - let commandId = "query-textedit-textarea-\(UUID().uuidString)" - let textEditBundleId = "com.apple.TextEdit" - let textAreaRole = ApplicationServices.kAXTextAreaRole as String - let requestedAttributes = ["AXRole", "AXValue", "AXSelectedText", "AXNumberOfCharacters"] - - do { - _ = try await setupTextEditAndGetInfo() - print("TextEdit setup completed for query test.") - } catch { - throw TestError.generic("TextEdit setup failed for query: \(error.localizedDescription)") - } - defer { - Task { await closeTextEdit() } - print("TextEdit close process initiated for query test.") - } - - let textAreaLocator = Locator( - criteria: [Criterion(attribute: "AXRole", value: textAreaRole)] - ) - - let commandEnvelope = createCommandEnvelope( - commandId: commandId, - command: .query, - application: textEditBundleId, - attributes: requestedAttributes, - locator: textAreaLocator - ) - - let jsonString = try encodeCommandToJSON(commandEnvelope) - - print("Sending query command to axorc: \(jsonString)") - let result = try runAXORCCommand(arguments: [jsonString]) - - let outputString = try validateCommandExecution( - output: result.output, - errorOutput: result.errorOutput, - exitCode: result.exitCode, - commandName: "query" - ) - - let queryResponse = try decodeQueryResponse(from: outputString, commandName: "query") - validateQueryResponseBasics(queryResponse, expectedCommandId: commandId, expectedCommand: .query) - XCTAssertNotEqual(queryResponse.data?.attributes , nil, "AXElement attributes should not be nil.") - - let attributes = queryResponse.data?.attributes - XCTAssertEqual( - attributes?["AXRole"]?.value as? String , textAreaRole, - Comment( - rawValue: "Element role should be \(textAreaRole). Got: \(String(describing: attributes?["AXRole"]?.value))" + let expectedRole = ApplicationServices.kAXTextAreaRole as String + let actualRole = elementData.attributes?[ApplicationServices.kAXRoleAttribute as String]?.value as? String + let attributeKeys = elementData.attributes?.keys.map { Array($0) } ?? [] + XCTAssertEqual( + actualRole , expectedRole, + Comment(rawValue: "Focused element role should be '\(expectedRole)'. Got: '\(actualRole ?? "nil")'. " + + "Attributes: \(attributeKeys)") ) - ) - XCTAssertTrue(attributes?["AXValue"]?.value is String, "AXValue should exist and be a string.") - XCTAssertTrue(attributes?["AXNumberOfCharacters"]?.value is Int, "AXNumberOfCharacters should exist and be an Int.") - - XCTAssertNotEqual(queryResponse.debugLogs , nil, "Debug logs should be present.") - XCTAssertTrue( - queryResponse.debugLogs? - .contains { $0.contains("Handling query command") || $0.contains("handleQuery completed") } == true, - "Debug logs should indicate query execution." - ) -} - -func testDescribeTextEditTextArea() async throws { - let commandId = "describe-textedit-textarea-\(UUID().uuidString)" - let textEditBundleId = "com.apple.TextEdit" - let textAreaRole = ApplicationServices.kAXTextAreaRole as String - - do { - _ = try await setupTextEditAndGetInfo() - print("TextEdit setup completed for describeElement test.") - } catch { - throw TestError.generic("TextEdit setup failed for describeElement: \(error.localizedDescription)") - } - defer { - Task { await closeTextEdit() } - print("TextEdit close process initiated for describeElement test.") - } - - let textAreaLocator = Locator( - criteria: [Criterion(attribute: "AXRole", value: textAreaRole)] - ) - - let commandEnvelope = createCommandEnvelope( - commandId: commandId, - command: .describeElement, - application: textEditBundleId, - locator: textAreaLocator - ) - - let jsonString = try encodeCommandToJSON(commandEnvelope) - - print("Sending describeElement command to axorc: \(jsonString)") - let result = try runAXORCCommand(arguments: [jsonString]) - - let outputString = try validateCommandExecution( - output: result.output, - errorOutput: result.errorOutput, - exitCode: result.exitCode, - commandName: "describeElement" - ) - - let queryResponse = try decodeQueryResponse(from: outputString, commandName: "describeElement") - validateQueryResponseBasics(queryResponse, expectedCommandId: commandId, expectedCommand: .describeElement) - - guard let attributes = queryResponse.data?.attributes else { - throw TestError.generic("Attributes dictionary is nil in describeElement response.") - } - - XCTAssertEqual( - attributes["AXRole"]?.value as? String , textAreaRole, - Comment( - rawValue: "Element role should be \(textAreaRole). Got: \(String(describing: attributes["AXRole"]?.value))" + XCTAssertTrue( + elementData.attributes?.keys.contains(ApplicationServices.kAXValueAttribute as String) == true, + "Focused element attributes should contain kAXValueAttribute as it was requested." ) - ) - XCTAssertTrue(attributes["AXRoleDescription"]?.value is String, "AXRoleDescription should exist.") - XCTAssertTrue(attributes["AXEnabled"]?.value is Bool, "AXEnabled should exist.") - XCTAssertNotEqual(attributes["AXPosition"]?.value , nil, "AXPosition should exist.") - XCTAssertNotEqual(attributes["AXSize"]?.value , nil, "AXSize should exist.") - XCTAssertGreaterThan( - attributes.count > 10, - Comment(rawValue: "Expected describeElement to return many attributes (e.g., , 10). Got \(attributes.count)") - ) + if let logs = queryResponse.debugLogs, !logs.isEmpty { + print("axorc Debug Logs:") + logs.forEach { print($0) } + } - XCTAssertNotEqual(queryResponse.debugLogs , nil, "Debug logs should be present.") - XCTAssertTrue( - queryResponse.debugLogs? - .contains { - $0.contains("Handling describeElement command") || $0.contains("handleDescribeElement completed") - } == - true, - "Debug logs should indicate describeElement execution." - ) -} - -// MARK: - Helper Functions - -private func createCommandEnvelope( - commandId: String, - command: CommandType, - application: String, - attributes: [String]? = nil, - locator: Locator? = nil, - debugLogging: Bool = true -) -> CommandEnvelope { - CommandEnvelope( - commandId: commandId, - command: command, - application: application, - attributes: attributes, - debugLogging: debugLogging, - locator: locator, - payload: nil - ) -} - -private func encodeCommandToJSON(_ commandEnvelope: CommandEnvelope) throws -> String { - let encoder = JSONEncoder() - encoder.outputFormatting = .withoutEscapingSlashes - let jsonData = try encoder.encode(commandEnvelope) - guard let jsonString = String(data: jsonData, encoding: String.Encoding.utf8) else { - throw TestError.generic("Failed to create JSON string for command.") - } - return jsonString -} - -private func decodeQueryResponse(from outputString: String, commandName: String) throws -> QueryResponse { - guard let responseData = outputString.data(using: String.Encoding.utf8) else { - throw TestError.generic("Could not convert output string to data for \(commandName). Output: \(outputString)") + await closeTextEdit() } - let decoder = JSONDecoder() - do { - return try decoder.decode(QueryResponse.self, from: responseData) - } catch { - throw TestError.generic( - "Failed to decode QueryResponse for \(commandName): \(error.localizedDescription). " + - "Original JSON: \(outputString)" + func testGetAttributesForTextEditApplication() async throws { + let commandId = "getattributes-textedit-app-\(UUID().uuidString)" + let textEditBundleId = "com.apple.TextEdit" + let requestedAttributes = ["AXRole", "AXTitle", "AXWindows", "AXFocusedWindow", "AXMainWindow", "AXIdentifier"] + + do { + _ = try await setupTextEditAndGetInfo() + print("TextEdit setup completed for getAttributes test.") + } catch { + throw TestError.generic("TextEdit setup failed for getAttributes: \(error.localizedDescription)") + } + defer { + Task { await closeTextEdit() } + print("TextEdit close process initiated for getAttributes test.") + } + + let appLocator = Locator(criteria: []) + + let commandEnvelope = createCommandEnvelope( + commandId: commandId, + command: .getAttributes, + application: textEditBundleId, + attributes: requestedAttributes, + locator: appLocator + ) + + let jsonString = try encodeCommandToJSON(commandEnvelope) + + print("Sending getAttributes command to axorc: \(jsonString)") + let result = try runAXORCCommand(arguments: [jsonString]) + + let outputString = try validateCommandExecution( + output: result.output, + errorOutput: result.errorOutput, + exitCode: result.exitCode, + commandName: "getAttributes" + ) + + let queryResponse = try decodeQueryResponse(from: outputString, commandName: "getAttributes") + validateQueryResponseBasics(queryResponse, expectedCommandId: commandId, expectedCommand: .getAttributes) + XCTAssertNotEqual(queryResponse.data?.attributes , nil, "AXElement attributes should not be nil.") + + let attributes = queryResponse.data?.attributes + XCTAssertEqual( + attributes?["AXRole"]?.value as? String , "AXApplication", + Comment( + rawValue: "Application role should be AXApplication. Got: \(String(describing: attributes?["AXRole"]?.value))" + ) + ) + XCTAssertEqual( + attributes?["AXTitle"]?.value as? String , "TextEdit", + Comment( + rawValue: "Application title should be TextEdit. Got: \(String(describing: attributes?["AXTitle"]?.value))" + ) + ) + + if let windowsAttr = attributes?["AXWindows"] { + XCTAssertTrue( + windowsAttr.value is [Any], + Comment(rawValue: "AXWindows should be an array. Type: \(type(of: windowsAttr.value))") + ) + if let windowsArray = windowsAttr.value as? [AnyCodable] { + XCTAssertTrue(!windowsArray.isEmpty, "AXWindows array should not be empty if TextEdit has windows.") + } else if let windowsArray = windowsAttr.value as? [Any] { + XCTAssertTrue(!windowsArray.isEmpty, "AXWindows array should not be empty (general type check).") + } + } else { + XCTAssertNotEqual(attributes?["AXWindows"] , nil, "AXWindows attribute should be present.") + } + + XCTAssertNotEqual(queryResponse.debugLogs , nil, "Debug logs should be present.") + XCTAssertTrue( + queryResponse.debugLogs? + .contains { + $0.contains("Handling getAttributes command") || $0.contains("handleGetAttributes completed") + } == + true, + "Debug logs should indicate getAttributes execution." ) } -} -private func validateCommandExecution( - output: String?, - errorOutput: String?, - exitCode: Int32, - commandName: String -) throws -> String { - XCTAssertEqual( - exitCode , 0, - Comment(rawValue: "axorc process should exit with 0 for \(commandName). Error: \(errorOutput ?? "N/A")") - ) - XCTAssertEqual( - errorOutput , nil || errorOutput!.isEmpty, - Comment(rawValue: "STDERR should be empty on success. Got: \(errorOutput ?? "")") - ) + func testQueryForTextEditTextArea() async throws { + let commandId = "query-textedit-textarea-\(UUID().uuidString)" + let textEditBundleId = "com.apple.TextEdit" + let textAreaRole = ApplicationServices.kAXTextAreaRole as String + let requestedAttributes = ["AXRole", "AXValue", "AXSelectedText", "AXNumberOfCharacters"] - guard let outputString = output, !outputString.isEmpty else { - throw TestError.generic("Output string was nil or empty for \(commandName).") + do { + _ = try await setupTextEditAndGetInfo() + print("TextEdit setup completed for query test.") + } catch { + throw TestError.generic("TextEdit setup failed for query: \(error.localizedDescription)") + } + defer { + Task { await closeTextEdit() } + print("TextEdit close process initiated for query test.") + } + + let textAreaLocator = Locator( + criteria: [Criterion(attribute: "AXRole", value: textAreaRole)] + ) + + let commandEnvelope = createCommandEnvelope( + commandId: commandId, + command: .query, + application: textEditBundleId, + attributes: requestedAttributes, + locator: textAreaLocator + ) + + let jsonString = try encodeCommandToJSON(commandEnvelope) + + print("Sending query command to axorc: \(jsonString)") + let result = try runAXORCCommand(arguments: [jsonString]) + + let outputString = try validateCommandExecution( + output: result.output, + errorOutput: result.errorOutput, + exitCode: result.exitCode, + commandName: "query" + ) + + let queryResponse = try decodeQueryResponse(from: outputString, commandName: "query") + validateQueryResponseBasics(queryResponse, expectedCommandId: commandId, expectedCommand: .query) + XCTAssertNotEqual(queryResponse.data?.attributes , nil, "AXElement attributes should not be nil.") + + let attributes = queryResponse.data?.attributes + XCTAssertEqual( + attributes?["AXRole"]?.value as? String , textAreaRole, + Comment( + rawValue: "Element role should be \(textAreaRole). Got: \(String(describing: attributes?["AXRole"]?.value))" + ) + ) + + XCTAssertTrue(attributes?["AXValue"]?.value is String, "AXValue should exist and be a string.") + XCTAssertTrue(attributes?["AXNumberOfCharacters"]?.value is Int, "AXNumberOfCharacters should exist and be an Int.") + + XCTAssertNotEqual(queryResponse.debugLogs , nil, "Debug logs should be present.") + XCTAssertTrue( + queryResponse.debugLogs? + .contains { $0.contains("Handling query command") || $0.contains("handleQuery completed") } == true, + "Debug logs should indicate query execution." + ) } - print("Received output from axorc (\(commandName)): \(outputString)") - return outputString -} + func testDescribeTextEditTextArea() async throws { + let commandId = "describe-textedit-textarea-\(UUID().uuidString)" + let textEditBundleId = "com.apple.TextEdit" + let textAreaRole = ApplicationServices.kAXTextAreaRole as String -private func validateQueryResponseBasics( - _ queryResponse: QueryResponse, - expectedCommandId: String, - expectedCommand: CommandType -) { - XCTAssertEqual(queryResponse.commandId , expectedCommandId) - XCTAssertEqual( - queryResponse.success , true, - Comment(rawValue: "Command should succeed. Error: \(queryResponse.error?.message ?? "None")") - ) - XCTAssertEqual(queryResponse.command , expectedCommand.rawValue) - XCTAssertEqual( - queryResponse.error , nil, - Comment(rawValue: "Error field should be nil. Got: \(queryResponse.error?.message ?? "N/A")") - ) - XCTAssertNotEqual(queryResponse.data , nil, "Data field should not be nil.") -} + do { + _ = try await setupTextEditAndGetInfo() + print("TextEdit setup completed for describeElement test.") + } catch { + throw TestError.generic("TextEdit setup failed for describeElement: \(error.localizedDescription)") + } + defer { + Task { await closeTextEdit() } + print("TextEdit close process initiated for describeElement test.") + } -} \ No newline at end of file + let textAreaLocator = Locator( + criteria: [Criterion(attribute: "AXRole", value: textAreaRole)] + ) + + let commandEnvelope = createCommandEnvelope( + commandId: commandId, + command: .describeElement, + application: textEditBundleId, + locator: textAreaLocator + ) + + let jsonString = try encodeCommandToJSON(commandEnvelope) + + print("Sending describeElement command to axorc: \(jsonString)") + let result = try runAXORCCommand(arguments: [jsonString]) + + let outputString = try validateCommandExecution( + output: result.output, + errorOutput: result.errorOutput, + exitCode: result.exitCode, + commandName: "describeElement" + ) + + let queryResponse = try decodeQueryResponse(from: outputString, commandName: "describeElement") + validateQueryResponseBasics(queryResponse, expectedCommandId: commandId, expectedCommand: .describeElement) + + guard let attributes = queryResponse.data?.attributes else { + throw TestError.generic("Attributes dictionary is nil in describeElement response.") + } + + XCTAssertEqual( + attributes["AXRole"]?.value as? String , textAreaRole, + Comment( + rawValue: "Element role should be \(textAreaRole). Got: \(String(describing: attributes["AXRole"]?.value))" + ) + ) + + XCTAssertTrue(attributes["AXRoleDescription"]?.value is String, "AXRoleDescription should exist.") + XCTAssertTrue(attributes["AXEnabled"]?.value is Bool, "AXEnabled should exist.") + XCTAssertNotEqual(attributes["AXPosition"]?.value , nil, "AXPosition should exist.") + XCTAssertNotEqual(attributes["AXSize"]?.value , nil, "AXSize should exist.") + XCTAssertGreaterThan( + attributes.count > 10, + Comment(rawValue: "Expected describeElement to return many attributes (e.g., , 10). Got \(attributes.count)") + ) + + XCTAssertNotEqual(queryResponse.debugLogs , nil, "Debug logs should be present.") + XCTAssertTrue( + queryResponse.debugLogs? + .contains { + $0.contains("Handling describeElement command") || $0.contains("handleDescribeElement completed") + } == + true, + "Debug logs should indicate describeElement execution." + ) + } + + // MARK: - Helper Functions + + private func createCommandEnvelope( + commandId: String, + command: CommandType, + application: String, + attributes: [String]? = nil, + locator: Locator? = nil, + debugLogging: Bool = true + ) -> CommandEnvelope { + CommandEnvelope( + commandId: commandId, + command: command, + application: application, + attributes: attributes, + debugLogging: debugLogging, + locator: locator, + payload: nil + ) + } + + private func encodeCommandToJSON(_ commandEnvelope: CommandEnvelope) throws -> String { + let encoder = JSONEncoder() + encoder.outputFormatting = .withoutEscapingSlashes + let jsonData = try encoder.encode(commandEnvelope) + guard let jsonString = String(data: jsonData, encoding: String.Encoding.utf8) else { + throw TestError.generic("Failed to create JSON string for command.") + } + return jsonString + } + + private func decodeQueryResponse(from outputString: String, commandName: String) throws -> QueryResponse { + guard let responseData = outputString.data(using: String.Encoding.utf8) else { + throw TestError.generic("Could not convert output string to data for \(commandName). Output: \(outputString)") + } + + let decoder = JSONDecoder() + do { + return try decoder.decode(QueryResponse.self, from: responseData) + } catch { + throw TestError.generic( + "Failed to decode QueryResponse for \(commandName): \(error.localizedDescription). " + + "Original JSON: \(outputString)" + ) + } + } + + private func validateCommandExecution( + output: String?, + errorOutput: String?, + exitCode: Int32, + commandName: String + ) throws -> String { + XCTAssertEqual( + exitCode , 0, + Comment(rawValue: "axorc process should exit with 0 for \(commandName). Error: \(errorOutput ?? "N/A")") + ) + XCTAssertEqual( + errorOutput , nil || errorOutput!.isEmpty, + Comment(rawValue: "STDERR should be empty on success. Got: \(errorOutput ?? "")") + ) + + guard let outputString = output, !outputString.isEmpty else { + throw TestError.generic("Output string was nil or empty for \(commandName).") + } + + print("Received output from axorc (\(commandName)): \(outputString)") + return outputString + } + + private func validateQueryResponseBasics( + _ queryResponse: QueryResponse, + expectedCommandId: String, + expectedCommand: CommandType + ) { + XCTAssertEqual(queryResponse.commandId , expectedCommandId) + XCTAssertEqual( + queryResponse.success , true, + Comment(rawValue: "Command should succeed. Error: \(queryResponse.error?.message ?? "None")") + ) + XCTAssertEqual(queryResponse.command , expectedCommand.rawValue) + XCTAssertEqual( + queryResponse.error , nil, + Comment(rawValue: "Error field should be nil. Got: \(queryResponse.error?.message ?? "N/A")") + ) + XCTAssertNotEqual(queryResponse.data , nil, "Data field should not be nil.") + } + +}