Espresso 自动化测试(四)-中文字符的输入
前言
由于Espresso 也是用于做UI自动化测试的,所以我们难免要拿它来跟UiAutomator进行比较了。 使用过UiAutomator的都应该知道,它不支持中文的输入,为此Appium引入了专门的appium的输入法来解决这个问题,那我们来试试看Espresso是否能够支持中文呢。
中文的支持
onView(withId(R.id.editTextUserInput)).perform(typeText("你好"));
代码很简单就是对一个文本框进行内容的输入。我们看看运行的结果
java.lang.RuntimeException: Failed to get key events for string 你好 (i.e. current IME does not understand how to translate the string into key events). As a workaround, you can use replaceText action to set the text directly in the EditText field.
at android.support.test.espresso.base.UiControllerImpl.injectString(UiControllerImpl.java:265)
at android.support.test.espresso.action.TypeTextAction.perform(TypeTextAction.java:105)
.....
好吧失败来,不开森了,难道Espresso也跟UiAutomator一样不支持中文的输入吗? 等等!! 我们好好的阅读下错误的信息
As a workaround, you can use replaceText action to set the text directly in the EditText field
原来这里已经给出了措施,直接使用replaceText 好的,我们重新试试看。
onView(withId(R.id.editTextUserInput)).perform(replaceText("你好"));
!!! 真的运行通过了,成功的输入了中文了。
不过问题还没完, 我们得搞懂typeText与replaceText的区别到底在哪里呢
查看源代码我们终于发现两者的区别了,typeText与replaceText方法分别是实例化了一个TypeTextAction以及ReplaceTextAction的对象,并且这两个类都实现了ViewAction 的接口。我们首先看看ReplaceTextAction 的perform方法的实现
@Override
public void perform(UiController uiController, View view) {
((EditText) view).setText(stringToBeSet);
}
原来如此一个直接是使用EditText的setText方法。
那下来我们再看看TypeTextAction的perform的方法实现吧。
@Override
public void perform(UiController uiController, View view) {
// No-op if string is empty.
if (stringToBeTyped.length() == 0) {
Log.w(TAG, "Supplied string is empty resulting in no-op (nothing is typed).");
return;
}
if (tapToFocus) {
// Perform a click.
new GeneralClickAction(Tap.SINGLE, GeneralLocation.CENTER, Press.FINGER)
.perform(uiController, view);
uiController.loopMainThreadUntilIdle();
}
try {
if (!uiController.injectString(stringToBeTyped)) {
Log.e(TAG, "Failed to type text: " + stringToBeTyped);
throw new PerformException.Builder()
.withActionDescription(this.getDescription())
.withViewDescription(HumanReadables.describe(view))
.withCause(new RuntimeException("Failed to type text: " + stringToBeTyped))
.build();
}
} catch (InjectEventSecurityException e) {
Log.e(TAG, "Failed to type text: " + stringToBeTyped);
throw new PerformException.Builder()
.withActionDescription(this.getDescription())
.withViewDescription(HumanReadables.describe(view))
.withCause(e)
.build();
}
}
typeTextAction相比来说就复杂了很多了,我们重点看到
uiController.injectString(stringToBeTyped)
这里才是真正的注入字符串的地方,我们再细细一瞧
@Override
public boolean injectString(String str) throws InjectEventSecurityException {
checkNotNull(str);
checkState(Looper.myLooper() == mainLooper, "Expecting to be on main thread!");
initialize();
// No-op if string is empty.
if (str.length() == 0) {
Log.w(TAG, "Supplied string is empty resulting in no-op (nothing is typed).");
return true;
}
boolean eventInjected = false;
KeyCharacterMap keyCharacterMap = getKeyCharacterMap();
// TODO(user): Investigate why not use (as suggested in javadoc of keyCharacterMap.getEvents):
// http://developer.android.com/reference/android/view/KeyEvent.html#KeyEvent(long,
// java.lang.String, int, int)
KeyEvent[] events = keyCharacterMap.getEvents(str.toCharArray());
if (events == null) {
throw new RuntimeException(String.format(
"Failed to get key events for string %s (i.e. current IME does not understand how to "
+ "translate the string into key events). As a workaround, you can use replaceText action"
+ " to set the text directly in the EditText field.", str));
}
Log.d(TAG, String.format("Injecting string: \"%s\"", str));
for (KeyEvent event : events) {
checkNotNull(event, String.format("Failed to get event for character (%c) with key code (%s)",
event.getKeyCode(), event.getUnicodeChar()));
eventInjected = false;
for (int attempts = 0; !eventInjected && attempts < 4; attempts++) {
attempts++;
// We have to change the time of an event before injecting it because
// all KeyEvents returned by KeyCharacterMap.getEvents() have the same
// time stamp and the system rejects too old events. Hence, it is
// possible for an event to become stale before it is injected if it
// takes too long to inject the preceding ones.
event = KeyEvent.changeTimeRepeat(event, SystemClock.uptimeMillis(), 0);
eventInjected = injectKeyEvent(event);
}
if (!eventInjected) {
Log.e(TAG, String.format("Failed to inject event for character (%c) with key code (%s)",
event.getUnicodeChar(), event.getKeyCode()));
break;
}
}
return eventInjected;
}
其实看到这里我们大概就能明白了,typeText是通过模拟事件注入的方式,它将传入的字符串转成字符数组,再分别获取到对应的KeyEvent后直接进行注入。
实际上我们在查看typeText以及replaceText的操作现象的时候就能够发现 typeText的内容是一个个输进去的,但是replaceText是直接显示结果的,所以有时候你连操作都没看清,用例就已经跑完了。
结束
这篇主要是讲了下Espresso的中文输入,还是挺有意思的。后续还有很多类似于Espresso关于recyclerView的操作,对webview的操作。这些都是很有必要了解的。