From 77c7198847c6378b263cc330da1cbb62002d55ae Mon Sep 17 00:00:00 2001 From: Christopher - RtF <58520035+christopher-rtf@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:12:08 -0400 Subject: [PATCH 1/2] For macOS 14 Sonoma, change Settings app 'Back' button search method --- .../SystemSettingsMainWindow_macOS13.swift | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/MorphicUIAutomation/MorphicUIAutomation/SystemSettings/macOS13AndNewer/SystemSettingsMainWindow_macOS13.swift b/MorphicUIAutomation/MorphicUIAutomation/SystemSettings/macOS13AndNewer/SystemSettingsMainWindow_macOS13.swift index 6942752..193152f 100644 --- a/MorphicUIAutomation/MorphicUIAutomation/SystemSettings/macOS13AndNewer/SystemSettingsMainWindow_macOS13.swift +++ b/MorphicUIAutomation/MorphicUIAutomation/SystemSettings/macOS13AndNewer/SystemSettingsMainWindow_macOS13.swift @@ -263,7 +263,23 @@ internal class SystemSettingsMainWindow_macOS13 { // STEP 2: find the Back button (which is a child of the toolbar) let backButtonA11yUIElement: MorphicA11yUIElement? do { - backButtonA11yUIElement = try toolbarUIElement.accessibilityUiElement.descendant(identifier: "go back", maxDepth: 1) + if #available(macOS 14.0, *) { + // macOS 14.0 and newer + backButtonA11yUIElement = try toolbarUIElement.accessibilityUiElement.dangerousFirstDescendant(where: { + guard $0.role == .button else { + return false + } + + guard let buttonLabel: String = try? $0.value(forAttribute: .description) else { + return false + } + + return buttonLabel == "Back" + }) + } else { + // macOS 13.x + backButtonA11yUIElement = try toolbarUIElement.accessibilityUiElement.descendant(identifier: "go back", maxDepth: 1) + } } catch let error { throw error } From 180207393079203c57cc20ff69b9119d007003be Mon Sep 17 00:00:00 2001 From: Christopher - RtF <58520035+christopher-rtf@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:22:13 -0400 Subject: [PATCH 2/2] Add MorphicA11yUIElementError class; enhance a11y error reporting detail --- .../project.pbxproj | 4 ++ .../MorphicA11yUIElement.swift | 66 ++++++++++++++----- .../MorphicA11yUIElementError.swift | 29 ++++++++ 3 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 MorphicMacOSNative/MorphicMacOSNative/AccessibilityUI/MorphicA11yUIElementError.swift diff --git a/MorphicMacOSNative/MorphicMacOSNative.xcodeproj/project.pbxproj b/MorphicMacOSNative/MorphicMacOSNative.xcodeproj/project.pbxproj index ae91bd6..a9503d5 100644 --- a/MorphicMacOSNative/MorphicMacOSNative.xcodeproj/project.pbxproj +++ b/MorphicMacOSNative/MorphicMacOSNative.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 9D6916182911B718002F3A21 /* MorphicProcess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6916172911B718002F3A21 /* MorphicProcess.swift */; }; 9D69161B2911B752002F3A21 /* MorphicCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D69161A2911B752002F3A21 /* MorphicCore.framework */; }; 9D69161C2911B752002F3A21 /* MorphicCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9D69161A2911B752002F3A21 /* MorphicCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + CE2313D42ABE1D3800075B18 /* MorphicA11yUIElementError.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2313D32ABE1D3800075B18 /* MorphicA11yUIElementError.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -38,6 +39,7 @@ 9D6916152911B6DB002F3A21 /* MorphicA11yUIElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MorphicA11yUIElement.swift; sourceTree = ""; }; 9D6916172911B718002F3A21 /* MorphicProcess.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MorphicProcess.swift; sourceTree = ""; }; 9D69161A2911B752002F3A21 /* MorphicCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MorphicCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + CE2313D32ABE1D3800075B18 /* MorphicA11yUIElementError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MorphicA11yUIElementError.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -85,6 +87,7 @@ 9D6916112911B6D2002F3A21 /* MorphicA11yAuthorization.swift */, 9D6916132911B6D6002F3A21 /* MorphicA11yUIAttributeValueCompatible.swift */, 9D6916152911B6DB002F3A21 /* MorphicA11yUIElement.swift */, + CE2313D32ABE1D3800075B18 /* MorphicA11yUIElementError.swift */, ); path = AccessibilityUI; sourceTree = ""; @@ -185,6 +188,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CE2313D42ABE1D3800075B18 /* MorphicA11yUIElementError.swift in Sources */, 9D6916182911B718002F3A21 /* MorphicProcess.swift in Sources */, 9D6916162911B6DB002F3A21 /* MorphicA11yUIElement.swift in Sources */, 9D6916122911B6D2002F3A21 /* MorphicA11yAuthorization.swift in Sources */, diff --git a/MorphicMacOSNative/MorphicMacOSNative/AccessibilityUI/MorphicA11yUIElement.swift b/MorphicMacOSNative/MorphicMacOSNative/AccessibilityUI/MorphicA11yUIElement.swift index 57b9eb2..2dc4dcf 100644 --- a/MorphicMacOSNative/MorphicMacOSNative/AccessibilityUI/MorphicA11yUIElement.swift +++ b/MorphicMacOSNative/MorphicMacOSNative/AccessibilityUI/MorphicA11yUIElement.swift @@ -85,6 +85,9 @@ public struct MorphicA11yUIElement { return result } catch let error as InitError { throw error + } catch let error { + assertionFailure("undocumented error path") + throw error } } @@ -94,8 +97,14 @@ public struct MorphicA11yUIElement { guard self.supportedAttributes.contains(.children) == true else { return [] } - guard let children: [MorphicA11yUIElement] = try? self.values(forAttribute: .children) else { - throw MorphicError.unspecified + let children: [MorphicA11yUIElement] + do { + children = try self.values(forAttribute: .children) + } catch let error as MorphicA11yUIElementError { + throw error + } catch let error { + assertionFailure("undocumented error path") + throw error } // return children @@ -129,12 +138,18 @@ public struct MorphicA11yUIElement { case .noValue: return nil default: - throw MorphicError.unspecified + throw MorphicA11yUIElementError.axError(error) } } // - guard let result = try? MorphicA11yAttributeValueCompatibleSampleImpl.fromCFTypeRef(valueAsCFTypeRef!) else { - throw MorphicError.unspecified + let result: FoundationA11yUIAttributeValueCompatible + do { + result = try MorphicA11yAttributeValueCompatibleSampleImpl.fromCFTypeRef(valueAsCFTypeRef!) + } catch let error as MorphicA11yUIAttributeValueCompatibleError { + throw MorphicA11yUIElementError.uiAttributeValueCompatibleError(error) + } catch let error { + assertionFailure("undocumented error path") + throw error } return (result as! T) } @@ -142,28 +157,47 @@ public struct MorphicA11yUIElement { // public func values(forAttribute attribute: NSAccessibility.Attribute) throws -> [T] where T: MorphicA11yUIAttributeValueCompatible { - guard let result: [T] = try? MorphicA11yUIElement.values(forAttribute: attribute, forAXUIElement: self.axUiElement) else { - throw MorphicError.unspecified + let result: [T] + do { + result = try MorphicA11yUIElement.values(forAttribute: attribute, forAXUIElement: self.axUiElement) + } catch let error as MorphicA11yUIElementError { + throw error + } catch let error { + assertionFailure("undocumented error path") + throw error } + return result } public static func values(forAttribute attribute: NSAccessibility.Attribute, forAXUIElement axUiElement: AXUIElement) throws -> [T] where T: MorphicA11yUIAttributeValueCompatible { var valuesAsCFArray: CFArray? = nil - guard let numberOfValues = try? MorphicA11yUIElement.valueCount(forAttribute: attribute, forAXUIElement: axUiElement) else { - throw MorphicError.unspecified + let numberOfValues: Int + do { + numberOfValues = try MorphicA11yUIElement.valueCount(forAttribute: attribute, forAXUIElement: axUiElement) + } catch MorphicA11yUIElementError.axError(let error) { + throw MorphicA11yUIElementError.axError(error) + } catch let error { + assertionFailure("undocumented error path") + throw error } let error = AXUIElementCopyAttributeValues(axUiElement, attribute.rawValue as CFString, 0, numberOfValues, &valuesAsCFArray) guard error == .success && valuesAsCFArray != nil else { - throw MorphicError.unspecified + throw MorphicA11yUIElementError.axError(error) } // var values: [T] = [] for valueAsCFTypeRef in valuesAsCFArray! as [CFTypeRef] { - guard let value = try? MorphicA11yAttributeValueCompatibleSampleImpl.fromCFTypeRef(valueAsCFTypeRef) else { - throw MorphicError.unspecified + let value: FoundationA11yUIAttributeValueCompatible + do { + value = try MorphicA11yAttributeValueCompatibleSampleImpl.fromCFTypeRef(valueAsCFTypeRef) + } catch let error as MorphicA11yUIAttributeValueCompatibleError { + throw MorphicA11yUIElementError.uiAttributeValueCompatibleError(error) + } catch let error { + assertionFailure("undocumented error path") + throw error } values.append(value as! T) } @@ -177,7 +211,7 @@ public struct MorphicA11yUIElement { var count: CFIndex = 0 let error = AXUIElementGetAttributeValueCount(axUiElement, attribute.rawValue as CFString, &count) guard error == .success else { - throw MorphicError.unspecified + throw MorphicA11yUIElementError.axError(error) } return count as Int } @@ -192,7 +226,7 @@ public struct MorphicA11yUIElement { let valueAsCFTypeRef = value.toCFTypeRef() let error = AXUIElementSetAttributeValue(axUiElement, attribute.rawValue as CFString, valueAsCFTypeRef) guard error == .success else { - throw MorphicError.unspecified + throw MorphicA11yUIElementError.axError(error) } } @@ -202,7 +236,7 @@ public struct MorphicA11yUIElement { var supportedActionNamesAsCFArray: CFArray? let error = AXUIElementCopyActionNames(axUiElement, &supportedActionNamesAsCFArray) guard error == .success && supportedActionNamesAsCFArray != nil else { - throw MorphicError.unspecified + throw MorphicA11yUIElementError.axError(error) } // var supportedActions: [NSAccessibility.Action] = [] @@ -219,7 +253,7 @@ public struct MorphicA11yUIElement { public func perform(action: NSAccessibility.Action) throws { let error = AXUIElementPerformAction(self.axUiElement, action.rawValue as CFString) guard error == .success else { - throw MorphicError.unspecified + throw MorphicA11yUIElementError.axError(error) } } } diff --git a/MorphicMacOSNative/MorphicMacOSNative/AccessibilityUI/MorphicA11yUIElementError.swift b/MorphicMacOSNative/MorphicMacOSNative/AccessibilityUI/MorphicA11yUIElementError.swift new file mode 100644 index 0000000..f7408bd --- /dev/null +++ b/MorphicMacOSNative/MorphicMacOSNative/AccessibilityUI/MorphicA11yUIElementError.swift @@ -0,0 +1,29 @@ +// Copyright 2021-2023 Raising the Floor - US, Inc. +// +// Licensed under the New BSD license. You may not use this file except in +// compliance with this License. +// +// You may obtain a copy of the License at +// https://github.com/raisingthefloor/morphic-macos/blob/master/LICENSE.txt +// +// The R&D leading to these results received funding from the: +// * Rehabilitation Services Administration, US Dept. of Education under +// grant H421A150006 (APCP) +// * National Institute on Disability, Independent Living, and +// Rehabilitation Research (NIDILRR) +// * Administration for Independent Living & Dept. of Education under grants +// H133E080022 (RERC-IT) and H133E130028/90RE5003-01-00 (UIITA-RERC) +// * European Union's Seventh Framework Programme (FP7/2007-2013) grant +// agreement nos. 289016 (Cloud4all) and 610510 (Prosperity4All) +// * William and Flora Hewlett Foundation +// * Ontario Ministry of Research and Innovation +// * Canadian Foundation for Innovation +// * Adobe Foundation +// * Consumer Electronics Association Foundation + +import Cocoa + +public enum MorphicA11yUIElementError: Error { + case axError(_ error: AXError) + case uiAttributeValueCompatibleError(_ error: MorphicA11yUIAttributeValueCompatibleError) +}