In Appium release 1.8.1 the said it can generate IME actions.
Appium 1.8.1 Release
How can I generate IME event enter with action ID 5 / Create action Done or Next.
Also is it possible to fire it via ADB?
I know I can fire the command adb shell input keyevent 66 to get enter event. What I want is to add to this command actionId.
This is unit test written by Appium for generating IMEAction.
This test is written for APIDemos.apk which can be found here
public class KeyCodeTest {
final By PRESS_RESULT_VIEW = By.id("io.appium.android.apis:id/text");
final Activity activity;
#Test
public void pressKeyAndGenerateIMEActionTest() {
activity = new Activity(driver.getCurrentPackage(), ".text.KeyEventText");
driver.startActivity(activity);
driver.pressKey(new KeyEvent()
.withKey(AndroidKey.ENTER)
.withFlag(KeyEventFlag.SOFT_KEYBOARD)
.withFlag(KeyEventFlag.KEEP_TOUCH_MODE)
.withFlag(KeyEventFlag.EDITOR_ACTION));
final String state = driver.findElement(PRESS_RESULT_VIEW).getText();
// This event won't update the view
assertTrue(state.isEmpty());
}
I believe if we can run this, it should show adb command details in appium server logs
The definition of different KeyEventFlag can be read here
You can use driver.pressKeyCode(int key, Integer metastate) to generate IME actions.
Key codes can be found here: io.appium.java_client.android.AndroidKeyCode
Make sure you're using UIAutomator2 by doing desiredCapabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, "uiautomator2");
To do it via adb you could run a shell command or use appium's relaxed security (run your appium with CL arg --relaxed-security) and mobile: shell command.
Eg: driver.executeScript("mobile:shell", "adb shell input keyevent 66");
Here's a full example:
#Test
public void testKeyEvent() {
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("platformName", "Android");
capabilities.setCapability("deviceName", "Android Emulator");
capabilities.setCapability("automationName", "UiAutomator2");
capabilities.setCapability("appPackage", "com.your.app.package");
capabilities.setCapability("appActivity", ".your.Activity");
AndroidDriver driver = new AndroidDriver<>(new URL("http://localhost:4723/wd/hub"), capabilities);
// Press enter
driver.pressKeyCode(AndroidKeyCode.ENTER);
// Press Editor action
driver.pressKeyCode(AndroidKeyCode.FLAG_EDITOR_ACTION);
// Send adb command
driver.executeScript("mobile:shell", "adb shell input keyevent 66");
// Switch ime using adb shell
driver.executeScript("mobile:shell", "adb shell ime set 5");
}
Related
I am using Appium 1.4.16 to automate apk file stored in my system in real android device. I am using java-client 3.4.1
Here is the code:
public static void main(String[] args) {
File app = new File("C:\\Users\\dell\\Downloads\\App.apk");
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("device", " Android");
//mandatory capabilities
capabilities.setCapability("deviceName","Android");
capabilities.setCapability("platformName","Android");
capabilities.setCapability("udid", "849e82c6");
capabilities.setCapability("app",app.getAbsolutePath());
capabilities.setCapability("appPackage", "xxxxxxxxxxx");
capabilities.setCapability("appActivity", "xxxxxxxxxx.MainActivity");
try{
RemoteWebDriver driver = new RemoteWebDriver(new URL("http://127.0.0.1:4723/wd/hub"),capabilities);
System.out.println("Device Started");
driver.manage().timeouts().implicitlyWait(25, TimeUnit.SECONDS);
RemoteWebElement number = (RemoteWebElement)driver.findElement(By.xpath("//android.widget.EditText[#resource-id='xxxx' and #content-desc = 'Mobile Number']"));
number.sendKeys("90002");
RemoteWebElement passwordKey = (RemoteWebElement)driver.findElement(By.xpath("//android.widget.EditText[#resource-id='yyyyy']"));
passwordKey.sendKeys("ezr123");
RemoteWebElement loginButton = (RemoteWebElement)driver.findElement(By.xpath("//android.view.View[#resource-id='ezm_submit_login_form']"));
loginButton.click();
}catch(Exception e){
e.printStackTrace();
}
}
}
I am not getting any error in Appium server but facing strange actions in UI.
While entering value in username field, it automatically clicks on Login Button then again enters value in password field. Here, it always starts with entering '2' in the field and ends with 8-digit password.But my supplied password is 6-digit long.Then nothing happens.
I have also tried giving Thread.sleep() between each action.But no change is observed.
Best solution is to tap on the element first using the xpath or Id, then type on the element , this is fail proof and will work
U can also give timeout after tap and also hide keyboard by pressing key value for back (4)
Or hide keyboard command
I am getting following exception:
org.openqa.selenium.WebDriverException: An unknown server-side error occurred while processing the command. (Original error: Soft keyboard not present, cannot hide keyboard) (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 368 milliseconds
I am using driver.hideKeyboard() to hide soft input keyboard that is open on the screen.How to ensure that keyboard is open before hiding it? OR any other workaround?
I also get this error, i correct it by using the following code in the setUp method :
capabilities.setCapability("unicodeKeyboard", true);
capabilities.setCapability("resetKeyboard", true);
You can check answers here :
Keyboard in Android physical device isn’t always hidden while using Appium
Use adb command to check whether keyboard has popped up or not
adb shell dumpsys input_method | grep mInputShown
Output : mShowRequested=true mShowExplicitlyRequested=false mShowForced=false mInputShown=true
if mInputShown=true then yes software keyboard has popped up. Then use driver.pressKeyCode(AndroidKeyCode.BACK);
One way to do using java is
Process p = Runtime.getRuntime().exec("adb shell dumpsys input_method | grep mInputShown");
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String outputText = "";
while ((outputText = in.readLine()) != null) {
if(!outputText.trim().equals("")){
String keyboardProperties[]=outputText.split(" ");
String keyValue[]=keyboardProperties[keyboardProperties.length-1].split("=");
String softkeyboardpresenseValue=keyValue[keyValue.length-1];
if(softkeyboardpresenseValue.equalsIgnoreCase("false")){
isKeyboardPresent=false;
}else{
isKeyboardPresent=true;
}
}
}
in.close();
PS: Please do not use driver.navigate().back() as its behavior may not be same on all devices.
This picture is self explanatory I think.
Anyone know why this might be happening, and if I can fix it?
Android.OS.Debug.IsDebuggerConnected only returns true when the process has the Java/jdb debugger connected. Use System.Diagnostics.Debugger.IsAttached to detect when Mono/Xamarin Studio debugger is connected..
You can verify the above statement with the following steps:
1.
Create a new Xamarin.Android app named DebugTest with the following source code:
[Activity (Name="com.companyname.debugtestapp.MainActivity", Label = "DebugTestApp", MainLauncher = true, Icon = "#mipmap/icon")]
public class MainActivity : Activity
{
int count = 1;
protected override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
// Get our button from the layout resource,
// and attach an event to it
Button button = FindViewById<Button> (Resource.Id.myButton);
button.Click += delegate {
string androidConnected = Android.OS.Debug.IsDebuggerConnected ? "JDB Debugger" : "";
string monoConnected = System.Diagnostics.Debugger.IsAttached ? "Mono Debugger" : "";
button.Text = "Debugging mode: " + androidConnected + " | " + monoConnected;
};
}
}
Some important things to note:
Override the default name of the activity using Name=com.companyname.debugtestapp.MainActivity so it doesn't use the MD5 sum of the assembly name (Explained here).
The button will display if it detects the debugger that is connected. Either JDB or Mono.
2.
Start debugging the app via Xamarin Studio. Click the button to see:
"Debugging mode: | Mono Debugger"
This means we have detected the Mono debugger.
3.
Kill the app.
4.
Connect the Java debugger to the app via the following set of terminal commands:
# Start the main activity via adb. This will open it in debug mode, meaning it will wait until a debugger is connected before proceeding.
adb -d shell am start -D -n "com.companyname.debugtestapp/com.companyname.debugtestapp.MainActivity"
# Discover the Java Debugger port. Copy the number outputted here...
adb jdwp
# Forward the debugger port to JDB. Replace '7602' with the port number outputted by the command above.
adb forward tcp:8000 jdwp:7602
# Start the Java debugger...
jdb -attach 127.0.0.1:8000
You should see:
Initializing jdb ...
>
This specifies the Java debugger is connected.
On your phone, click the button. You should see the text **Debugging mode: JDB Debugger | **.
I want to run any app (say Settings) after rebooting tablet. Can I use os.system or do I have to use other methods.
import os,time
for i in range(0,3):
os.system("adb reboot")
time.sleep(60)
Yes, you can use os.system to execute ADB commands. If you want to validate the command executed successfully, take a look at the check_output(...) function which is apart of the subprocess library. This code snipet is how I choose to implement the check_output function. For the full code look here.
def _run_command(self, cmd):
"""
Execute an adb command via the subprocess module. If the process exits with
a exit status of zero, the output is encapsulated into a ADBCommandResult and
returned. Otherwise, an ADBExecutionError is thrown.
"""
try:
output = check_output(cmd, stderr=subprocess.STDOUT)
return ADBCommandResult(0,output)
except CalledProcessError as e:
raise ADBProcessError(e.cmd, e.returncode, e.output)
To launch an application you can use the command am start -n yourpackagename/.activityname. To launch the Settings App, run adb shell am start -n com.android.settings/com.android.settings.Settings. This stackoverflow question shows you in detail the options you can use to start the application via a command line intent.
Other tips:
I created an ADB wrapper written in python along with a few other python utilities that may aid in what you are trying to accomplish. For example, instead of calling time.sleep(60) to wait for the reboot, you use adb to poll the status of the property sys.boot_completed and once the property is set the device has finished booting and you can launch any application. Below is a reference implementation you can use.
def wait_boot_complete(self, encryption='off'):
"""
When data at rest encryption is turned on, there needs to be a waiting period
during boot up for the user to enter the DAR password. This function will wait
till the password has been entered and the phone has finished booting up.
OR
Wait for the BOOT_COMPLETED intent to be broadcast by check the system
property 'sys.boot_completed'. A ADBProcessError is thrown if there is an
error communicating with the device.
This method assumes the phone will eventually reach the boot completed state.
A check is needed to see if the output length is zero because the property
is not initialized with a 0 value. It is created once the intent is broadcast.
"""
if encryption is 'on':
decrypted = None
target = 'trigger_restart_framework'
print 'waiting for framework restart'
while decrypted is None:
status = self.adb.adb_shell(self.serial, "getprop vold.decrypt")
if status.output.strip() == 'trigger_restart_framework':
decrypted = 'true'
#Wait for boot to complete. The boot completed intent is broadcast before
#boot is actually completed when encryption is enabled. So 'key' off the
#animation.
status = self.adb.adb_shell(self.serial, "getprop init.svc.bootanim").output.strip()
print 'wait for animation to start'
while status == 'stopped':
status = self.adb.adb_shell(self.serial, "getprop init.svc.bootanim").output.strip()
status = self.adb.adb_shell(self.serial, "getprop init.svc.bootanim").output.strip()
print 'waiting for animation to finish'
while status == 'running':
status = self.adb.adb_shell(self.serial, "getprop init.svc.bootanim").output.strip()
else:
boot = False
while(not boot):
self.adb.adb_wait_for_device(self.serial)
res = self.adb.adb_shell(self.serial, "getprop sys.boot_completed")
if len(res.output.strip()) != 0 and int(res.output.strip()) is 1:
boot = True
Knowing the basic key mappings described in ADB Shell Input Events I get the emulation of text input and special keys working quite well. But what about Unicode characters? For instance I want to use umlauts from the German QWERTZ keyboard layout.
This gets me:
$ adb shell input text ö
Killed
So it seems to crash and
adb shell input text \xFC
prints xFC on the input. I have tried to the the events with getevent but I haven't found a direct mapping, I've also looked into the keymapping file /system/usr/keylayout/Qwerty.kl
I believe the only possibility is via the clipboard, but as pointed out in the question Pasting text into Android emulator clipboard using adb shell it seems to be unknown how to use it for Android Ice Cream Sandwich or later..
I wrote a virtual keyboard that accept broadcast intent, so you can send unicode characters to the editText view via adb.
for e.g.
adb shell am broadcast -a ADB_INPUT_TEXT --es msg "你好嗎! Hello!"
Here is the github project:
https://github.com/senzhk/ADBKeyBoard
Hope this little project would help.
Actually ADBKeyBoard is very good! Thanks for Eric Tang !
Some useful extensions for comfortable usage:
Switch to ADBKeyBoard from adb:
adb shell ime set com.android.adbkeyboard/.AdbIME
Check your available le virtual keyboards:
ime list -a
Use simple quote characters -not double as in example above- if your shell not accepts "!" (explanation sign)
adb shell am broadcast -a ADB_INPUT_TEXT --es msg 'Accented characters here'
Switch back to original virtual keyboard: (swype in my case...)
adb shell ime set com.nuance.swype.dtc/com.nuance.swype.input.IME
Use adb over wifi to simplify your life... :)
input won't work because it can only send single key event through the virtual keyboard (check the source code if you don't know what I mean).
I think the only way left is using Instrumentation. I guess you can create a test for your Activity and then do something like this:
final Instrumentation instrumentation = getInstrumentation();
final long downTime = SystemClock.uptimeMillis();
final long eventTime = SystemClock.uptimeMillis();
final KeyEvent altDown = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_GRAVE, 1, KeyEvent.META_ALT_LEFT_ON);
final KeyEvent altUp = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_GRAVE, 1, KeyEvent.META_ALT_LEFT_ON);
instrumentation.sendKeySync(altDown);
instrumentation.sendCharacterSync(KeyEvent.KEYCODE_A);
instrumentation.sendKeySync(altUp);
instrumentation.sendKeySync(altDown);
instrumentation.sendCharacterSync(KeyEvent.KEYCODE_E);
instrumentation.sendKeySync(altUp);
instrumentation.sendKeySync(altDown);
instrumentation.sendCharacterSync(KeyEvent.KEYCODE_I);
instrumentation.sendKeySync(altUp);
instrumentation.sendKeySync(altDown);
instrumentation.sendCharacterSync(KeyEvent.KEYCODE_O);
instrumentation.sendKeySync(altUp);
instrumentation.sendKeySync(altDown);
instrumentation.sendCharacterSync(KeyEvent.KEYCODE_U);
instrumentation.sendKeySync(altUp);
This will send the modified keys: àèìòù
update 2022
https://stackoverflow.com/a/71367206/236465 shows another solution using AndroidViewClient/culebra and CulebraTester2-public backend.