Accessing nested UIElements in Xcode 7 UI Testing - ios

I am having trouble locating XCUIElements on a screen for the app I am testing. I realize you can access a button for example via something like:
app.buttons[].elementBoundByIndex(0)
But the problem is sometimes, the component is not found. Like in a case where I have a Button in a cell in a UITableView. I try to make an XCUIElementQuery to find the button, and it is not there. I try to look for tables or tableviews or collection views and even though they are in the view controller, they are not found in UI Testing. The count of the returned array will be zero.
I attempted originally to record the test, but clicking the element I am trying to access did not work. Xcode detected it as an "Other Element" and when trying to tap during, playback the application does not advance.
Is there a high level way to access a component like a UIView high in the UI hierarchy to cascade down?

I didn't know this at the time, but what I was looking for was basically debugDescription:
Set a breakpoint when you hit the area you're trying to debug. Then
po print(app.debugDescription)
in the debug console. You will see the hierarchy then.

Ideally you should set an accessibilityIdentifier on your button and search for it via that. The accessibilityIdentifier should be unique for elements on the screen. You can set an accessibilityIdentifier in the Identity Inspector in Interface Builder (command-option-3) or in code directly. Once you have one, the query looks like:
app.buttons["SomeAccessibilityIdentifier"]

Ryan Poolos answer was the best answer for me as it solved my issue with nested UI Elements
We had to solve it by removing a few accessibility identifiers on superviews in the stack. Not ideal but did get it working without changing actual functionality. – Ryan Poolos
So with that in mind, I found the xib file with the element in question, selected the element, selected the tab "Show the Identity Inspector" on the right panel and unchecked the Accessibility checkbox.
I then did a recording of the element which resulted in:
[[[[[XCUIApplication alloc] init].scrollViews.tables childrenMatchingType:XCUIElementTypeOther] elementBoundByIndex:index] childrenMatchingType:XCUIElementTypeButton].element;
Notice how XCUIElementTypeOther is in the query. This was not the case when the accessibility identifiers were enabled as I would get:
[[[[[XCUIApplication alloc] init].scrollViews.tables.otherElements containingType:XCUIElementTypeStaticText identifier:#"username"] childrenMatchingType:XCUIElementTypeButton].element tap];
Obviously I wouldnt know the username as it would always change and this was a major problem. But after removing the accessibility identifiers,
containingType:XCUIElementTypeStaticText identifier:#"username"
changed to
childrenMatchingType:XCUIElementTypeOther] elementBoundByIndex:index
SUCCESS :)

In my own experience you being able to find an element (an UIView in your case as UIButton inherits from UIview) through its accessibilityIdentifier depends on how you added it to the view.
I assume that in your case you added the button to the cell programatically with addSubView. If that's the case, probably you will not be able to access to it. I've had the same problem and asked the question here but no proper solution at the moment, so my advice is try to avoid adding views with addSubView the moment...

Related

MKMapView Legal label shows as <unlocalized>

I've added an MKMapView as a subview on one of my ViewControllers in an XIB. The map works alright in terms of showing directions. But the Legal text on the map (bottom left) shows as <unlocalized> instead of having a text saying Legal in whatever language is selected (or even English).
How do I fix this? Thanks.
Note: My app supports 6 languages if it makes a difference (but none of the XIBs are localized)
I couldn`t reproduce this but it seems like an issue in the localization file. This guy has a similar problem and resolved it by removing localization files (and creating new I think).
Of course, you can always set directly your localized string to Legal MKAttributionLabel, by getting it from MKMapView subviews, and using setValue(_:forKey:) with _innerText and _strokeText keys. But notice that this is private API :)
I can think of 2 possible solution but didn't. One is to check which subview of all your MKMapView's subviews for that specific label and add move it out of the window or scale it down to 0. The other one would be similar but by subclassing you MKMapView and try to hide this label in layoutSubviews method.

iOS accessibility doesn't recognize anything on the screen

I'm trying to make my app accessible (use voice-over properly).
It works just fine in the first screen (login), but after the login no element gets the accessibility focus. It seems to be stuck.
Accessibility inspector's audit gives me this issue for all of the "should-be-accessible" elements in the screen:
This element appears to display text that should be represented using the accessibility API
When pressing the question mark I get:
Determine if any part of the content should be exposed as separate accessibility children
Does anybody have an idea? Did you get this warning?
P.S.
Apologize in advance, but I can't share my code because of security reasons.
Solved my problem.
Apparently, i've added another view after the login and then animated it off screen, but did not remove from superview.
It caused the app to lose accessibility focus (the focus was on the status bar only).
After removing the view, my app got the accessibility focus again.
My lesson from this problem - remove unnecessary views!
P.S
It's legacy code - wasn't written by me :)
You can follow three things :
You can all the view hierarchy and check whether accessibilityElements are properly set or not.
Unnecessary views isAccessibilityElement property should be set as false.
remove Unnecessary views.

Calabash iOS how to tap the back button item

Guys just cant figure out how to tap on BackButtonItem from Navigation Bar with Calabash framework, I'm setting accessibilityLabel like this:
self.navigationItem.leftBarButtonItem?.accessibilityLabel = "goBack"
and trying to test it like this with no luck:
touch("* marked:'goBack'")
touch_transition('navigationItemButtonView first',
"* marked:'#{goBack}'")
The problem is that UINavigationItem, UITabBarItem, and UIToobarItem are converted, at runtime, to Views. For example, a UITabBarItem is converted to a UIToobarButton. In the conversion, the accessibilityIdentifier and accessibilityLabel are not preserved.
# This will probably get you the left navbar button
query("UINavigationItemButtonView index:0")
There are ways to enforce that an accessibilityIdentifier is preserved, such as making the navigation item from a custom view.
Take a look at the briar bars/navbar.rb for inspiration. I do not recommend using briar in your project; its life is uncertain (I am the maintainer).
I'd recommend trying query "all * marked:'goBack'" and if that still doesn't return any results, just try a query "all *" and see if the label shows up in the results.
You could also try setting the accessibilityIdentifier of the view - generally that's the preferred way to set up views for automation.
Just updating it might be helpful for someone who wish to use default back button
touch('navigationItemButtonView first')
This will take you back.
Found from calabash predefined steps.

Does setting a button's UIAccessibilityTrait remove the word "button" from it's accessibilityLabel?

I ran into a weird situation just now. I have a container view in our app which needs to read out all of the views when tapped, but only one view should be able to be accessed individually. Because some of the views are complex views with their own subviews, I setup my ADA logic to read the accessibilityLabel of a view if it has one, and if not, to build one from the subviews.
Well, today we had to add the word "button" to the help button when it's being read because the container view was tapped. So I simply added the word "button" to the label's accessibilityLabel and everything worked fine. Then I realized, this may change it to read "more information button button" when tapped individually, since I had set the accessibilityTraits property of the button to be UIAccessibilityTraitButton.
However, to my surprise, it read it correctly as "more information button." Does Apple detect extraneous "button's" in an accessibilityLabel and remove them when the trait is set to be a button? Would they also remove extraneous "label's" from a button when it's trait is set to be a label? I find this extremely cool, and incredible forethought on Apple's engineer's parts, if this is the way it works.
Edit: If this is the way it's done, could someone point me to where this is documented? I wasn't able to find it, although I'm currently still working on this fix so I didn't do the most in depth search I could have done.
I've had some time now to do some proper testing. VoiceOver will remove the extra labels if you set the traits properly. This allows for you to have the type read in a container view, and still set an accessibilityTraits.
So, for example, if you set the accessibilityTraits to UIAccessibilityTraitButton and the last word in your accessibilityLabel is button, it will remove the last word and only read button once. If, however, you set the accessibilityTraits to `UIAccessibilityTraitImage', it will not remove the button, and will end its reading with "button image". Changing the last word to "image", however, has it only read image once.
Pretty nice forethought on Apple's part with that implementation.

UIAccessibility - containers

There's a "Containers" rotor option in Voiceover which allows the user to quickly navigate through "high level" sections of the screen via single finger swipe up and swipe down actions. For example, the Calendar app has three high level items: navbar, contents and toolbar.
My app uses custom UIView subclasses and, no matter what I try to do, all my views seem to belong to a single container. I can't split them into logical sections. I tried putting them in separate views implementing the UIAccessibilityContainer protocol and setting a few of the accessibility properties on the parent views.
Does anyone know how to create multiple containers?
I did some digging on this issue and think its a private trait Apple is using. First I noticed the only containers recognized are standard UIKit type objects like UITableViews, UITabBars, UINavigationBars, etc. So next I used the debugger to inspect the value of the accessibility traits for these components. They're all 0x200000000000. Just to be sure I didn't miss an UIAccessibilityTrait I checked all of their values. None of them match the value. Furthermore if you set your views accessibility traits to this mysterious value it'll work just like you want! I tried determining the location of this constant but didn't have much luck. If you want to do more digging it looks like apple stores accessibilityTraits using an NSObject category that uses associated objects with some constant value named AXTraitsIdentifier.
Practically speaking you could do something like the below but since its not defined in a public API its functionality could change in the future
//Note the navBar has to be run through a voice over pass before the value is set :( or you can just directly set the value to 0x200000000000.
myContainerView.accessibilityTraits = navBar.accessibilityTraits;
I'd love to hear if anyone one else has info on this? So far I haven't found an ideal solution.
I have been able to make the views in my app reachable by single finger swipe up and swipe down actions when the "Containers" rotor option is selected by setting the accessibilityContainerType property of my views to semanticGroup.

Resources