-
Notifications
You must be signed in to change notification settings - Fork 118
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #382 from wordpress-mobile/add/ui-text-formatting-…
…tests Add UI Tests for Simple Text Formatting
- Loading branch information
Showing
4 changed files
with
531 additions
and
0 deletions.
There are no files selected for viewing
121 changes: 121 additions & 0 deletions
121
app/src/androidTest/java/org/wordpress/aztec/demo/BetterClickAction.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
app/src/androidTest/java/org/wordpress/aztec/demo/BetterScrollToAction.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
} | ||
} |
Oops, something went wrong.