25

This is how I simply create UIAlertController and present it on the screen:

private class func showAlertWithTitle(title: String, message: String) {

    let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
    //alert.accessibilityLabel = "my string here"      //doesnt work
    let action = UIAlertAction(title: "OK", style: .Default) { action in
        alert.dismissViewControllerAnimated(true, completion: nil)
    }

    alert.addAction(action)
    UIStoryboard.topViewController()?.presentViewController(alert, animated: true, completion: nil)
}

and this is how I access it under UITests:

emailAlert = app.alerts["First Name"] //for title "First Name"

but I would like to set there custom identifier and access this by firstName like this:

emailAlert = app.alerts["firstName"]

Is it possible?

Community
  • 1
  • 1
Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358

4 Answers4

15

This is an old thread but someone might use this.

I was able to set the accessibility identifier like this:

let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.view.accessibilityIdentifier = "custom_alert"
alert.view.accessibilityValue = "\(title)-\(message)"

alert.addAction(
    UIAlertAction(
        title: "ALERT_BUTTON_OK".localized,
        style: .default,
        handler: handler
    )
)

present(alert, animated: true)

That way I can access the alert by accessibility identifier and check its contents in accessibility value.

It is not perfect of course, but it works - at least for my testing using Appium.

Jan
  • 2,295
  • 1
  • 17
  • 16
  • I've been looking for a way to give an alert a specific identifier. This was the answer I've been looking for. Thanks! Oh, and it works fine in Swift 4. – Phontaine Judd Oct 12 '18 at 07:03
  • 4
    You write that you check its contents, but how do you check the text of the button? It has no accessibility id. – simpleuser Aug 16 '19 at 21:28
6

The only way I figured out to do this was to use Apple's private APIs. You call valueForKey on the UIAlertAction object with this super secret key: "__representer" to get whats called a _UIAlertControllerActionView.

    let alertView = UIAlertController(title: "This is Alert!", message: "This is a message!", preferredStyle: .Alert)
    let okAction = UIAlertAction(title: "OK", style: .Default, handler: nil)

    alertView.addAction(okAction)

    self.presentViewController(alertView, animated: true, completion: {
        let alertButton = action.valueForKey("__representer")
        let view = alertButton as? UIView
        view?.accessibilityIdentifier = "okAction_AID"
    })

This has to be done in the completion handler because that that _UIAlertControllerActionView won't exist until the view is presented. On a side note in my project I used these following extensions to make things easier / more readable:

extension UIAlertController {
    func applyAccessibilityIdentifiers()
    {
        for action in actions
        {
            let label = action.valueForKey("__representer")
            let view = label as? UIView
            view?.accessibilityIdentifier = action.getAcAccessibilityIdentifier()
        }

    }

}

extension UIAlertAction
{
    private struct AssociatedKeys {
        static var AccessabilityIdentifier = "nsh_AccesabilityIdentifier"
    }

    func setAccessibilityIdentifier(accessabilityIdentifier: String)
    {
        objc_setAssociatedObject(self, &AssociatedKeys.AccessabilityIdentifier, accessabilityIdentifier, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
    }

    func getAcAccessibilityIdentifier() -> String?
    {
        return objc_getAssociatedObject(self, &AssociatedKeys.AccessabilityIdentifier) as? String
    }
}

So the above code would be rewritten:

    let alertView = UIAlertController(title: NSLocalizedString("NMN_LOGINPAGECONTROLLER_ERROR_TITLE", comment: ""), message: message as String, preferredStyle:.Alert)
    let okAction = UIAlertAction(title: NSLocalizedString("NMN_OK", comment: ""), style: .Default, handler: nil)
    okAction.setAccessibilityIdentifier(InvalidLoginAlertView_AID)


    alertView.addAction(okAction)


    self.presentViewController(alertView, animated: true, completion: {
        alertView.applyAccessibilityIdentifiers()
    })

My first attempt involved trying to navigate the view hierarchy but that became difficult since UIAlertControllerActionView was not a part of the public API. Anyway I'd probably would try to ifdef out the valueForKey("__representer") for builds submitted for the app store or Apple might give you a spanking.

Anthony Olds
  • 101
  • 1
  • 7
  • 2
    This worked. I just opened a bug with Apple in the hope they would do something if many of us complained. – user1366265 Jul 13 '17 at 15:44
  • Any idea how to set the accessibility identifier for the title and the message though? – user1366265 Jul 13 '17 at 15:45
  • Sorry I'm not sure. Only needed to set accessibility identifiers for automation purposes so never had the need to access the title or message. – Anthony Olds Jul 19 '17 at 00:20
  • Thanks @AnthonyOlds ! its worked for me. but I have one question why you used " __representer" ? is there any documentation for it? – Prema Janoti Jan 11 '18 at 12:08
  • I think these keys are undocumented but I learned that this specific key would work from the accepted answer in this stack overflow question: https://stackoverflow.com/questions/26460706/uialertcontroller-custom-font-size-color/36136625 – Anthony Olds Jan 14 '18 at 23:59
  • hacking into the apple api by using valueForKey provides the highest chance of getting rejected when applying for review – David Seek Aug 29 '18 at 17:56
  • Nowadays you could probably just use: `action.accessibilityIdentifier` since `UIAlertAction` now extends `UIAccessibilityIdentification` – Gering Mar 28 '23 at 12:04
1

Right now I have a UIAlertAction called addCamera and I'm just doing:

addCamera.accessibilityLabel = "camera-autocomplete-action-photo"

That allows me to tap it in UI Tests as follows:

app.sheets.buttons["camera-autocomplete-action-photo"].firstMatch.tap()

Alper
  • 3,424
  • 4
  • 39
  • 45
  • 17
    That's extremely unhelpful towards those who actually need accessibility help. – idrougge May 09 '19 at 13:41
  • You must not use "programming named" labels for testing, as they are read by users with accessibility enabled, instead use accesibility identifiers. – kikeenrique Dec 02 '20 at 18:15
-1

From Apple docs...

https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/UIKitUICatalog/UIAlertView.html

Making Alert Views Accessible

Alert views are accessible by default. Accessibility for alert views pertains to the alert title, alert message, and button titles. If VoiceOver is activated, it speaks the word “alert” when an alert is shown, then speaks its title followed by its message if set. As the user taps a button, VoiceOver speaks its title and the word “button.” As the user taps a text field, VoiceOver speaks its value and “text field” or “secure text field.”

tanya
  • 508
  • 4
  • 12
  • 4
    This does not mention how to set accessibility identifiers at all. – user1366265 Jul 13 '17 at 14:58
  • No it doesn't, but it's Apple's recommended way of accessing Alerts. – tanya Jul 14 '17 at 16:13
  • 5
    well this sucks especially if there are localizations involved in the process. – Zonily Jame May 24 '18 at 10:59
  • 2
    That text talks about making the alerts accessible to users, which is only slightly related to using an accessibilityIdentifier to access them for UI testing. An accessibilityIdentifier is not really related to the other accessibility* items except by name similarity; it's not used at all by users. – simpleuser Aug 16 '19 at 21:33
  • 2
    This is talking about UIAlertViews and not UIAlertController, it has nothing to do with the question – Alaa Eddine Cherbib Sep 09 '20 at 13:07