2013年11月26日火曜日

Espresso で EditText に日本語を入力する方法

Espresso には、テキストをタイプする ViewActions.typeText() というメソッドが用意されています。
このメソッドは、引数で渡されたテキストの各文字に対応する KeyCode を入力するものです。
そのため、typeText("日本語") としても"日本語"は入力されませんし、ソフトキーボードが日本語キーボードのときに typeText("andoroido") とすると、"あんどろいど" と入力されます。
また困ったことに、ソフトキーボードが日本語キーボードのときに typeText("12345") とすると、全角で入力されます。orz


日本語を入力するには、setText() で直接セットするしかありません。
そのための ViewAction を実装したクラスを用意しました。

  1.     public void testInputJapanese() {  
  2.         onView(R.id.editText1).perform(clearText(), new InputTextAction("日本語"));  
  3.         onView(R.id.editText1).check(matches(withText("日本語")));  
  4.     }  
  5.   
  6.     public final class InputTextAction implements ViewAction {  
  7.         private final String mText;  
  8.   
  9.         public InputTextAction(String text) {  
  10.             checkNotNull(text);  
  11.             mText = text;  
  12.         }  
  13.   
  14.         @SuppressWarnings("unchecked")  
  15.         @Override  
  16.         public Matcher<view> getConstraints() {  
  17.             return allOf(isDisplayed(), isAssignableFrom(EditText.class));  
  18.         }  
  19.   
  20.         @Override  
  21.         public void perform(UiController uiController, View view) {  
  22.             ((EditText) view).setText(mText);  
  23.         }  
  24.   
  25.         @Override  
  26.         public String getDescription() {  
  27.             return "set text";  
  28.         }  
  29.     }  
  30. </view>  



ちなみに typeText() の実体は TypeTextAction クラスです。

ViewActions.java
  1. public final class ViewActions {  
  2.   ...  
  3.   
  4.   public static ViewAction typeText(String stringToBeTyped) {  
  5.     return new TypeTextAction(stringToBeTyped);  
  6.   }  
  7. }  
こちらでは UiController の injectString() を利用しています。また、SearchView にも入力できるみたいです。

TypeTextAction.java
  1. public final class TypeTextAction implements ViewAction {  
  2.   private static final String TAG = TypeTextAction.class.getSimpleName();  
  3.   private final String stringToBeTyped;  
  4.   
  5.   /** 
  6.    * Constructs {@link TypeTextAction} with given string. If the string is empty it results in no-op 
  7.    * (nothing is typed). 
  8.    * 
  9.    * @param stringToBeTyped String To be typed by {@link TypeTextAction} 
  10.    */  
  11.   public TypeTextAction(String stringToBeTyped) {  
  12.     checkNotNull(stringToBeTyped);  
  13.     this.stringToBeTyped = stringToBeTyped;  
  14.   }  
  15.   
  16.   @SuppressWarnings("unchecked")  
  17.   @Override  
  18.   public Matcher<view> getConstraints() {  
  19.     Matcher<view> matchers = allOf(isDisplayed());  
  20.     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {  
  21.        return allOf(matchers, supportsInputMethods());  
  22.     } else {  
  23.        // SearchView does not support input methods itself (rather it delegates to an internal text  
  24.        // view for input).  
  25.        return allOf(matchers, anyOf(supportsInputMethods(), isAssignableFrom(SearchView.class)));  
  26.     }  
  27.   }  
  28.   
  29.   @Override  
  30.   public void perform(UiController uiController, View view) {  
  31.     // No-op if string is empty.  
  32.     if (stringToBeTyped.length() == 0) {  
  33.       Log.w(TAG, "Supplied string is empty resulting in no-op (nothing is typed).");  
  34.       return;  
  35.     }  
  36.   
  37.     // Perform a click.  
  38.     new GeneralClickAction(Tap.SINGLE, GeneralLocation.CENTER, Press.PINPOINT)  
  39.         .perform(uiController, view);  
  40.     uiController.loopMainThreadUntilIdle();  
  41.   
  42.     try {  
  43.       if (!uiController.injectString(stringToBeTyped)) {  
  44.         Log.e(TAG, "Failed to type text: " + stringToBeTyped);  
  45.         throw new PerformException.Builder()  
  46.           .withActionDescription(this.getDescription())  
  47.           .withViewDescription(HumanReadables.describe(view))  
  48.           .withCause(new RuntimeException("Failed to type text: " + stringToBeTyped))  
  49.           .build();  
  50.       }  
  51.     } catch (InjectEventSecurityException e) {  
  52.       Log.e(TAG, "Failed to type text: " + stringToBeTyped);  
  53.       throw new PerformException.Builder()  
  54.         .withActionDescription(this.getDescription())  
  55.         .withViewDescription(HumanReadables.describe(view))  
  56.         .withCause(e)  
  57.         .build();  
  58.     }  
  59.   }  
  60.   
  61.   @Override  
  62.   public String getDescription() {  
  63.     return "type text";  
  64.   }  
  65. }  
  66. </view></view>  
UiController はインタフェースで、実装クラスは UiControllerImpl.java です。

UiControllerImpl.java
  1. @Override  
  2. public boolean injectString(String str) throws InjectEventSecurityException {  
  3.   checkNotNull(str);  
  4.   checkState(Looper.myLooper() == mainLooper, "Expecting to be on main thread!");  
  5.   initialize();  
  6.   
  7.   // No-op if string is empty.  
  8.   if (str.length() == 0) {  
  9.     Log.w(TAG, "Supplied string is empty resulting in no-op (nothing is typed).");  
  10.     return true;  
  11.   }  
  12.   
  13.   boolean eventInjected = false;  
  14.   KeyCharacterMap keyCharacterMap = getKeyCharacterMap();  
  15.   
  16.   // TODO(user): Investigate why not use (as suggested in javadoc of keyCharacterMap.getEvents):  
  17.   // http://developer.android.com/reference/android/view/KeyEvent.html#KeyEvent(long,  
  18.   // java.lang.String, int, int)  
  19.   KeyEvent[] events = keyCharacterMap.getEvents(str.toCharArray());  
  20.   checkNotNull(events, "Failed to get events for string " + str);  
  21.   Log.d(TAG, String.format("Injecting string: \"%s\"", str));  
  22.   
  23.   for (KeyEvent event : events) {  
  24.     checkNotNull(event, String.format("Failed to get event for character (%c) with key code (%s)",  
  25.         event.getKeyCode(), event.getUnicodeChar()));  
  26.   
  27.     eventInjected = false;  
  28.     for (int attempts = 0; !eventInjected && attempts < 4; attempts++) {  
  29.       attempts++;  
  30.   
  31.       // We have to change the time of an event before injecting it because  
  32.       // all KeyEvents returned by KeyCharacterMap.getEvents() have the same  
  33.       // time stamp and the system rejects too old events. Hence, it is  
  34.       // possible for an event to become stale before it is injected if it  
  35.       // takes too long to inject the preceding ones.  
  36.       event = KeyEvent.changeTimeRepeat(event, SystemClock.uptimeMillis(), 0);  
  37.       eventInjected = injectKeyEvent(event);  
  38.     }  
  39.   
  40.     if (!eventInjected) {  
  41.       Log.e(TAG, String.format("Failed to inject event for character (%c) with key code (%s)",  
  42.           event.getUnicodeChar(), event.getKeyCode()));  
  43.       break;  
  44.     }  
  45.   }  
  46.   
  47.   return eventInjected;  
  48. }  



0 件のコメント:

コメントを投稿