Устранение неудобств разработчика – автоматизация iOS
При автоматизации приложений iOS с помощью Appium команда QA стремится использовать xPath для поиска элементов пользовательского интерфейса. Но xPath очень медленный.
Итак, нам пришла в голову идея использовать AccessibilityIdentifier, предоставляемый фреймворком UIKit, и она отлично сработала.
Предоставление AccessibilityIdentifier для каждого элемента пользовательского интерфейса – непростая задача. Кроме того, чтобы гарантировать, что все будущие элементы пользовательского интерфейса, которые будут включать эту переменную, должны быть подвержены ошибкам.
Мы искали лучшие способы и пришли к безумной идее автоматически генерировать AccessibilityIdentifier для каждой переменной.
Мы изучили API-интерфейс Swift Mirror, который внутренне использует отражение, и нашли способ зеркального отображения экземпляра во время выполнения. Mirror API очень помог в переборе всех переменных внутри экземпляра.
Но есть один нюанс. Mirror API предоставляет переменные только для чтения . Итак, нам пришлось использовать UnsafePointer для изменения экземпляра непосредственно на уровне его памяти.
Пора увидеть код.
protocol AccessbilityIdentifierInjector {
func injectAccessibilityIdentifiers()
}
Протокол AccessibilityIdentifierInjector предоставляет функцию с реализацией по умолчанию для автоматического создания идентификаторов доступности.
extension AccessibilityIdentifierInjector {
func injectAccessibilityIdentifiers() {
var mirror: Mirror? = Mirror(reflecting: self)
repeat {
if let mirror = mirror {
injectOn(mirror: mirror)
}
mirror = mirror?.superclassMirror
} while (mirror != nil)
}
private func injectOn(mirror: Mirror) {
for (name, value) in mirror.children {
if var value = value as? UIView {
UnsafeMutablePointer(&value).pointee.accessibilityIdentifier = name
}
}
}
}
injectAccesibilityIdentifiers () делает две вещи:
- Итерирует, начиная с текущего экземпляра и заканчивая его суперклассом до тех пор, пока не будет суперкласса.
- Фильтрует только типы UIView и внедряет AccessibilityIdentifier, используя объект UnsafePointer этой переменной.
Затем нам нужно заставить UIView и UIViewController соответствовать этому протоколу.
extension UIViewController: AccessibilityIdentifierInjector {}
extension UIView: AccessibilityIdentifierInjector {}
вызов функцию injectAccessibilityIdentifiers () после ее инициализации.
class BaseView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
injectAccessibilityIdentifiers()
}
}
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
injectAccessibilityIdentifiers()
}
}
Предостережение: мы не сможем установить accessibilityIdentifier для “ленивых” переменных, если он не будет инициализирован при вызове функции injectAccessibilityIdentifiers () .