Устранение неудобств разработчика – автоматизация 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 () .

Ответить