Можно ли использовать тестирование пользовательского интерфейса Xcode в приложении с помощью SpriteKit?

Я хочу использовать тестирование пользовательского интерфейса для игры, используя SKSpriteKit.

Поскольку мои первые попытки не сработали, мне интересно, можно ли использовать тестирование пользовательского интерфейса Xcode со SpriteKit.


person Dominique Vial    schedule 15.12.2015    source источник
comment
Интересно, можно ли протестировать узлы SpriteKit с помощью пользовательского интерфейса, создав объекты UIAccessibilityElement, относящиеся к каждому узлу.   -  person Chris Livdahl    schedule 18.01.2017
comment
@ChrisLivdahl Да, это интересная идея. На сеансе 406 WWDC2015 тестирование пользовательского интерфейса в Xcode заявило: слои, спрайты и другие графические объекты недоступны по умолчанию. Таким образом, спрайты по умолчанию не видны для UIAccessibility. Мне нужно понять, как добавить видимые спрайты в UIAccessibility.   -  person Dominique Vial    schedule 19.02.2017
comment
@ChrisLivdahl Я последовал вашей идее и мне удалось получить доступ к SKSpriteNode в тестах пользовательского интерфейса. В ближайшие дни дам ответ.   -  person Dominique Vial    schedule 07.03.2017


Ответы (4)


Основная идея состоит в том, чтобы создать материал доступности для элементов, которые вы хотите протестировать в пользовательском интерфейсе. Это значит:

  1. Список всех доступных элементов, содержащихся в сцене

  2. Настройте параметры для каждого из этих элементов, особенно для framedata.

Шаг за шагом

Этот ответ предназначен для Swift 3 и в основном основан на Доступность (закадровый голос) с помощью Sprite Kit

Допустим, я хочу сделать кнопку SpriteKit с именем tapMe доступной.

Список доступных элементов.

Добавьте массив UIAccessibilityElement в сцену.

 var accessibleElements: [UIAccessibilityElement] = []

Цикл жизни сцены

Мне нужно обновить два метода: didMove(to:)и willMove(from:).

override func didMove(to view: SKView) {
    isAccessibilityElement       = false
    tapMe.isAccessibilityElement = true
}

Поскольку сцена является контроллером доступности, в документации указано, что она должна возвращать False в isAccessibilityElement.

А также:

override func willMove(from view: SKView) {
    accessibleElements.removeAll()
}

Переопределить методы UIAccessibilityContainer

Задействовано 3 метода: accessibilityElementCount(), accessibilityElement(at index:) и index(ofAccessibilityElement. Пожалуйста, позвольте мне представить метод initAccessibility(), который я опишу позже.

override func accessibilityElementCount() -> Int {
    initAccessibility()
    return accessibleElements.count
}

override func accessibilityElement(at index: Int) -> Any? {

    initAccessibility()
    if (index < accessibleElements.count) {
        return accessibleElements[index]
    } else {
        return nil
    }
}

override func index(ofAccessibilityElement element: Any) -> Int {
    initAccessibility()
    return accessibleElements.index(of: element as! UIAccessibilityElement)!
}

Инициализировать специальные возможности для сцены

func initAccessibility() {

    if accessibleElements.count == 0 {

        // 1.
        let elementForTapMe   = UIAccessibilityElement(accessibilityContainer: self.view!)

        // 2.
        var frameForTapMe = tapMe.frame

        // From Scene to View
        frameForTapMe.origin = (view?.convert(frameForTapMe.origin, from: self))!

        // Don't forget origins are different for SpriteKit and UIKit:
        // - SpriteKit is bottom/left
        // - UIKit is top/left
        //              y
        //  ┌────┐       ▲
        //  │    │       │   x
        //  ◉────┘       └──▶
        //
        //                   x
        //  ◉────┐       ┌──▶
        //  │    │       │
        //  └────┘     y ▼
        //
        // Thus before the following conversion, origin value indicate the bottom/left edge of the frame.
        // We then need to move it to top/left by retrieving the height of the frame.
        //


        frameForTapMe.origin.y = frameForTapMe.origin.y - frameForTapMe.size.height


        // 3.
        elementForTapMe.accessibilityLabel   = "tap Me"
        elementForTapMe.accessibilityFrame   = frameForTapMe
        elementForTapMe.accessibilityTraits  = UIAccessibilityTraitButton

        // 4.
        accessibleElements.append(elementForTapMe)

    }
}
  1. Создать UIAccessibilityElement для tapMe
  2. Вычислить данные кадра по координатам устройства. Не забывайте, что frame находится в верхнем/левом углу UIKit.
  3. Установить данные для UIAccessibilityElement
  4. Добавьте этот UIAccessibilityElement в список всех доступных элементов в сцене.

Теперь tapMe доступен с точки зрения тестирования пользовательского интерфейса.

использованная литература

person Dominique Vial    schedule 08.03.2017
comment
Это круто! Тоже очень хорошая запись! - person Chris Livdahl; 06.04.2017
comment
@ChrisLivdahl Спасибо! - person Dominique Vial; 07.04.2017
comment
Я изо всех сил пытался заставить это работать, пока не понял, что рамка доступности моей сцены была неправильной. Несмотря на то, что вы установили для isAccessibilityElement сцены значение false, вам все равно нужно убедиться, что accessibilityFrame вашей сцены соответствует границам представления. - person Chris Garrett; 17.02.2018

Согласно обсуждению на форуме разработчиков Apple, интеграция UITest со SpriteKit в настоящее время невозможна:

Вероятно, в настоящее время это невозможно, но, вероятно, может быть

Обновление 2017-02-19

Согласно комментарию @ChrisLivdahl, это может быть достигнуто с помощью UIAccessibility — Session 406, Тестирование пользовательского интерфейса в Xcode, WWDC 2015.

Идея состоит в том, чтобы сделать элемент, необходимый для тестирования пользовательского интерфейса.

person Dominique Vial    schedule 04.01.2016
comment
Для полноты можете добавить ссылку на это обсуждение на форуме? - Ничего, нашел! Буду редактировать сам... - person Ali Beadle; 05.10.2016

Я реализовал пример проекта на основе вашего потрясающего ответа (@Domsware) и подтвердил этот трюк хорошо работает как для Xcode UI Платформа тестирования и KIF.

Надеюсь, этот пример поможет всем, кто интересуется этой темой :)

person hiiwave    schedule 21.12.2017

Обновление в начале 2021 года

Гораздо более короткое решение, протестированное с SpriteKit, приложением SwiftUI и Swift 5.4

Более ранние подходы больше не работали при использовании XCUI тестов. Основой моего приложения является приложение SwiftUI, основное представление которого имеет SKScene. Для того, чтобы это работало, в конце концов, это было довольно просто, и мне потребовалось гораздо меньше шагов для работы.

<сильный>1. Деактивируйте доступность сцены, добавив только одну строку в метод didMove()

override func didMove(to view: SKView) {
    isAccessibilityElement = false
}

Как упоминалось в ответе Доминика Виала

<сильный>2. Соответствие ваших узлов протоколу UIAccessibilityIdentification

class YourNode: SKSpriteNode, UIAccessibilityIdentification {
    var accessibilityIdentifier: String?
//...
}

Упоминается здесь

Любой узел, который должен быть доступен для теста пользовательского интерфейса, должен соответствовать этому протоколу. Обновите расширение, вместо того, чтобы создавать подклассы для каждого узла, который я сейчас использую, расширение ниже.

<сильный>3. Назначьте accessibilityIdentifier и активируйте доступность объектов ваших узлов.

let yourNode = YourNode()
yourNode.isAccessibilityElement = true
yourNode.accessibilityIdentifier = "nodeID"

<сильный>4. Вот и все! Проведите тесты!

func testingNodes() throws {
   app = XCUIApplication()
   app.launch()
   let node = app.otherElements["nodeID"]
   XCTAssert(node.waitForExistence(timeout: 1))
}

<сильный>5. Необязательно: задайте accessibilityTraits

yourNode.accessibilityTraits = [.button, .updatesFrequently]


let nodeAsButton = app.buttons["nodeID"]

Вы можете установить конкретную черту, например .button, чтобы сообщить API специальных возможностей, какой у вас узел. Это полезно, чтобы лучше различать ваши узлы во время тестирования, а также если вы планируете реализовать фактические функции доступности для пользователя, тогда это должно быть правильно установлено для его работы.

Обновление 1 — Использование расширений:

Вместо того, чтобы создавать подклассы узлов, я использую следующее расширение для SKNode. Теперь я установил только accessibilityLabel, а не accessibilityIdentifier.

 extension SKNode: UIAccessibilityIdentification {
    public var accessibilityIdentifier: String? {
        get {
            super.accessibilityLabel
        }
        set(accessibilityIdentifier) {
            super.accessibilityLabel = accessibilityIdentifier
        }
    }
}

Обновление 2. Доступность всех потомков SKScene

Чтобы узлы и все их потомки были доступны, я использовал следующее расширение на SKNode. Это добавляет каждый узел к accessibilityElements сцены.

extension SKNode: UIAccessibilityIdentification {
    public var accessibilityIdentifier: String? {
        get {
            super.accessibilityLabel
        }
        set(accessibilityIdentifier) {
            super.accessibilityLabel = accessibilityIdentifier
        }
    }

    func makeUITestAccessible(label: String, traits: UIAccessibilityTraits) {

        accessibilityLabel = label
        isAccessibilityElement = true
        accessibilityTraits = traits

        if let scene = scene {
            if scene.accessibilityElements == nil {
                scene.accessibilityElements = [self]
            } else {
                scene.accessibilityElements?.append(self)
            }
        }
    }
}
person Marco Boerner    schedule 07.02.2021