SwiftUI 应用程序的 UI 测试在计时器过去后找不到设置了可访问性标识符的元素

问题描述

应用:

该应用有一个标签云,它使用由 Text 触发的 ZStack 每隔几秒在 ForEach 视图中添加删除标签ObservableObjectZStack一个 accessibilityIdentifier 集。

界面测试:

在 UI 测试中,我首先设置了 XCTWaiter。经过一段时间后,我检查 XCUIElement (ZStack) 与 accessibilityIdentifier 是否存在。 之后,我为 ZStack 类型的所有后代查询 XCUIElement .staticText 我还查询 XCUIApplication获取 .staticText

类型的后代

以下问题:

XCTWaiter 设置得太长时,等待时间太长。它不再找到带有其标识符的 ZStack XCUIElement

如果 XCTWaiter 设置为较短的等待时间或被移除,则会找到 ZStack XCUIElement。但它永远不会找到它的 .staticText 类型的后代。但它们确实存在,因为我可以发现它们是 XCUIApplication 本身的后代。

我的假设:

我假设 ZStack 及其标识符只能通过测试找到,只要它没有后代。并且因为它此时还没有任何后代,稍后为其后代查询 ZStack XCUIElement 也会失败,因为 XCUIElement 似乎只代表 ZStack它被捕获的时间。

或者,也许我在错误的位置附加了 accessibilityIdentifierZStack,或者 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))

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...