XCUITest - neither element nor descendant has keyboard focus - swift

I am testing adding a comment to my app, on my other UI tests I have used the typeText function and everything works perfectly fine. I have also clicked to make Connect hardware keyboard' is off. The app terminates testing and shows the error UI Testing Failure - Neither element nor any descendant has keyboard focus during the addComment method. Any ideas?
func testAddComment() {
let featuredPage = self.app.tabBars["Featured"]
if featuredPage.exists {
featuredPage.tap()
}
sleep(2)
let featuredOffer = self.app.tables.cells.elementBoundByIndex(1)
if featuredOffer.exists {
featuredOffer.tap()
}
sleep(2)
let addComment = self.app.staticTexts["Add a comment"]
if addComment.exists {
addComment.tap()
addComment.typeText("Test comment")
}
sleep(2)
let postComment = self.app.buttons["Send"]
if postComment.exists {
postComment.tap()
}
sleep(2)
}

Likely cause:
An extremely common cause of this symptom is to have enabled a parent view of the field in question as an accessibility element. Ensure that none of the parents of the field are enabled as accessibility elements, and the problem is likely to be resolved.
Explanation
This error message can be particularly confusing when you can see that a previous step in the XCUITest has correctly activated a text field or similar as the first responder, the keyboard has appeared, and functionally the field has keyboard focus.
Essentially, the error message is slightly misleading as the tendency is to focus on the keyboard focus part and think that it is telling you that nothing has keyboard focus. Instead, what the message is telling you is that none of the accessibility elements that XCUITest can access have the keyboard focus. And so it can't direct text to the field in question.
Accessibility elements cannot have children that are also accessibility elements. Therefore, if you place a number of fields inside a view, but then mark that parent view as being an accessibility element, all of the field as its subviews become invisible to the accessibility frameworks. You can see and verify this is what is happening using the Accessibility Inspector app on the Mac alongside the simulator: the fields you are trying to target will not be selectable, and instead there will be a parent view that is.

I found the way around this best was to use menuItem and to paste what I wanted to the textField . This was a strange problem as both textField and staticText both didn't allow the test to run functionally. This is an issue I have reported to apple.
Here is my fix
let addComment = self.app.staticTexts["Add a comment"]
if addComment.exists {
addComment.tap()
UIPasteboard.generalPasteboard().string = "Test Comment"
let commentTextField = self.app.staticTexts["Add a comment"]
commentTextField.pressForDuration(1.1)
sleep(1)
app.menuItems.elementBoundByIndex(2).tap()
}

You can only use .typeText() on an input element. Static texts are not input elements - they're just text. Your addComment constant is probably the placeholder text within your text field. Tapping on the placeholder text probably activates the text field and keyboard, but you cannot call .typeText() on the placeholder text as the text field is its parent, not its descendant.
You need to call .typeText() on your text field, which should have focus after you tap in it.
let addComment = self.app.textFields["addCommentIdentifier"]
if addComment.exists {
addComment.tap()
addComment.typeText("Test comment")
}

You can try solving this issue by using .doubletap() before you & enter the value in .typeText(string) it worked for me.
let addComment = self.app.staticTexts["Add a comment"]
if addComment.exists {
addComment.doubleTap()
addComment.typeText("Test comment")

Turn off I/O -> Keyboard -> Connect Hardware Keyboard in the Simulator menu.
When this option is on, the Simulator doesn't bring up the soft keyboard, so the OS (sometimes) thinks the text field is not focused. Most of my tests have no problem typing in text fields with this option on, but some fail, especially search bars.

Related

Updating an NSTextField while controlling an NSTableView [duplicate]

What's the easiest way to have an NSTextField with a "recommendation list" dynamically shown below it as the user types? Just like Safari's address bar that has a menu of some sorts (I'm pretty confident Safari's address bar suggestions is menu since it has rounded corners, blue gradient selection, and background blurring).
I've tried using NSTextView's autocompletion facility but found it was inadequate:
It tries to complete words instead of the whole text fields – in other words, selecting an autocomplete suggestion will only replace the current word.
It nudges the autocompletion list forward and align it with the insertion point instead of keeping it align with the text field.
In the sample screenshot above whenever I selected the autocomplete suggestion the text field only replaces K with the suggested item in the list, which results in Abadi Abadi Kurniawan.
These are what I'd like to achieve:
Whenever a suggestion is selected, the entire text field is replaced with the suggestion.
Keep the suggestion list aligned with the text field's left side.
Note: This is not a question about adding progress indicator behind a text field.
The Safari address bar uses a separate window. Apple has example project CustomMenus and it only takes an hour or two to customize it.
Developer session explaining what has to be done Key Event Handling in Cocoa Applications
If you want to be able to select multiple words you need to provide own FieldEditor (credits should go for someone else)
- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(nullable id)client;
{
if ([client isKindOfClass:[NSSearchField class]])
{
if (!_mlFieldEditor)
{
_mlFieldEditor = [[MLFieldEditor alloc] init];
[_mlFieldEditor setFieldEditor:YES];
}
return _mlFieldEditor;
}
return nil;
}
- (void)insertCompletion:(NSString *)word forPartialWordRange:(NSRange)charRange movement:(NSInteger)movement isFinal:(BOOL)flag
{
// suppress completion if user types a space
if (movement == NSRightTextMovement) return;
// show full replacements
if (charRange.location != 0) {
charRange.length += charRange.location;
charRange.location = 0;
}
[super insertCompletion:word forPartialWordRange:charRange movement:movement isFinal:flag];
if (movement == NSReturnTextMovement)
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"MLSearchFieldAutocompleted" object:self userInfo:nil];
}
}
This only addresses half of your answer, but I believe you need to subclass NSTextView and implement the - (NSRange)rangeForUserCompletion method, returning the range of the entire string in the text field. This should make sure that it doesn't just autocomplete the most recently entered word.
If you want a custom menu, you're going to have to do that yourself, probably by implementing the -controlTextDidChange: method and displaying a custom view with a table when appropriate.

Another UIMenuController won't display problem

and thanks in advance for your help. I assure you that I've read most everything on here about UIMenuController problems. I really think I've covered it all. Clearly I've missed something.
In a nutshell, I'm trying to replicate the "Replace ..." edit menu behavior (but with my own function different than Replace). (If you're not familiar, when a word is selected, the Replace... option in the edit menu will bring up a second menu which shows possible alternate spellings for the word.)
In a UITextView (sub-classed), I select some text. The default gesture recognizer causes the edit menu to come up with the expected items, including my added "Translate..." option. When I click on "Translate..." in the menu, the menu closes and invokes my selector code. That code changes the menu items to the sub-choices I want. I call
UIMenuController.shared.showMenu(from: self, rect: textBounds).
I see the calls to canPerformAction() to verify that the "sub-menu" items I've added are recognized, but the menu never shows up. The notification for willShowWindowNotification (which occurs when the first menu is opened) does not happen for this sub-menu.
Here is the code:
#objc func translateSelectionMenu()
{
let sharedMC = UIMenuController.shared
// Create menu choices for the translate sub-menu.
let charChoice = UIMenuItem(title: "To Chars", action: #selector(translateChars))
let byteChoice = UIMenuItem(title: "Byte Decimal", action: #selector(translateByte))
let halfChoice = UIMenuItem(title: "2-Byte Decimal", action: #selector(translateHalf))
savedMenuItems = sharedMC.menuItems
sharedMC.menuItems = [charChoice, byteChoice, halfChoice]
... for brevity, I've omitted the code here which determines the bounds of the user's
text selection. The resulting numbers are shown below.
let textBounds = CGRect(x: 114.1, y: 73, width: 48, height: 55)
// let windowBounds = convert(textBounds, to: nil)
// sharedMC.update() not needed
self.becomeFirstResponder() // TextView is already the first responder. This does nothing.
sharedMC.showMenu(from: self, rect: textBounds)
}
Note that the TextView IS and must remain first-responder. (Changing it loses the users selection.) So I've implemented all of this in the subclass of the UITextView that is showing the user's text. I have tried using the UITextView-referenced bounds and the window-referenced bounds but neither works.
If I move one of the end-points of the selected text or just click in the selection, this causes the menu to be shown again, and it has my sub-menu items in it as expected. I know this should work because "Replace..." does it all the time.
Things I've verified:
My sub-class of UITextView is a UIView.
UserInteractionIsEnabled is true (since I can select the text).
There is only one window, but I am calling self.window.makeKeyAndVisible() at the point where canBecomeFirstResonder is called.
I have implemented canBecomeFirstResponder() (returning True). (It is called right before the gesture recognizer brings up the first menu but not after that.)
I do call self.becomeFirstResponder() (even though it already is).
I have implemented canPerformAction(). This is called a lot both with first-menu and sub-menu items. I return True for the items I want to use.
What else? Thanks!!
I asked Apple for help on this. The fix is to add
sharedMC.hideMenu()
right before the call to showMenu().
I think the issue is that my code is not what had presented the Menu originally and so I had to hide it before my code could show it. I note (from notifications) that the menu was not officially "hidden" at all (even though it was no longer visible after pressing my Translate... button).
I also tried just changing the menuItems and calling update(), but that also didn't work, probably again for the same reason.

XCode UITest will not give NSTextField keyboard focus

So I have a window appear by clicking on a menu item. Normally the the first textBox would gain immediate keyboard focus, but in the UITest nothing in the window gains keyboard focus (no focus ring). This is problematic becuase it stops me from typing into the textfield with textField.typeText("someText")
I have tried .click() on the window and on the textbox to try and give it focus but nothing seems to bring the window or textbox in focus.
Any help will be greatly appreciated
Example
MainMenu().menuItemForWindow.click() // Opens window
app.windows["windowTitle"].textFields["textBoxId"].click() // Clicks but field does not gain focus
app.windows["windowTitle"].textFields["textBoxId"].typeText("SomeText") // Is not typed into the textField
As a side note, I have verified that all the elements I am querying do actually exist.
EDIT:
I have gotten it to work by literally spamming typeText() until it changes the value of the given text box with something like
if let oldValue = textbox.value as? String {
var newValue: String? = oldValue
while newValue == oldValue {
textbox.typeText("a")
newValue = textbox.value as? String
}
//If we get here then we have edited the text box
textbox.typeText(XCUIDeleteKey) // Get rid of spam text
//TYpe what I want
//...
}
However this method is hacky and I can't really put a time out except from heuristics (roughly 15-30s) so I was hoping someone could help explain a better way of ensuring focus on the textbox or at least an explanation of what I am doing wrong originally. Any help would be greatly appreciated.
Here are two possible ideas for you:
Assign an Accessibility Id to the object you are trying to interact with, and try addressing it via the Accessibility Id. From https://blog.metova.com/guide-xcode-ui-test:
For element identification there is additionally elementMatchingPredicate(NSPredicate) and elementMatchingType(XCUIElementType, identifier: String?).
These are separate from containingPredicate(NSPredicate) and containingType(XCUIElementType, identifier: String?) which are checking the element for items inside it whereas the elementMatching... options are checking the values on the element itself. Finding the correct element will often include combinations of several query attributes.
Enable Accessibility in the System Preferences | Accessibility as described here: https://support.apple.com/en-us/HT204434 and then send keyboard commands to set your focus.

How to set first responder for NSTextView in Swift?

Edit: in a macOS project
I have a simple ViewController which I display as popover on a status item menu app.
I change the text of the view text with a NSTableView, depending of which item is clicked. The code I use is similar to this one:
mainTextField.insertText(newStr, replacementRange: theRange)
(I use insertText for the purpose to have the change recorded in undo manager)
Then I highlight the text:
// create the new NSRange
let range = NSRange(location: startRange, length: newStrLength)
// select the range in field
mainTextField.selectedRange = range
All work fine, except that the text is highlighted but with a light grey instead of the usual sky blue, indicating that the control is not the first responder. And when I click on the field the selection disappear.
Actually I would like that the NSTextView becomes first responder so I can directly copy the selected text.
Edit: if I press Tab key on the keyboard I got the textView to become first responder (and the grey selection becomes standard sky blue).
Corrected Answer
In AppKit, you need:
if mainTextField.acceptsFirstResponder {
mainTextField.window?.makeFirstResponder(mainTextField)
}
In this case, it's probably safe to not check acceptsFirstResponder, but it doesn't hurt either.
Original Answer (UIKit)
You need to call mainTextField.becomeFirstResponder().

How to remove/disable On-Keyboard Textbox when Using UIInput NGUI?

I am using NGUI's UIInput class to accept text input from user. when i start typing in text box in mobile devices. a key board appears and it has an another textbox within it self, with "OK"/"Done" button (Like a keyboard accessory view, if we're talking about iPhone).
Can i disable that text box appearing within keyboard ? Or its not even possible and i am shooting just blanks ?
From what i could gather by search for a while is, the appearance of keyboard is handled buy Unity's "TouchScreenKeyboard" class. but according to Unity Scripting reference there is nothing which could hide the textfield inside the keyboard.
Unity Scripting reference: TouchInputKeyboard
PS:- I should still be able to put input in textbox by directly typing into them, i just want an extra textbox within the key board to be removed.
TO be more clear i have attached images explaining this
This is the screen.
When i start typing in one of the textbox. a keyboard appears like the following.
as you can see the text box just above the keyboard is not the original one.
Did you try checking "Hide Input Check box" in Inspector view of that UIInput Textbox ?
private void OnGUI()
{
TouchScreenKeyboard.hideInput=true;
}
I don't know why it is, but I have had this problem as well and the "hide input" checkbox for some reason doesn't seem to do really anything other then change the keyboard text box from one line to multi line.
I did a little bit of digging and came across a quick lead that will enable that hide input check box.
This fix is Update() in UIInput.cs around 650
else if (inputType == InputType.Password)
{
TouchScreenKeyboard.hideInput = true;
kt = TouchScreenKeyboardType.Default;
val = mValue;
mSelectionStart = mSelectionEnd;
}
else
{
if(hideInput)
{
TouchScreenKeyboard.hideInput = true;
}
else
{
TouchScreenKeyboard.hideInput = false;
}
kt = (TouchScreenKeyboardType)((int)keyboardType);
val = mValue;
mSelectionStart = mSelectionEnd;
}
I added a check in the else statement