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.
Related
I am using an NSCollectionView where each NSCollectionViewItem uses a QLPreviewView to get a rendering of a file's content.
(This is an attempt at a file browser for images and other previewable files.)
Initially, this works fine.
However, once collection items are getting re-used, I get an assertion error (both in 10.13 and 10.14):
[QL] Assertion failure (unreachable code) - [… MyPreviewView activated … doc:[QLPreviewDocument …]] is already activated
Apparently, before I can re-use a NSCollectionViewItem, the previously used QLPreviewItem needs to be set to inactive state somehow. How do I do that?
I've tried to send the close message to the QLPreviewView instance but that doesn't make a difference.
I also do not get a dealloc call on my QLPreviewView subclass, which suggests that the object is still referenced by something else, possibly the QLPreviewDocument, which then gets confused about the change of state.
I have made a demo project available on github: https://github.com/tempelmann/NSCollectionViewWithQLPreview
To test: Run it, then scroll down. When reaching items 50 to 60, the assertion will be triggered.
The fix is to set QLPrewiewView's shouldCloseWithWindow property to NO.
This, I suspect, tells the controller behind the scenes not to attach itself to higher level structures, i.e. tells it to remain self-sufficient.
So, adding this line to the code that sets up a new MyPrewiewView object in the sample code's ViewController.m file prevents the error:
qlView.shouldCloseWithWindow = NO;
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.
Inside one my tabview tabs I have a 'Logout' button with this
var application = require("application");
application.run({ moduleName: "app-login" });
However I get:
RangeError: Maximum call stack size exceeded
when the code is executed. Ultimately I'm trying to get back to the login screen when the user needs to logout, in full screen (i.e not within the tab). The architecture of the application is based similar to the tabview-template example, with frames for each tab.
Any help much appreciated!
Indeed the application.run() is the method that bootstraps your app. You shouldn't call it twice.
The nested frames feature isn't officially supported and isn't documented, but it's possible in NativeScript core. The reason it's not yet official is due to some unexpected side effects with ActionBars and such. In your scenario you should simply get the correct Frame and call navigate() on it. I created a simple playground example here - https://play.nativescript.org/?template=play-tsc&id=tq6B2K
The key in this example is to assign an id to each Frame in your xml, so that you can use the getFrameById() method to find the correct Frame in the hierarchy.
Also, note that I have set actionBarHidden to true on the page with the tabs. If this is not set, you would see two nested ActionBars.
I may be misunderstanding your architecture, but I don't think calling the bootstrap function application.run({ moduleName: "app-login" }); more than once in the app's lifecycle is valid as the app is already running (regardless the screen it's on).
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)
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.