swiftuidatepickerxcuitest

How do I dismiss the DatePicker popup in UI tests that are running on iOS 16 devices?


I have defined a DatePicker to have the compact style, as follows:

struct ContentView: View {
    
    @State private var selectedDate = Date()
    
    var body: some View {
        DatePicker("Date Selected", selection: $selectedDate, displayedComponents: [.date])
            .accessibilityIdentifier("DatePicker")
            .datePickerStyle(.compact)
    }
}

I have defined a UI test that:

  1. Taps on the DatePicker to show the DatePicker popup; then
  2. Taps on one of the dates in the DatePicker popup to select that date; and then
  3. Taps on the DatePicker again to dismiss the DatePicker popup.

Here's the code:

func test_date_picker() {
    let application = XCUIApplication()

    // 0. Launch the app
    application.launch()
        
    // 1. Show the DatePicker popup
    application.datePickers["DatePicker"].tap()
        
    // 2. Change the selected date
    application.datePickers.collectionViews.buttons["Friday, November 25"].tap()
        
    // 3. Dismiss the DatePicker popup
    application.datePickers["DatePicker"].tap()
}

This test works on iOS 14 and iOS 15 devices. Sadly, the final application.datePickers["DatePicker"].tap() call is failing on iOS 16 devices because application.datePickers["DatePicker"] is not hittable. How do I dismiss the DatePicker popup on iOS 16 devices?

You can find a minimal application project that demonstrates the problem here.


Solution

  • I posted this same question on the Apple Developer Forums (here) and got back a response that has worked for me.

    The idea is to define a forceTap() extension function on the XCUIElement class, as follows:

    extension XCUIElement {
        func forceTap() {
            if (isHittable) {
                tap()
            } else {
                coordinate(withNormalizedOffset: CGVector(dx:0.0, dy:0.0)).tap()
            }
        }
    }
    

    Explanation: If the XCUIElement instance is hittable, then the XCUIElement instance's tap() function is called. Otherwise, an XCUICoordinate instance is created for a point within the XCUIElement instance and that XCUICoordinate instance's tap() function is called instead.

    The final line of my test function is now changed to the following:

    application.datePickers["DatePicker"].forceTap()