是否可以在使用 SpriteKit 的应用程序上使用 Xcode UI 测试?

Posted

技术标签:

【中文标题】是否可以在使用 SpriteKit 的应用程序上使用 Xcode UI 测试?【英文标题】:Is it possible to use Xcode UI Testing on an app using SpriteKit? 【发布时间】: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。我会在接下来的日子里给出答案。 【参考方案1】:

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

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

    为每个元素配置设置,尤其是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)

    

    tapMe 创建UIAccessibilityElement 计算设备坐标上的帧数据。不要忘记frame 的原点是 UIKit 的左上角 为UIAccessibilityElement设置数据 将此UIAccessibilityElement 添加到场景中所有可访问元素的列表中。

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

参考文献

Session 406, UI Testing in Xcode, WWDC 2015

eyes off eyes on — Voiceover accessibility in SpriteKit

How do I support VoiceOver in a SpriteKit game? | Apple Developer Forums

swift - Accessibility (Voice Over) with Sprite Kit - Stack Overflow

【讨论】:

这太棒了!写得也很好! @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 Framework 和 KIF 都适用。

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

【讨论】:

【参考方案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)
            
        
    

【讨论】:

以上是关于是否可以在使用 SpriteKit 的应用程序上使用 Xcode UI 测试?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以在使用 SpriteKit 的应用程序上使用 Xcode UI 测试?

是否可以使用SpriteKit在应用程序上使用Xcode UI Testing?

是否可以围绕 SpriteKit 中的任意点旋转节点?

是否可以在Spritekit项目中以编程方式更改纹理色调?

如何混合 UIKit 和 SpriteKit?

是否可以仅针对 iphone 5 和 6 发布使用 spritekit 和 swift 开发的游戏