计时器经过后,SwiftUI 应用程序的 UI 测试未找到设置了可访问性标识符的元素
Posted
技术标签:
【中文标题】计时器经过后,SwiftUI 应用程序的 UI 测试未找到设置了可访问性标识符的元素【英文标题】:UI Test for SwiftUI app does't find element with set accessibilityIdentifier after a timer has passed 【发布时间】:2021-01-19 19:03:02 【问题描述】:应用:
该应用程序有一个标签云,它使用由ObservableObject
触发的ForEach
每隔几秒添加和删除标签作为Text
视图到ZStack
。 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
。
或者我可能在错误的位置为ZStack
附加了accessibilityIdentifier
,或者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()
【问题讨论】:
【参考方案1】:据我所知,您似乎没有满足您设置的 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))
【讨论】:
对 A. 和 B. 我设置的 XCTWaiter 只不过是 sleep(),用 sleep 替换它根本不会改变结果。 :/ 见这里:***.com/a/65657873/12764795 C. 如果我等待存在它也不能解决问题。假设在该测试之前还有其他 waitForExistence 或延迟也会在同一个地方失败。以上是关于计时器经过后,SwiftUI 应用程序的 UI 测试未找到设置了可访问性标识符的元素的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI:具有计时器样式的文本在提供的日期更改后不会更新