Is it possible to use Xcode UI Testing on an app using SpriteKit?
The main idea is to create the accessibility material for elements that you want to UI test. That's mean:
List all accessible elements contained in the scene
Configure settings for each of these elements, especially
frame
data.
Step by Step
This answer is for Swift 3 and is mainly based on Accessibility (Voice Over) with Sprite Kit
Let's say I want to make the SpriteKit button named tapMe
accessible.
List of accessible elements.
Add an array of UIAccessibilityElement
to the Scene.
var accessibleElements: [UIAccessibilityElement] = []
Scene's cycle life
I need to update two methods: didMove(to:)
and willMove(from:)
.
override func didMove(to view: SKView) {
isAccessibilityElement = false
tapMe.isAccessibilityElement = true
}
As scene is the accessibility controller, documentation stated it must return False
to isAccessibilityElement
.
And:
override func willMove(from view: SKView) {
accessibleElements.removeAll()
}
Override UIAccessibilityContainer methods
3 methods are involved: accessibilityElementCount()
, accessibilityElement(at index:)
and index(ofAccessibilityElement
. Please allow me to introduce an initAccessibility()
method I'll describe later.
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)!
}
Initialize accessibility for the Scene
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)
}
}
- Create
UIAccessibilityElement
fortapMe
- Compute frame data on device's coordinates. Don't forget that
frame
's origin is the top/left corner for UIKit - Set data for
UIAccessibilityElement
- Add this
UIAccessibilityElement
to list of all accessible elements in scene.
Now tapMe
is accessible from UI testing perspective.
References
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
According to Apple developer forum discussion, Integrate UITest with SpriteKit is not currently possible:
This is likely not currently possible, but it probably could be
Update 2017-02-19
According to comment by @ChrisLivdahl this may be achieved by using UIAccessibility — Session 406, UI Testing in Xcode, WWDC 2015.
The idea is to make the element needed UI Testable.
I've implemented an example project based on your (@Domsware's) awesome answer, and I've confirmed this trick works well for both Xcode UI Testing Framework and KIF.
Hope this example helps for anyone who is interested in this topic :)