问题描述
应用:
该应用有一个标签云,它使用由 Text
触发的 ZStack
每隔几秒在 ForEach
视图中添加和删除标签到 ObservableObject
。 ZStack
有一个 accessibilityIdentifier
集。
界面测试:
在 UI 测试中,我首先设置了 XCTWaiter
。经过一段时间后,我检查 XCUIElement
(ZStack
) 与 accessibilityIdentifier
是否存在。
之后,我为 ZStack
类型的所有后代查询 XCUIElement
.staticText
我还查询 XCUIApplication
以获取 .staticText
以下问题:
当 XCTWaiter
设置得太长时,等待时间太长。它不再找到带有其标识符的 ZStack
XCUIElement
。
如果 XCTWaiter
设置为较短的等待时间或被移除,则会找到 ZStack
XCUIElement
。但它永远不会找到它的 .staticText
类型的后代。但它们确实存在,因为我可以发现它们是 XCUIApplication
本身的后代。
我的假设:
我假设 ZStack
及其标识符只能通过测试找到,只要它没有后代。并且因为它此时还没有任何后代,稍后为其后代查询 ZStack
XCUIElement
也会失败,因为 XCUIElement
似乎只代表 ZStack
它被捕获的时间。
或者,也许我在错误的位置附加了 accessibilityIdentifier
的 ZStack
,或者 SwiftUI 会在有后代时立即删除它,我应该只向后代添加标识符。但这是否意味着我只能从 XCUIApplication
本身查询后代,而不能从另一个 XCUIElement 查询?那会使 .children(matching:)
变得毫无用处。
这是启用测试的 SwiftUI 中单视图 iOS 应用的代码。
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()
}
}
解决方法
据我所知,您没有满足您设置的 XCTestExpectation:
let exp = expectation(description: "Waiting...")
过去,我使用 XCTestExpecation 异步代码来表示完成。所以就像在这个例子中,我设置了 XCTestExpectation 并提供了描述,然后进行了网络调用,并在网络调用完成后运行了 exp.fulfill()
。
let exp = expectation(description: "Recieved success message after uploading onboarding model")
_ = UserController.upsertUserOnboardingModel().subscribe(onNext: { (response) in
expectation.fulfill()
},onError: { (error) in
print("\(#function): \(error)")
XCTFail()
})
wait(for: [exp],timeout: 10)
步骤应该是:创建期望 -> 在 X 秒内实现期望。
我在您提供的代码中没有看到任何 exp.fulfill()
,所以看起来这是一个缺失的步骤。
建议的解决方案:
A.所以你可以做的是在某个时间点完成你的 exp
timeout: x
中指定的秒数。
B.或者你只想延迟你可以使用 sleep(3)
C.或者,如果您想等待存在,请选择一个元素并使用 .waitForExistence(timeout: Int)
示例:XCTAssert(app.searchFields["Search for cars here"].waitForExistence(timeout: 5))