Update to Swift 6.0 tools version and fix test indentation
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b0fef02e0f
commit
3bb4cfbe77
@ -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
|
||||
|
||||
@ -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.")
|
||||
}
|
||||
|
||||
}
|
||||
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.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user