Move to XCTest for stability

This commit is contained in:
Peter Steinberger 2025-05-30 01:42:07 +02:00
parent b46ae09ee9
commit d4d899da85
7 changed files with 195 additions and 208 deletions

View File

@ -1,13 +1,12 @@
import AppKit
@testable import AXorcist
import Foundation
import Testing
import XCTest
// MARK: - Action Command Tests
@Test("Perform Action: Set Value of TextEdit Text Area")
@MainActor
func performActionSetTextEditTextAreaValue() async throws {
class ActionIntegrationTests: XCTestCase {
func testPerformActionSetTextEditTextAreaValue() async throws {
let actionCommandId = "performaction-setvalue-\(UUID().uuidString)"
let queryCommandId = "query-verify-setvalue-\(UUID().uuidString)"
let textEditBundleId = "com.apple.TextEdit"
@ -37,9 +36,7 @@ func performActionSetTextEditTextAreaValue() async throws {
)
}
@Test("Extract Text from TextEdit Text Area")
@MainActor
func extractTextFromTextEditTextArea() async throws {
func testExtractTextFromTextEditTextArea() async throws {
let setValueCommandId = "setvalue-for-extract-\(UUID().uuidString)"
let extractTextCommandId = "extracttext-textedit-textarea-\(UUID().uuidString)"
let textEditBundleId = "com.apple.TextEdit"
@ -89,9 +86,9 @@ private func performSetValueAction(
let response = try await executeCommand(performActionEnvelope)
#expect(response.commandId == actionCommandId)
#expect(
response.success == true,
XCTAssertEqual(response.commandId , actionCommandId)
XCTAssertEqual(
response.success , true,
"performAction command was not successful. Error: \(response.error?.message ?? "N/A")"
)
@ -115,9 +112,9 @@ private func verifyTextValue(
let response = try await executeCommand(queryEnvelope)
#expect(response.commandId == queryCommandId)
#expect(
response.success == true,
XCTAssertEqual(response.commandId , queryCommandId)
XCTAssertEqual(
response.success , true,
"Query (verify) command failed. Error: \(response.error?.message ?? "N/A")"
)
@ -126,12 +123,12 @@ private func verifyTextValue(
}
let retrievedValue = attributes["AXValue"]?.value as? String
#expect(
retrievedValue == expectedText,
XCTAssertEqual(
retrievedValue , expectedText,
"AXValue did not match. Expected: '\(expectedText)'. Got: '\(retrievedValue ?? "nil")'"
)
#expect(response.debugLogs != nil)
XCTAssertNotEqual(response.debugLogs , nil)
}
private func extractAndVerifyText(
@ -150,25 +147,25 @@ private func extractAndVerifyText(
let response = try await executeCommand(extractTextEnvelope)
#expect(response.commandId == extractTextCommandId)
#expect(
response.success == true,
XCTAssertEqual(response.commandId , extractTextCommandId)
XCTAssertEqual(
response.success , true,
"extractText command failed. Error: \(response.error?.message ?? "N/A")"
)
#expect(response.command == CommandType.extractText.rawValue)
XCTAssertEqual(response.command , CommandType.extractText.rawValue)
guard let attributes = response.data?.attributes else {
throw TestError.generic("Attributes nil in extractText response.")
}
let extractedValue = attributes["AXValue"]?.value as? String
#expect(
extractedValue == expectedText,
XCTAssertEqual(
extractedValue , expectedText,
"Extracted text did not match. Expected: '\(expectedText)'. Got: '\(extractedValue ?? "nil")'"
)
#expect(response.debugLogs != nil)
#expect(
XCTAssertNotEqual(response.debugLogs , nil)
XCTAssertTrue(
response.debugLogs?
.contains { log in
log.contains("Handling extractText command") ||
@ -190,9 +187,9 @@ private func executeCommand(_ command: CommandEnvelope) async throws -> QueryRes
let result = try runAXORCCommand(arguments: [jsonString])
let (output, errorOutput, exitCode) = (result.output, result.errorOutput, result.exitCode)
#expect(exitCode == 0, Comment(rawValue: "Command failed. Error: \(errorOutput ?? "N/A")"))
#expect(
errorOutput == nil || errorOutput!.isEmpty,
XCTAssertEqual(exitCode , 0, Comment(rawValue: "Command failed. Error: \(errorOutput ?? "N/A")"))
XCTAssertEqual(
errorOutput , nil || errorOutput!.isEmpty,
Comment(rawValue: "STDERR should be empty. Got: \(errorOutput ?? "")")
)
@ -212,3 +209,5 @@ private func executeCommand(_ command: CommandEnvelope) async throws -> QueryRes
throw TestError.generic("Failed to decode response: \(error.localizedDescription). JSON: \(outputString)")
}
}
}

View File

@ -1,11 +1,11 @@
import AppKit
@testable import AXorcist
import Testing
import XCTest
// MARK: - Application Query Tests
@Test("Get All Running Applications")
func getAllApplications() async throws {
class ApplicationQueryTests: XCTestCase {
func testGetAllApplications() async throws {
let command = CommandEnvelope(
commandId: "test-get-all-apps",
command: .collectAll,
@ -23,8 +23,8 @@ func getAllApplications() async throws {
let result = try runAXORCCommand(arguments: [jsonString])
#expect(result.exitCode == 0, "Command should succeed")
#expect(result.output != nil, "Should have output")
XCTAssertEqual(result.exitCode , 0, "Command should succeed")
XCTAssertNotEqual(result.output , nil, "Should have output")
guard let output = result.output,
let responseData = output.data(using: String.Encoding.utf8)
@ -34,28 +34,26 @@ func getAllApplications() async throws {
let response = try JSONDecoder().decode(SimpleSuccessResponse.self, from: responseData)
#expect(response.success == true)
XCTAssertEqual(response.success , true)
// TODO: Fix response type - SimpleSuccessResponse doesn't have data property
// The following code expects response.data which doesn't exist
/*
#expect(response.data?["elements"] != nil, "Should have elements")
XCTAssertNotEqual(response.data?["elements"] , nil, "Should have elements")
if let elements = response.data?["elements"] as? [[String: Any]] {
#expect(!elements.isEmpty, "Should have at least one application")
XCTAssertTrue(!elements.isEmpty, "Should have at least one application")
// Check for Finder
let appTitles = elements.compactMap { element -> String? in
guard let attrs = element["attributes"] as? [String: Any] else { return nil }
return attrs["AXTitle"] as? String
}
#expect(appTitles.contains("Finder"), "Finder should be running")
XCTAssertTrue(appTitles.contains("Finder"), "Finder should be running")
}
*/
}
@Test("Get Windows of TextEdit")
@MainActor
func getWindowsOfApplication() async throws {
func testGetWindowsOfApplication() async throws {
await closeTextEdit()
try await Task.sleep(for: .milliseconds(500))
@ -86,7 +84,7 @@ func getWindowsOfApplication() async throws {
let result = try runAXORCCommand(arguments: [jsonString])
#expect(result.exitCode == 0)
XCTAssertEqual(result.exitCode , 0)
guard let output = result.output,
let responseData = output.data(using: String.Encoding.utf8)
@ -96,24 +94,23 @@ func getWindowsOfApplication() async throws {
let response = try JSONDecoder().decode(SimpleSuccessResponse.self, from: responseData)
#expect(response.success == true)
XCTAssertEqual(response.success , true)
// TODO: Fix response type - SimpleSuccessResponse doesn't have data property
/*
if let elements = response.data?["elements"] as? [[String: Any]] {
#expect(!elements.isEmpty, "Should have at least one window")
XCTAssertTrue(!elements.isEmpty, "Should have at least one window")
for window in elements {
if let attrs = window["attributes"] as? [String: Any] {
#expect(attrs["AXRole"] as? String == "AXWindow")
#expect(attrs["AXTitle"] != nil, "Window should have title")
XCTAssertEqual(attrs["AXRole"] as? String , "AXWindow")
XCTAssertNotEqual(attrs["AXTitle"] , nil, "Window should have title")
}
}
}
*/
}
@Test("Query Non-Existent Application")
func queryNonExistentApp() async throws {
func testQueryNonExistentApp() async throws {
let command = CommandEnvelope(
commandId: "test-nonexistent",
command: .query,
@ -131,7 +128,7 @@ func queryNonExistentApp() async throws {
let result = try runAXORCCommand(arguments: [jsonString])
// Command should succeed but return no elements
#expect(result.exitCode == 0)
XCTAssertEqual(result.exitCode , 0)
guard let output = result.output,
let responseData = output.data(using: String.Encoding.utf8)
@ -145,7 +142,9 @@ func queryNonExistentApp() async throws {
// For non-existent app, we expect success but should check message or details
// to verify no elements were found. Since SimpleSuccessResponse doesn't
// have element data, we verify through the success status and message.
#expect(response.message.contains("No") || response.message.contains("not found") || response.message.isEmpty,
XCTAssertTrue(response.message.contains("No") || response.message.contains("not found") || response.message.isEmpty,
"Message should indicate no elements found or be empty")
}
}
}

View File

@ -1,12 +1,11 @@
import AppKit
@testable import AXorcist
import Testing
import XCTest
// MARK: - Batch Command Tests
@Test("Batch Command: GetFocusedElement and Query TextEdit")
@MainActor
func batchCommandGetFocusedElementAndQuery() async throws {
class BatchIntegrationTests: XCTestCase {
func testBatchCommandGetFocusedElementAndQuery() async throws {
let batchCommandId = "batch-textedit-\(UUID().uuidString)"
let focusedElementSubCmdId = "batch-sub-getfocused-\(UUID().uuidString)"
let querySubCmdId = "batch-sub-querytextarea-\(UUID().uuidString)"
@ -85,12 +84,12 @@ private func executeBatchCommand(_ command: CommandEnvelope) async throws -> Bat
let result = try runAXORCCommand(arguments: [jsonString])
let (output, errorOutput, exitCode) = (result.output, result.errorOutput, result.exitCode)
#expect(
exitCode == 0,
XCTAssertEqual(
exitCode , 0,
Comment(rawValue: "axorc process for batch command should exit with 0. Error: \(errorOutput ?? "N/A")")
)
#expect(
errorOutput == nil || errorOutput!.isEmpty,
XCTAssertEqual(
errorOutput , nil || errorOutput!.isEmpty,
Comment(rawValue: "STDERR should be empty. Got: \(errorOutput ?? "")")
)
@ -113,25 +112,27 @@ private func verifyBatchResponse(
querySubCmdId: String,
textAreaRole: String
) {
#expect(batchResponse.commandId == batchCommandId)
#expect(batchResponse.success == true, "Batch command should succeed")
#expect(batchResponse.results.count == 2, "Expected 2 results")
XCTAssertEqual(batchResponse.commandId , batchCommandId)
XCTAssertEqual(batchResponse.success , true, "Batch command should succeed")
XCTAssertEqual(batchResponse.results.count , 2, "Expected 2 results")
// Verify first sub-command
let result1 = batchResponse.results[0]
#expect(result1.commandId == focusedElementSubCmdId)
#expect(result1.success == true, "GetFocusedElement should succeed")
#expect(result1.command == CommandType.getFocusedElement.rawValue)
#expect(result1.data != nil)
#expect(result1.data?.attributes?["AXRole"]?.value as? String == textAreaRole)
XCTAssertEqual(result1.commandId , focusedElementSubCmdId)
XCTAssertEqual(result1.success , true, "GetFocusedElement should succeed")
XCTAssertEqual(result1.command , CommandType.getFocusedElement.rawValue)
XCTAssertNotEqual(result1.data , nil)
XCTAssertEqual(result1.data?.attributes?["AXRole"]?.value as? String , textAreaRole)
// Verify second sub-command
let result2 = batchResponse.results[1]
#expect(result2.commandId == querySubCmdId)
#expect(result2.success == true, "Query should succeed")
#expect(result2.command == CommandType.query.rawValue)
#expect(result2.data != nil)
#expect(result2.data?.attributes?["AXRole"]?.value as? String == textAreaRole)
XCTAssertEqual(result2.commandId , querySubCmdId)
XCTAssertEqual(result2.success , true, "Query should succeed")
XCTAssertEqual(result2.command , CommandType.query.rawValue)
XCTAssertNotEqual(result2.data , nil)
XCTAssertEqual(result2.data?.attributes?["AXRole"]?.value as? String , textAreaRole)
#expect(batchResponse.debugLogs != nil)
XCTAssertNotEqual(batchResponse.debugLogs , nil)
}
}

View File

@ -1,6 +1,5 @@
import AppKit
@testable import AXorcist
import Testing
import XCTest
// Result struct for AXORC commands

View File

@ -1,12 +1,11 @@
import AppKit
@testable import AXorcist
import Testing
import XCTest
// MARK: - Element Search and Navigation Tests
@Test("Search Elements by Role")
@MainActor
func searchElementsByRole() async throws {
class ElementSearchTests: XCTestCase {
func testSearchElementsByRole() async throws {
await closeTextEdit()
try await Task.sleep(for: .milliseconds(500))
@ -37,7 +36,7 @@ func searchElementsByRole() async throws {
let result = try runAXORCCommand(arguments: [jsonString])
#expect(result.exitCode == 0)
XCTAssertEqual(result.exitCode , 0)
guard let output = result.output,
let responseData = output.data(using: String.Encoding.utf8)
@ -47,19 +46,17 @@ func searchElementsByRole() async throws {
let response = try JSONDecoder().decode(QueryResponse.self, from: responseData)
#expect(response.success == true)
XCTAssertEqual(response.success , true)
if let data = response.data, let attributes = data.attributes {
// For a query response, we should find button elements
if let role = attributes["AXRole"]?.value as? String {
#expect(role == "AXButton", "Should find button elements")
XCTAssertEqual(role , "AXButton", "Should find button elements")
}
}
}
@Test("Describe Element with Hierarchy")
@MainActor
func describeElementHierarchy() async throws {
func testDescribeElementHierarchy() async throws {
await closeTextEdit()
try await Task.sleep(for: .milliseconds(500))
@ -91,7 +88,7 @@ func describeElementHierarchy() async throws {
let result = try runAXORCCommand(arguments: [jsonString])
#expect(result.exitCode == 0)
XCTAssertEqual(result.exitCode , 0)
guard let output = result.output,
let responseData = output.data(using: String.Encoding.utf8)
@ -101,20 +98,18 @@ func describeElementHierarchy() async throws {
let response = try JSONDecoder().decode(QueryResponse.self, from: responseData)
#expect(response.success == true)
#expect(response.data != nil)
XCTAssertEqual(response.success , true)
XCTAssertNotEqual(response.data , nil)
// Check hierarchy
if let data = response.data, let attributes = data.attributes {
if let role = attributes["AXRole"]?.value as? String {
#expect(role == "AXApplication", "Should find application element")
XCTAssertEqual(role , "AXApplication", "Should find application element")
}
}
}
@Test("Set and Verify Text Content")
@MainActor
func setAndVerifyText() async throws {
func testSetAndVerifyText() async throws {
await closeTextEdit()
try await Task.sleep(for: .milliseconds(500))
@ -145,7 +140,7 @@ func setAndVerifyText() async throws {
}
var result = try runAXORCCommand(arguments: [setJsonString])
#expect(result.exitCode == 0)
XCTAssertEqual(result.exitCode , 0)
// Query to verify
let queryText = CommandEnvelope(
@ -163,7 +158,7 @@ func setAndVerifyText() async throws {
}
result = try runAXORCCommand(arguments: [queryJsonString])
#expect(result.exitCode == 0)
XCTAssertEqual(result.exitCode , 0)
guard let output = result.output,
let responseData = output.data(using: String.Encoding.utf8)
@ -173,17 +168,15 @@ func setAndVerifyText() async throws {
let response = try JSONDecoder().decode(QueryResponse.self, from: responseData)
#expect(response.success == true)
XCTAssertEqual(response.success , true)
if let data = response.data, let attributes = data.attributes {
if let value = attributes["AXValue"]?.value as? String {
#expect(value.contains("Hello from AXorcist tests!"), "Should find the text we set")
XCTAssertTrue(value.contains("Hello from AXorcist tests!"), "Should find the text we set")
}
}
}
@Test("Extract Text from Window")
@MainActor
func testExtractText() async throws {
await closeTextEdit()
try await Task.sleep(for: .milliseconds(500))
@ -232,7 +225,7 @@ func testExtractText() async throws {
}
let result = try runAXORCCommand(arguments: [extractJsonString])
#expect(result.exitCode == 0)
XCTAssertEqual(result.exitCode , 0)
guard let output = result.output,
let responseData = output.data(using: String.Encoding.utf8)
@ -242,16 +235,18 @@ func testExtractText() async throws {
let response = try JSONDecoder().decode(QueryResponse.self, from: responseData)
#expect(response.success == true)
XCTAssertEqual(response.success , true)
if let data = response.data, let attributes = data.attributes {
// For extract text commands, check for extracted text in attributes
if let extractedText = attributes["extractedText"]?.value as? String {
#expect(extractedText.contains("This is test content"), "Should extract the test content")
#expect(extractedText.contains("multiple lines"), "Should extract multiple lines")
XCTAssertTrue(extractedText.contains("This is test content"), "Should extract the test content")
XCTAssertTrue(extractedText.contains("multiple lines"), "Should extract multiple lines")
} else if let value = attributes["AXValue"]?.value as? String {
#expect(value.contains("This is test content"), "Should extract the test content")
#expect(value.contains("multiple lines"), "Should extract multiple lines")
XCTAssertTrue(value.contains("This is test content"), "Should extract the test content")
XCTAssertTrue(value.contains("multiple lines"), "Should extract multiple lines")
}
}
}
}

View File

@ -1,11 +1,11 @@
@testable import AXorcist
import Foundation
import Testing
import XCTest
// MARK: - Ping Command Tests
@Test("Test Ping via STDIN")
func pingViaStdin() async throws {
class PingIntegrationTests: XCTestCase {
func testPingViaStdin() async throws {
let inputJSON = """
{
"command_id": "test_ping_stdin",
@ -20,38 +20,37 @@ func pingViaStdin() async throws {
arguments: ["--stdin"]
)
#expect(
result.exitCode == 0,
XCTAssertEqual(
result.exitCode , 0,
Comment(rawValue: "axorc command failed with status \(result.exitCode). Error: \(result.errorOutput ?? "N/A")")
)
#expect(
result.errorOutput == nil || result.errorOutput!.isEmpty,
XCTAssertEqual(
result.errorOutput , nil || result.errorOutput!.isEmpty,
Comment(rawValue: "Expected no error output, but got: \(result.errorOutput!)")
)
guard let outputString = result.output else {
#expect(Bool(false), Comment(rawValue: "Output was nil for ping via STDIN"))
XCTAssertTrue(Bool(false), Comment(rawValue: "Output was nil for ping via STDIN"))
return
}
guard let responseData = outputString.data(using: String.Encoding.utf8) else {
#expect(
XCTAssertTrue(
Bool(false),
Comment(rawValue: "Failed to convert output to Data for ping via STDIN. Output: \(outputString)")
)
return
}
let decodedResponse = try JSONDecoder().decode(SimpleSuccessResponse.self, from: responseData)
#expect(decodedResponse.success == true)
#expect(
decodedResponse.message == "Ping handled by AXORCCommand. Input source: STDIN",
XCTAssertEqual(decodedResponse.success , true)
XCTAssertEqual(
decodedResponse.message , "Ping handled by AXORCCommand. Input source: STDIN",
Comment(rawValue: "Unexpected success message: \(decodedResponse.message)")
)
#expect(decodedResponse.details == "Hello from testPingViaStdin")
XCTAssertEqual(decodedResponse.details , "Hello from testPingViaStdin")
}
@Test("Test Ping via --file")
func pingViaFile() async throws {
func testPingViaFile() async throws {
let payloadMessage = "Hello from testPingViaFile"
let inputJSON = """
{
@ -65,74 +64,72 @@ func pingViaFile() async throws {
let result = try runAXORCCommand(arguments: ["--file", tempFilePath])
#expect(
result.exitCode == 0,
XCTAssertEqual(
result.exitCode , 0,
Comment(rawValue: "axorc command failed with status \(result.exitCode). Error: \(result.errorOutput ?? "N/A")")
)
#expect(
result.errorOutput == nil || result.errorOutput!.isEmpty,
XCTAssertEqual(
result.errorOutput , nil || result.errorOutput!.isEmpty,
Comment(rawValue: "Expected no error output, but got: \(result.errorOutput ?? "N/A")")
)
guard let outputString = result.output else {
#expect(Bool(false), Comment(rawValue: "Output was nil for ping via file"))
XCTAssertTrue(Bool(false), Comment(rawValue: "Output was nil for ping via file"))
return
}
guard let responseData = outputString.data(using: String.Encoding.utf8) else {
#expect(
XCTAssertTrue(
Bool(false),
Comment(rawValue: "Failed to convert output to Data for ping via file. Output: \(outputString)")
)
return
}
let decodedResponse = try JSONDecoder().decode(SimpleSuccessResponse.self, from: responseData)
#expect(decodedResponse.success == true)
#expect(
XCTAssertEqual(decodedResponse.success , true)
XCTAssertTrue(
decodedResponse.message.lowercased().contains("file: \(tempFilePath.lowercased())"),
Comment(rawValue: "Message should contain file path. Got: \(decodedResponse.message)")
)
#expect(decodedResponse.details == payloadMessage)
XCTAssertEqual(decodedResponse.details , payloadMessage)
}
@Test("Test Ping via direct positional argument")
func pingViaDirectPayload() async throws {
func testPingViaDirectPayload() async throws {
let payloadMessage = "Hello from testPingViaDirectPayload"
let inputJSON =
"{\"command_id\":\"test_ping_direct\",\"command\":\"ping\",\"payload\":{\"message\":\"\(payloadMessage)\"}}"
let result = try runAXORCCommand(arguments: [inputJSON])
#expect(
result.exitCode == 0,
XCTAssertEqual(
result.exitCode , 0,
Comment(rawValue: "axorc command failed with status \(result.exitCode). Error: \(result.errorOutput ?? "N/A")")
)
#expect(
result.errorOutput == nil || result.errorOutput!.isEmpty,
XCTAssertEqual(
result.errorOutput , nil || result.errorOutput!.isEmpty,
Comment(rawValue: "Expected no error output, but got: \(result.errorOutput ?? "N/A")")
)
guard let outputString = result.output else {
#expect(Bool(false), Comment(rawValue: "Output was nil for ping via direct payload"))
XCTAssertTrue(Bool(false), Comment(rawValue: "Output was nil for ping via direct payload"))
return
}
guard let responseData = outputString.data(using: String.Encoding.utf8) else {
#expect(
XCTAssertTrue(
Bool(false),
Comment(rawValue: "Failed to convert output to Data for ping via direct payload. Output: \(outputString)")
)
return
}
let decodedResponse = try JSONDecoder().decode(SimpleSuccessResponse.self, from: responseData)
#expect(decodedResponse.success == true)
#expect(
XCTAssertEqual(decodedResponse.success , true)
XCTAssertTrue(
decodedResponse.message.contains("Direct Argument Payload"),
Comment(rawValue: "Unexpected success message: \(decodedResponse.message)")
)
#expect(decodedResponse.details == payloadMessage)
XCTAssertEqual(decodedResponse.details , payloadMessage)
}
@Test("Test Error: Multiple Input Methods (stdin and file)")
func errorMultipleInputMethods() async throws {
func testErrorMultipleInputMethods() async throws {
let inputJSON = """
{
"command_id": "test_error_multiple_inputs",
@ -148,18 +145,18 @@ func errorMultipleInputMethods() async throws {
arguments: ["--file", tempFilePath]
)
#expect(
result.exitCode == 0,
XCTAssertEqual(
result.exitCode , 0,
Comment(rawValue: "axorc command should return 0 with error on stdout. Status: \(result.exitCode). " +
"Error STDOUT: \(result.output ?? "nil"). Error STDERR: \(result.errorOutput ?? "nil")")
)
guard let outputString = result.output, !outputString.isEmpty else {
#expect(Bool(false), Comment(rawValue: "Output was nil or empty for multiple input methods error test"))
XCTAssertTrue(Bool(false), Comment(rawValue: "Output was nil or empty for multiple input methods error test"))
return
}
guard let responseData = outputString.data(using: String.Encoding.utf8) else {
#expect(
XCTAssertTrue(
Bool(false),
Comment(
rawValue: "Failed to convert output to Data for multiple input methods error. Output: \(outputString)"
@ -168,42 +165,43 @@ func errorMultipleInputMethods() async throws {
return
}
let errorResponse = try JSONDecoder().decode(ErrorResponse.self, from: responseData)
#expect(errorResponse.success == false)
#expect(
XCTAssertEqual(errorResponse.success , false)
XCTAssertTrue(
errorResponse.error.message.contains("Multiple input flags specified"),
Comment(rawValue: "Unexpected error message: \(errorResponse.error.message)")
)
}
@Test("Test Error: No Input Provided for Ping")
func errorNoInputProvidedForPing() async throws {
func testErrorNoInputProvidedForPing() async throws {
let result = try runAXORCCommand(arguments: [])
#expect(
result.exitCode == 0,
XCTAssertEqual(
result.exitCode , 0,
Comment(rawValue: "axorc should return 0 with error on stdout. Status: \(result.exitCode). " +
"Error STDOUT: \(result.output ?? "nil"). Error STDERR: \(result.errorOutput ?? "nil")")
)
guard let outputString = result.output, !outputString.isEmpty else {
#expect(Bool(false), Comment(rawValue: "Output was nil or empty for no input test."))
XCTAssertTrue(Bool(false), Comment(rawValue: "Output was nil or empty for no input test."))
return
}
guard let responseData = outputString.data(using: String.Encoding.utf8) else {
#expect(
XCTAssertTrue(
Bool(false),
Comment(rawValue: "Failed to convert output to Data for no input error. Output: \(outputString)")
)
return
}
let errorResponse = try JSONDecoder().decode(ErrorResponse.self, from: responseData)
#expect(errorResponse.success == false)
#expect(
errorResponse.commandId == "input_error",
XCTAssertEqual(errorResponse.success , false)
XCTAssertEqual(
errorResponse.commandId , "input_error",
Comment(rawValue: "Expected commandId to be input_error, got \(errorResponse.commandId)")
)
#expect(
XCTAssertTrue(
errorResponse.error.message.contains("No JSON input method specified"),
Comment(rawValue: "Unexpected error message for no input: \(errorResponse.error.message)")
)
}
}

View File

@ -1,16 +1,16 @@
import AppKit
@testable import AXorcist
import Testing
import XCTest
// MARK: - Query Command Tests
@Test("Launch TextEdit, Get Focused Element via STDIN")
func launchAndQueryTextEdit() async throws {
class QueryIntegrationTests: XCTestCase {
func testLaunchAndQueryTextEdit() async throws {
await closeTextEdit()
try await Task.sleep(for: .milliseconds(500))
let (pid, _) = try await setupTextEditAndGetInfo()
#expect(pid != 0, "PID should not be zero after TextEdit setup")
XCTAssertNotEqual(pid , 0, "PID should not be zero after TextEdit setup")
let commandId = "focused_textedit_test_\(UUID().uuidString)"
let attributesToFetch: [String] = [
@ -61,13 +61,13 @@ func launchAndQueryTextEdit() async throws {
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) } ?? []
#expect(
actualRole == expectedRole,
XCTAssertEqual(
actualRole , expectedRole,
Comment(rawValue: "Focused element role should be '\(expectedRole)'. Got: '\(actualRole ?? "nil")'. " +
"Attributes: \(attributeKeys)")
)
#expect(
XCTAssertTrue(
elementData.attributes?.keys.contains(ApplicationServices.kAXValueAttribute as String) == true,
"Focused element attributes should contain kAXValueAttribute as it was requested."
)
@ -80,9 +80,7 @@ func launchAndQueryTextEdit() async throws {
await closeTextEdit()
}
@Test("Get Attributes for TextEdit Application")
@MainActor
func getAttributesForTextEditApplication() async throws {
func testGetAttributesForTextEditApplication() async throws {
let commandId = "getattributes-textedit-app-\(UUID().uuidString)"
let textEditBundleId = "com.apple.TextEdit"
let requestedAttributes = ["AXRole", "AXTitle", "AXWindows", "AXFocusedWindow", "AXMainWindow", "AXIdentifier"]
@ -122,38 +120,38 @@ func getAttributesForTextEditApplication() async throws {
let queryResponse = try decodeQueryResponse(from: outputString, commandName: "getAttributes")
validateQueryResponseBasics(queryResponse, expectedCommandId: commandId, expectedCommand: .getAttributes)
#expect(queryResponse.data?.attributes != nil, "AXElement attributes should not be nil.")
XCTAssertNotEqual(queryResponse.data?.attributes , nil, "AXElement attributes should not be nil.")
let attributes = queryResponse.data?.attributes
#expect(
attributes?["AXRole"]?.value as? String == "AXApplication",
XCTAssertEqual(
attributes?["AXRole"]?.value as? String , "AXApplication",
Comment(
rawValue: "Application role should be AXApplication. Got: \(String(describing: attributes?["AXRole"]?.value))"
)
)
#expect(
attributes?["AXTitle"]?.value as? String == "TextEdit",
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"] {
#expect(
XCTAssertTrue(
windowsAttr.value is [Any],
Comment(rawValue: "AXWindows should be an array. Type: \(type(of: windowsAttr.value))")
)
if let windowsArray = windowsAttr.value as? [AnyCodable] {
#expect(!windowsArray.isEmpty, "AXWindows array should not be empty if TextEdit has windows.")
XCTAssertTrue(!windowsArray.isEmpty, "AXWindows array should not be empty if TextEdit has windows.")
} else if let windowsArray = windowsAttr.value as? [Any] {
#expect(!windowsArray.isEmpty, "AXWindows array should not be empty (general type check).")
XCTAssertTrue(!windowsArray.isEmpty, "AXWindows array should not be empty (general type check).")
}
} else {
#expect(attributes?["AXWindows"] != nil, "AXWindows attribute should be present.")
XCTAssertNotEqual(attributes?["AXWindows"] , nil, "AXWindows attribute should be present.")
}
#expect(queryResponse.debugLogs != nil, "Debug logs should be present.")
#expect(
XCTAssertNotEqual(queryResponse.debugLogs , nil, "Debug logs should be present.")
XCTAssertTrue(
queryResponse.debugLogs?
.contains {
$0.contains("Handling getAttributes command") || $0.contains("handleGetAttributes completed")
@ -163,9 +161,7 @@ func getAttributesForTextEditApplication() async throws {
)
}
@Test("Query for TextEdit Text Area")
@MainActor
func queryForTextEditTextArea() async throws {
func testQueryForTextEditTextArea() async throws {
let commandId = "query-textedit-textarea-\(UUID().uuidString)"
let textEditBundleId = "com.apple.TextEdit"
let textAreaRole = ApplicationServices.kAXTextAreaRole as String
@ -208,30 +204,28 @@ func queryForTextEditTextArea() async throws {
let queryResponse = try decodeQueryResponse(from: outputString, commandName: "query")
validateQueryResponseBasics(queryResponse, expectedCommandId: commandId, expectedCommand: .query)
#expect(queryResponse.data?.attributes != nil, "AXElement attributes should not be nil.")
XCTAssertNotEqual(queryResponse.data?.attributes , nil, "AXElement attributes should not be nil.")
let attributes = queryResponse.data?.attributes
#expect(
attributes?["AXRole"]?.value as? String == textAreaRole,
XCTAssertEqual(
attributes?["AXRole"]?.value as? String , textAreaRole,
Comment(
rawValue: "Element role should be \(textAreaRole). Got: \(String(describing: attributes?["AXRole"]?.value))"
)
)
#expect(attributes?["AXValue"]?.value is String, "AXValue should exist and be a string.")
#expect(attributes?["AXNumberOfCharacters"]?.value is Int, "AXNumberOfCharacters should exist and be an Int.")
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.")
#expect(queryResponse.debugLogs != nil, "Debug logs should be present.")
#expect(
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."
)
}
@Test("Describe TextEdit Text Area")
@MainActor
func describeTextEditTextArea() async throws {
func testDescribeTextEditTextArea() async throws {
let commandId = "describe-textedit-textarea-\(UUID().uuidString)"
let textEditBundleId = "com.apple.TextEdit"
let textAreaRole = ApplicationServices.kAXTextAreaRole as String
@ -277,24 +271,24 @@ func describeTextEditTextArea() async throws {
throw TestError.generic("Attributes dictionary is nil in describeElement response.")
}
#expect(
attributes["AXRole"]?.value as? String == textAreaRole,
XCTAssertEqual(
attributes["AXRole"]?.value as? String , textAreaRole,
Comment(
rawValue: "Element role should be \(textAreaRole). Got: \(String(describing: attributes["AXRole"]?.value))"
)
)
#expect(attributes["AXRoleDescription"]?.value is String, "AXRoleDescription should exist.")
#expect(attributes["AXEnabled"]?.value is Bool, "AXEnabled should exist.")
#expect(attributes["AXPosition"]?.value != nil, "AXPosition should exist.")
#expect(attributes["AXSize"]?.value != nil, "AXSize should exist.")
#expect(
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)")
Comment(rawValue: "Expected describeElement to return many attributes (e.g., , 10). Got \(attributes.count)")
)
#expect(queryResponse.debugLogs != nil, "Debug logs should be present.")
#expect(
XCTAssertNotEqual(queryResponse.debugLogs , nil, "Debug logs should be present.")
XCTAssertTrue(
queryResponse.debugLogs?
.contains {
$0.contains("Handling describeElement command") || $0.contains("handleDescribeElement completed")
@ -357,12 +351,12 @@ private func validateCommandExecution(
exitCode: Int32,
commandName: String
) throws -> String {
#expect(
exitCode == 0,
XCTAssertEqual(
exitCode , 0,
Comment(rawValue: "axorc process should exit with 0 for \(commandName). Error: \(errorOutput ?? "N/A")")
)
#expect(
errorOutput == nil || errorOutput!.isEmpty,
XCTAssertEqual(
errorOutput , nil || errorOutput!.isEmpty,
Comment(rawValue: "STDERR should be empty on success. Got: \(errorOutput ?? "")")
)
@ -379,15 +373,17 @@ private func validateQueryResponseBasics(
expectedCommandId: String,
expectedCommand: CommandType
) {
#expect(queryResponse.commandId == expectedCommandId)
#expect(
queryResponse.success == true,
XCTAssertEqual(queryResponse.commandId , expectedCommandId)
XCTAssertEqual(
queryResponse.success , true,
Comment(rawValue: "Command should succeed. Error: \(queryResponse.error?.message ?? "None")")
)
#expect(queryResponse.command == expectedCommand.rawValue)
#expect(
queryResponse.error == nil,
XCTAssertEqual(queryResponse.command , expectedCommand.rawValue)
XCTAssertEqual(
queryResponse.error , nil,
Comment(rawValue: "Error field should be nil. Got: \(queryResponse.error?.message ?? "N/A")")
)
#expect(queryResponse.data != nil, "Data field should not be nil.")
XCTAssertNotEqual(queryResponse.data , nil, "Data field should not be nil.")
}
}