2

I have a button with a foreground image in a ZStack:

Button(action: {
    self.highlighted = !self.highlighted
}) {
    ZStack {
        Text("Text")
        if self.highlighted {
            Image("highlighted").resizable()
        }
    }
}

The foreground image ("highlighted") is only visible if the variable is true. A button click flips the highlighted variable. So if the button is clicked it is highlighted and if it is clicked again it is not highlighted anymore. I now want to have a UiTest in which the button is clicked and the test checks if the Image "highlighted" exists. This is what I have as UiTest, but it fails at the last assertion:

func test_highlight() {
    let app = XCUIApplication()
    let button = app.buttons["my_button"]
    XCTAssertTrue(button.exists)
    button.tap()
    XCTAssertTrue(button.images["highlighted"].exists) // <-- Fails here
}

Is this possible in UiTests. If yes, how? If not, what is the alternative?

L3n95
  • 1,505
  • 3
  • 25
  • 49

2 Answers2

2

Accessibility engine does not see internals of button, but if you change like

Button(action: {
    self.highlighted = !self.highlighted
}) {
    if self.highlighted {
        Image("highlighted").resizable()
    } else {
       Text("Text")
    }
}

then you can verify toggle by UT (tested with Xcode 12.1 / iOS 14.1)

func test_highlight() {
    let app = XCUIApplication()
    app.launch()
    let button = app.buttons["Text"]     // << fits button label
    XCTAssertTrue(button.exists)
    button.tap()

    let highlighted_button = app.buttons["highlighted"] // fits button image name
    XCTAssertTrue(highlighted_button.exists)
}

Update: possible variant for transparent image

struct DemoView: View {
    @State private var highlighted = false
    var body: some View {
        Button(action: {
             self.highlighted = !self.highlighted
        }) {
             ZStack {
                  Text("Text")
                  if self.highlighted {
                        Image("flag-1").resizable()
                  }
             }
        }
        .accessibility(identifier: highlighted ? "highlighted" : "button" )
    }
}

func test_highlight() {
     let app = XCUIApplication()
     app.launch()
     let button = app.buttons["button"]
     XCTAssertTrue(button.exists)
     button.tap()

     let highlighted = app.buttons["highlighted"]
     XCTAssertTrue(highlighted.exists)
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Okay, but then the text is not visible anymore right? If the engine does not see the internals is there any other way to test it (with normal unit tests?). I can't imagine that I am the only one with such a scenario. – L3n95 Nov 22 '20 at 11:27
  • You did not say you have transparent image. Ok, see updated with possible variant - identify button depending on its state. – Asperi Nov 22 '20 at 11:41
  • 1
    Okay that works. But to be honest I don't really like to write code in the project files just to make tests work. This is a pity, if this is the only way to test it. – L3n95 Nov 22 '20 at 11:58
2

Okay that works. But to be honest I don't really like to write code in the project files just to make tests work. This is a pity, if this is the only way to test it

I agree and I wouldn’t utilise the identifier. However, to be fair, what you’re asking for isn’t something I think you commonly test at UI test level ?

Perhaps test resources and business logic (I.e your bool) via unit and UI-looks-good via snapshot testing?

Personally I would here verify UI by accessibility/values/labels etc of elements along user integration flow.

Example

If you configure the button to be properly accessible, you can verify the ‘selected’ state of the button.

let localisedstring = “localised-text”

Button(action: {
    self.highlighted = !self.highlighted
}) {
    ZStack {
        Text(localisedstring)
        if self.highlighted {
            Image("highlighted").resizable()
        }
    }
}
.accessibilityTraits(isHighlighted ? [.button, .selected] : [.button])
.accessibilityLabel(localisedString)

Then in a test you can do:

// Given base button 
let button = app.buttons[“button-id”]
XCTAssertFalse(button.isSelected) 

// When user taps 
button.tap()

// Then button should be selected
XCTAssertTrue(button.isSelected) 

// And whatever-else that button should do 

In a similar way to the utilising the identifier, you’ve made it testable, and also better supported accessibility users like voice over!

Edit: written on my iPad so please take as pseudo ‍♂️

lacking-cypher
  • 483
  • 4
  • 16