The app:
The app has a tag cloud that adds and removes tags as Text
views every few seconds to a ZStack
using ForEach
triggered by an ObservableObject
. The ZStack
has an accessibilityIdentifier
set.
The UI test:
In the UI Test I have set a XCTWaiter
first. After a certain period of time has passed I then check if the XCUIElement
(ZStack
) with the accessibilityIdentifier
exists.
After that I query the ZStack
XCUIElement
for all descendants of type .staticText
I also query the XCUIApplication
for its descendants of type .staticText
The following issues:
When the XCTWaiter
is set too wait too long. It does not find the ZStack
XCUIElement
with its identifier anymore.
If the XCTWaiter
is set to a low wait time or removed the ZStack
XCUIElement
will be found. But it will never find its descendants of type .staticText
. They do exists though because I can find them as descendant of XCUIApplication
itself.
My assumption:
I assume that the ZStack
with its identifier can only be found by the tests as long as it does not have descendants. And because it doesn't have any descendants at this moment yet, querying the ZStack
XCUIElement
for its descendants later also fails because the XCUIElement
seems to only represent the ZStack
at the time it was captured.
Or maybe I have attached the accessibilityIdentifier
for the ZStack
at the wrong place or SwiftUI is removing it as soon as there are descendants and I should add identifiers to the descendants only. But that would mean I can only query descendants from XCUIApplication
itself and never from another XCUIElement? That would make the .children(matching:)
quite useless.
Here is the code for a single view iOS app in SwiftUI with tests enabled.
MyAppUITests.swift
import XCTest
class MyAppUITests: XCTestCase {
func testingTheInitialView() throws {
let app = XCUIApplication()
app.launch()
let exp = expectation(description: "Waiting for tag cloud to be populated.")
_ = XCTWaiter.wait(for: [exp], timeout: 1) // 1. If timeout is set higher
let tagCloud = app.otherElements["TagCloud"]
XCTAssert(tagCloud.exists) // 2. The ZStack with the identifier "TagCloud" does not exist anymore.
let tagsDescendingFromTagCloud = tagCloud.descendants(matching: .staticText)
XCTAssert(tagsDescendingFromTagCloud.firstMatch.waitForExistence(timeout: 2)) // 4. However, it never finds the tags as the descendants of the tagCloud
let tagsDescendingFromApp = app.descendants(matching: .staticText)
XCTAssert(tagsDescendingFromApp.firstMatch.waitForExistence(timeout: 2)) // 3. It does find the created tags here.
}
}
ContentView.swift:
import SwiftUI
struct ContentView: View {
private let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
@ObservedObject var tagModel = TagModel()
var body: some View {
ZStack {
ForEach(tagModel.tags, id: \.self) { label in
TagView(label: label)
}
.onReceive(timer) { _ in
self.tagModel.addNextTag()
if tagModel.tags.count > 3 {
self.tagModel.removeOldestTag()
}
}
}.animation(Animation.easeInOut(duration: 4.0))
.accessibilityIdentifier("TagCloud")
}
}
class TagModel: ObservableObject {
@Published var tags = [String]()
func addNextTag() {
tags.append(String( Date().timeIntervalSince1970 ))
}
func removeOldestTag() {
tags.remove(at: 0)
}
}
struct TagView: View {
@State private var show: Bool = true
@State private var position: CGPoint = CGPoint(x: Int.random(in: 50..<250), y: Int.random(in: 100..<200))
let label: String
var body: some View {
let text = Text(label)
.position(position)
.opacity(show ? 0.0 : 1.0)
.onAppear {
show.toggle()
}
return text
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}