SwiftUI toggle not being toggled in UI test
Asked Answered
C

2

12

I can't get UI tests to toggle a Toggle in a SwiftUI Form. It seems that app.switches[*name*].tap() does nothing. Does anyone else have experienced this? Ideas?

The code below is a demonstration of the issue. It's a simple form with four toggles that generates a int value based on the toggles positions.

I have the following SwiftUI view:

struct ContentView: View {
    
    @State var sw1: Bool = false
    @State var sw2: Bool = false
    @State var sw3: Bool = false
    @State var sw4: Bool = false
    
    
    var valueString: String {
        var ret: Int = 0
        if sw1 {
            ret = ret + 1
        }
        if sw2 {
            ret = ret + 2
        }
        if sw3 {
            ret = ret + 4
        }
        if sw4 {
            ret = ret + 8
        }
        return String(ret)
    }
    
    var body: some View {
        NavigationStack {
            Form {
                Section("Binary switches"){
                    Toggle("1", isOn: $sw1)
                        .accessibilityIdentifier("sw1")
                    Toggle("2", isOn: $sw2)
                        .accessibilityIdentifier("sw2")
                    Toggle("4", isOn: $sw3)
                        .accessibilityIdentifier("sw3")
                    Toggle("8", isOn: $sw4)
                        .accessibilityIdentifier("sw4")
                }
                Section("Value") {
                    Text(valueString)
                        .accessibilityIdentifier("value")
                        .accessibilityValue(valueString)
                }
            }
            .navigationTitle("Test Form")
        }
    }
}

And the following UI tests Code:

func testRandomSwitches() throws {
    let app = XCUIApplication()
    app.launch()
    
    // Get the binary switches
    let sw1 = app.switches["sw1"]
    let sw2 = app.switches["sw2"]
    let sw3 = app.switches["sw3"]
    let sw4 = app.switches["sw4"]
    
    // randomly switch on some of the switches
    let randomOnes = [sw1, sw2, sw3, sw4].map { _ in Bool.random() }
    if randomOnes[0] { sw1.tap() }
    if randomOnes[1] { sw2.tap() }
    if randomOnes[2] { sw3.tap() }
    if randomOnes[3] { sw4.tap() }
    
    // calculate the expected value
    let expectedValue = randomOnes.enumerated()
        .filter { $0.element }
        .map { 1 << $0.offset }
        .reduce(0, +)
    
    // get the value text and assert that it matches the expected value
    let value = app.staticTexts["value"].value as? String ?? ""
    XCTAssertEqual(value, String(expectedValue))
}

When I run this test, the switches never get toggled, and the value string always reads "0". The UITest code fails on XCTAssertEqual(value, String(expectedValue)). I'm not sure what I'm doing wrong.

If I try to record the toggle tap I always get the error "Timestamped Event Matching Error: Failed to find matching element".

Can anyone help me figure out why the switches aren't getting toggled in my UI tests?

Carmacarmack answered 20/4, 2023 at 9:39 Comment(2)
Does this answer your question? How to correctly write a UI test for a Swift UI ToggleTrophoplasm
Unfortunately not. The answers suggest adding a .accessibilityIdentifier() to the toggle, witch I already have.Carmacarmack
C
27

Figured it out. It seems that I need to access the first switch in the switch. Really weird. But I gess UI testing is weird. It would be much appreciated if anyone got a less hacky solution.

Non working code:

sw1.tap()

Working code:

sw1.switches.firstMatch.tap()
Carmacarmack answered 20/4, 2023 at 11:13 Comment(5)
Wow this did it for me too.. thank you! Would have never figured that out!Browse
This behavior just started with Xcode 14.3. It may always have been intentional for Apple and the glitch just got fixed but who knows. Thank you!!!Hazaki
Still persists in Xcode 15.Ale
Has anyone seen anything like this in UIKit as well?Sludgy
I'm wondering about the switches (aka toggle buttons) the docs for firstMatch (developer.apple.com/documentation/xctest/… ) My issue now that I can toggle it - is to consistently & reliably find the toggle in a known state.Flintshire
L
0

If, for some reason, anyone is trying to do this through Appium/WebdriverIO, I used this post as a jumping off point and here was the soln I came up with.

  this.confirmPickupSwitchParent = "~confirmPickupSwitch";
  this.childSwitches = "-ios class chain:**/XCUIElementTypeSwitch";

  async swipeToConfirmPickup() {
    const confirmPickupSwitchSelector = await this.driver.$(
      this.confirmPickupSwitchParent
    );

    const insideSwitchSelector = await confirmPickupSwitchSelector.$(
      this.childSwitches
    );
    await insideSwitchSelector.click();
  }

Basically find the parent Toggle/Switch using the accessibilityID and then search for the embedded child Toggle/Switch using the iOS class chain.

Lacielacing answered 19/9, 2023 at 17:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.