How to Assert an Obscured XCUIElement is Not Hittable on Screen with Xcode UI Testing - xcode-ui-testing

I hope I can clearly explain what I'm about to ask -- please be patient with me if the clarity isn't there yet.
I'm encountering a snag with UI Testing when it comes to views in the view hierarchy that are "hidden" behind other top-facing views. I know XCUIElement objects have the exists and isHittable interfaces that we can leverage, but these interfaces are not helping me in my trying to assert that an element is not hittable when it's obscured behind a view that's blocking it from sight.
Anybody have insight in how to properly assert this?

I believe the unfortunate answer is "You can't". XCUITest doesn't have a way to tell you if something is rendered visibly on the screen (or that something is in the UI View's foreground). You can, however, set userInteractionEnabled to false for the element itself when a view is drawn over it or otherwise have your app code signify that it is not intended for the user to access it; your code can then assert that the element is not enabled.
This could have the positive side effect of eliminating race conditions where you interact with a button while the view that would hide it is being drawn over it.
You can also simply assert the existence of the element that is obscuring the element you're asserting is obscured, or you can optionally use CGRect's logic to assert that the UI View being drawn over your hopefully-no-longer-visible UI element fully obscures it.

One weird behavior of exists() is that it returns true even if the element is off the screen. You have to jump through some hoops to make sure the element is actually visible to the user.
To test that an element is actually on screen we can assert that the element's frame is contained in the window's frame.
let window = app.windows.element(boundBy: 0)
let element = app.buttons["On"]
XCTAssert(window.frame.contains(element.frame))

I would use hittable() to ensure it is actionable

Related

Embedding NSStackView as NSTableCellView in NSTableView

I'm currently working on a prototype for a todo type app. I have a table which contains the user tasks. What I want to do is only present the user with pertinent task information. But to edit additional information, they would click on a disclosure button to expand the cell.
I was thinking of two possible ways to handle this:
Expanding NSTableViewCell
Using an NSStackView as the contents of each cell
If using the NSTableViewCell, I would probably have two NSViews to represent the cell (top part and lower part).
If using the NSStackView, I'd have an easy means of encapsulating the parts.
I suppose another method could also be just building it entirely with NSStackView.
The more difficult aspect of this seems to be related to the actual expansion/collapse of the cell.
I understand this could be deemed the type of question that's asking for an opinion. I've never built a MacOS app. So I'm looking for some guidance as to the best method to approach the problem versus spinning my wheels on approaches that are destined to not be productive.
Thanks!
In the end, it looks like the best thing to do is use an NSTableCellView with two NSViews for the top and bottom half. I had the case of this as well as the NSStackView working. But in the end, I found that using NSStackView to collapse or expand requires a call to make noteHeightOfRows work anyways.
So it would initially seem that it's not worth the effort of expanding it unless I have a more complicated cell where say I wanted a top, middle, and bottom, where the middle could expand and contract. While I would still need to use noteHeightOfRows, it would allow for it.
However, there is one benefit of using the NSStackView. The animation is much smoother for the collapse. I've found the NSTableCellView method with a top and bottom NSView shows signs of "tearing" as it collapses. This is what appears in the bottom edge, while horizontal, jitters. This is particularly apparent if you either spam the button or if the cell is selected because the bottom of the outline can sometimes grow in height.
I also found that when using NSAnimationContext to help make it look a little smoother, I'd see strange behavior. Like the hide would happen at the wrong time (even though it was in the completionHandler. I think the root cause of that are what becomes overlapping animations.

Tabbing between NSTextFields with nextKeyView

I have a single NSViewController with the following layout, set using a storyboard:
The nextKeyView outlet of each of the NSTextFields is configured to be the next NSTextField in the order presented on the screenshot. For example, I chose the server NSTextField in IB and dragged from the nextKeyView outlet in Connectivity inspector to the login NSTextField, and did the same for the rest of the fields.
When the app is launched, any tab press on any of the field moves the selection to the first NSTextField. How do I achieve the desired tabbing between the fields?
I tried this in the respective WindowController, but to no avail:
- (void)windowDidLoad {
[super windowDidLoad];
self.window.initialFirstResponder = self.serverTextField;
}
This seems to be the most detailed answer, from Justin Bur posted to cocoa-dev mailing list (31 Jan 2007).
On several occasions over the years, people have asked why their key
view loop doesn't work properly. Most of these queries never get
answered on the list. After failing to find help for my key view loop
problems either on this list or on web sites, I did some
experimenting.
The key view loop can be problematic to deal with. It is designed to
just work magically, so in most cases it's not an issue. But if it
doesn't work, it's pretty difficult to figure out why not. Here are
some guidelines for getting a working key view loop.
Consider whether you can settle for an automatically generated key view loop. Each responder's top left corner determines its placement
in the loop. The loop proceeds from upper left to lower right, row by
row (at least for left-to-right scripts). This is by far the easiest
solution. To enable this, make sure the window's initialFirstResponder
is nil. See also -[NSWindow recalculateKeyViewLoop].
If the automatic key view loop is not suitable, set up your own key view loop using Interface Builder as much as possible. The window's
initialFirstResponder outlet must be set, in order to disable
automatic key loop generation. From that responder around the loop,
set the nextKeyView outlet of each item in the loop. (If desired, the
last item's nextKeyView can be set to the first item, thus closing the
loop.) For any view with scrollbars (NSTextView, NSTableView, etc.),
you should use the enclosing NSScrollView when setting nextKeyView.
If you have any responders created in code, splice them into the key view loop early (preferably in awakeFromNib
or maybe -[NSWindowController windowDidLoad]).
For each (sequence of) new item(s), you must use call -[NSView setNextKeyView:] thus: once to make
the previous item point to the (first) new one, (calls to make each
new item point to the next), and finally to make the (last) new item
point to its successor.
If your window has a toolbar, toolbar items that are interested in becoming key view will automatically add and remove themselves as the
toolbar is shown or hidden. The toolbar does not take into account the
return value of -[NSWindow autorecalculatesKeyViewLoop]. Toolbar items
are always placed in the loop before the top leftmost item. There is
no easy way to change this.
Once the window has been displayed, it can be extremely difficult to modify the key view loop - in particular if you are using
NSScrollView or NSTabView. These (and others?) are special cases
because they insert their contained views into the loop automatically.
For information on the initialFirstResponder and key view loop of an
NSTabViewItem, see the AppKit release notes for OS X 10.1
.
If you have items that should sometimes be in the loop and other times not, it is not advisable to attempt to splice them in and out of
the loop. Instead, subclass -[NSResponder acceptsFirstResponder] for
these items. If an item returns NO from this method, it will be left
out of the loop (temporarily); if it returns YES, it will come back
into the loop. Alternately, if the item is derived from NSControl (it
probably is), you can call setRefusesFirstResponder: on it.
If you make a mistake, your key view loop will cease to function, either in one direction or in both. Once it breaks it stays broken. To
debug, comment out calls to setNextKeyView: or
setInitialFirstResponder: until it works again. The offending call is
likely trying to modify the key view loop in the presence of
NSScrollView or NSTabView, after these objects have already done their
behind-the-scenes loop-munging. Move the calls to an earlier point, or
do without. (If you have no calls to setNextKeyView:, then check your
nib - make sure the window's initialFirstResponder is set and that
nextKeyView outlets are chained together the way you want.)
In System Preferences/Keyboard & Mouse/Keyboard Shortcuts, at the bottom of the pane under "Full keyboard access", you can control
whether key view loops include all controls or only text fields and
scrolling lists (^F7 to toggle). You should test your key view loops
with this setting in each state.
These guidelines were determined by experiment and may not be entirely
accurate. Corrections and further explanations are most welcome.
Set the window's initialFirstResponder in windowDidLoad of the window controller or viewWillAppear of the view controller. If initialFirstResponder isn't set before the window's makeKeyAndOrderFront, recalculateKeyViewLoop is called.

How to keep a hidden view in responder chain?

I have a NSSplitViewController and in one of the items I have multiple buttons with keyboard shortcuts.
Once the user hides the item, the shortcuts don't fire.
Is there any way to keep the buttons in the hidden view as part of the responder chain?
Sounds like the simple answer is no, according to Apple's docs. A simple workaround, however, might be to move the buttons out of the visible area by, say, shifting their bounds right by 10,000 or so. If they are in a scrollview/clipview that would expand to show the items in their new position, then this would not work so well, but if they aren't, it ought to work fine. If they are in a scrollview, then you might find a way to make them completely transparent, to achieve a similar effect.
That said, perhaps it is worth considering whether you have the right design in the first place, since having buttons that are not visible respond to key events is a questionable design from a user-interface perspective (as reflected by the fact that Apple tries to prevent it). Maybe those keyboard events should really be getting handled by a view higher in the view hierarchy, or by the window, or some such entity?

Creating a view with draggable text elements

I am trying to create a view for a kind of brainstorming application like, for example, OmniGraffle, with elements that contain textviews and can be dragged around. (Also, the should be connectable with arrows, but that is not (yet) the problem)
I did my homework and searched via google and read books about cocoa, but there seems to be no similar example around.
Since I am also new to cocoa, I’m a bit helpless here.
The thing I am sure of is, that I need a custom view in which I can create my elements - what I tried until now to do that is:
First, I searched for the syntax to add subwindows to a window to create my elements. Subwindows, I imagined, would automatically be movable and come to front and so on.
The problem: As the experienced Cocoa-programmers of you probably are not surprised, I was stunned to find nothing about anything like that - this seems to be something, that is just not intended in Cocoa?!
Then I thought about creating subviews that contain a custom view for the title bar drawing (where the user can click to drag the element) and a NSTextView.
Problems:
I read, that it is not so clever to create dozens of subviews in a window because that would be very slow (or would that be not so bad in this case because all the subviews would be instances of always the same class?).
Also I can’t find out how to load a subview from a nib- or xib-file. Would I need a viewController? Or would that make the dozens-of-instances-problem even worse?
And Apple tells you not to overlap subviews (okay, that would be not so important, but I really wonder how the guys at OmniGroup made OmniGraffle...)
Because of that, I now wanted to do the title-bar-drawing in the surrounding custom view and create the textview programmatically (as I understand, a text-“view“ ist not really a view and takes its functionality from NSCell to reduce all the effort with the views?).
Problems:
Even that failed because I was not able to create a textview that doesn’t fill the complete window (the initWithFrame: of the [[NSScrollView alloc] initWithFrame: aRect] just seems to be ignored or do I get that wrong?).
Also, there should be some buttons on each element in the final application. I imagine that would be easier to accomplish with a subview from a nib-file for each element?
Well, now that nothing works and the more I read, the more problems seem to occur, I am pretty confused and frustrated.
How could I realize such a program? Could someone please push me in the right direction?
I created a class for the draggable elements where I save position, size and text in instance variables. In my view, every new element instance is added to an array (for now, this works without a controller). The array is used to draw all the elements in a loop in drawRect:. For the text of the element I just use a NSTextFieldCell which is set to the saved text from every element in the same loop.
That way it is also possible to overlap the elements.

Getting rid of the focus rect on an NSCell. Only shows up when right clicking

I have an NSCell subclass that I do all kinds of custom drawing in. The only time things seem to be entirely out of my control is when right clicking a cell to show the associated context menu. While showing it's context menu, the table (or cell - not sure which exactly is doing this) draws a focus rect. I would like to get rid of this, or at least find a way to draw my own version of it that better fits within my interface.
I have tried about 15 different methods that seem like they could give me control over this focus rect drawing itself (various first responder methods, various drawing methods, various highlight color methods, etc.), but I have yet to find something that changes anything about it.
Screenshot of the problem: http://twitpic.com/3zx2t
I am almost annoyed enough to class-dump AppKit and try to find whatever private method it's using to draw this annoying focus rect. Nothing else I do has any effect on it. Any help here would really save both my sanity and lots of future hair pulling.
Thanks so much!
Without completely overriding your table view's -drawRect: this is the only other way I know to get rid of the context menu highlight rect.
- (void)_drawContextMenuHighlightForIndexes:(NSIndexSet *)rowIndexes clipRect:(NSRect)rect {
return;
}
This is the method, used in Leopard at least, to draw the highlight around cells that will be activated upon by a context menu. It is unfortunately called directly by NSTableView's -drawRect: method and as far as I know there is no other way to signal your disinterest in having those highlights drawn.
Of course this is private API, it may stop working in Snow Leopard, or some other release. But at worst that means that somewhere along the way the context menu highlight may start up again in your programs on newer releases or some other functionality using this method may not work later. You have been warned.
On 10.5 and later, NSTableView allows you to bring up a contextual menu on any item in the table (that is, without having the side effect of changing the selection.)
It draws that 'focus rect' to indicate which item(s) are being acted on by the contextual menu.

Resources