UI XCTest, elements which exist in view - xcode

I am new to UI test writing.
I wanted to know if it is possible to know what elements are/exists in view. I want to know how many and how they are called.
I tried something like this to loop through all elements, but it does not work.
for element in app.accessibilityElements! {
print(element)
}

You're looking for the debugDescription method of XCUIElement, in your case to get the entire hierarchy of the current visible window:
app.debugDescription
Quoting header comments:
Provides debugging information about the element. The data in the string will vary based on the
time at which it is captured, but it may include any of the following as well as additional data:
• Values for the elements attributes.
• The entire tree of descendants rooted at the element.
• The element's query.
This data should be used for debugging only - depending on any of the data as part of a test is unsupported.

You could always have a break point in the testcase and then make some print calls to the console.
po app.accessibilityElements
po app.accessibilityElements.elementBoundByIndex(0)
po app.buttons["Icon Menu Light"]
etc.
And see what comes back in the output view hierarchy to reference, or just a simple call to po app will print the hierarchy.
Once you know a particular view exists.. app.buttons["Icon Menu Light"].exists.. you can then try using something like this to confirm the button/element is visible within the current view by passing it to a helper function like:
func isElementInView(element: XCUIElement) -> Bool {
let window = XCUIApplication().windows.elementBoundByIndex(0)
return CGRectContainsRect(window.frame, element.frame) }
This will tell you whether the element is visible on the screen, because the element.exist call will return true, even if the element is off screen and hasnt been shown to the user (i.e. if something hides off screen and then transitions into the frame)

Related

How can I determine what part of text in a scroll view is visible on screen from an Xcode UI test?

I'm new to the Xcode User Interface testing framework. I can successfully manipulate the screen elements, but cannot work out how to produce a meaningful assertion about what text is visible in a scrolling view.
The test I would like to write would go as follows: launch the app, type lots of text into a text view (enough that the first line scrolls out of view), assert that the first line of text is not visible, scroll the view back up to the top, then assert that the first line is now visible. Note that the purpose of this test is to ensure my app has wired things up correctly, not to test Apple's code.
XCUIApplication allows me to type into my NSTextView instance, and also allows me to scroll the associated NSScrollView. But how do I assert whether the first line of text is currently visible? The value attribute on XCUIElement provides the entire text content of the view, whether or not it is currently displayed.
The accessibilityRange(forLine:) and accessibilityString(for:) methods on NSTextView would be ideal, but I can't see how to access them as the UI test only has access to an XCUIElement, not the underlying NSTextView.
Have I missed something, or is there a better way to approach this?
If you set the accessibility identifier in the storyboard or in code for the text view you can get the text view via (assuming you gave it the id "textview1" and the window it's in has the default accessibility identifier of "Window"):
let textview1TextView = app.windows["Window"].textViews["textview1"]
but that won't actually get you what you need.
Instead, set the accessibility identifier of the scrollview and get that:
let scrollview = app.windows["Window"].scrollViews["scrollview1"]
Then use that to get the scrollbars (you should only have one in this case; you can use scrollbars.count to check.
let scrollbars = scrollview.scrollBars
print("scrollbars count: \(scrollbars.count)")
Then you can use the value attribute of the scrollbar to get it's value:
(you're converting a XCUIElemenTypeQueryProvider into an XCUIElement so you can get it's value):
let val = scrollbars.element.value
it will be 0 at the top and a floating point value when scrolled (one line of text in my test code showed a value of {{0.02409638554216868}}.
Documentation that will help you explore further:
XCUIElementTypeQueryProvider
XCUIElementAttributes
Note that you can put a breakpoint in the middle of your test, run it and then use the debugger console to examine things:
(lldb) po scrollbars.element.value
t = 749.66s Find the ScrollBar ▿ Optional<Any>
- some : 0
(lldb) po scrollbars.element.value
t = 758.17s Find the ScrollBar ▿ Optional<Any>
- some : 0.05421686746987952
and while in the debugger you can even interact with your app's window to scroll it manually (which is what I did between typing in those two po calls), or perhaps add text and so on.
OK OP now noted that they're interested in the specific text showing or not rather than the first line in view or not (which is what I previously answered above).
Here's a bit of a hack, but I think it'll work:
Use XCUICoordinate's click(forDuration:, thenDragTo:) method to select the first line of text (use the view frame to calculate coordinates) and then use the typeKey( modifierFlags:) to invoke the edit menu "copy" command. Then use NSPasteboard methods to get the pasteboard contents and check the text.
Here's a quick test I did to validate the approach (selecting the first line of text using XCUICoordinate as noted above is left as an exercise for the reader):
NSPasteboard.general.clearContents()
// stopped at break point on next line and I manually selected the text of the first line of text in my test app and then hit continue in the debugger
textview1TextView.typeKey("c", modifierFlags:.command)
print( NSPasteboard.general.pasteboardItems?.first?.string(forType: NSPasteboard.PasteboardType.string) ?? "none" );
-> "the text of the first line" was printed to the console.
Note that you can scroll the selection off screen so you have to not scroll after doing the select or you won't be getting the answer you want.

How to deal with recycling lists

I'm building a UI test suite for an iOS app using XCUITest api. The app uses recycle lists and I need to access specific cells of those lists during my tests as shown in the code below:
let cells: XCUIElementQuery = app.descendants(matching: XCUIElement.ElementType.any).matching(identifier: "cells_accessibility_id")
let cell: XCUIElement = cells.element(boundBy: index)
cell.tap()
My problem is that since this is a recycle list, as soon it scrolls by any reason during the test (like animations), cells are unloaded, "cells" won't return all items and then "index" won't get the right cell from the list or becomes out of bounds.
Is there another way that I can retrieve the whole list regardless of element visibility? Or do I have to change my tests/try another approach?
You can not rely on the indices of reusable cells as you pointed out, however there is usually a way to eliminate the dependency on having to test cells at certain indices. The solution will depend on what you want to test, but here are some possible alternative strategies:
If your table cells are always the same, you could give them each a hard-coded identifier based on their contents.
If your table cells have dynamic content in them, you could use stub test data for each test to make it so that there is only one cell on screen (the one which is relevant to the test).
If you can identify the cell you want by the views contained within the cell, you can search each cell for the relevant views before selecting it, instead of relying on its index.

Xcode UITest sometimes does not find property of XCUIElement

In my UI tests, the frame property of some XCUIElement are found, but not of others.
The accessibility identifiers used below are set in storyboard, and app is initialised in setUp() as XCUIApplication().
Here is the storyboard layout:
The two UI elements used in the test are Text Field and Add Button.
Here is the relevant code:
func test() {
// given
let mainViewNavigationBar = app.navigationBars[„NavBar“]
let navBarHeight = mainViewNavigationBar.frame.size.height
print("navBarHeight: \(navBarHeight)") // is printed out correctly
let addShoppingItemTextField = app.textFields["TextField"]
let textFieldHeight = addShoppingItemTextField.frame.size.height // error breakpoint here
print("textFieldHeight: \(textFieldHeight)")
}
The test stops at an error breakpoint at the second last line with the following message:
No matches found for Find: Descendants matching type TextField from input {(
Application, 0x60000019f070, pid: 13114, label: ‚xxx‘
)}
I do not understand why the frame property, which should be defined for all XCUIElement, is found in the first case, but not in the second.
EDIT
Oletha pointed out below, that my constant addShoppingItemTextField is an XCUIElementQuery that should be resolved when I try to read the frame property of the textField.
Indeed, when the program stops at the test error breakpoint and I print its description, I get
Printing description of addShoppingItemTextField:
Query chain:
→Find: Target Application 0x6080000a6ea0
↪︎Find: Descendants matching type TextField
↪︎Find: Elements matching predicate '"TextField" IN identifiers'
But the find fails, although Accessibility is enabled, and the Accessibility Identifier is set to TextField:
I also inserted in the app
print(textField.accessibilityIdentifier!)
in viewDidLoad(), and it printed out TextField correctly.
As a workaround, I set the test to recording, and tapped the textField. This created code for the access to the textField. I then replaced let addShoppingItemTextField = app.textFields["TextField"] by (the right side was generated by the recording):
let addShoppingItemTextField = app.otherElements.containing(.navigationBar, identifier:"WatchNotOK")
.children(matching: .other).element.children(matching: .other).element
.children(matching: .other).element
And now the code works without errors.
So it seems to me that the query for the accessibility identifier of a textField does not work correctly.
EDIT 2
I give up: Without changing anything in the storyboard, the test now stops with the same test error (No matches found for Find: Elements matching predicate '"WatchNotOK" IN identifiers‘) at the line let navBarHeight = mainViewNavigationBar.frame.size.height. This did work all the time.
This indicates to me that Xcode UI tests are broken.
I contacted Apple, and they found my bug:
The view of my main view controller had its accessibility property set to true. This was wrong; it must be set to false:
The explanation is found in the docs to isAccessibilityElement:
The default value for this property is false unless the receiver is a standard UIKit control, in which case the value is true.
Assistive applications can get information only about objects that are represented by accessibility elements. Therefore, if you implement a custom control or view that should be accessible to users with disabilities, set this property to true. The only exception to this practice is a view that merely serves as a container for other items that should be accessible. Such a view should implement the UIAccessibilityContainer protocol and set this property to false.
As soon as I set accessibility of the main view to false, the UI test succeeded.
In addition with above answers... I would like to add one point
This may happen because the XCUIElement you are accessing is not available on screen.
Suppose you are executing test case for login screen and simulator is launching with dashboard not with login screen. This happen with my case. I tried to logout and then execute test case. Error disappears
The problem is not that the frame property is not found on the element, it's that the element itself could not be found.
Every XCUIElement is derived from an XCUIElementQuery. The first attempt to resolve the query is not, as you might expect, when you assign the value of addShoppingItemTextField, but the first time you access a property (other than exists) on addShoppingItemTextField.
Therefore, when you try to access the frame property on the XCUIElement object, the query for finding that element is resolved, but the element is not found - so you get the error saying 'No matches found...' on the line where you access frame. This can be a bit misleading, but the problem you're encountering is that the element could not be found. Try adjusting your query.

UI Automating a VB6 application from .NET

For the last few days i have been trying to figure out the best way to get AutomationElement for a specific control in a vb6 application.
My initial way of doing so was by doing a search with the following condition:
new PropertyCondition(AutomationElement.NameProperty, controlName)
I was under the assumption that this was working correctly for about a week in a little test VB6 application.
but i few days ago i realized something... when i dragged a vb6 textbox into the form, the 'Name' property and 'Text' property were both set to 'Text1'
So when i searched with:
new PropertyCondition(AutomationElement.NameProperty, 'Text1')
it return the correct element, but if i then went and set the 'Text' property to '' the same search would bring nothing back.
Question: Has anyone found a way to get a AutomationElement based on a the VB6 control name
What i have tried:
getting the MSAA equivalent interface and looking at the 'Name' property - Result: ''
http://msdn.microsoft.com/en-us/library/windows/desktop/dd318490%28v=vs.85%29.aspx
getting the control based on other properties (AutomationId, RuntimeId) - Result: AutomationId - not all controls seem to have this property available - RuntimeId - changes each time the app runs
I have looked at alot of different sites the main one is listed below - while some say they have manage to get it working - i don't believe i can see how they do it.. or i just dont understand it :$
http://blogs.msdn.com/b/brianmcm/archive/2006/01/17/getting-the-winforms-id-of-a-control.aspx
While i have access to the demo app, i will not access to the production app as that has been created by a third party.
What i plan on doing from here is to get the Automation element based on their position on the form..
Thank you
Can't comment due to low rep. Do you absolutely HAVE to have an AutomationElement?
You may want to look at invoking [user32.dll] (http://pinvoke.net/default.aspx/user32.EnumChildWindows). Look at FindWindowEx, GetWindow, EnumWindows, EnumChildWindows, GetWindowText, etc.
You need the handle of the parent window, so you can use this loop to get it. From there you can use the other functions to get the information you need about the control.
IntPtr hWnd = IntPtr.Zero;
foreach(var process in System.Diagnostics.Process.GetProcesses())
if(condition)
hWnd = process.Handle;
Comment with the exact information you need out of the VB6 window, and I'll give you better code.
You can use the relational position of the AutomationElement in a specific Window (or any other container for that matter), in order to detect it. For example, if you have 5 TextBox AutomationElements in your main window, and you're certain that the order will not be changing, you could create a PropertyCondition on the TextBox class name, and then use the FindAll method to return a collection of AutomationElements and iterate through it, querying the BoundingRectangle property to find out which is the lowest one (or middle, or any other position, for that matter).
I would create a helper method that would return a Dictionary<int,AutomationElement> with the key being the visual position of the AutomationElement, and the value being the AutomationElement itself.
This way you can avoid using a specific Point on your screen (any window size change or element positioning will easily break your code) while not being bound to the AutomationId property.

understanding Xcode debugger var display

newbie question: can anyone tip me to how to understand/interpret what is displayed in the debugger var pane?
Ex: I am passing an NSDictionary as a method param. I set a breakpoint so I can examine the values in the dictionary. The image below (if it comes through..) shows the expanded view of this var in the debugger. It correctly reports that it contains 3 name/value pairs but as I expand all the sections, I simply can not find where these are stored.
Do I have to create local vars of these name/value pairs in order to view them when I want to check? I know I can use NSLog or printf but sometimes I just want a quick peek.
Right click the variable, click "Edit Summary Format" and type the following:
{(NSString*)[$VAR description]}:s
This replaces the GDB formatter for NSDictionary with a call to the more expensive description method.
That is, instead "x key/value pairs", you'll see the contents of the dictionary as produced by -[NSDictionary description].
This is the same as typing po dictionary in the console window. Or right clicking the variable and choosing "Print Description". Both of them call the description method of the object.
If you are curious, you can find this formatter at /Developer/Library/Xcode/CustomDataViews/Foundation.plist under the key NSDictionary. What you type as replacement is saved in /Users/USERNAME/Library/Developer/Xcode/UserData/Debugger/CustomDataFormatters and will persist across runs until you delete that file.
A NSDictionary is really a class cluster and few people know the inside structure. At this point you ain't going to find much use for that debugger tree.
This is what you are looking for :
Click on your dict, then click on the little "i" icon :-)

Resources