Search code examples
iosxcodeswiftuipickerviewxcode-ui-testing

"Failed to get refreshed snapshot" error when UI testing with a UIPickerView


I have a time selector view that uses a UIPickerView with 6 sections, hours, minutes, seconds and fixed label wheels between them.

When I try to run a UI test against this view in XCode 7, the simulator freezes and I eventually see the error:

"Failed to get refreshed snapshot"

My test currently just attempts to interact with the cancel button in the view:

app.buttons["Cancel"].tap()

I've also tried waiting for the cancel button to exist. Removing the picker view from the UIView stops the test freezing, so I know the problem lies there.

The UIPickerViewDelegate and UIPickerViewDataSource methods are as below:

let HOUR_INDEX = 0
let HOUR_LABEL_INDEX = 1
let MINUTE_INDEX = 2
let MINUTE_LABEL_INDEX = 3
let SECOND_INDEX = 4
let SECOND_LABEL_INDEX = 5

let TOTAL_COMPONENTS = 6

func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
    return TOTAL_COMPONENTS
}

// returns the # of rows in each component..
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    if component == HOUR_INDEX {
        return 12 * 100
    } else if component == MINUTE_INDEX || component == SECOND_INDEX {
        return 60 * 100
    } else {
        return 1
    }
}

func pickerView(
    pickerView: UIPickerView,
    attributedTitleForRow row: Int,
    forComponent component: Int) -> NSAttributedString? {
        let paragraphStyle = NSMutableParagraphStyle()
        if component == HOUR_INDEX || component == MINUTE_INDEX || component == SECOND_INDEX {
            paragraphStyle.alignment = NSTextAlignment.Right
        } else {
            paragraphStyle.alignment = NSTextAlignment.Left
        }

        if let titleData = getPickerTitle(row, forComponent: component) {
            let myTitle = NSAttributedString(string: titleData,
                attributes: [
                    NSParagraphStyleAttributeName: paragraphStyle
            ])
            return myTitle
        }
        return nil
}

func getPickerTitle(row: Int, forComponent component: Int) -> String? {
    if component == HOUR_INDEX {
        return "\(row%12)"
    } else if component == MINUTE_INDEX || component == SECOND_INDEX {
        return "\(row%60)"
    } else {
        if component == HOUR_LABEL_INDEX {
            return "h"
        }
        if component == MINUTE_LABEL_INDEX {
            return "m"
        }
        if component == SECOND_LABEL_INDEX {
            return "s"
        }
        return nil
    }
}

Solution

  • The error relating to getting a snapshot may very well be caused by too many elements in the accessibility hierarchy. In order to be able to query the UI the testing framework is generating the complete hierarchy.

    I see that you're using the trick of multiplying the number of rows in the hour, minute, and second picker to create the effect of seemingly looping picker items. With the current implementation between the three wheels 13200 UI elements will be generated in the accessibility hierarchy.

    I am using the same picker view trick and have seen the same error. By reducing the number of rows to less than a hundred per picker wheel when running tests there's no problem getting a snapshot of the accessibility hierarchy in my experience.