【问题标题】:Is it possible to use Xcode UI Testing on an app using SpriteKit?是否可以在使用 SpriteKit 的应用程序上使用 Xcode UI 测试?
【发布时间】:2015-12-15 15:51:36
【问题描述】:

我想对使用 SKSpriteKit 的游戏进行 UI 测试。

由于我的第一次尝试没有成功,我想知道是否可以将 Xcode UI 测试与 SpriteKit 一起使用。

【问题讨论】:

  • 我想知道是否可以通过制作与每个节点相关的 UIAccessibilityElement 对象来对 SpriteKit 节点进行 UI 测试。
  • @ChrisLivdahl 是的,这是一个有趣的想法。在 WWDC2015 的 Session 406 上,Xcode 中的 UI 测试声明:默认情况下无法访问“图层、精灵和其他图形对象”。所以精灵在默认情况下对 UIAccessibility 是不可见的。我要弄清楚的是如何添加对 UIAccessibility 可见的精灵。
  • @ChrisLivdahl 我按照你的想法成功地在 UI 测试中访问了 SKSpriteNode。我会在接下来的日子里给出答案。

标签: swift sprite-kit accessibility xcode-ui-testing uiaccessibility


【解决方案1】:

主要思想是为要进行 UI 测试的元素创建可访问性材料。意思是:

  1. 列出场景中包含的所有可访问元素

  2. 为每个元素配置设置,尤其是framedata。

一步一步

这个答案是针对 Swift 3 的,主要基于Accessibility (Voice Over) with Sprite Kit

假设我想让名为 tapMe 的 SpriteKit 按钮可访问。

可访问元素列表。

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. tapMe 创建UIAccessibilityElement
  2. 计算设备坐标上的帧数据。不要忘记frame 的原点是 UIKit 的左上角
  3. UIAccessibilityElement设置数据
  4. 将此UIAccessibilityElement 添加到场景中所有可访问元素的列表中。

现在可以从 UI 测试的角度访问 tapMe

参考文献

【讨论】:

  • 这太棒了!写得也很好!
  • @ChrisLivdahl 谢谢!
  • 我一直在努力让它工作,直到我意识到我的场景的可访问性框架不正确。即使您将场景的 isAccessibilityElement 设置为 false,您仍然需要确保场景的 accessibilityFrame 与视图的边界相匹配。
【解决方案2】:

根据Apple developer forum discussion,目前无法将 UITest 与 SpriteKit 集成:

这可能目前不可能,但可能是

2017-02-19 更新

根据@ChrisLivdahl 的评论,这可以通过使用 UIAccessibility — Session 406, UI Testing in Xcode, WWDC 2015 来实现。

这个想法是使 UI 所需的元素可测试。

【讨论】:

  • 为了完整起见,您能否添加此论坛讨论的链接? - 没关系 - 找到了!将自己进行编辑...
【解决方案3】:

我已经根据您 (@Domsware's) 的出色回答实现了 example project,并且我已经确认此技巧对 Xcode UI Testing FrameworkKIF 都适用。

希望此示例对任何对此主题感兴趣的人有所帮助:)

【讨论】:

    【解决方案4】:

    2021 年初更新

    使用 SpriteKit、SwiftUI App 和 Swift 5.4 测试的更短的解决方案

    在使用 XCUI 测试时,早期的方法似乎不再适用。我的应用程序的基础是一个以SKScene 作为主视图的 SwiftUI 应用程序。为了让它工作,它实际上非常简单,我工作所需的步骤要少得多。

    1.通过在didMove() 方法中仅添加一行来停用场景的可访问性

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

    Dominique Vial's answer中所述

    2。使您的节点符合 UIAccessibilityIdentification 协议

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

    提到here

    UI 测试需要访问的任何节点都需要符合此协议。 更新扩展,而不是对我现在使用的每个节点进行子类化。

    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"]
    

    您可以设置特定的 trait,例如 .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)
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-09
      • 1970-01-01
      • 1970-01-01
      • 2016-05-06
      • 1970-01-01
      • 2012-10-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多