Skip to content

Commit

Permalink
Merge pull request #382 from wordpress-mobile/add/ui-text-formatting-…
Browse files Browse the repository at this point in the history
…tests

Add UI Tests for Simple Text Formatting
  • Loading branch information
0nko authored Jun 19, 2017
2 parents 2e987a6 + 3f26f95 commit a22b37b
Show file tree
Hide file tree
Showing 4 changed files with 531 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package org.wordpress.aztec.demo;

import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
import static org.hamcrest.Matchers.allOf;

import android.support.test.espresso.core.deps.guava.base.Optional;
import android.support.test.espresso.PerformException;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.action.CoordinatesProvider;
import android.support.test.espresso.action.PrecisionDescriber;
import android.support.test.espresso.action.Tap;
import android.support.test.espresso.action.Tapper;
import android.support.test.espresso.util.HumanReadables;
import android.view.View;
import android.view.ViewConfiguration;
import android.webkit.WebView;
import org.hamcrest.Matcher;

/**
* Enables clicking on views with 65% or greater displaying.
*/
public final class BetterClickAction implements ViewAction {
private final CoordinatesProvider coordinatesProvider;
private final Tapper tapper;
private final PrecisionDescriber precisionDescriber;
private final Optional<ViewAction> rollbackAction;
public BetterClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider,
PrecisionDescriber precisionDescriber) {
this(tapper, coordinatesProvider, precisionDescriber, null);
}
public BetterClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider,
PrecisionDescriber precisionDescriber, ViewAction rollbackAction) {
this.coordinatesProvider = coordinatesProvider;
this.tapper = tapper;
this.precisionDescriber = precisionDescriber;
this.rollbackAction = Optional.fromNullable(rollbackAction);
}
@Override
@SuppressWarnings("unchecked")
public Matcher<View> getConstraints() {
// Check that at least 65% of the element is displayed (instead of default 90%)
Matcher<View> standardConstraint = isDisplayingAtLeast(65);
if (rollbackAction.isPresent()) {
return allOf(standardConstraint, rollbackAction.get().getConstraints());
} else {
return standardConstraint;
}
}
@Override
public void perform(UiController uiController, View view) {
float[] coordinates = coordinatesProvider.calculateCoordinates(view);
float[] precision = precisionDescriber.describePrecision();
Tapper.Status status = Tapper.Status.FAILURE;
int loopCount = 0;
// Native event injection is quite a tricky process. A tap is actually 2
// seperate motion events which need to get injected into the system. Injection
// makes an RPC call from our app under test to the Android system server, the
// system server decides which window layer to deliver the event to, the system
// server makes an RPC to that window layer, that window layer delivers the event
// to the correct UI element, activity, or window object. Now we need to repeat
// that 2x. for a simple down and up. Oh and the down event triggers timers to
// detect whether or not the event is a long vs. short press. The timers are
// removed the moment the up event is received (NOTE: the possibility of eventTime
// being in the future is totally ignored by most motion event processors).
//
// Phew.
//
// The net result of this is sometimes we'll want to do a regular tap, and for
// whatever reason the up event (last half) of the tap is delivered after long
// press timeout (depending on system load) and the long press behaviour is
// displayed (EG: show a context menu). There is no way to avoid or handle this more
// gracefully. Also the longpress behavour is app/widget specific. So if you have
// a seperate long press behaviour from your short press, you can pass in a
// 'RollBack' ViewAction which when executed will undo the effects of long press.
while (status != Tapper.Status.SUCCESS && loopCount < 3) {
try {
status = tapper.sendTap(uiController, coordinates, precision);
} catch (RuntimeException re) {
throw new PerformException.Builder()
.withActionDescription(this.getDescription())
.withViewDescription(HumanReadables.describe(view))
.withCause(re)
.build();
}
int duration = ViewConfiguration.getPressedStateDuration();
// ensures that all work enqueued to process the tap has been run.
if (duration > 0) {
uiController.loopMainThreadForAtLeast(duration);
}
if (status == Tapper.Status.WARNING) {
if (rollbackAction.isPresent()) {
rollbackAction.get().perform(uiController, view);
} else {
break;
}
}
loopCount++;
}
if (status == Tapper.Status.FAILURE) {
throw new PerformException.Builder()
.withActionDescription(this.getDescription())
.withViewDescription(HumanReadables.describe(view))
.withCause(new RuntimeException(String.format("Couldn't "
+ "click at: %s,%s precision: %s, %s . Tapper: %s coordinate provider: %s precision " +
"describer: %s. Tried %s times. With Rollback? %s", coordinates[0], coordinates[1],
precision[0], precision[1], tapper, coordinatesProvider, precisionDescriber, loopCount,
rollbackAction.isPresent())))
.build();
}
if (tapper == Tap.SINGLE && view instanceof WebView) {
// WebViews will not process click events until double tap
// timeout. Not the best place for this - but good for now.
uiController.loopMainThreadForAtLeast(ViewConfiguration.getDoubleTapTimeout());
}
}
@Override
public String getDescription() {
return tapper.toString().toLowerCase() + " click";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.wordpress.aztec.demo;

import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anyOf;

import android.support.test.espresso.PerformException;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.matcher.ViewMatchers.Visibility;
import android.support.test.espresso.util.HumanReadables;

import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;

import org.hamcrest.Matcher;

/**
* Enables scrolling to the given view with 65% or greater displaying. View must be a descendant of a ScrollView.
*/
public final class BetterScrollToAction implements ViewAction {
private static final String TAG = BetterScrollToAction.class.getSimpleName();

@SuppressWarnings("unchecked")
@Override
public Matcher<View> getConstraints() {
return allOf(withEffectiveVisibility(Visibility.VISIBLE), isDescendantOfA(anyOf(
isAssignableFrom(ScrollView.class), isAssignableFrom(HorizontalScrollView.class))));
}

@Override
public void perform(UiController uiController, View view) {
// Check that at least 65% of the element is displayed (instead of default 90%)
if (isDisplayingAtLeast(65).matches(view)) {
Log.i(TAG, "View is already displayed. Returning.");
return;
}
Rect rect = new Rect();
view.getDrawingRect(rect);
if (!view.requestRectangleOnScreen(rect, true /* immediate */)) {
Log.w(TAG, "Scrolling to view was requested, but none of the parents scrolled.");
}
uiController.loopMainThreadUntilIdle();
// Check that at least 65% of the element is displayed (instead of default 90%)
if (!isDisplayingAtLeast(65).matches(view)) {
throw new PerformException.Builder()
.withActionDescription(this.getDescription())
.withViewDescription(HumanReadables.describe(view))
.withCause(new RuntimeException(
"Scrolling to view was attempted, but the view is not displayed"))
.build();
}
}

@Override
public String getDescription() {
return "scroll to";
}
}
Loading

0 comments on commit a22b37b

Please sign in to comment.