Is it possible to "toggle software keyboard" via the code in UI test?
Asked Answered
W

7

36

I have UI tests which testing login functionality (and uses it to test other stuff), but sometimes when focus is changed from one field to another - the keyboard hides, and although the cursor is blinking in the field, I getting error on field.typeText - no focused fields to fill.

Somehow I realized, that clicking on a Hardware -> Keyboard -> toggle software keyboard makes keyboard to persist on the screen, so test is works well. But I need to make it working on any testing device, on any developer machine, so I want to set this option programmatically without annoying "if test fails, go to … and set … by hand" in readme of the project.

Is it possible?

Weixel answered 24/6, 2016 at 9:52 Comment(0)
C
22

The simulator's .plist file changed to add support for multiple simulators. The ConnectHardwareKeyboard boolean is now nested underneath the device's UDID. Luckily this UDID is also stored in the plist file. You can add this code using 'run script' under your UITest target's build phases.

Xcode 9 answer:

#grab the UDID from the plist
UDID=$(defaults read com.apple.iphonesimulator CurrentDeviceUDID)

#overwrite the existing value with false
#OR if the plist doesn't have that value add it in
/usr/libexec/PlistBuddy -c "Set :DevicePreferences:$UDID:ConnectHardwareKeyboard 
false" ~/Library/Preferences/com.apple.iphonesimulator.plist 
|| 
/usr/libexec/PlistBuddy -c  "Add :DevicePreferences:$UDID:ConnectHardwareKeyboard
bool false" ~/Library/Preferences/com.apple.iphonesimulator.plist

Or you can use this other code to affect all simulators:

/usr/libexec/PlistBuddy -c "Print :DevicePreferences" ~/Library/Preferences/com.apple.iphonesimulator.plist | perl -lne 'print $1 if /^    (\S*) =/' | while read -r a; do /usr/libexec/PlistBuddy -c "Set :DevicePreferences:$a:ConnectHardwareKeyboard
false" ~/Library/Preferences/com.apple.iphonesimulator.plist || /usr/libexec/PlistBuddy -c  "Add :DevicePreferences:$a:ConnectHardwareKeyboard
bool false" ~/Library/Preferences/com.apple.iphonesimulator.plist; done
Closegrained answered 14/12, 2017 at 19:38 Comment(5)
You should avoid using PlistBuddy to edit preferences since it bypasses cfprefsd.Catechol
@JeremyHuddlestonSequoia What's the alternative?Sunstone
defaults, probably, but I don't think it can do the complex modification of property lists required here.Plovdiv
The answer is very good. But for me CurrentDeviceUDID as read from defaults differed from the device used for the test. I found TARGET_DEVICE_IDENTIFIER in the environment of the test that contained the correct device UDIDGrime
does not work for latest xcode...Submarginal
P
60

Tested in Xcode 10.3 & Xcode 11. The snippet below needs to be located in the app target (not the test bundle) — for instance, in AppDelegate.swift. It will disable any hardware keyboards from automatically connecting by setting any UIKeyboardInputMode's automaticHardwareLayout properties to nil.

🔥 Does not depend on the settings of the Simulator.

#if targetEnvironment(simulator)
// Disable hardware keyboards.
let setHardwareLayout = NSSelectorFromString("setHardwareLayout:")
UITextInputMode.activeInputModes
    // Filter `UIKeyboardInputMode`s.
    .filter({ $0.responds(to: setHardwareLayout) })
    .forEach { $0.perform(setHardwareLayout, with: nil) }
#endif

Or Objective-C:

#if TARGET_IPHONE_SIMULATOR
SEL setHardwareLayout = NSSelectorFromString(@"setHardwareLayout:");
for (UITextInputMode *inputMode in [UITextInputMode activeInputModes]) {
    if ([inputMode respondsToSelector:setHardwareLayout]) {
        // Note: `performSelector:withObject:` will complain, so we have to use some dark magic.
        ((void (*)(id, SEL, id))[inputMode methodForSelector:setHardwareLayout])(inputMode, setHardwareLayout, NULL);
    }
}
#endif
Phillane answered 23/8, 2019 at 0:1 Comment(8)
Do you know how to do it in AppDelegate.m?Alien
I don’t understand. Do you want the objective-c equivalent?Phillane
Yes please :-) Our app is a React Native app where AppDelegate is in objective-c.Alien
This seems to be the only solution that is working.Jeep
Can be added to AppDelegate:didFinishLaunchingWithOptions to great effect. Will fix tests, but won't even annoy you by altering the simulator setting. Top solution!Binette
Looks nice/working as expected in unit (vs UI) tests (so in this case it's part of the tests, not the host app): I'm doing some snapshot tests. I wonder though how to revert it properly, for the sake of less side effects in the tests...Sicard
Don't remove the , with: nil) from the swift code, it results in runtime exceptions.Crudden
For those using fastlane snapshot this is the working solution that worked for me. Thank you @ChrisZielinskiBullock
C
22

The simulator's .plist file changed to add support for multiple simulators. The ConnectHardwareKeyboard boolean is now nested underneath the device's UDID. Luckily this UDID is also stored in the plist file. You can add this code using 'run script' under your UITest target's build phases.

Xcode 9 answer:

#grab the UDID from the plist
UDID=$(defaults read com.apple.iphonesimulator CurrentDeviceUDID)

#overwrite the existing value with false
#OR if the plist doesn't have that value add it in
/usr/libexec/PlistBuddy -c "Set :DevicePreferences:$UDID:ConnectHardwareKeyboard 
false" ~/Library/Preferences/com.apple.iphonesimulator.plist 
|| 
/usr/libexec/PlistBuddy -c  "Add :DevicePreferences:$UDID:ConnectHardwareKeyboard
bool false" ~/Library/Preferences/com.apple.iphonesimulator.plist

Or you can use this other code to affect all simulators:

/usr/libexec/PlistBuddy -c "Print :DevicePreferences" ~/Library/Preferences/com.apple.iphonesimulator.plist | perl -lne 'print $1 if /^    (\S*) =/' | while read -r a; do /usr/libexec/PlistBuddy -c "Set :DevicePreferences:$a:ConnectHardwareKeyboard
false" ~/Library/Preferences/com.apple.iphonesimulator.plist || /usr/libexec/PlistBuddy -c  "Add :DevicePreferences:$a:ConnectHardwareKeyboard
bool false" ~/Library/Preferences/com.apple.iphonesimulator.plist; done
Closegrained answered 14/12, 2017 at 19:38 Comment(5)
You should avoid using PlistBuddy to edit preferences since it bypasses cfprefsd.Catechol
@JeremyHuddlestonSequoia What's the alternative?Sunstone
defaults, probably, but I don't think it can do the complex modification of property lists required here.Plovdiv
The answer is very good. But for me CurrentDeviceUDID as read from defaults differed from the device used for the test. I found TARGET_DEVICE_IDENTIFIER in the environment of the test that contained the correct device UDIDGrime
does not work for latest xcode...Submarginal
C
11

Prior to Xcode 9, you can work around this by disabling the hardware keyboard in Simulator.app which will cause the software keyboard to always be present. Eg:

defaults write com.apple.iphonesimulator ConnectHardwareKeyboard -bool NO
Catechol answered 26/6, 2016 at 7:24 Comment(5)
Looks like after enabling it by hand in simulator it stays enabled for UI tests too. Sad.Weixel
Yeah, but you can at least automate disabling it before running your UI tests and then re-enabling it after the UI tests. Please make sure you file a radar at bugreport.apple.com about this bug.Catechol
This doesn't work anymore since this property now belongs to each simulator udid, see my answer.Whitewall
I used this solution (from a different source) for a while, but it was randomly failing if the tests were being run through the console (for instance on the CI server). I switched to the solution where keyboard is disabled on AppDelegate and it seems to work now. The solution is: https://mcmap.net/q/418304/-is-it-possible-to-quot-toggle-software-keyboard-quot-via-the-code-in-ui-testBuskined
also does not work for latest xcode...Submarginal
W
6

Following Brooks great answer, this will work for all simulators:

/usr/libexec/PlistBuddy -c "Print :DevicePreferences" ~/Library/Preferences/com.apple.iphonesimulator.plist | perl -lne 'print $1 if /^    (\S*) =/' | while read -r a; do /usr/libexec/PlistBuddy -c "Set :DevicePreferences:$a:ConnectHardwareKeyboard
false" ~/Library/Preferences/com.apple.iphonesimulator.plist || /usr/libexec/PlistBuddy -c  "Add :DevicePreferences:$a:ConnectHardwareKeyboard
bool false" ~/Library/Preferences/com.apple.iphonesimulator.plist; done
Whitewall answered 13/4, 2018 at 19:53 Comment(6)
I can't mark two answers as correct, and your answer is rather enhancement of Brooks` answer, so I'd recommend you to edit his answer and add variant for all udids.Weixel
Not working on Xcode 9.4, returns Unrecognized Type: falseBranton
Tested and working on Xcode 9.4.1. These are not multiple lines, it's one long line. Also try not to paste rich text. Sometimes doesn't work on the first run. You can also try with this file github.com/evertoncunha/cocoaheads-2018-07/blob/master/fastlane/…Whitewall
Thank you for the script @EvertonCunha. Unfortunately for me when I run it, I can see that the plist gets edited but the simulator still doesn't show the keyboard. I even checked your gist where I saw that you included a command to kill the simulator but that didn't work for me either. Any clues?Eldreda
@RaphaelOliveira this is still happening to me. Seems like only 'toggle software keyboard' via the menu in the Simulator will persist it. Would love to know how to do this via the command line.Islean
It seems that the recent Simulator versions are very bugged, even custom menu shortcuts aren't working anymore :(Whitewall
S
2

Tested with Xcode 10.1 I tried different approaches but none of them solve how to get the simulator UDID

#Find the UDID of the booted simulator
#And use PlistBuddy to change the flag to true or false
#Set the variable useHardwareKeyboard with the desired result
#Relaunch the simulator
useHardwareKeyboard=false
export UDID=$(xcrun simctl list devices | grep "(Booted)" | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})")
/usr/libexec/PlistBuddy -c "Set :DevicePreferences:$UDID:ConnectHardwareKeyboard ${useHardwareKeyboard}" ~/Library/Preferences/com.apple.iphonesimulator.plist
xcrun simctl shutdown $UDID
xcrun simctl boot $UDID

You can also verify these changes.

Find your simulator UDID: (More here: https://nshipster.com/simctl/)

xcrun simctl list devices | grep "(Booted)" | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})"

Run one of these commands and find your changes based on the UDID from above:

defaults read com.apple.iphonesimulator
#OR
open ~/Library/Preferences/com.apple.iphonesimulator.plist
Steelwork answered 3/7, 2019 at 16:34 Comment(0)
H
2

inspired by real_kappa_guy s answer above, I managed to do it without restarting the simulator by checking if the keyboard is enabled and then running apple script to send the keyboard shortcut Cmd-shift-k (need to be added to pre-test action):

if test `/usr/libexec/PlistBuddy -c "Print DevicePreferences:${TARGET_DEVICE_IDENTIFIER}:ConnectHardwareKeyboard" ~/Library/Preferences/com.apple.iphonesimulator.plist` == true; then 
osascript <<EOD
  tell application "Simulator" to activate
  tell application "System Events"
      keystroke "K" using {command down, shift down}
  end tell
EOD
fi
Higa answered 23/11, 2020 at 11:49 Comment(0)
P
1

For Xcode 10.2, none of these solutions work for me, the plist is changed correctly but keyboard is till hidden. I have to use oascript to press Ctrl + Shift + K on Simulator if the keyboard is not displayed. It's not beauty but it's the only workaround that works.

tell application "Simulator" to activate
tell application "System Events"
    keystroke "K" using {command down, shift down}
end tell
Phyte answered 15/7, 2019 at 9:17 Comment(4)
Nice workaround. I faced similar problem and just found that after setting the defaults ConnectHardwareKeyboard NO, we have to restart the simulator may be using "killall Simulator" shell command.Hacker
I thought of using an AppleScript like yours, but the problem is that cmd-shift-K is a toggle, so if the simulator already has the hardware keyboard configuration you want, it will reverse it.Gillie
@GuillaumeLaurent in my case, I only call the script if the hardware keyboard is not displayed. how to determine it is totally depended on what use are using for your UI testsPhyte
thanks for this. The only problem with this is that it will re-enable the keyboard if it's disabled, so if you do a check before hand it works! (see my answer below)Higa

© 2022 - 2024 — McMap. All rights reserved.