#android #android-espresso
Вопрос:
Мой интеграционный тест Эспрессо должен выдавать кнопку «Назад» при длительном нажатии на тестируемое приложение, не требуя никаких изменений в исходном коде приложения. В то время как мои тесты могут успешно выдавать обычную кнопку «Назад» (с помощью androidx.test.espresso.Espresso.pressBack()) и длительное нажатие на заданное местоположение пикселя на дисплее (с помощью androidx.test.espresso.action.GeneralClickAction), они не могут успешно выполнить длительное нажатие кнопки «Назад». Приложение не показывает никаких изменений в пользовательском интерфейсе, хотя оно отлично работает, если вручную долго нажимать во время тестовой работы.
Как мне заставить его работать? Позаимствовав у Эспрессо, я попробовал следующее, изменив только имена классов и конструкторы ключевых событий.
onView(isRoot()).выполнить(действия просмотра.действия с настройками(новое действие возврата(true)));
***публичный конечный класс pressslongbackaction расширяет базу данных LongPressKeyEventActionBase {
private final boolean conditional;
public PressLongBackAction(boolean conditional) {
this(conditional, new EspressoKey.Builder().withKeyCode(KeyEvent.KEYCODE_BACK).build());
}
public PressLongBackAction(boolean conditional, EspressoKey espressoKey) {
super(espressoKey);
this.conditional = conditional;
}
@Override
public void perform(UiController uiController, View view) {
Activity initialActivity = getCurrentActivity();
new Exception().printStackTrace(System.out);
super.perform(uiController, view);
new Exception().printStackTrace(System.out);
// Wait for a Stage change of the initial activity.
waitForStageChangeInitialActivity(uiController, initialActivity);
// Wait until there are no other pending activities in a foreground stage.
waitForPendingForegroundActivities(uiController, conditional);
}
}
***класс LongPressKeyEventActionBase реализует viewAction { закрытый статический ТЕГ конечной строки = «KeyEventTestActionBase»;
public static final int BACK_ACTIVITY_TRANSITION_MILLIS_DELAY = 150;
public static final int CLEAR_TRANSITIONING_ACTIVITIES_ATTEMPTS = 4;
public static final int CLEAR_TRANSITIONING_ACTIVITIES_MILLIS_DELAY = 150;
final EspressoKey espressoKey;
LongPressKeyEventActionBase(EspressoKey espressoKey) {
this.espressoKey = checkNotNull(espressoKey);
}
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}
@Override
public String getDescription() {
return String.format(Locale.ROOT, "send %s key event", this.espressoKey);
}
@Override
public void perform(UiController uiController, View view) {
try {
if (!sendKeyEvent(uiController)) {
Log.e(TAG, "Failed to inject espressoKey event: " this.espressoKey);
throw new PerformException.Builder()
.withActionDescription(this.getDescription())
.withViewDescription(HumanReadables.describe(view))
.withCause(
new RuntimeException("Failed to inject espressoKey event " this.espressoKey))
.build();
}
} catch (InjectEventSecurityException e) {
Log.e(TAG, "Failed to inject espressoKey event: " this.espressoKey);
throw new PerformException.Builder()
.withActionDescription(this.getDescription())
.withViewDescription(HumanReadables.describe(view))
.withCause(e)
.build();
}
}
private boolean sendKeyEvent(UiController controller) throws InjectEventSecurityException {
boolean injected = false;
long eventTime = SystemClock.uptimeMillis();
for (int attempts = 0; !injected amp;amp; attempts < 4; attempts ) {
final KeyEvent keyEvent = new KeyEvent(
eventTime,
eventTime,
KeyEvent.ACTION_DOWN,
this.espressoKey.getKeyCode(),
0,
this.espressoKey.getMetaState(),
-1,
0,
KeyEvent.FLAG_LONG_PRESS);
Log.d(TAG, "keyEvent : " keyEvent.toString());
injected = controller.injectKeyEvent(keyEvent);
}
if (!injected) {
// it is not a transient failure... :(
return false;
}
injected = false;
eventTime = SystemClock.uptimeMillis();
for (int attempts = 0; !injected amp;amp; attempts < 4; attempts ) {
final KeyEvent keyEvent = new KeyEvent(
eventTime,
eventTime,
KeyEvent.ACTION_UP,
this.espressoKey.getKeyCode(),
0,
this.espressoKey.getMetaState(),
-1,
0,
KeyEvent.FLAG_LONG_PRESS );
Log.d(TAG, "keyEvent : " keyEvent.toString());
injected = controller.injectKeyEvent(keyEvent);
}
return injected;
}
static Activity getCurrentActivity() {
Collection<Activity> resumedActivities =
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);
return getOnlyElement(resumedActivities);
}
static void waitForStageChangeInitialActivity(UiController controller, Activity initialActivity) {
if (isActivityResumed(initialActivity)) {
// The activity transition hasn't happened yet, wait for it.
controller.loopMainThreadForAtLeast(BACK_ACTIVITY_TRANSITION_MILLIS_DELAY);
if (isActivityResumed(initialActivity)) {
Log.e(
TAG,
"Back was pressed but there was no Activity stage transition in "
BACK_ACTIVITY_TRANSITION_MILLIS_DELAY
"ms, possibly due to a delay calling super.onBackPressed() from your Activity.");
}
}
}
private static boolean isActivityResumed(Activity activity) {
return ActivityLifecycleMonitorRegistry.getInstance().getLifecycleStageOf(activity)
== Stage.RESUMED;
}
static void waitForPendingForegroundActivities(UiController controller, boolean conditional) {
ActivityLifecycleMonitor activityLifecycleMonitor =
ActivityLifecycleMonitorRegistry.getInstance();
boolean pendingForegroundActivities = false;
for (int attempts = 0; attempts < CLEAR_TRANSITIONING_ACTIVITIES_ATTEMPTS; attempts ) {
controller.loopMainThreadUntilIdle();
pendingForegroundActivities = hasTransitioningActivities(activityLifecycleMonitor);
if (pendingForegroundActivities) {
controller.loopMainThreadForAtLeast(CLEAR_TRANSITIONING_ACTIVITIES_MILLIS_DELAY);
} else {
break;
}
}
// Pressing back can kill the app: log a warning.
if (!hasForegroundActivities(activityLifecycleMonitor)) {
if (conditional) {
throw new NoActivityResumedException("Pressed back and killed the app");
}
Log.w(TAG, "Pressed back and hopped to a different process or potentially killed the app");
}
if (pendingForegroundActivities) {
Log.e(
TAG,
"Back was pressed and left the application in an inconsistent state even after "
(CLEAR_TRANSITIONING_ACTIVITIES_MILLIS_DELAY
* CLEAR_TRANSITIONING_ACTIVITIES_ATTEMPTS)
"ms.");
}
}
}
The KeyEvent for a normal press on the Back button looks like this: ***logcat:
2021-05-12 11:49:57.501 31118-31118/com… D/…: keyEvent : KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x80, repeatCount=0, eventTime=1234519744, downTime=1234519744, deviceId=-1, displayId=0, source=0x0 }
2021-05-12 11:49:57.508 31118-31118/com… D/ViewRootImpl@5a0d36[ActivityMain]: ViewPostImeInputStage processKey 0
2021-05-12 11:49:57.510 31118-31118/com… D/…: keyEvent : KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x80, repeatCount=0, eventTime=1234519753, downTime=1234519753, deviceId=-1, displayId=0, source=0x0 }
2021-05-12 11:49:57.513 31118-31118/com… D/ViewRootImpl@5a0d36[ActivityMain]: ViewPostImeInputStage processKey 1
2021-05-12 11:50:04.682 31118-31118/com… D/…: keyEvent : KeyEvent { action=ACTION_DOWN, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x0, repeatCount=0, eventTime=1234526925, downTime=1234526925, deviceId=-1, displayId=0, source=0x0 }
2021-05-12 11:50:04.686 31118-31118/com… D/ViewRootImpl@5a0d36[ActivityMain]: ViewPostImeInputStage processKey 0
2021-05-12 11:50:04.688 31118-31118/com… D/…: keyEvent : KeyEvent { action=ACTION_UP, keyCode=KEYCODE_BACK, scanCode=0, metaState=0, flags=0x0, repeatCount=0, eventTime=1234526931, downTime=1234526931, deviceId=-1, displayId=0, source=0x0 }
2021-05-12 11:50:04.691 31118-31118/com… D/ViewRootImpl@5a0d36[ActivityMain]: ViewPostImeInputStage processKey 1
2021-05-12 11:50:05.101 1314-1418/? V/WindowManager: Relayout Window{5dfbe22d0 u0 com…/com…ui.ActivityMain}: viewVisibility=0 req=2048×1536 WM.LayoutParams{(0,0)(fillxfill) sim=#20 ty=1 fl=#410500 wanim=0x1030465 needsMenuKey=1 naviIconColor=0}
2021-05-12 11:50:05.107 31118-31118/com… D/ViewRootImpl@5a0d36[ActivityMain]: Relayout returned: oldFrame=[0,0][2048,1536] newFrame=[0,0][2048,1536] result=0x1 surface={isValid=true 547395848192} surfaceGenerationChanged=false